Compare commits

...

13 Commits

Author SHA1 Message Date
Timothee
73ce7e8dab N°7071 - Remove CMDBSource::MYSQL_DEFAULT_PORT 2025-09-17 17:22:48 +02:00
odain
d589d9d05a N°4789 - fix broken tests in ci 2025-09-16 16:37:31 +02:00
odain
c0c9ea9287 N°4789 - Parse datamodel module.xxx.php files instead of interpreting them (#746) - namespacing ModuleFileReader classes 2025-09-16 15:38:30 +02:00
odain
fae2bcc6e9 N°4720 - deprecation removal RenderLinks 2025-09-15 16:40:10 +02:00
Stephen Abello
53047d35fe N°8617 - New portal theme doesn't work with Safari 2025-09-15 14:30:07 +02:00
Anne-Catherine
909469ce97 N°2364 - API : remove old linkedset persistance (#733) 2025-09-12 16:16:02 +02:00
Anne-Cath
dacb54285c N°4820 - Remove pre-3.0 console files - date.js, hovertip.js jquery.layout.min.js 2025-09-12 16:01:55 +02:00
Benjamin Dalsass
5af93ca92a N°8637 - Alerts from dependabot, vulnerable libraries
- remove deprecated call to getExpressionParser
2025-09-12 15:38:01 +02:00
Benjamin Dalsass
3fa500c9c1 N°8699 - attributedef.class.inc.php to PSR4 [3-PSR4]
- Add namespaces
- Add use statements
- reformat code
2025-09-12 15:23:42 +02:00
Benjamin Dalsass
f0adbbba29 N°8699 - attributedef.class.inc.php to PSR4 [2-DISPATCH]
- Restore attributedef.class.inc.php with requires
- Update each attribute file code
2025-09-12 15:23:42 +02:00
Benjamin Dalsass
bbdb30f421 N°8699 - attributedef.class.inc.php to PSR4 [1-DUPLICATE]
- Duplicates attributedef.class.inc.php as attributedefrequires.class.inc.php (will contains the require_once directives)
- Duplicates attributedef.class.inc.php for each final class files AttributeApplicationLanguage.php, AttributeArchiveDate.php... to keep VCS
 history
- Remove attributedef.class.inc.php file
2025-09-12 15:23:42 +02:00
odain
897b5d452e N°8715 - Broken UIFieldSet/UIFieldInput via twig rendering 2025-09-12 15:00:40 +02:00
odain
fdd1479c8e N°4720 - fix portal issue 2025-09-12 14:36:19 +02:00
101 changed files with 14637 additions and 15098 deletions

View File

@@ -419,7 +419,7 @@ class DisplayBlock
$oSet->OptimizeColumnLoad(array($oSet->GetClassAlias() => array())); // No need to load all the columns just to get the id
while($oObject = $oSet->Fetch())
{
$aKeys[] = $oObject->GetKey();
$aKeys[] = $oObject->GetKey();
}
$oSet->Rewind();
if (count($aKeys) > 0)
@@ -772,10 +772,6 @@ class DisplayBlock
$oBlock = $this->RenderList($aExtraParams, $oPage);
break;
case 'links':
$oBlock = $this->RenderLinks($oPage, $aExtraParams);
break;
case static::ENUM_STYLE_ACTIONS:
$oBlock = $this->RenderActions($aExtraParams);
break;
@@ -795,11 +791,11 @@ class DisplayBlock
case static::ENUM_STYLE_CHART:
$oBlock = $this->RenderChart($sId, $aQueryParams, $aExtraParams);
break;
case static::ENUM_STYLE_CHART_AJAX:
$oBlock = $this->RenderChartAjax($aExtraParams);
break;
default:
// Unsupported style, do nothing.
$sHtml .= Dict::format('UI:Error:UnsupportedStyleOfBlock', $this->m_sStyle);
@@ -897,7 +893,7 @@ JS
$sClass = $this->m_oFilter->GetClass();
$bConditionAdded = false;
// If the condition is an external key with a class having a hierarchy, use a "below" criteria
if (MetaModel::IsValidAttCode($sClass, $sFilterCode))
{
@@ -906,13 +902,13 @@ JS
if ($oAttDef->IsExternalKey())
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass());
if ($sHierarchicalKeyCode !== false)
{
$oFilter = new DBObjectSearch($oAttDef->GetTargetClass());
if (($sOpCode == 'IN') && is_array($condition))
{
$oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition));
$oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition));
}
else
{
@@ -935,21 +931,21 @@ JS
$bConditionAdded = true;
}
}
// In all other cases, just add the condition directly
if (!$bConditionAdded)
{
$this->m_oFilter->AddCondition($sFilterCode, $condition, null); // Use the default 'loose' operator
}
}
static protected function GetConditionIN($oFilter, $sFilterCode, $condition)
{
$oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias());
$sListExpr = '('.implode(', ', CMDBSource::Quote($condition)).')';
$sOQLCondition = $oField->RenderExpression()." IN $sListExpr";
$oNewCondition = Expression::FromOQL($sOQLCondition);
return $oNewCondition;
return $oNewCondition;
}
/**
@@ -1565,54 +1561,6 @@ JS
return $oContentBlock;
}
/**
* @deprecated 3.1.0 N°5957
*
* @param WebPage $oPage
* @param array $aExtraParams
*
* @return \Combodo\iTop\Application\UI\Base\Component\Html\Html|\Combodo\iTop\Application\UI\Base\Layout\UIContentBlock|string
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \ReflectionException
*/
protected function RenderLinks(WebPage $oPage, array $aExtraParams)
{
// Note: No deprecation ticket yet as we want to wait and see if people / code actually use this method, in which case we might keep it.
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('This method is most likely not used throughout the application and will be removed soon. If you ever see this message, please inform us.');
$oBlock = null;
if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) {
$oBlock = cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams);
} else {
$sClass = $this->m_oFilter->GetClass();
$oAttDef = MetaModel::GetAttributeDef($sClass, $this->m_aParams['target_attr']);
$sTargetClass = $oAttDef->GetTargetClass();
$oBlock = new Html('<p>'.Dict::Format('UI:NoObject_Class_ToDisplay', MetaModel::GetName($sTargetClass)).'</p>');
$bDisplayMenu = isset($this->m_aParams['menu']) ? $this->m_aParams['menu'] == true : true;
if ($bDisplayMenu) {
if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) {
$sDefaults = '';
if (isset($this->m_aParams['default'])) {
foreach ($this->m_aParams['default'] as $sName => $sValue) {
$sDefaults .= '&'.urlencode($sName).'='.urlencode($sValue);
}
}
$oBlock->AddHtml("<p><a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=modify_links&class=$sClass&sParams&link_attr=".$aExtraParams['link_attr']."&id=".$aExtraParams['object_id']."&target_class=$sTargetClass&addObjects=true$sDefaults\">".Dict::Format('UI:ClickToCreateNew',
Metamodel::GetName($sClass))."</a></p>\n");
}
}
}
return $oBlock;
}
/**
* @param string|null $sChartId
* @param array $aQueryParams

File diff suppressed because it is too large Load Diff

View File

@@ -42,11 +42,6 @@ class CMDBSource
const ENUM_DB_VENDOR_MARIADB = 'MariaDB';
const ENUM_DB_VENDOR_PERCONA = 'Percona';
/**
* @since 2.7.10 3.0.4 3.1.2 3.0.2 N°6889 constant creation
* @internal will be removed in a future version
*/
const MYSQL_DEFAULT_PORT = 3306;
/**
* Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
@@ -220,7 +215,7 @@ class CMDBSource
* @param string $sServer server variable to update
* @param int|null $iPort port variable to update, will return null if nothing is specified in $sDbHost
*
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°6889 will return null in $iPort if port isn't present in $sDbHost. Use {@see MYSQL_DEFAULT_PORT} if needed
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°6889 will return null in $iPort if port isn't present in $sDbHost.
*
* @link http://php.net/manual/en/mysqli.persistconns.php documentation for the "p:" prefix (persistent connexion)
*/

View File

@@ -495,8 +495,8 @@ class OqlObjectQuery extends OqlQuery
{
throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
}
$aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeExternalKey');
$aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeObjectKey');
$aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey::class);
$aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeObjectKey::class);
$aAllKeys = array_merge($aExtKeys, $aObjKeys);
if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
{
@@ -557,7 +557,7 @@ class OqlObjectQuery extends OqlQuery
}
$aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
$sAttType = $aAttList[$sExtKeyAttCode];
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, 'AttributeHierarchicalKey') && ($sAttType != 'AttributeHierarchicalKey'))
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class) && ($sAttType != \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class))
{
throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
}

View File

@@ -513,6 +513,15 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
{
$bUpdateFromDelta = true;
}
} else {
//@since 3.2.2 N°2364 - API : remove old linkedset persistance
/* Goo pattern to use:
* $oCISet = $oTicket->Get(functioncis_list);
* $oCISet->AddItem(MetaModel::NewObject(lnkFunctionCIToTicket, array(ci_id=> 12345));
* $oCISet->RemoveItem(123456);
* $oTicket->Set(functionalcis_list, $oCISet);
*/
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('old pattern - please get previous value of the linked set, modify it and set it back to the host object');
}
if ($bUpdateFromDelta)
@@ -528,7 +537,6 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
else
{
// For backward compatibility reasons, let's rebuild a delta...
// Reset the delta
$this->iCursor = 0;
$this->aAdded = array();

View File

@@ -30,7 +30,8 @@
{% if aTilesRendering[brick.GetId] is defined %}
{{ aTilesRendering[brick.GetId]|raw }}
{% else %}
{% include '' ~ brick.GetTemplatePath with {brick: brick} %}
{% set twigPath = brick.GetTemplatePath('tile') %}
{% include '' ~ twigPath with {brick: brick} %}
{% endif %}
{% endif %}
{% endfor %}

View File

@@ -162,6 +162,8 @@
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal-clipboard.js'|add_itop_version }}"></script>
{# User Preferences #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/user_preferences.js'|add_itop_version }}"></script>
{# Polyfill for custom elements #}
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'node_modules/@ungap/custom-elements/es.js'|add_itop_version }}"></script>
{# custom elements #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/custom_elements/base_element.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/custom_elements/tile_element.js'|add_itop_version }}"></script>

View File

@@ -1,467 +0,0 @@
/*
* Date prototype extensions. Doesn't depend on any
* other code. Doens't overwrite existing methods.
*
* Adds dayNames, abbrDayNames, monthNames and abbrMonthNames static properties and isLeapYear,
* isWeekend, isWeekDay, getDaysInMonth, getDayName, getMonthName, getDayOfYear, getWeekOfYear,
* setDayOfYear, addYears, addMonths, addDays, addHours, addMinutes, addSeconds methods
*
* Copyright (c) 2006 Jörn Zaefferer and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
*
* Additional methods and properties added by Kelvin Luck: firstDayOfWeek, dateFormat, zeroTime, asString, fromString -
* I've added my name to these methods so you know who to blame if they are broken!
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
/**
* An Array of day names starting with Sunday.
*
* @example dayNames[0]
* @result 'Sunday'
*
* @name dayNames
* @type Array
* @cat Plugins/Methods/Date
*/
Date.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
/**
* An Array of abbreviated day names starting with Sun.
*
* @example abbrDayNames[0]
* @result 'Sun'
*
* @name abbrDayNames
* @type Array
* @cat Plugins/Methods/Date
*/
Date.abbrDayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
/**
* An Array of month names starting with Janurary.
*
* @example monthNames[0]
* @result 'January'
*
* @name monthNames
* @type Array
* @cat Plugins/Methods/Date
*/
Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
/**
* An Array of abbreviated month names starting with Jan.
*
* @example abbrMonthNames[0]
* @result 'Jan'
*
* @name monthNames
* @type Array
* @cat Plugins/Methods/Date
*/
Date.abbrMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
/**
* The first day of the week for this locale.
*
* @name firstDayOfWeek
* @type Number
* @cat Plugins/Methods/Date
* @author Kelvin Luck
*/
Date.firstDayOfWeek = 1;
/**
* The format that string dates should be represented as (e.g. 'dd/mm/yyyy' for UK, 'mm/dd/yyyy' for US, 'yyyy-mm-dd' for Unicode etc).
*
* @name format
* @type String
* @cat Plugins/Methods/Date
* @author Kelvin Luck
*/
//Date.format = 'dd/mm/yyyy';
//Date.format = 'mm/dd/yyyy';
Date.format = 'yyyy-mm-dd';
//Date.format = 'dd mmm yy';
/**
* The first two numbers in the century to be used when decoding a two digit year. Since a two digit year is ambiguous (and date.setYear
* only works with numbers < 99 and so doesn't allow you to set years after 2000) we need to use this to disambiguate the two digit year codes.
*
* @name format
* @type String
* @cat Plugins/Methods/Date
* @author Kelvin Luck
*/
Date.fullYearStart = '20';
(function() {
/**
* Adds a given method under the given name
* to the Date prototype if it doesn't
* currently exist.
*
* @private
*/
function add(name, method) {
if( !Date.prototype[name] ) {
Date.prototype[name] = method;
}
};
/**
* Checks if the year is a leap year.
*
* @example var dtm = new Date("01/12/2008");
* dtm.isLeapYear();
* @result true
*
* @name isLeapYear
* @type Boolean
* @cat Plugins/Methods/Date
*/
add("isLeapYear", function() {
var y = this.getFullYear();
return (y%4==0 && y%100!=0) || y%400==0;
});
/**
* Checks if the day is a weekend day (Sat or Sun).
*
* @example var dtm = new Date("01/12/2008");
* dtm.isWeekend();
* @result false
*
* @name isWeekend
* @type Boolean
* @cat Plugins/Methods/Date
*/
add("isWeekend", function() {
return this.getDay()==0 || this.getDay()==6;
});
/**
* Check if the day is a day of the week (Mon-Fri)
*
* @example var dtm = new Date("01/12/2008");
* dtm.isWeekDay();
* @result false
*
* @name isWeekDay
* @type Boolean
* @cat Plugins/Methods/Date
*/
add("isWeekDay", function() {
return !this.isWeekend();
});
/**
* Gets the number of days in the month.
*
* @example var dtm = new Date("01/12/2008");
* dtm.getDaysInMonth();
* @result 31
*
* @name getDaysInMonth
* @type Number
* @cat Plugins/Methods/Date
*/
add("getDaysInMonth", function() {
return [31,(this.isLeapYear() ? 29:28),31,30,31,30,31,31,30,31,30,31][this.getMonth()];
});
/**
* Gets the name of the day.
*
* @example var dtm = new Date("01/12/2008");
* dtm.getDayName();
* @result 'Saturday'
*
* @example var dtm = new Date("01/12/2008");
* dtm.getDayName(true);
* @result 'Sat'
*
* @param abbreviated Boolean When set to true the name will be abbreviated.
* @name getDayName
* @type String
* @cat Plugins/Methods/Date
*/
add("getDayName", function(abbreviated) {
return abbreviated ? Date.abbrDayNames[this.getDay()] : Date.dayNames[this.getDay()];
});
/**
* Gets the name of the month.
*
* @example var dtm = new Date("01/12/2008");
* dtm.getMonthName();
* @result 'Janurary'
*
* @example var dtm = new Date("01/12/2008");
* dtm.getMonthName(true);
* @result 'Jan'
*
* @param abbreviated Boolean When set to true the name will be abbreviated.
* @name getDayName
* @type String
* @cat Plugins/Methods/Date
*/
add("getMonthName", function(abbreviated) {
return abbreviated ? Date.abbrMonthNames[this.getMonth()] : Date.monthNames[this.getMonth()];
});
/**
* Get the number of the day of the year.
*
* @example var dtm = new Date("01/12/2008");
* dtm.getDayOfYear();
* @result 11
*
* @name getDayOfYear
* @type Number
* @cat Plugins/Methods/Date
*/
add("getDayOfYear", function() {
var tmpdtm = new Date("1/1/" + this.getFullYear());
return Math.floor((this.getTime() - tmpdtm.getTime()) / 86400000);
});
/**
* Get the number of the week of the year.
*
* @example var dtm = new Date("01/12/2008");
* dtm.getWeekOfYear();
* @result 2
*
* @name getWeekOfYear
* @type Number
* @cat Plugins/Methods/Date
*/
add("getWeekOfYear", function() {
return Math.ceil(this.getDayOfYear() / 7);
});
/**
* Set the day of the year.
*
* @example var dtm = new Date("01/12/2008");
* dtm.setDayOfYear(1);
* dtm.toString();
* @result 'Tue Jan 01 2008 00:00:00'
*
* @name setDayOfYear
* @type Date
* @cat Plugins/Methods/Date
*/
add("setDayOfYear", function(day) {
this.setMonth(0);
this.setDate(day);
return this;
});
/**
* Add a number of years to the date object.
*
* @example var dtm = new Date("01/12/2008");
* dtm.addYears(1);
* dtm.toString();
* @result 'Mon Jan 12 2009 00:00:00'
*
* @name addYears
* @type Date
* @cat Plugins/Methods/Date
*/
add("addYears", function(num) {
this.setFullYear(this.getFullYear() + num);
return this;
});
/**
* Add a number of months to the date object.
*
* @example var dtm = new Date("01/12/2008");
* dtm.addMonths(1);
* dtm.toString();
* @result 'Tue Feb 12 2008 00:00:00'
*
* @name addMonths
* @type Date
* @cat Plugins/Methods/Date
*/
add("addMonths", function(num) {
var tmpdtm = this.getDate();
this.setMonth(this.getMonth() + num);
if (tmpdtm > this.getDate())
this.addDays(-this.getDate());
return this;
});
/**
* Add a number of days to the date object.
*
* @example var dtm = new Date("01/12/2008");
* dtm.addDays(1);
* dtm.toString();
* @result 'Sun Jan 13 2008 00:00:00'
*
* @name addDays
* @type Date
* @cat Plugins/Methods/Date
*/
add("addDays", function(num) {
this.setDate(this.getDate() + num);
return this;
});
/**
* Add a number of hours to the date object.
*
* @example var dtm = new Date("01/12/2008");
* dtm.addHours(24);
* dtm.toString();
* @result 'Sun Jan 13 2008 00:00:00'
*
* @name addHours
* @type Date
* @cat Plugins/Methods/Date
*/
add("addHours", function(num) {
this.setHours(this.getHours() + num);
return this;
});
/**
* Add a number of minutes to the date object.
*
* @example var dtm = new Date("01/12/2008");
* dtm.addMinutes(60);
* dtm.toString();
* @result 'Sat Jan 12 2008 01:00:00'
*
* @name addMinutes
* @type Date
* @cat Plugins/Methods/Date
*/
add("addMinutes", function(num) {
this.setMinutes(this.getMinutes() + num);
return this;
});
/**
* Add a number of seconds to the date object.
*
* @example var dtm = new Date("01/12/2008");
* dtm.addSeconds(60);
* dtm.toString();
* @result 'Sat Jan 12 2008 00:01:00'
*
* @name addSeconds
* @type Date
* @cat Plugins/Methods/Date
*/
add("addSeconds", function(num) {
this.setSeconds(this.getSeconds() + num);
return this;
});
/**
* Sets the time component of this Date to zero for cleaner, easier comparison of dates where time is not relevant.
*
* @example var dtm = new Date();
* dtm.zeroTime();
* dtm.toString();
* @result 'Sat Jan 12 2008 00:01:00'
*
* @name zeroTime
* @type Date
* @cat Plugins/Methods/Date
* @author Kelvin Luck
*/
add("zeroTime", function() {
this.setMilliseconds(0);
this.setSeconds(0);
this.setMinutes(0);
this.setHours(0);
return this;
});
/**
* Returns a string representation of the date object according to Date.format.
* (Date.toString may be used in other places so I purposefully didn't overwrite it)
*
* @example var dtm = new Date("01/12/2008");
* dtm.asString();
* @result '12/01/2008' // (where Date.format == 'dd/mm/yyyy'
*
* @name asString
* @type Date
* @cat Plugins/Methods/Date
* @author Kelvin Luck
*/
add("asString", function() {
var r = Date.format;
return r
.split('yyyy').join(this.getFullYear())
.split('yy').join((this.getFullYear() + '').substring(2))
.split('mmm').join(this.getMonthName(true))
.split('mm').join(_zeroPad(this.getMonth()+1))
.split('dd').join(_zeroPad(this.getDate()));
});
/**
* Returns a new date object created from the passed String according to Date.format or false if the attempt to do this results in an invalid date object
* (We can't simple use Date.parse as it's not aware of locale and I chose not to overwrite it incase it's functionality is being relied on elsewhere)
*
* @example var dtm = Date.fromString("12/01/2008");
* dtm.toString();
* @result 'Sat Jan 12 2008 00:00:00' // (where Date.format == 'dd/mm/yyyy'
*
* @name fromString
* @type Date
* @cat Plugins/Methods/Date
* @author Kelvin Luck
*/
Date.fromString = function(s)
{
var f = Date.format;
var d = new Date('01/01/1977');
var iY = f.indexOf('yyyy');
if (iY > -1) {
d.setFullYear(Number(s.substr(iY, 4)));
} else {
// TODO - this doesn't work very well - are there any rules for what is meant by a two digit year?
d.setFullYear(Number(Date.fullYearStart + s.substr(f.indexOf('yy'), 2)));
}
var iM = f.indexOf('mmm');
if (iM > -1) {
var mStr = s.substr(iM, 3);
for (var i=0; i<Date.abbrMonthNames.length; i++) {
if (Date.abbrMonthNames[i] == mStr) break;
}
d.setMonth(i);
} else {
d.setMonth(Number(s.substr(f.indexOf('mm'), 2)) - 1);
}
d.setDate(Number(s.substr(f.indexOf('dd'), 2)));
if (isNaN(d.getTime())) {
return false;
}
return d;
};
// utility method
var _zeroPad = function(num) {
var s = '0'+num;
return s.substring(s.length-2)
//return ('0'+num).substring(-2); // doesn't work on IE :(
};
})();

View File

@@ -1,458 +0,0 @@
/**
* Hovertip - easy and elegant tooltips
*
* By Dave Cohen <http://dave-cohen.com>
* With ideas and and javascript code borrowed from many folks.
* (See URLS in the comments)
*
* Licensed under GPL.
* Requires jQuery.js. <http://jquery.com>,
* which may be distributed under a different licence.
*
* $Date: 2006-09-15 12:49:19 -0700 (Fri, 15 Sep 2006) $
* $Rev: $
* $Id:$
*
* This plugin helps you create tooltips. It supports:
*
* hovertips - these appear under the mouse when mouse is over the target
* element.
*
* clicktips - these appear in the document when the target element is
* clicked.
*
* You may define behaviors for additional types of tooltips.
*
* There are a variety of ways to add tooltips. Each of the following is
* supported:
*
* <p>blah blah blah
* <span>important term</span>
* <span class="tooltip">text that appears.</span>
* blah blah blah</p>
*
* or,
*
* <p>blah blah blah
* <span hovertip="termdefinition">important term</span>
* blah blah blah</p>
* <div id="termdefinition" class="hovertip"><h1>term definition</h1><p>the term means...</p></div>
*
* or,
*
* <p>blah blah blah
* <span id="term">important term</span>
* blah blah blah</p>
* <div target="term" class="hovertip"><h1>term definition</h1><p>the term means...</p></div>
*
*
* Hooks are available to customize both the behavior of activated tooltips,
* and the syntax used to mark them up.
*
*/
//// mouse events ////
/**
* To make hovertips appear correctly we need the exact mouse position.
* These functions make that possible.
*/
// use globals to track mouse position
var hovertipMouseX;
var hovertipMouseY;
function hovertipMouseUpdate(e) {
var mouse = hovertipMouseXY(e);
hovertipMouseX = mouse[0];
hovertipMouseY = mouse[1];
}
// http://www.howtocreate.co.uk/tutorials/javascript/eventinfo
function hovertipMouseXY(e) {
if( !e ) {
if( window.event ) {
//Internet Explorer
e = window.event;
} else {
//total failure, we have no way of referencing the event
return;
}
}
if( typeof( e.pageX ) == 'number' ) {
//most browsers
var xcoord = e.pageX;
var ycoord = e.pageY;
} else if( typeof( e.clientX ) == 'number' ) {
//Internet Explorer and older browsers
//other browsers provide this, but follow the pageX/Y branch
var xcoord = e.clientX;
var ycoord = e.clientY;
var badOldBrowser = ( window.navigator.userAgent.indexOf( 'Opera' ) + 1 ) ||
( window.ScriptEngine && ScriptEngine().indexOf( 'InScript' ) + 1 ) ||
( navigator.vendor == 'KDE' );
if( !badOldBrowser ) {
if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
//IE 4, 5 & 6 (in non-standards compliant mode)
xcoord += document.body.scrollLeft;
ycoord += document.body.scrollTop;
} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
//IE 6 (in standards compliant mode)
xcoord += document.documentElement.scrollLeft;
ycoord += document.documentElement.scrollTop;
}
}
} else {
//total failure, we have no way of obtaining the mouse coordinates
return;
}
return [xcoord, ycoord];
}
//// target selectors ////
/**
* These selectors find the targets for a given tooltip element.
* Several methods are supported.
*
* You may write your own selector functions to customize.
*/
/**
* For this model:
* <span hovertip="ht1">target term</span>...
* <div class="hovertip" id="ht1">tooltip text</div>
*/
targetSelectById = function(el, config) {
var id;
var selector;
if (id = el.getAttribute('id')) {
selector = '*[@'+config.attribute+'=\''+id+'\']';
return $(selector);
}
};
/**
* For this model:
* <span id="ht1">target term</span>...
* <div class="hovertip" target="ht1">tooltip text</div>
*/
targetSelectByTargetAttribute = function(el, config) {
target_list = el.getAttribute('target');
if (target_list) {
// use for attribute to specify targets
target_ids = target_list.split(' ');
var selector = '#' + target_ids.join(',#');
return $(selector);
}
};
/**
* For this model:
* <span>target term</span><span class="hovertip">tooltip text</span>
*/
targetSelectByPrevious = function(el, config) {
return $(el.previousSibling);
}
/**
* Make all siblings targets. Experimental.
*/
targetSelectBySiblings = function(el, config) {
return $(el).siblings();
}
//// prepare tip elements ////
/**
* The tooltip element needs special preparation. You may define your own
* prepare functions to cusomize the behavior.
*/
// adds a close link to clicktips
clicktipPrepareWithCloseLink = function(o, config) {
return o.append("<a class='clicktip_close'><span>close</span></a>")
.find('a.clicktip_close').click(function(e) {
o.hide();
return false;
}).end();
};
// ensure that hovertips do not disappear when the mouse is over them.
// also position the hovertip as an absolutely positioned child of body.
hovertipPrepare = function(o, config) {
return o.hover(function() {
hovertipHideCancel(this);
}, function() {
hovertipHideLater(this);
}).css('position', 'absolute').each(hovertipPosition);
};
// do not modify tooltips when preparing
hovertipPrepareNoOp = function(o, config) {
return o;
}
//// manipulate tip elements /////
/**
* A variety of functions to modify tooltip elements
*/
// move tooltips to body, so they are not descended from other absolutely
// positioned elements.
hovertipPosition = function(i) {
document.body.appendChild(this);
}
hovertipIsVisible = function(el) {
return (jQuery.css(el, 'display') != 'none');
}
// show the tooltip under the mouse.
// Introduce a delay, so tip appears only if cursor rests on target for more than an instant.
hovertipShowUnderMouse = function(el) {
hovertipHideCancel(el);
if (!hovertipIsVisible(el)) {
el.ht.showing = // keep reference to timer
window.setTimeout(function() {
el.ht.tip.css({
'position':'absolute',
'top': hovertipMouseY + 'px',
'left': hovertipMouseX + 'px'})
.show();
}, el.ht.config.showDelay);
}
};
// do not hide
hovertipHideCancel = function(el) {
if (el.ht.hiding) {
window.clearTimeout(el.ht.hiding);
el.ht.hiding = null;
}
};
// Hide a tooltip, but only after a delay.
// The delay allow the tip to remain when user moves mouse from target to tooltip
hovertipHideLater = function(el) {
if (el.ht.showing) {
window.clearTimeout(el.ht.showing);
el.ht.showing = null;
}
if (el.ht.hiding) {
window.clearTimeout(el.ht.hiding);
el.ht.hiding = null;
}
el.ht.hiding =
window.setTimeout(function() {
if (el.ht.hiding) {
// fadeOut, slideUp do not work on Konqueror
el.ht.tip.hide();
}
}, el.ht.config.hideDelay);
};
//// prepare target elements ////
/**
* As we prepared the tooltip elements, the targets also need preparation.
*
* You may define your own custom behavior.
*/
// when clicked on target, toggle visibilty of tooltip
clicktipTargetPrepare = function(o, el, config) {
return o.addClass(config.attribute + '_target')
.click(function() {
el.ht.tip.toggle();
return false;
});
};
// when hover over target, make tooltip appear
hovertipTargetPrepare = function(o, el, config) {
return o.addClass(config.attribute + '_target')
.hover(function() {
// show tip when mouse over target
hovertipShowUnderMouse(el);
},
function() {
// hide the tip
// add a delay so user can move mouse from the target to the tip
hovertipHideLater(el);
});
};
/**
* hovertipActivate() is our jQuery plugin function. It turns on hovertip or
* clicktip behavior for a set of elements.
*
* @param config
* controls aspects of tooltip behavior. Be sure to define
* 'attribute', 'showDelay' and 'hideDelay'.
*
* @param targetSelect
* function finds the targets of a given tooltip element.
*
* @param tipPrepare
* function alters the tooltip to display and behave properly
*
* @param targetPrepare
* function alters the target to display and behave properly.
*/
jQuery.fn.hovertipActivate = function(config, targetSelect, tipPrepare, targetPrepare) {
//alert('activating ' + this.size());
// unhide so jquery show/hide will work.
return this.css('display', 'block')
.hide() // don't show it until click
.each(function() {
if (!this.ht)
this.ht = new Object();
this.ht.config = config;
// find our targets
var targets = targetSelect(this, config);
if (targets && targets.size()) {
if (!this.ht.targets)
this.ht.targets = targetPrepare(targets, this, config);
else
this.ht.targets.add(targetPrepare(targets, this, config));
// listen to mouse move events so we know exatly where to place hovetips
targets.mousemove(hovertipMouseUpdate);
// prepare the tooltip element
// is it bad form to call $(this) here?
if (!this.ht.tip)
this.ht.tip = tipPrepare($(this), config);
}
})
;
};
/**
* Here's an example ready function which shows how to enable tooltips.
*
* You can make this considerably shorter by choosing only the markup style(s)
* you will use.
*
* You may also remove the code that wraps hovertips to produce drop-shadow FX
*
* Invoke this function or one like it from your $(document).ready().
*
* Here, we break the action up into several timout callbacks, to avoid
* locking up browsers.
*/
function hovertipInit() {
// specify the attribute name we use for our clicktips
var clicktipConfig = {'attribute':'clicktip'};
/**
* To enable this style of markup (id on tooltip):
* <span clicktip="foo">target</span>...
* <div id="foo" class="clicktip">blah blah</div>
*/
window.setTimeout(function() {
$('.clicktip').hovertipActivate(clicktipConfig,
targetSelectById,
clicktipPrepareWithCloseLink,
clicktipTargetPrepare);
}, 0);
/**
* To enable this style of markup (id on target):
* <span id="foo">target</span>...
* <div target="foo" class="clicktip">blah blah</div>
*/
window.setTimeout(function() {
$('.clicktip').hovertipActivate(clicktipConfig,
targetSelectByTargetAttribute,
clicktipPrepareWithCloseLink,
clicktipTargetPrepare);
}, 0);
// specify our configuration for hovertips, including delay times (millisec)
var hovertipConfig = {'attribute':'hovertip',
'showDelay': 300,
'hideDelay': 700};
// use <div class='hovertip'>blah blah</div>
var hovertipSelect = 'div.hovertip';
// OPTIONAL: here we wrap each hovertip to apply special effect. (i.e. drop shadow):
$(hovertipSelect).css('display', 'block').addClass('hovertip_wrap3').
wrap("<div class='hovertip_wrap0'><div class='hovertip_wrap1'><div class='hovertip_wrap2'>" +
"</div></div></div>").each(function() {
// fix class and attributes for newly wrapped elements
var tooltip = this.parentNode.parentNode.parentNode;
if (this.getAttribute('target'))
tooltip.setAttribute('target', this.getAttribute('target'));
if (this.getAttribute('id')) {
var id = this.getAttribute('id');
this.removeAttribute('id');
tooltip.setAttribute('id', id);
}
});
hovertipSelect = 'div.hovertip_wrap0';
// end optional FX section
/**
* To enable this style of markup (id on tooltip):
* <span hovertip="foo">target</span>...
* <div id="foo" class="hovertip">blah blah</div>
*/
window.setTimeout(function() {
$(hovertipSelect).hovertipActivate(hovertipConfig,
targetSelectById,
hovertipPrepare,
hovertipTargetPrepare);
}, 0);
/**
* To enable this style of markup (id on target):
* <span id="foo">target</span>...
* <div target="foo" class="hovertip">blah blah</div>
*/
window.setTimeout(function() {
$(hovertipSelect).hovertipActivate(hovertipConfig,
targetSelectByTargetAttribute,
hovertipPrepare,
hovertipTargetPrepare);
}, 0);
/**
* This next section enables this style of markup:
* <foo><span>target</span><span class="hovertip">blah blah</span></foo>
*
* With drop shadow effect.
*
*/
var hovertipSpanSelect = 'span.hovertip';
// activate hovertips with wrappers for FX (drop shadow):
$(hovertipSpanSelect).css('display', 'block').addClass('hovertip_wrap3').
wrap("<span class='hovertip_wrap0'><span class='hovertip_wrap1'><span class='hovertip_wrap2'>" +
"</span></span></span>").each(function() {
// fix class and attributes for newly wrapped elements
var tooltip = this.parentNode.parentNode.parentNode;
if (this.getAttribute('target'))
tooltip.setAttribute('target', this.getAttribute('target'));
if (this.getAttribute('id')) {
var id = this.getAttribute('id');
this.removeAttribute('id');
tooltip.setAttribute('id', id);
}
});
hovertipSpanSelect = 'span.hovertip_wrap0';
window.setTimeout(function() {
$(hovertipSpanSelect)
.hovertipActivate(hovertipConfig,
targetSelectByPrevious,
hovertipPrepare,
hovertipTargetPrepare);
}, 0);
}

View File

@@ -1,142 +0,0 @@
/*
jquery.layout 1.3.0 - Release Candidate 30.79
$Date: 2013-01-01 08:00:00 (Tue, 1 Jan 2013) $
$Rev: 303007 $
Copyright (c) 2013 Kevin Dalman (http://allpro.net)
Based on work by Fabrizio Balliano (http://www.fabrizioballiano.net)
Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
SEE: http://layout.jquery-dev.net/LICENSE.txt
Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc30.79
Docs: http://layout.jquery-dev.net/documentation.html
Tips: http://layout.jquery-dev.net/tips.html
Help: http://groups.google.com/group/jquery-ui-layout
*/
(function(b){var a=Math.min,d=Math.max,c=Math.floor,f=function(a){return"string"===b.type(a)},j=function(a,d){if(b.isArray(d))for(var c=0,j=d.length;c<j;c++){var h=d[c];try{f(h)&&(h=eval(h)),b.isFunction(h)&&h(a)}catch(k){}}};b.layout={version:"1.3.rc30.79",revision:0.033007,browser:{},effects:{slide:{all:{duration:"fast"},north:{direction:"up"},south:{direction:"down"},east:{direction:"right"},west:{direction:"left"}},drop:{all:{duration:"slow"},north:{direction:"up"},south:{direction:"down"},east:{direction:"right"},
west:{direction:"left"}},scale:{all:{duration:"fast"}},blind:{},clip:{},explode:{},fade:{},fold:{},puff:{},size:{all:{easing:"swing"}}},config:{optionRootKeys:"effects panes north south west east center".split(" "),allPanes:["north","south","west","east","center"],borderPanes:["north","south","west","east"],oppositeEdge:{north:"south",south:"north",east:"west",west:"east"},offscreenCSS:{left:"-99999px",right:"auto"},offscreenReset:"offscreenReset",hidden:{visibility:"hidden"},visible:{visibility:"visible"},
resizers:{cssReq:{position:"absolute",padding:0,margin:0,fontSize:"1px",textAlign:"left",overflow:"hidden"},cssDemo:{background:"#DDD",border:"none"}},togglers:{cssReq:{position:"absolute",display:"block",padding:0,margin:0,overflow:"hidden",textAlign:"center",fontSize:"1px",cursor:"pointer",zIndex:1},cssDemo:{background:"#AAA"}},content:{cssReq:{position:"relative"},cssDemo:{overflow:"auto",padding:"10px"},cssDemoPane:{overflow:"hidden",padding:0}},panes:{cssReq:{position:"absolute",margin:0},cssDemo:{padding:"10px",
background:"#FFF",border:"1px solid #BBB",overflow:"auto"}},north:{side:"top",sizeType:"Height",dir:"horz",cssReq:{top:0,bottom:"auto",left:0,right:0,width:"auto"}},south:{side:"bottom",sizeType:"Height",dir:"horz",cssReq:{top:"auto",bottom:0,left:0,right:0,width:"auto"}},east:{side:"right",sizeType:"Width",dir:"vert",cssReq:{left:"auto",right:0,top:"auto",bottom:"auto",height:"auto"}},west:{side:"left",sizeType:"Width",dir:"vert",cssReq:{left:0,right:"auto",top:"auto",bottom:"auto",height:"auto"}},
center:{dir:"center",cssReq:{left:"auto",right:"auto",top:"auto",bottom:"auto",height:"auto",width:"auto"}}},callbacks:{},getParentPaneElem:function(a){a=b(a);if(a=a.data("layout")||a.data("parentLayout")){a=a.container;if(a.data("layoutPane"))return a;a=a.closest("."+b.layout.defaults.panes.paneClass);if(a.data("layoutPane"))return a}return null},getParentPaneInstance:function(a){return(a=b.layout.getParentPaneElem(a))?a.data("layoutPane"):null},getParentLayoutInstance:function(a){return(a=b.layout.getParentPaneElem(a))?
a.data("parentLayout"):null},getEventObject:function(b){return"object"===typeof b&&b.stopPropagation?b:null},parsePaneName:function(a){var d=b.layout.getEventObject(a);d&&(d.stopPropagation(),a=b(this).data("layoutEdge"));a&&!/^(west|east|north|south|center)$/.test(a)&&(b.layout.msg('LAYOUT ERROR - Invalid pane-name: "'+a+'"'),a="error");return a},plugins:{draggable:!!b.fn.draggable,effects:{core:!!b.effects,slide:b.effects&&(b.effects.slide||b.effects.effect&&b.effects.effect.slide)}},onCreate:[],
onLoad:[],onReady:[],onDestroy:[],onUnload:[],afterOpen:[],afterClose:[],scrollbarWidth:function(){return window.scrollbarWidth||b.layout.getScrollbarSize("width")},scrollbarHeight:function(){return window.scrollbarHeight||b.layout.getScrollbarSize("height")},getScrollbarSize:function(a){var d=b('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll;"></div>').appendTo("body"),c={width:d.css("width")-d[0].clientWidth,height:d.height()-d[0].clientHeight};
d.remove();window.scrollbarWidth=c.width;window.scrollbarHeight=c.height;return a.match(/^(width|height)$/)?c[a]:c},showInvisibly:function(b,a){if(b&&b.length&&(a||"none"===b.css("display"))){var d=b[0].style,d={display:d.display||"",visibility:d.visibility||""};b.css({display:"block",visibility:"hidden"});return d}return{}},getElementDimensions:function(a,c){var f={css:{},inset:{}},j=f.css,h={bottom:0},k=b.layout.cssNum,p=a.offset(),O,R,D;f.offsetLeft=p.left;f.offsetTop=p.top;c||(c={});b.each(["Left",
"Right","Top","Bottom"],function(d,k){O=j["border"+k]=b.layout.borderWidth(a,k);R=j["padding"+k]=b.layout.cssNum(a,"padding"+k);D=k.toLowerCase();f.inset[D]=0<=c[D]?c[D]:R;h[D]=f.inset[D]+O});j.width=a.width();j.height=a.height();j.top=k(a,"top",!0);j.bottom=k(a,"bottom",!0);j.left=k(a,"left",!0);j.right=k(a,"right",!0);f.outerWidth=a.outerWidth();f.outerHeight=a.outerHeight();f.innerWidth=d(0,f.outerWidth-h.left-h.right);f.innerHeight=d(0,f.outerHeight-h.top-h.bottom);f.layoutWidth=a.innerWidth();
f.layoutHeight=a.innerHeight();return f},getElementStyles:function(b,a){var d={},c=b[0].style,f=a.split(","),k=["Top","Bottom","Left","Right"],j=["Color","Style","Width"],h,p,D,x,A,r;for(x=0;x<f.length;x++)if(h=f[x],h.match(/(border|padding|margin)$/))for(A=0;4>A;A++)if(p=k[A],"border"===h)for(r=0;3>r;r++)D=j[r],d[h+p+D]=c[h+p+D];else d[h+p]=c[h+p];else d[h]=c[h];return d},cssWidth:function(a,c){if(0>=c)return 0;var f=!b.layout.browser.boxModel?"border-box":b.support.boxSizing?a.css("boxSizing"):
"content-box",j=b.layout.borderWidth,h=b.layout.cssNum,k=c;"border-box"!==f&&(k-=j(a,"Left")+j(a,"Right"));"content-box"===f&&(k-=h(a,"paddingLeft")+h(a,"paddingRight"));return d(0,k)},cssHeight:function(a,c){if(0>=c)return 0;var f=!b.layout.browser.boxModel?"border-box":b.support.boxSizing?a.css("boxSizing"):"content-box",j=b.layout.borderWidth,h=b.layout.cssNum,k=c;"border-box"!==f&&(k-=j(a,"Top")+j(a,"Bottom"));"content-box"===f&&(k-=h(a,"paddingTop")+h(a,"paddingBottom"));return d(0,k)},cssNum:function(a,
d,c){a.jquery||(a=b(a));var f=b.layout.showInvisibly(a);d=b.css(a[0],d,!0);c=c&&"auto"==d?d:Math.round(parseFloat(d)||0);a.css(f);return c},borderWidth:function(a,d){a.jquery&&(a=a[0]);var c="border"+d.substr(0,1).toUpperCase()+d.substr(1);return"none"===b.css(a,c+"Style",!0)?0:Math.round(parseFloat(b.css(a,c+"Width",!0))||0)},isMouseOverElem:function(a,d){var c=b(d||this),f=c.offset(),j=f.top,f=f.left,k=f+c.outerWidth(),c=j+c.outerHeight(),h=a.pageX,p=a.pageY;return b.layout.browser.msie&&0>h&&0>
p||h>=f&&h<=k&&p>=j&&p<=c},msg:function(a,d,c,f){b.isPlainObject(a)&&window.debugData?("string"===typeof d?(f=c,c=d):"object"===typeof c&&(f=c,c=null),c=c||"log( <object> )",f=b.extend({sort:!1,returnHTML:!1,display:!1},f),!0===d||f.display?debugData(a,c,f):window.console&&console.log(debugData(a,c,f))):d?alert(a):window.console?console.log(a):(d=b("#layoutLogger"),d.length||(d=b('<div id="layoutLogger" style="position: '+(b.support.fixedPosition?"fixed":"absolute")+'; top: 5px; z-index: 999999; max-width: 25%; overflow: hidden; border: 1px solid #000; border-radius: 5px; background: #FBFBFB; box-shadow: 0 2px 10px rgba(0,0,0,0.3);"><div style="font-size: 13px; font-weight: bold; padding: 5px 10px; background: #F6F6F6; border-radius: 5px 5px 0 0; cursor: move;"><span style="float: right; padding-left: 7px; cursor: pointer;" title="Remove Console" onclick="$(this).closest(\'#layoutLogger\').remove()">X</span>Layout console.log</div><ul style="font-size: 13px; font-weight: none; list-style: none; margin: 0; padding: 0 0 2px;"></ul></div>').appendTo("body"),
d.css("left",b(window).width()-d.outerWidth()-5),b.ui.draggable&&d.draggable({handle:":first-child"})),d.children("ul").append('<li style="padding: 4px 10px; margin: 0; border-top: 1px solid #CCC;">'+a.replace(/\</g,"&lt;").replace(/\>/g,"&gt;")+"</li>"))}};var h=navigator.userAgent.toLowerCase(),p=/(chrome)[ \/]([\w.]+)/.exec(h)||/(webkit)[ \/]([\w.]+)/.exec(h)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(h)||/(msie) ([\w.]+)/.exec(h)||0>h.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(h)||
[],h=p[1]||"",p=p[2]||0,x="msie"===h;b.layout.browser={version:p,safari:"webkit"===h,webkit:"chrome"===h,msie:x,isIE6:x&&6==p,boxModel:!x||!1!==b.support.boxModel};h&&(b.layout.browser[h]=!0);x&&b(function(){b.layout.browser.boxModel=b.support.boxModel});b.layout.defaults={name:"",containerClass:"ui-layout-container",inset:null,scrollToBookmarkOnLoad:!0,resizeWithWindow:!0,resizeWithWindowDelay:200,resizeWithWindowMaxDelay:0,maskPanesEarly:!1,onresizeall_start:null,onresizeall_end:null,onload_start:null,
onload_end:null,onunload_start:null,onunload_end:null,initPanes:!0,showErrorMessages:!0,showDebugMessages:!1,zIndex:null,zIndexes:{pane_normal:0,content_mask:1,resizer_normal:2,pane_sliding:100,pane_animate:1E3,resizer_drag:1E4},errors:{pane:"pane",selector:"selector",addButtonError:"Error Adding Button\nInvalid ",containerMissing:"UI Layout Initialization Error\nThe specified layout-container does not exist.",centerPaneMissing:"UI Layout Initialization Error\nThe center-pane element does not exist.\nThe center-pane is a required element.",
noContainerHeight:"UI Layout Initialization Warning\nThe layout-container \"CONTAINER\" has no height.\nTherefore the layout is 0-height and hence 'invisible'!",callbackError:"UI Layout Callback Error\nThe EVENT callback is not a valid function."},panes:{applyDemoStyles:!1,closable:!0,resizable:!0,slidable:!0,initClosed:!1,initHidden:!1,contentSelector:".ui-layout-content",contentIgnoreSelector:".ui-layout-ignore",findNestedContent:!1,paneClass:"ui-layout-pane",resizerClass:"ui-layout-resizer",togglerClass:"ui-layout-toggler",
buttonClass:"ui-layout-button",minSize:0,maxSize:0,spacing_open:6,spacing_closed:6,togglerLength_open:50,togglerLength_closed:50,togglerAlign_open:"center",togglerAlign_closed:"center",togglerContent_open:"",togglerContent_closed:"",resizerDblClickToggle:!0,autoResize:!0,autoReopen:!0,resizerDragOpacity:1,maskContents:!1,maskObjects:!1,maskZindex:null,resizingGrid:!1,livePaneResizing:!1,liveContentResizing:!1,liveResizingTolerance:1,sliderCursor:"pointer",slideTrigger_open:"click",slideTrigger_close:"mouseleave",
slideDelay_open:300,slideDelay_close:300,hideTogglerOnSlide:!1,preventQuickSlideClose:b.layout.browser.webkit,preventPrematureSlideClose:!1,tips:{Open:"Open",Close:"Close",Resize:"Resize",Slide:"Slide Open",Pin:"Pin",Unpin:"Un-Pin",noRoomToOpen:"Not enough room to show this panel.",minSizeWarning:"Panel has reached its minimum size",maxSizeWarning:"Panel has reached its maximum size"},showOverflowOnHover:!1,enableCursorHotkey:!0,customHotkeyModifier:"SHIFT",fxName:"slide",fxSpeed:null,fxSettings:{},
fxOpacityFix:!0,animatePaneSizing:!1,children:null,containerSelector:"",initChildren:!0,destroyChildren:!0,resizeChildren:!0,triggerEventsOnLoad:!1,triggerEventsDuringLiveResize:!0,onshow_start:null,onshow_end:null,onhide_start:null,onhide_end:null,onopen_start:null,onopen_end:null,onclose_start:null,onclose_end:null,onresize_start:null,onresize_end:null,onsizecontent_start:null,onsizecontent_end:null,onswap_start:null,onswap_end:null,ondrag_start:null,ondrag_end:null},north:{paneSelector:".ui-layout-north",
size:"auto",resizerCursor:"n-resize",customHotkey:""},south:{paneSelector:".ui-layout-south",size:"auto",resizerCursor:"s-resize",customHotkey:""},east:{paneSelector:".ui-layout-east",size:200,resizerCursor:"e-resize",customHotkey:""},west:{paneSelector:".ui-layout-west",size:200,resizerCursor:"w-resize",customHotkey:""},center:{paneSelector:".ui-layout-center",minWidth:0,minHeight:0}};b.layout.optionsMap={layout:"name instanceKey stateManagement effects inset zIndexes errors zIndex scrollToBookmarkOnLoad showErrorMessages maskPanesEarly outset resizeWithWindow resizeWithWindowDelay resizeWithWindowMaxDelay onresizeall onresizeall_start onresizeall_end onload onload_start onload_end onunload onunload_start onunload_end".split(" "),
center:"paneClass contentSelector contentIgnoreSelector findNestedContent applyDemoStyles triggerEventsOnLoad showOverflowOnHover maskContents maskObjects liveContentResizing containerSelector children initChildren resizeChildren destroyChildren onresize onresize_start onresize_end onsizecontent onsizecontent_start onsizecontent_end".split(" "),noDefault:["paneSelector","resizerCursor","customHotkey"]};b.layout.transformData=function(a,d){var c=d?{panes:{},center:{}}:{},f,j,k,h,p,x,D;if("object"!==
typeof a)return c;for(j in a){f=c;p=a[j];k=j.split("__");D=k.length-1;for(x=0;x<=D;x++)h=k[x],x===D?f[h]=b.isPlainObject(p)?b.layout.transformData(p):p:(f[h]||(f[h]={}),f=f[h])}return c};b.layout.backwardCompatibility={map:{applyDefaultStyles:"applyDemoStyles",childOptions:"children",initChildLayout:"initChildren",destroyChildLayout:"destroyChildren",resizeChildLayout:"resizeChildren",resizeNestedLayout:"resizeChildren",resizeWhileDragging:"livePaneResizing",resizeContentWhileDragging:"liveContentResizing",
triggerEventsWhileDragging:"triggerEventsDuringLiveResize",maskIframesOnResize:"maskContents",useStateCookie:"stateManagement.enabled","cookie.autoLoad":"stateManagement.autoLoad","cookie.autoSave":"stateManagement.autoSave","cookie.keys":"stateManagement.stateKeys","cookie.name":"stateManagement.cookie.name","cookie.domain":"stateManagement.cookie.domain","cookie.path":"stateManagement.cookie.path","cookie.expires":"stateManagement.cookie.expires","cookie.secure":"stateManagement.cookie.secure",
noRoomToOpenTip:"tips.noRoomToOpen",togglerTip_open:"tips.Close",togglerTip_closed:"tips.Open",resizerTip:"tips.Resize",sliderTip:"tips.Slide"},renameOptions:function(a){function d(b,c){for(var f=b.split("."),k=f.length-1,j={branch:a,key:f[k]},r=0,q;r<k;r++)q=f[r],j.branch=void 0==j.branch[q]?c?j.branch[q]={}:{}:j.branch[q];return j}var c=b.layout.backwardCompatibility.map,f,j,k,h;for(h in c)f=d(h),k=f.branch[f.key],void 0!==k&&(j=d(c[h],!0),j.branch[j.key]=k,delete f.branch[f.key])},renameAllOptions:function(a){var d=
b.layout.backwardCompatibility.renameOptions;d(a);a.defaults&&("object"!==typeof a.panes&&(a.panes={}),b.extend(!0,a.panes,a.defaults),delete a.defaults);a.panes&&d(a.panes);b.each(b.layout.config.allPanes,function(b,c){a[c]&&d(a[c])});return a}};b.fn.layout=function(h){function p(e){if(!e)return!0;var w=e.keyCode;if(33>w)return!0;var m={38:"north",40:"south",37:"west",39:"east"},a=e.shiftKey,g=e.ctrlKey,t,n,d,c;g&&(37<=w&&40>=w)&&r[m[w]].enableCursorHotkey?c=m[w]:(g||a)&&b.each(k.borderPanes,function(e,
b){t=r[b];n=t.customHotkey;d=t.customHotkeyModifier;if(a&&"SHIFT"==d||g&&"CTRL"==d||g&&a)if(n&&w===(isNaN(n)||9>=n?n.toUpperCase().charCodeAt(0):n))return c=b,!1});if(!c||!y[c]||!r[c].closable||q[c].isHidden)return!0;na(c);e.stopPropagation();return e.returnValue=!1}function x(e){if(H()){this&&this.tagName&&(e=this);var w;f(e)?w=y[e]:b(e).data("layoutRole")?w=b(e):b(e).parents().each(function(){if(b(this).data("layoutRole"))return w=b(this),!1});if(w&&w.length){var m=w.data("layoutEdge");e=q[m];e.cssSaved&&
X(m);if(e.isSliding||e.isResizing||e.isClosed)e.cssSaved=!1;else{var a={zIndex:r.zIndexes.resizer_normal+1},g={},t=w.css("overflow"),n=w.css("overflowX"),d=w.css("overflowY");"visible"!=t&&(g.overflow=t,a.overflow="visible");n&&!n.match(/(visible|auto)/)&&(g.overflowX=n,a.overflowX="visible");d&&!d.match(/(visible|auto)/)&&(g.overflowY=n,a.overflowY="visible");e.cssSaved=g;w.css(a);b.each(k.allPanes,function(e,b){b!=m&&X(b)})}}}}function X(e){if(H()){this&&this.tagName&&(e=this);var w;f(e)?w=y[e]:
b(e).data("layoutRole")?w=b(e):b(e).parents().each(function(){if(b(this).data("layoutRole"))return w=b(this),!1});if(w&&w.length){e=w.data("layoutEdge");e=q[e];var m=e.cssSaved||{};!e.isSliding&&!e.isResizing&&w.css("zIndex",r.zIndexes.pane_normal);w.css(m);e.cssSaved=!1}}}var G=b.layout.browser,k=b.layout.config,Q=b.layout.cssWidth,O=b.layout.cssHeight,R=b.layout.getElementDimensions,D=b.layout.getElementStyles,Ma=b.layout.getEventObject,A=b.layout.parsePaneName,r=b.extend(!0,{},b.layout.defaults);
r.effects=b.extend(!0,{},b.layout.effects);var q={id:"layout"+b.now(),initialized:!1,paneResizing:!1,panesSliding:{},container:{innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0,layoutWidth:0,layoutHeight:0},north:{childIdx:0},south:{childIdx:0},east:{childIdx:0},west:{childIdx:0},center:{childIdx:0}},ba={north:null,south:null,east:null,west:null,center:null},M={data:{},set:function(e,b,m){M.clear(e);M.data[e]=setTimeout(b,m)},clear:function(e){var b=M.data;b[e]&&(clearTimeout(b[e]),delete b[e])}},
ca=function(e,w,m){var a=r;(a.showErrorMessages&&!m||m&&a.showDebugMessages)&&b.layout.msg(a.name+" / "+e,!1!==w);return!1},C=function(e,w,m){var a=w&&f(w),g=a?q[w]:q,t=a?r[w]:r,n=r.name,d=e+(e.match(/_/)?"":"_end"),c=d.match(/_end$/)?d.substr(0,d.length-4):"",l=t[d]||t[c],h="NC",k=[];!a&&"boolean"===b.type(w)&&(m=w,w="");if(l)try{f(l)&&(l.match(/,/)?(k=l.split(","),l=eval(k[0])):l=eval(l)),b.isFunction(l)&&(h=k.length?l(k[1]):a?l(w,y[w],g,t,n):l(z,g,t,n))}catch(j){ca(r.errors.callbackError.replace(/EVENT/,
b.trim((w||"")+" "+d)),!1),"string"===b.type(j)&&string.length&&ca("Exception: "+j,!1)}!m&&!1!==h&&(a?(m=y[w],t=r[w],g=q[w],m.triggerHandler("layoutpane"+d,[w,m,g,t,n]),c&&m.triggerHandler("layoutpane"+c,[w,m,g,t,n])):(u.triggerHandler("layout"+d,[z,g,t,n]),c&&u.triggerHandler("layout"+c,[z,g,t,n])));a&&"onresize_end"===e&&db(w+"",!0);return h},eb=function(e){if(!G.mozilla){var b=y[e];"IFRAME"===q[e].tagName?b.css(k.hidden).css(k.visible):b.find("IFRAME").css(k.hidden).css(k.visible)}},ya=function(e){var b=
y[e];e=k[e].dir;b={minWidth:1001-Q(b,1E3),minHeight:1001-O(b,1E3)};"horz"===e&&(b.minSize=b.minHeight);"vert"===e&&(b.minSize=b.minWidth);return b},fa=function(e,w,m){m||(m=k[e].dir);f(w)&&w.match(/%/)&&(w="100%"===w?-1:parseInt(w,10)/100);if(0===w)return 0;if(1<=w)return parseInt(w,10);var a=r,g=0;"horz"==m?g=v.innerHeight-(y.north?a.north.spacing_open:0)-(y.south?a.south.spacing_open:0):"vert"==m&&(g=v.innerWidth-(y.west?a.west.spacing_open:0)-(y.east?a.east.spacing_open:0));if(-1===w)return g;
if(0<w)return c(g*w);if("center"==e)return 0;m="horz"===m?"height":"width";a=y[e];e="height"===m?U[e]:!1;var g=b.layout.showInvisibly(a),t=a.css(m),n=e?e.css(m):0;a.css(m,"auto");e&&e.css(m,"auto");w="height"===m?a.outerHeight():a.outerWidth();a.css(m,t).css(g);e&&e.css(m,n);return w},ga=function(e,b){var a=y[e],E=r[e],g=q[e],t=b?E.spacing_open:0,E=b?E.spacing_closed:0;return!a||g.isHidden?0:g.isClosed||g.isSliding&&b?E:"horz"===k[e].dir?a.outerHeight()+t:a.outerWidth()+t},Y=function(e,b){if(H()){var m=
r[e],E=q[e],g=k[e],t=g.dir;g.sizeType.toLowerCase();var g=void 0!=b?b:E.isSliding,n=m.spacing_open,c=k.oppositeEdge[e],f=q[c],l=y[c],h=!l||!1===f.isVisible||f.isSliding?0:"horz"==t?l.outerHeight():l.outerWidth(),c=(!l||f.isHidden?0:r[c][!1!==f.isClosed?"spacing_closed":"spacing_open"])||0,f="horz"==t?v.innerHeight:v.innerWidth,l=ya("center"),l="horz"==t?d(r.center.minHeight,l.minHeight):d(r.center.minWidth,l.minWidth),g=f-n-(g?0:fa("center",l,t)+h+c),t=E.minSize=d(fa(e,m.minSize),ya(e).minSize),g=
E.maxSize=a(m.maxSize?fa(e,m.maxSize):1E5,g),E=E.resizerPosition={},n=v.inset.top,h=v.inset.left,c=v.innerWidth,f=v.innerHeight,m=m.spacing_open;switch(e){case "north":E.min=n+t;E.max=n+g;break;case "west":E.min=h+t;E.max=h+g;break;case "south":E.min=n+f-g-m;E.max=n+f-t-m;break;case "east":E.min=h+c-g-m,E.max=h+c-t-m}}},Na=function(e,a){var m=b(e),E=m.data("layoutRole"),g=m.data("layoutEdge"),t=r[g][E+"Class"],g="-"+g,n=m.hasClass(t+"-closed")?"-closed":"-open",d="-closed"===n?"-open":"-closed",n=
t+"-hover "+(t+g+"-hover ")+(t+n+"-hover ")+(t+g+n+"-hover ");a&&(n+=t+d+"-hover "+(t+g+d+"-hover "));"resizer"==E&&m.hasClass(t+"-sliding")&&(n+=t+"-sliding-hover "+(t+g+"-sliding-hover "));return b.trim(n)},Oa=function(e,a){var m=b(a||this);e&&"toggler"===m.data("layoutRole")&&e.stopPropagation();m.addClass(Na(m))},da=function(e,a){var m=b(a||this);m.removeClass(Na(m,!0))},fb=function(){var e=b(this).data("layoutEdge"),a=q[e];!a.isClosed&&(!a.isResizing&&!q.paneResizing)&&(b.fn.disableSelection&&
b("body").disableSelection(),r.maskPanesEarly&&va(e,{resizing:!0}))},gb=function(e,a){var m=a||this,E=b(m).data("layoutEdge"),g=E+"ResizerLeave";M.clear(E+"_openSlider");M.clear(g);a?q.paneResizing||(b.fn.enableSelection&&b("body").enableSelection(),r.maskPanesEarly&&za()):M.set(g,function(){gb(e,m)},200)},H=function(){return q.initialized||q.creatingLayout?!0:Aa()},Aa=function(e){var a=r;if(!u.is(":visible"))return!e&&(G.webkit&&"BODY"===u[0].tagName)&&setTimeout(function(){Aa(!0)},50),!1;if(!hb("center").length)return ca(a.errors.centerPaneMissing);
q.creatingLayout=!0;b.extend(v,R(u,a.inset));A(void 0);b.each(k.allPanes,function(e,b){ib(b,!0)});Pa();b.each(k.borderPanes,function(e,b){y[b]&&q[b].isVisible&&(Y(b),ha(b))});ia("center");b.each(k.allPanes,function(e,b){jb(b)});a.scrollToBookmarkOnLoad&&(e=self.location,e.hash&&e.replace(e.hash));z.hasParentLayout?a.resizeWithWindow=!1:a.resizeWithWindow&&b(window).bind("resize."+K,Ab);delete q.creatingLayout;q.initialized=!0;j(z,b.layout.onReady);C("onload_end");return!0},Qa=function(e,a){var m=
A.call(this,e),d=y[m];if(d){var g=U[m],t=q[m],n=r[m],c=r.stateManagement||{},n=a?n.children=a:n.children;if(b.isPlainObject(n))n=[n];else if(!n||!b.isArray(n))return;b.each(n,function(e,a){b.isPlainObject(a)&&(a.containerSelector?d.find(a.containerSelector):g||d).each(function(){var e=b(this),g=e.data("layout");if(!g){kb({container:e,options:a},t);if(c.includeChildren&&q.stateData[m]){var g=(q.stateData[m].children||{})[a.instanceKey],w=a.stateManagement||(a.stateManagement={autoLoad:!0});!0===w.autoLoad&&
g&&(w.autoSave=!1,w.includeChildren=!0,w.autoLoad=b.extend(!0,{},g))}(g=e.layout(a))&&Ba(m,g)}})})}},kb=function(e,b){var a=e.container,d=e.options,g=d.stateManagement,t=d.instanceKey||a.data("layoutInstanceKey");t||(t=(g&&g.cookie?g.cookie.name:"")||d.name);t=t?t.replace(/[^\w-]/gi,"_").replace(/_{2,}/g,"_"):"layout"+ ++b.childIdx;d.instanceKey=t;a.data("layoutInstanceKey",t);return t},Ba=function(e,a){var m=y[e],d=ba[e],g=q[e];b.isPlainObject(d)&&(b.each(d,function(e,b){b.destroyed&&delete d[e]}),
b.isEmptyObject(d)&&(d=ba[e]=null));!a&&!d&&(a=m.data("layout"));a&&(a.hasParentLayout=!0,m=a.options,kb(a,g),d||(d=ba[e]={}),d[m.instanceKey]=a.container.data("layout"));z[e].children=ba[e];a||Qa(e)},Ab=function(){var e=r,b=Number(e.resizeWithWindowDelay);10>b&&(b=100);M.clear("winResize");M.set("winResize",function(){M.clear("winResize");M.clear("winResizeRepeater");var b=R(u,e.inset);(b.innerWidth!==v.innerWidth||b.innerHeight!==v.innerHeight)&&oa()},b);M.data.winResizeRepeater||lb()},lb=function(){var e=
Number(r.resizeWithWindowMaxDelay);0<e&&M.set("winResizeRepeater",function(){lb();oa()},e)},mb=function(){C("onunload_start");j(z,b.layout.onUnload);C("onunload_end")},nb=function(e){e=e?e.split(","):k.borderPanes;b.each(e,function(e,a){var d=r[a];if(d.enableCursorHotkey||d.customHotkey)return b(document).bind("keydown."+K,p),!1})},hb=function(e){e=r[e].paneSelector;if("#"===e.substr(0,1))return u.find(e).eq(0);var b=u.children(e).eq(0);return b.length?b:u.children("form:first").children(e).eq(0)},
ib=function(e,b){if(b||H()){var m=r[e],c=q[e],g=k[e],t=g.dir,n="center"===e,f={},h=y[e],l,j;h?Ra(e,!1,!0,!1):U[e]=!1;h=y[e]=hb(e);if(h.length){h.data("layoutCSS")||h.data("layoutCSS",D(h,"position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border"));z[e]={name:e,pane:y[e],content:U[e],options:r[e],state:q[e],children:ba[e]};h.data({parentLayout:z,layoutPane:z[e],layoutEdge:e,layoutRole:"pane"}).css(g.cssReq).css("zIndex",r.zIndexes.pane_normal).css(m.applyDemoStyles?
g.cssDemo:{}).addClass(m.paneClass+" "+m.paneClass+"-"+e).bind("mouseenter."+K,Oa).bind("mouseleave."+K,da);g={hide:"",show:"",toggle:"",close:"",open:"",slideOpen:"",slideClose:"",slideToggle:"",size:"sizePane",sizePane:"sizePane",sizeContent:"",sizeHandles:"",enableClosable:"",disableClosable:"",enableSlideable:"",disableSlideable:"",enableResizable:"",disableResizable:"",swapPanes:"swapPanes",swap:"swapPanes",move:"swapPanes",removePane:"removePane",remove:"removePane",createChildren:"",resizeChildren:"",
resizeAll:"resizeAll",resizeLayout:"resizeAll"};for(j in g)h.bind("layoutpane"+j.toLowerCase()+"."+K,z[g[j]||j]);Sa(e,!1);n||(l=c.size=fa(e,m.size),n=fa(e,m.minSize)||1,j=fa(e,m.maxSize)||1E5,0<l&&(l=d(a(l,j),n)),c.autoResize=m.autoResize,c.isClosed=!1,c.isSliding=!1,c.isResizing=!1,c.isHidden=!1,c.pins||(c.pins=[]));c.tagName=h[0].tagName;c.edge=e;c.noRoom=!1;c.isVisible=!0;ob(e);"horz"===t?f.height=O(h,l):"vert"===t&&(f.width=Q(h,l));h.css(f);"horz"!=t&&ia(e,!0);q.initialized&&(Pa(e),nb(e));m.initClosed&&
m.closable&&!m.initHidden?ja(e,!0,!0):m.initHidden||m.initClosed?Ta(e):c.noRoom||h.css("display","block");h.css("visibility","visible");m.showOverflowOnHover&&h.hover(x,X);q.initialized&&jb(e)}else y[e]=!1}},jb=function(e){var b=y[e],a=q[e],d=r[e];b&&(b.data("layout")&&Ba(e,b.data("layout")),a.isVisible&&(q.initialized?oa():pa(e),d.triggerEventsOnLoad?C("onresize_end",e):db(e,!0)),d.initChildren&&d.children&&Qa(e))},ob=function(e){e=e?e.split(","):k.borderPanes;b.each(e,function(e,b){var a=y[b],g=
F[b],d=q[b],c=k[b].side,f={};if(a){switch(b){case "north":f.top=v.inset.top;f.left=v.inset.left;f.right=v.inset.right;break;case "south":f.bottom=v.inset.bottom;f.left=v.inset.left;f.right=v.inset.right;break;case "west":f.left=v.inset.left;break;case "east":f.right=v.inset.right}a.css(f);g&&d.isClosed?g.css(c,v.inset[c]):g&&!d.isHidden&&g.css(c,v.inset[c]+ga(b))}})},Pa=function(e){e=e?e.split(","):k.borderPanes;b.each(e,function(e,a){var d=y[a];F[a]=!1;P[a]=!1;if(d){var g=r[a],d=q[a],c="#"===g.paneSelector.substr(0,
1)?g.paneSelector.substr(1):"",n=g.resizerClass,f=g.togglerClass,h="-"+a,l=z[a],j=l.resizer=F[a]=b("<div></div>"),l=l.toggler=g.closable?P[a]=b("<div></div>"):!1;!d.isVisible&&g.slidable&&j.attr("title",g.tips.Slide).css("cursor",g.sliderCursor);j.attr("id",c?c+"-resizer":"").data({parentLayout:z,layoutPane:z[a],layoutEdge:a,layoutRole:"resizer"}).css(k.resizers.cssReq).css("zIndex",r.zIndexes.resizer_normal).css(g.applyDemoStyles?k.resizers.cssDemo:{}).addClass(n+" "+n+h).hover(Oa,da).hover(fb,gb).appendTo(u);
g.resizerDblClickToggle&&j.bind("dblclick."+K,na);l&&(l.attr("id",c?c+"-toggler":"").data({parentLayout:z,layoutPane:z[a],layoutEdge:a,layoutRole:"toggler"}).css(k.togglers.cssReq).css(g.applyDemoStyles?k.togglers.cssDemo:{}).addClass(f+" "+f+h).hover(Oa,da).bind("mouseenter",fb).appendTo(j),g.togglerContent_open&&b("<span>"+g.togglerContent_open+"</span>").data({layoutEdge:a,layoutRole:"togglerContent"}).data("layoutRole","togglerContent").data("layoutEdge",a).addClass("content content-open").css("display",
"none").appendTo(l),g.togglerContent_closed&&b("<span>"+g.togglerContent_closed+"</span>").data({layoutEdge:a,layoutRole:"togglerContent"}).addClass("content content-closed").css("display","none").appendTo(l),pb(a));var g=a,B=b.layout.plugins.draggable,g=g?g.split(","):k.borderPanes;b.each(g,function(e,a){var g=r[a];if(!B||!y[a]||!g.resizable)return g.resizable=!1,!0;var m=q[a],w=r.zIndexes,d=k[a],c="horz"==d.dir?"top":"left",t=F[a],n=g.resizerClass,f=0,l,h,E=n+"-drag",j=n+"-"+a+"-drag",J=n+"-dragging",
zb=n+"-"+a+"-dragging",cb=n+"-dragging-limit",v=n+"-"+a+"-dragging-limit",x=!1;m.isClosed||t.attr("title",g.tips.Resize).css("cursor",g.resizerCursor);t.draggable({containment:u[0],axis:"horz"==d.dir?"y":"x",delay:0,distance:1,grid:g.resizingGrid,helper:"clone",opacity:g.resizerDragOpacity,addClasses:!1,zIndex:w.resizer_drag,start:function(e,w){g=r[a];m=q[a];h=g.livePaneResizing;if(!1===C("ondrag_start",a))return!1;m.isResizing=!0;q.paneResizing=a;M.clear(a+"_closeSlider");Y(a);l=m.resizerPosition;
f=w.position[c];t.addClass(E+" "+j);x=!1;b("body").disableSelection();va(a,{resizing:!0})},drag:function(e,b){x||(b.helper.addClass(J+" "+zb).css({right:"auto",bottom:"auto"}).children().css("visibility","hidden"),x=!0,m.isSliding&&y[a].css("zIndex",w.pane_sliding));var d=0;b.position[c]<l.min?(b.position[c]=l.min,d=-1):b.position[c]>l.max&&(b.position[c]=l.max,d=1);d?(b.helper.addClass(cb+" "+v),window.defaultStatus=0<d&&a.match(/(north|west)/)||0>d&&a.match(/(south|east)/)?g.tips.maxSizeWarning:
g.tips.minSizeWarning):(b.helper.removeClass(cb+" "+v),window.defaultStatus="");h&&Math.abs(b.position[c]-f)>=g.liveResizingTolerance&&(f=b.position[c],p(e,b,a))},stop:function(e,g){b("body").enableSelection();window.defaultStatus="";t.removeClass(E+" "+j);m.isResizing=!1;q.paneResizing=!1;p(e,g,a,!0)}})});var p=function(b,e,a,g){var m=e.position,w=k[a];b=r[a];e=q[a];var d;switch(a){case "north":d=m.top;break;case "west":d=m.left;break;case "south":d=v.layoutHeight-m.top-b.spacing_open;break;case "east":d=
v.layoutWidth-m.left-b.spacing_open}d-=v.inset[w.side];g?(!1!==C("ondrag_end",a)&&Ca(a,d,!1,!0),za(!0),e.isSliding&&va(a,{resizing:!0})):Math.abs(d-e.size)<b.liveResizingTolerance||(Ca(a,d,!1,!0),ea.each(qb))};d.isVisible?Ua(a):(Da(a),ma(a,!0))}});qa()},Sa=function(b,a){if(H()){var m=r[b],d=m.contentSelector,g=z[b],c=y[b],n;d&&(n=g.content=U[b]=m.findNestedContent?c.find(d).eq(0):c.children(d).eq(0));n&&n.length?(n.data("layoutRole","content"),n.data("layoutCSS")||n.data("layoutCSS",D(n,"height")),
n.css(k.content.cssReq),m.applyDemoStyles&&(n.css(k.content.cssDemo),c.css(k.content.cssDemoPane)),c.css("overflowX").match(/(scroll|auto)/)&&c.css("overflow","hidden"),q[b].content={},!1!==a&&pa(b)):g.content=U[b]=!1}},qb=function(){var e=b(this),a=e.data("layoutMask"),a=q[a];"IFRAME"==a.tagName&&a.isVisible&&e.css({top:a.offsetTop,left:a.offsetLeft,width:a.outerWidth,height:a.outerHeight})},va=function(e,a){var m=k[e],d=["center"],g=r.zIndexes,c=b.extend({objectsOnly:!1,animation:!1,resizing:!0,
sliding:q[e].isSliding},a),n,f;c.resizing&&d.push(e);c.sliding&&d.push(k.oppositeEdge[e]);"horz"===m.dir&&(d.push("west"),d.push("east"));b.each(d,function(e,a){f=q[a];n=r[a];if(f.isVisible&&(n.maskObjects||!c.objectsOnly&&n.maskContents)){for(var m=b([]),d,w=0,h=ea.length;w<h;w++)d=ea.eq(w),d.data("layoutMask")===a&&(m=m.add(d));if(!m.length){m=y[a];d=q[a];var w=r[a],h=r.zIndexes,j=b([]),E,k,v,p,x;if(w.maskContents||w.maskObjects)for(x=0;x<(w.maskObjects?2:1);x++)E=w.maskObjects&&0==x,k=document.createElement(E?
"iframe":"div"),v=b(k).data("layoutMask",a),k.className="ui-layout-mask ui-layout-mask-"+a,p=k.style,p.display="block",p.position="absolute",p.background="#FFF",E&&(k.frameborder=0,k.src="about:blank",p.opacity=0,p.filter="Alpha(Opacity='0')",p.border=0),"IFRAME"==d.tagName?(p.zIndex=h.pane_normal+1,u.append(k)):(v.addClass("ui-layout-mask-inside-pane"),p.zIndex=w.maskZindex||h.content_mask,p.top=0,p.left=0,p.width="100%",p.height="100%",m.append(k)),j=j.add(k),ea=ea.add(k);m=j}m.each(function(){qb.call(this);
this.style.zIndex=f.isSliding?g.pane_sliding+1:g.pane_normal+1;this.style.display="block"})}})},za=function(a){if(a||!q.paneResizing)ea.hide();else if(!a&&!b.isEmptyObject(q.panesSliding)){a=ea.length-1;for(var d,m;0<=a;a--)m=ea.eq(a),d=m.data("layoutMask"),r[d].maskObjects||m.hide()}},Ra=function(a,d,m,c){if(H()){a=A.call(this,a);var g=y[a],t=U[a],n=F[a],f=P[a];g&&b.isEmptyObject(g.data())&&(g=!1);t&&b.isEmptyObject(t.data())&&(t=!1);n&&b.isEmptyObject(n.data())&&(n=!1);f&&b.isEmptyObject(f.data())&&
(f=!1);g&&g.stop(!0,!0);var h=r[a],l=ba[a],j=b.isPlainObject(l)&&!b.isEmptyObject(l);c=void 0!==c?c:h.destroyChildren;j&&c&&(b.each(l,function(a,b){b.destroyed||b.destroy(!0);b.destroyed&&delete l[a]}),b.isEmptyObject(l)&&(l=ba[a]=null,j=!1));g&&d&&!j?g.remove():g&&g[0]&&(d=h.paneClass,c=d+"-"+a,d=[d,d+"-open",d+"-closed",d+"-sliding",c,c+"-open",c+"-closed",c+"-sliding"],b.merge(d,Na(g,!0)),g.removeClass(d.join(" ")).removeData("parentLayout").removeData("layoutPane").removeData("layoutRole").removeData("layoutEdge").removeData("autoHidden").unbind("."+
K),j&&t?(t.width(t.width()),b.each(l,function(a,b){b.resizeAll()})):t&&t.css(t.data("layoutCSS")).removeData("layoutCSS").removeData("layoutRole"),g.data("layout")||g.css(g.data("layoutCSS")).removeData("layoutCSS"));f&&f.remove();n&&n.remove();z[a]=y[a]=U[a]=F[a]=P[a]=!1;m||oa()}},Ea=function(a){var b=y[a],d=b[0].style;r[a].useOffscreenClose?(b.data(k.offscreenReset)||b.data(k.offscreenReset,{left:d.left,right:d.right}),b.css(k.offscreenCSS)):b.hide().removeData(k.offscreenReset)},rb=function(a){var b=
y[a];a=r[a];var d=k.offscreenCSS,c=b.data(k.offscreenReset),g=b[0].style;b.show().removeData(k.offscreenReset);a.useOffscreenClose&&c&&(g.left==d.left&&(g.left=c.left),g.right==d.right&&(g.right=c.right))},Ta=function(a,b){if(H()){var d=A.call(this,a),c=r[d],g=q[d],t=F[d];y[d]&&!g.isHidden&&!(q.initialized&&!1===C("onhide_start",d))&&(g.isSliding=!1,delete q.panesSliding[d],t&&t.hide(),!q.initialized||g.isClosed?(g.isClosed=!0,g.isHidden=!0,g.isVisible=!1,q.initialized||Ea(d),ia("horz"===k[d].dir?
"":"center"),(q.initialized||c.triggerEventsOnLoad)&&C("onhide_end",d)):(g.isHiding=!0,ja(d,!1,b)))}},Fa=function(a,b,d,c){if(H()){a=A.call(this,a);var g=q[a];y[a]&&g.isHidden&&!1!==C("onshow_start",a)&&(g.isShowing=!0,g.isSliding=!1,delete q.panesSliding[a],!1===b?ja(a,!0):ra(a,!1,d,c))}},na=function(a,b){if(H()){var d=Ma(a),c=A.call(this,a),g=q[c];d&&d.stopImmediatePropagation();g.isHidden?Fa(c):g.isClosed?ra(c,!!b):ja(c)}},ja=function(a,b,d,c){function g(){l.isMoving=!1;ma(t,!0);var a=k.oppositeEdge[t];
q[a].noRoom&&(Y(a),ha(a));if(!c&&(q.initialized||h.triggerEventsOnLoad))p||C("onclose_end",t),p&&C("onshow_end",t),v&&C("onhide_end",t)}var t=A.call(this,a);if(!q.initialized&&y[t]){a=t;var n=q[a];Ea(a);n.isClosed=!0;n.isVisible=!1;Da(a)}else if(H()){var f=y[t],h=r[t],l=q[t],j,p,v;u.queue(function(a){if(!f||!h.closable&&!l.isShowing&&!l.isHiding||!b&&l.isClosed&&!l.isShowing)return a();var e=!l.isShowing&&!1===C("onclose_start",t);p=l.isShowing;v=l.isHiding;delete l.isShowing;delete l.isHiding;if(e)return a();
j=!d&&!l.isClosed&&"none"!=h.fxName_close;l.isMoving=!0;l.isClosed=!0;l.isVisible=!1;v?l.isHidden=!0:p&&(l.isHidden=!1);l.isSliding?wa(t,!1):ia("horz"===k[t].dir?"":"center",!1);Da(t);j?(Ga(t,!0),f.hide(h.fxName_close,h.fxSettings_close,h.fxSpeed_close,function(){Ga(t,!1);l.isClosed&&g();a()})):(Ea(t),g(),a())})}},Da=function(a){if(F[a]){var d=F[a],c=P[a],f=r[a],g=k[a].side,t=f.resizerClass,n=f.togglerClass,h="-"+a;d.css(g,v.inset[g]).removeClass(t+"-open "+t+h+"-open").removeClass(t+"-sliding "+
t+h+"-sliding").addClass(t+"-closed "+t+h+"-closed");f.resizable&&b.layout.plugins.draggable&&d.draggable("disable").removeClass("ui-state-disabled").css("cursor","default").attr("title","");c&&(c.removeClass(n+"-open "+n+h+"-open").addClass(n+"-closed "+n+h+"-closed").attr("title",f.tips.Open),c.children(".content-open").hide(),c.children(".content-closed").css("display","block"));Va(a,!1);q.initialized&&qa()}},ra=function(a,b,d,c){function g(){j.isMoving=!1;eb(f);j.isSliding||ia("vert"==k[f].dir?
"center":"",!1);Ua(f)}if(H()){var f=A.call(this,a),n=y[f],h=r[f],j=q[f],l,p;u.queue(function(a){if(!n||!h.resizable&&!h.closable&&!j.isShowing||j.isVisible&&!j.isSliding)return a();if(j.isHidden&&!j.isShowing)a(),Fa(f,!0);else{j.autoResize&&j.size!=h.size?ka(f,h.size,!0,!0,!0):Y(f,b);var e=C("onopen_start",f);if("abort"===e)return a();"NC"!==e&&Y(f,b);if(j.minSize>j.maxSize)return Va(f,!1),!c&&h.tips.noRoomToOpen&&alert(h.tips.noRoomToOpen),a();b?wa(f,!0):j.isSliding?wa(f,!1):h.slidable&&ma(f,!1);
j.noRoom=!1;ha(f);p=j.isShowing;delete j.isShowing;l=!d&&j.isClosed&&"none"!=h.fxName_open;j.isMoving=!0;j.isVisible=!0;j.isClosed=!1;p&&(j.isHidden=!1);l?(Ga(f,!0),n.show(h.fxName_open,h.fxSettings_open,h.fxSpeed_open,function(){Ga(f,!1);j.isVisible&&g();a()})):(rb(f),g(),a())}})}},Ua=function(a,d){var c=y[a],f=F[a],g=P[a],h=r[a],n=q[a],j=k[a].side,p=h.resizerClass,l=h.togglerClass,u="-"+a;f.css(j,v.inset[j]+ga(a)).removeClass(p+"-closed "+p+u+"-closed").addClass(p+"-open "+p+u+"-open");n.isSliding?
f.addClass(p+"-sliding "+p+u+"-sliding"):f.removeClass(p+"-sliding "+p+u+"-sliding");da(0,f);h.resizable&&b.layout.plugins.draggable?f.draggable("enable").css("cursor",h.resizerCursor).attr("title",h.tips.Resize):n.isSliding||f.css("cursor","default");g&&(g.removeClass(l+"-closed "+l+u+"-closed").addClass(l+"-open "+l+u+"-open").attr("title",h.tips.Close),da(0,g),g.children(".content-closed").hide(),g.children(".content-open").css("display","block"));Va(a,!n.isSliding);b.extend(n,R(c));q.initialized&&
(qa(),pa(a,!0));if(!d&&(q.initialized||h.triggerEventsOnLoad)&&c.is(":visible"))C("onopen_end",a),n.isShowing&&C("onshow_end",a),q.initialized&&C("onresize_end",a)},sb=function(a){function b(){g.isClosed?g.isMoving||ra(c,!0):wa(c,!0)}if(H()){var d=Ma(a),c=A.call(this,a),g=q[c];a=r[c].slideDelay_open;d&&d.stopImmediatePropagation();g.isClosed&&d&&"mouseenter"===d.type&&0<a?M.set(c+"_openSlider",b,a):b()}},Wa=function(a){function c(){g.isClosed?wa(f,!1):g.isMoving||ja(f)}if(H()){var m=Ma(a),f=A.call(this,
a);a=r[f];var g=q[f],h=g.isMoving?1E3:300;!g.isClosed&&!g.isResizing&&("click"===a.slideTrigger_close?c():a.preventQuickSlideClose&&g.isMoving||a.preventPrematureSlideClose&&m&&b.layout.isMouseOverElem(m,y[f])||(m?M.set(f+"_closeSlider",c,d(a.slideDelay_close,h)):c()))}},Ga=function(a,b){var d=y[a],c=q[a],g=r[a],f=r.zIndexes;b?(va(a,{animation:!0,objectsOnly:!0}),d.css({zIndex:f.pane_animate}),"south"==a?d.css({top:v.inset.top+v.innerHeight-d.outerHeight()}):"east"==a&&d.css({left:v.inset.left+v.innerWidth-
d.outerWidth()})):(za(),d.css({zIndex:c.isSliding?f.pane_sliding:f.pane_normal}),"south"==a?d.css({top:"auto"}):"east"==a&&!d.css("left").match(/\-99999/)&&d.css({left:"auto"}),G.msie&&(g.fxOpacityFix&&"slide"!=g.fxName_open&&d.css("filter")&&1==d.css("opacity"))&&d[0].style.removeAttribute("filter"))},ma=function(a,b){var d=r[a],c=F[a],g=d.slideTrigger_open.toLowerCase();if(c&&(!b||d.slidable)){g.match(/mouseover/)?g=d.slideTrigger_open="mouseenter":g.match(/(click|dblclick|mouseenter)/)||(g=d.slideTrigger_open=
"click");if(d.resizerDblClickToggle&&g.match(/click/))c[b?"unbind":"bind"]("dblclick."+K,na);c[b?"bind":"unbind"](g+"."+K,sb).css("cursor",b?d.sliderCursor:"default").attr("title",b?d.tips.Slide:"")}},wa=function(a,b){function d(b){M.clear(a+"_closeSlider");b.stopPropagation()}var c=r[a],g=q[a],f=r.zIndexes,h=c.slideTrigger_close.toLowerCase(),j=b?"bind":"unbind",k=y[a],l=F[a];M.clear(a+"_closeSlider");b?(g.isSliding=!0,q.panesSliding[a]=!0,ma(a,!1)):(g.isSliding=!1,delete q.panesSliding[a]);k.css("zIndex",
b?f.pane_sliding:f.pane_normal);l.css("zIndex",b?f.pane_sliding+2:f.resizer_normal);h.match(/(click|mouseleave)/)||(h=c.slideTrigger_close="mouseleave");l[j](h,Wa);"mouseleave"===h&&(k[j]("mouseleave."+K,Wa),l[j]("mouseenter."+K,d),k[j]("mouseenter."+K,d));b?"click"===h&&!c.resizable&&(l.css("cursor",b?c.sliderCursor:"default"),l.attr("title",b?c.tips.Close:"")):M.clear(a+"_closeSlider")},ha=function(a,d,c,f){d=r[a];var g=q[a],h=k[a],n=y[a],j=F[a],p="vert"===h.dir,l=!1;if("center"===a||p&&g.noVerticalRoom)(l=
0<=g.maxHeight)&&g.noRoom?(rb(a),j&&j.show(),g.isVisible=!0,g.noRoom=!1,p&&(g.noVerticalRoom=!1),eb(a)):!l&&!g.noRoom&&(Ea(a),j&&j.hide(),g.isVisible=!1,g.noRoom=!0);"center"!==a&&(g.minSize<=g.maxSize?(g.size>g.maxSize?ka(a,g.maxSize,c,!0,f):g.size<g.minSize?ka(a,g.minSize,c,!0,f):j&&(g.isVisible&&n.is(":visible"))&&(c=g.size+v.inset[h.side],b.layout.cssNum(j,h.side)!=c&&j.css(h.side,c)),g.noRoom&&(g.wasOpen&&d.closable?d.autoReopen?ra(a,!1,!0,!0):g.noRoom=!1:Fa(a,g.wasOpen,!0,!0))):g.noRoom||(g.noRoom=
!0,g.wasOpen=!g.isClosed&&!g.isSliding,g.isClosed||(d.closable?ja(a,!0,!0):Ta(a,!0))))},Ca=function(a,b,d,c,g){if(H()){a=A.call(this,a);var f=r[a],h=q[a];g=g||f.livePaneResizing&&!h.isResizing;h.autoResize=!1;ka(a,b,d,c,g)}},ka=function(e,c,f,h,g){function j(){for(var a="width"===ua?l.outerWidth():l.outerHeight(),a=[{pane:n,count:1,target:c,actual:a,correct:c===a,attempt:c,cssSize:D}],e=a[0],h={},t="Inaccurate size after resizing the "+n+"-pane.";!e.correct;){h={pane:n,count:e.count+1,target:c};h.attempt=
e.actual>c?d(0,e.attempt-(e.actual-c)):d(0,e.attempt+(c-e.actual));h.cssSize=("horz"==k[n].dir?O:Q)(y[n],h.attempt);l.css(ua,h.cssSize);h.actual="width"==ua?l.outerWidth():l.outerHeight();h.correct=c===h.actual;1===a.length&&(ca(t,!1,!0),ca(e,!1,!0));ca(h,!1,!0);if(3<a.length)break;a.push(h);e=a[a.length-1]}J.size=c;b.extend(J,R(l));J.isVisible&&l.is(":visible")&&(x&&x.css(B,c+v.inset[B]),pa(n));!f&&(!Z&&q.initialized&&J.isVisible)&&C("onresize_end",n);f||(J.isSliding||ia("horz"==k[n].dir?"":"center",
Z,g),qa());e=k.oppositeEdge[n];c<G&&q[e].noRoom&&(Y(e),ha(e,!1,f));1<a.length&&ca(t+"\nSee the Error Console for details.",!0,!0)}if(H()){var n=A.call(this,e),p=r[n],J=q[n],l=y[n],x=F[n],B=k[n].side,ua=k[n].sizeType.toLowerCase(),Z=J.isResizing&&!p.triggerEventsDuringLiveResize,z=!0!==h&&p.animatePaneSizing,G,D;u.queue(function(e){Y(n);G=J.size;c=fa(n,c);c=d(c,fa(n,p.minSize));c=a(c,J.maxSize);if(c<J.minSize)e(),ha(n,!1,f);else{if(!g&&c===G)return e();J.newSize=c;!f&&(q.initialized&&J.isVisible)&&
C("onresize_start",n);D=("horz"==k[n].dir?O:Q)(y[n],c);if(z&&l.is(":visible")){var h=b.layout.effects.size[n]||b.layout.effects.size.all,h=p.fxSettings_size.easing||h.easing,v=r.zIndexes,u={};u[ua]=D+"px";J.isMoving=!0;l.css({zIndex:v.pane_animate}).show().animate(u,p.fxSpeed_size,h,function(){l.css({zIndex:J.isSliding?v.pane_sliding:v.pane_normal});J.isMoving=!1;delete J.newSize;j();e()})}else l.css(ua,D),delete J.newSize,l.is(":visible")?j():(J.size=c,b.extend(J,R(l))),e()}})}},ia=function(a,c,
f){a=(a?a:"east,west,center").split(",");b.each(a,function(a,e){if(y[e]){var h=r[e],j=q[e],k=y[e],p=!0,l={},u=b.layout.showInvisibly(k),B={top:ga("north",!0),bottom:ga("south",!0),left:ga("west",!0),right:ga("east",!0),width:0,height:0};B.width=v.innerWidth-B.left-B.right;B.height=v.innerHeight-B.bottom-B.top;B.top+=v.inset.top;B.bottom+=v.inset.bottom;B.left+=v.inset.left;B.right+=v.inset.right;b.extend(j,R(k));if("center"===e){if(!f&&j.isVisible&&B.width===j.outerWidth&&B.height===j.outerHeight)return k.css(u),
!0;b.extend(j,ya(e),{maxWidth:B.width,maxHeight:B.height});l=B;j.newWidth=l.width;j.newHeight=l.height;l.width=Q(k,l.width);l.height=O(k,l.height);p=0<=l.width&&0<=l.height;if(!q.initialized&&h.minWidth>B.width){var h=h.minWidth-j.outerWidth,B=r.east.minSize||0,x=r.west.minSize||0,Z=q.east.size,z=q.west.size,A=Z,D=z;0<h&&(q.east.isVisible&&Z>B)&&(A=d(Z-B,Z-h),h-=Z-A);0<h&&(q.west.isVisible&&z>x)&&(D=d(z-x,z-h),h-=z-D);if(0===h){Z&&Z!=B&&ka("east",A,!0,!0,f);z&&z!=x&&ka("west",D,!0,!0,f);ia("center",
c,f);k.css(u);return}}}else{j.isVisible&&!j.noVerticalRoom&&b.extend(j,R(k),ya(e));if(!f&&!j.noVerticalRoom&&B.height===j.outerHeight)return k.css(u),!0;l.top=B.top;l.bottom=B.bottom;j.newSize=B.height;l.height=O(k,B.height);j.maxHeight=l.height;p=0<=j.maxHeight;p||(j.noVerticalRoom=!0)}p?(!c&&q.initialized&&C("onresize_start",e),k.css(l),"center"!==e&&qa(e),j.noRoom&&(!j.isClosed&&!j.isHidden)&&ha(e),j.isVisible&&(b.extend(j,R(k)),q.initialized&&pa(e))):!j.noRoom&&j.isVisible&&ha(e);k.css(u);delete j.newSize;
delete j.newWidth;delete j.newHeight;if(!j.isVisible)return!0;"center"===e&&(j=G.isIE6||!G.boxModel,y.north&&(j||"IFRAME"==q.north.tagName)&&y.north.css("width",Q(y.north,v.innerWidth)),y.south&&(j||"IFRAME"==q.south.tagName)&&y.south.css("width",Q(y.south,v.innerWidth)));!c&&q.initialized&&C("onresize_end",e)}})},oa=function(a){A(a);if(u.is(":visible"))if(q.initialized){if(!0===a&&b.isPlainObject(r.outset)&&u.css(r.outset),b.extend(v,R(u,r.inset)),v.outerHeight){!0===a&&ob();if(!1===C("onresizeall_start"))return!1;
var d,c,f;b.each(["south","north","east","west"],function(a,b){y[b]&&(c=r[b],f=q[b],f.autoResize&&f.size!=c.size?ka(b,c.size,!0,!0,!0):(Y(b),ha(b,!1,!0,!0)))});ia("",!0,!0);qa();b.each(k.allPanes,function(a,b){(d=y[b])&&q[b].isVisible&&C("onresize_end",b)});C("onresizeall_end")}}else Aa()},db=function(a,d){var c=A.call(this,a);r[c].resizeChildren&&(d||Ba(c),c=ba[c],b.isPlainObject(c)&&b.each(c,function(a,b){b.destroyed||b.resizeAll()}))},pa=function(a,c){if(H()){var h=A.call(this,a),h=h?h.split(","):
k.allPanes;b.each(h,function(a,e){function h(a){return d(u.css.paddingBottom,parseInt(a.css("marginBottom"),10)||0)}function j(){var a=r[e].contentIgnoreSelector,a=p.nextAll().not(".ui-layout-mask").not(a||":lt(0)"),b=a.filter(":visible"),d=b.filter(":last");v={top:p[0].offsetTop,height:p.outerHeight(),numFooters:a.length,hiddenFooters:a.length-b.length,spaceBelow:0};v.spaceAbove=v.top;v.bottom=v.top+v.height;v.spaceBelow=d.length?d[0].offsetTop+d.outerHeight()-v.bottom+h(d):h(p)}var m=y[e],p=U[e],
l=r[e],u=q[e],v=u.content;if(!m||!p||!m.is(":visible"))return!0;if(!p.length&&(Sa(e,!1),!p))return;if(!1!==C("onsizecontent_start",e)){if(!u.isMoving&&!u.isResizing||l.liveContentResizing||c||void 0==v.top)j(),0<v.hiddenFooters&&"hidden"===m.css("overflow")&&(m.css("overflow","visible"),j(),m.css("overflow","hidden"));m=u.innerHeight-(v.spaceAbove-u.css.paddingTop)-(v.spaceBelow-u.css.paddingBottom);if(!p.is(":visible")||v.height!=m){var x=p,l=x;f(x)?l=y[x]:x.jquery||(l=b(x));x=O(l,m);l.css({height:x,
visibility:"visible"});0<x&&0<l.innerWidth()?l.data("autoHidden")&&(l.show().data("autoHidden",!1),G.mozilla||l.css(k.hidden).css(k.visible)):l.data("autoHidden")||l.hide().data("autoHidden",!0);v.height=m}q.initialized&&C("onsizecontent_end",e)}})}},qa=function(a){a=(a=A.call(this,a))?a.split(","):k.borderPanes;b.each(a,function(a,d){var e=r[d],g=q[d],h=y[d],j=F[d],p=P[d],u;if(h&&j){var l=k[d].dir,x=g.isClosed?"_closed":"_open",B=e["spacing"+x],z=e["togglerAlign"+x],x=e["togglerLength"+x],A;if(0===
B)j.hide();else{!g.noRoom&&!g.isHidden&&j.show();"horz"===l?(A=v.innerWidth,g.resizerLength=A,h=b.layout.cssNum(h,"left"),j.css({width:Q(j,A),height:O(j,B),left:-9999<h?h:v.inset.left})):(A=h.outerHeight(),g.resizerLength=A,j.css({height:O(j,A),width:Q(j,B),top:v.inset.top+ga("north",!0)}));da(e,j);if(p){if(0===x||g.isSliding&&e.hideTogglerOnSlide){p.hide();return}p.show();if(!(0<x)||"100%"===x||x>A)x=A,z=0;else if(f(z))switch(z){case "top":case "left":z=0;break;case "bottom":case "right":z=A-x;break;
default:z=c((A-x)/2)}else h=parseInt(z,10),z=0<=z?h:A-x+h;if("horz"===l){var D=Q(p,x);p.css({width:D,height:O(p,B),left:z,top:0});p.children(".content").each(function(){u=b(this);u.css("marginLeft",c((D-u.outerWidth())/2))})}else{var C=O(p,x);p.css({height:C,width:Q(p,B),top:z,left:0});p.children(".content").each(function(){u=b(this);u.css("marginTop",c((C-u.outerHeight())/2))})}da(0,p)}if(!q.initialized&&(e.initHidden||g.isHidden))j.hide(),p&&p.hide()}}})},pb=function(a){if(H()){var b=A.call(this,
a);a=P[b];var d=r[b];a&&(d.closable=!0,a.bind("click."+K,function(a){a.stopPropagation();na(b)}).css("visibility","visible").css("cursor","pointer").attr("title",q[b].isClosed?d.tips.Open:d.tips.Close).show())}},Va=function(a,d){b.layout.plugins.buttons&&b.each(q[a].pins,function(c,f){b.layout.buttons.setPinState(z,b(f),a,d)})},u=b(this).eq(0);if(!u.length)return ca(r.errors.containerMissing);if(u.data("layoutContainer")&&u.data("layout"))return u.data("layout");var y={},U={},F={},P={},ea=b([]),v=
q.container,K=q.id,z={options:r,state:q,container:u,panes:y,contents:U,resizers:F,togglers:P,hide:Ta,show:Fa,toggle:na,open:ra,close:ja,slideOpen:sb,slideClose:Wa,slideToggle:function(a){a=A.call(this,a);na(a,!0)},setSizeLimits:Y,_sizePane:ka,sizePane:Ca,sizeContent:pa,swapPanes:function(a,c){function f(a){var d=y[a],c=U[a];return!d?!1:{pane:a,P:d?d[0]:!1,C:c?c[0]:!1,state:b.extend(!0,{},q[a]),options:b.extend(!0,{},r[a])}}function h(a,c){if(a){var e=a.P,f=a.C,g=a.pane,j=k[c],m=b.extend(!0,{},q[c]),
n=r[c],w={resizerCursor:n.resizerCursor};b.each(["fxName","fxSpeed","fxSettings"],function(a,b){w[b+"_open"]=n[b+"_open"];w[b+"_close"]=n[b+"_close"];w[b+"_size"]=n[b+"_size"]});y[c]=b(e).data({layoutPane:z[c],layoutEdge:c}).css(k.hidden).css(j.cssReq);U[c]=f?b(f):!1;r[c]=b.extend(!0,{},a.options,w);q[c]=b.extend(!0,{},a.state);e.className=e.className.replace(RegExp(n.paneClass+"-"+g,"g"),n.paneClass+"-"+c);Pa(c);j.dir!=k[g].dir?(e=p[c]||0,Y(c),e=d(e,q[c].minSize),Ca(c,e,!0,!0)):F[c].css(j.side,v.inset[j.side]+
(q[c].isVisible?ga(c):0));a.state.isVisible&&!m.isVisible?Ua(c,!0):(Da(c),ma(c,!0));a=null}}if(H()){var g=A.call(this,a);q[g].edge=c;q[c].edge=g;if(!1===C("onswap_start",g)||!1===C("onswap_start",c))q[g].edge=g,q[c].edge=c;else{var j=f(g),n=f(c),p={};p[g]=j?j.state.size:0;p[c]=n?n.state.size:0;y[g]=!1;y[c]=!1;q[g]={};q[c]={};P[g]&&P[g].remove();P[c]&&P[c].remove();F[g]&&F[g].remove();F[c]&&F[c].remove();F[g]=F[c]=P[g]=P[c]=!1;h(j,c);h(n,g);j=n=p=null;y[g]&&y[g].css(k.visible);y[c]&&y[c].css(k.visible);
oa();C("onswap_end",g);C("onswap_end",c)}}},showMasks:va,hideMasks:za,initContent:Sa,addPane:ib,removePane:Ra,createChildren:Qa,refreshChildren:Ba,enableClosable:pb,disableClosable:function(a,b){if(H()){var c=A.call(this,a),d=P[c];d&&(r[c].closable=!1,q[c].isClosed&&ra(c,!1,!0),d.unbind("."+K).css("visibility",b?"hidden":"visible").css("cursor","default").attr("title",""))}},enableSlidable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&b.data("draggable")&&(r[a].slidable=!0,q[a].isClosed&&ma(a,
!0))}},disableSlidable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&(r[a].slidable=!1,q[a].isSliding?ja(a,!1,!0):(ma(a,!1),b.css("cursor","default").attr("title",""),da(null,b[0])))}},enableResizable:function(a){if(H()){a=A.call(this,a);var b=F[a],c=r[a];b&&b.data("draggable")&&(c.resizable=!0,b.draggable("enable"),q[a].isClosed||b.css("cursor",c.resizerCursor).attr("title",c.tips.Resize))}},disableResizable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&b.data("draggable")&&(r[a].resizable=
!1,b.draggable("disable").css("cursor","default").attr("title",""),da(null,b[0]))}},allowOverflow:x,resetOverflow:X,destroy:function(a,c){b(window).unbind("."+K);b(document).unbind("."+K);"object"===typeof a?A(a):c=a;u.clearQueue().removeData("layout").removeData("layoutContainer").removeClass(r.containerClass).unbind("."+K);ea.remove();b.each(k.allPanes,function(a,b){Ra(b,!1,!0,c)});u.data("layoutCSS")&&!u.data("layoutRole")&&u.css(u.data("layoutCSS")).removeData("layoutCSS");"BODY"===v.tagName&&
(u=b("html")).data("layoutCSS")&&u.css(u.data("layoutCSS")).removeData("layoutCSS");j(z,b.layout.onDestroy);mb();for(var d in z)d.match(/^(container|options)$/)||delete z[d];z.destroyed=!0;return z},initPanes:H,resizeAll:oa,runCallbacks:C,hasParentLayout:!1,children:ba,north:!1,south:!1,west:!1,east:!1,center:!1},Xa;var V,Ya,N,Ha,la,sa,W;h=b.layout.transformData(h,!0);h=b.layout.backwardCompatibility.renameAllOptions(h);if(!b.isEmptyObject(h.panes)){V=b.layout.optionsMap.noDefault;la=0;for(sa=V.length;la<
sa;la++)N=V[la],delete h.panes[N];V=b.layout.optionsMap.layout;la=0;for(sa=V.length;la<sa;la++)N=V[la],delete h.panes[N]}V=b.layout.optionsMap.layout;var Bb=b.layout.config.optionRootKeys;for(N in h)Ha=h[N],0>b.inArray(N,Bb)&&0>b.inArray(N,V)&&(h.panes[N]||(h.panes[N]=b.isPlainObject(Ha)?b.extend(!0,{},Ha):Ha),delete h[N]);b.extend(!0,r,h);b.each(k.allPanes,function(a,c){k[c]=b.extend(!0,{},k.panes,k[c]);Ya=r.panes;W=r[c];if("center"===c){V=b.layout.optionsMap.center;a=0;for(sa=V.length;a<sa;a++)if(N=
V[a],!h.center[N]&&(h.panes[N]||!W[N]))W[N]=Ya[N]}else{W=r[c]=b.extend(!0,{},Ya,W);var d=r[c],f=r.panes;d.fxSettings||(d.fxSettings={});f.fxSettings||(f.fxSettings={});b.each(["_open","_close","_size"],function(a,e){var h="fxName"+e,j="fxSpeed"+e,k="fxSettings"+e,l=d[h]=d[h]||f[h]||d.fxName||f.fxName||"none",p=b.effects&&(b.effects[l]||b.effects.effect&&b.effects.effect[l]);if("none"===l||!r.effects[l]||!p)l=d[h]="none";l=r.effects[l]||{};h=l.all||null;l=l[c]||null;d[j]=d[j]||f[j]||d.fxSpeed||f.fxSpeed||
null;d[k]=b.extend(!0,{},h,l,f.fxSettings,d.fxSettings,f[k],d[k])});delete d.fxName;delete d.fxSpeed;delete d.fxSettings;W.resizerClass||(W.resizerClass="ui-layout-resizer");W.togglerClass||(W.togglerClass="ui-layout-toggler")}W.paneClass||(W.paneClass="ui-layout-pane")});var Ia=h.zIndex,xa=r.zIndexes;0<Ia&&(xa.pane_normal=Ia,xa.content_mask=d(Ia+1,xa.content_mask),xa.resizer_normal=d(Ia+2,xa.resizer_normal));delete r.panes;var Cb=r,tb=q;tb.creatingLayout=!0;j(z,b.layout.onCreate);if(!1===C("onload_start"))Xa=
"cancel";else{var Za=u[0],$=b("html"),ub=v.tagName=Za.tagName,vb=v.id=Za.id,wb=v.className=Za.className,L=r,Ja=L.name,$a={},Ka=u.data("parentLayout"),La=u.data("layoutEdge"),ab=Ka&&La,ta=b.layout.cssNum,bb,aa;v.ref=(L.name?L.name+" layout / ":"")+ub+(vb?"#"+vb:wb?".["+wb+"]":"");v.isBody="BODY"===ub;!ab&&!v.isBody&&(bb=u.closest("."+b.layout.defaults.panes.paneClass),Ka=bb.data("parentLayout"),La=bb.data("layoutEdge"),ab=Ka&&La);u.data({layout:z,layoutContainer:K}).addClass(L.containerClass);
var xb={destroy:"",initPanes:"",resizeAll:"resizeAll",resize:"resizeAll"};for(Ja in xb)u.bind("layout"+Ja.toLowerCase()+"."+K,z[xb[Ja]||Ja]);ab&&(z.hasParentLayout=!0,Ka.refreshChildren(La,z));u.data("layoutCSS")||(v.isBody?(u.data("layoutCSS",b.extend(D(u,"position,margin,padding,border"),{height:u.css("height"),overflow:u.css("overflow"),overflowX:u.css("overflowX"),overflowY:u.css("overflowY")})),$.data("layoutCSS",b.extend(D($,"padding"),{height:"auto",overflow:$.css("overflow"),overflowX:$.css("overflowX"),
overflowY:$.css("overflowY")}))):u.data("layoutCSS",D(u,"position,margin,padding,border,top,bottom,left,right,width,height,overflow,overflowX,overflowY")));try{$a={overflow:"hidden",overflowX:"hidden",overflowY:"hidden"};u.css($a);L.inset&&!b.isPlainObject(L.inset)&&(aa=parseInt(L.inset,10)||0,L.inset={top:aa,bottom:aa,left:aa,right:aa});if(v.isBody)L.outset?b.isPlainObject(L.outset)||(aa=parseInt(L.outset,10)||0,L.outset={top:aa,bottom:aa,left:aa,right:aa}):L.outset={top:ta($,"paddingTop"),bottom:ta($,
"paddingBottom"),left:ta($,"paddingLeft"),right:ta($,"paddingRight")},$.css($a).css({height:"100%",border:"none",padding:0,margin:0}),G.isIE6?(u.css({width:"100%",height:"100%",border:"none",padding:0,margin:0,position:"relative"}),L.inset||(L.inset=R(u).inset)):(u.css({width:"auto",height:"auto",margin:0,position:"absolute"}),u.css(L.outset)),b.extend(v,R(u,L.inset));else{var yb=u.css("position");(!yb||!yb.match(/(fixed|absolute|relative)/))&&u.css("position","relative");u.is(":visible")&&(b.extend(v,
R(u,L.inset)),1>v.innerHeight&&ca(L.errors.noContainerHeight.replace(/CONTAINER/,v.ref)))}ta(u,"minWidth")&&u.parent().css("overflowX","auto");ta(u,"minHeight")&&u.parent().css("overflowY","auto")}catch(Db){}nb();b(window).bind("unload."+K,mb);j(z,b.layout.onLoad);Cb.initPanes&&Aa();delete tb.creatingLayout;Xa=q.initialized}return"cancel"===Xa?null:z}})(jQuery);
(function(b){b.ui||(b.ui={});b.ui.cookie={acceptsCookies:!!navigator.cookieEnabled,read:function(a){for(var d=document.cookie,d=d?d.split(";"):[],c,f=0,j=d.length;f<j;f++)if(c=b.trim(d[f]).split("="),c[0]==a)return decodeURIComponent(c[1]);return null},write:function(a,d,c){var f="",j="",h=!1;c=c||{};var p=c.expires||null,x=b.type(p);"date"===x?j=p:"string"===x&&0<p&&(p=parseInt(p,10),x="number");"number"===x&&(j=new Date,0<p?j.setDate(j.getDate()+p):(j.setFullYear(1970),h=!0));j&&(f+=";expires="+
j.toUTCString());c.path&&(f+=";path="+c.path);c.domain&&(f+=";domain="+c.domain);c.secure&&(f+=";secure");document.cookie=a+"="+(h?"":encodeURIComponent(d))+f},clear:function(a){b.ui.cookie.write(a,"",{expires:-1})}};b.cookie||(b.cookie=function(a,d,c){var f=b.ui.cookie;if(null===d)f.clear(a);else{if(void 0===d)return f.read(a);f.write(a,d,c)}});b.layout.plugins.stateManagement=!0;b.layout.config.optionRootKeys.push("stateManagement");b.layout.defaults.stateManagement={enabled:!1,autoSave:!0,autoLoad:!0,
animateLoad:!0,includeChildren:!0,stateKeys:"north.size,south.size,east.size,west.size,north.isClosed,south.isClosed,east.isClosed,west.isClosed,north.isHidden,south.isHidden,east.isHidden,west.isHidden",cookie:{name:"",domain:"",path:"",expires:"",secure:!1}};b.layout.optionsMap.layout.push("stateManagement");b.layout.state={saveCookie:function(a,d,c){var f=a.options,j=f.stateManagement;c=b.extend(!0,{},j.cookie,c||null);a=a.state.stateData=a.readState(d||j.stateKeys);b.ui.cookie.write(c.name||f.name||
"Layout",b.layout.state.encodeJSON(a),c);return b.extend(!0,{},a)},deleteCookie:function(a){a=a.options;b.ui.cookie.clear(a.stateManagement.cookie.name||a.name||"Layout")},readCookie:function(a){a=a.options;return(a=b.ui.cookie.read(a.stateManagement.cookie.name||a.name||"Layout"))?b.layout.state.decodeJSON(a):{}},loadCookie:function(a){var d=b.layout.state.readCookie(a);d&&(a.state.stateData=b.extend(!0,{},d),a.loadState(d));return d},loadState:function(a,d,c){if(b.isPlainObject(d)&&!b.isEmptyObject(d))if(d=
a.state.stateData=b.layout.transformData(d),c=b.extend({animateLoad:!1,includeChildren:a.options.stateManagement.includeChildren},c),a.state.initialized){var f=!c.animateLoad,j,h,p,x;b.each(b.layout.config.borderPanes,function(c,G){S=d[G];b.isPlainObject(S)&&(s=S.size,j=S.initClosed,h=S.initHidden,ar=S.autoResize,p=a.state[G],x=p.isVisible,ar&&(p.autoResize=ar),x||a._sizePane(G,s,!1,!1,!1),!0===h?a.hide(G,f):!0===j?a.close(G,!1,f):!1===j?a.open(G,!1,f):!1===h&&a.show(G,!1,f),x&&a._sizePane(G,s,!1,
!1,f))});if(c.includeChildren){var I,T;b.each(a.children,function(a,c){(I=d[a]?d[a].children:0)&&c&&b.each(c,function(a,b){T=I[a];b&&T&&b.loadState(T)})})}}else{var S=b.extend(!0,{},d);b.each(b.layout.config.allPanes,function(a,b){S[b]&&delete S[b].children});b.extend(!0,a.options,S)}},readState:function(a,d){"string"===b.type(d)&&(d={keys:d});d||(d={});var c=a.options.stateManagement,f=d.includeChildren,f=void 0!==f?f:c.includeChildren,c=d.stateKeys||c.stateKeys,j={isClosed:"initClosed",isHidden:"initHidden"},
h=a.state,p=b.layout.config.allPanes,x={},I,T,S,X,G,k;b.isArray(c)&&(c=c.join(","));for(var c=c.replace(/__/g,".").split(","),Q=0,O=c.length;Q<O;Q++)I=c[Q].split("."),T=I[0],I=I[1],0>b.inArray(T,p)||(S=h[T][I],void 0!=S&&("isClosed"==I&&h[T].isSliding&&(S=!0),(x[T]||(x[T]={}))[j[I]?j[I]:I]=S));f&&b.each(p,function(c,d){G=a.children[d];X=h.stateData[d];b.isPlainObject(G)&&!b.isEmptyObject(G)&&(k=x[d]||(x[d]={}),k.children||(k.children={}),b.each(G,function(a,c){c.state.initialized?k.children[a]=b.layout.state.readState(c):
X&&(X.children&&X.children[a])&&(k.children[a]=b.extend(!0,{},X.children[a]))}))});return x},encodeJSON:function(a){function d(a){var f=[],j=0,h,p,x,I=b.isArray(a);for(h in a)p=a[h],x=typeof p,"string"==x?p='"'+p+'"':"object"==x&&(p=d(p)),f[j++]=(!I?'"'+h+'":':"")+p;return(I?"[":"{")+f.join(",")+(I?"]":"}")}return d(a)},decodeJSON:function(a){try{return b.parseJSON?b.parseJSON(a):window.eval("("+a+")")||{}}catch(d){return{}}},_create:function(a){var d=b.layout.state,c=a.options.stateManagement;b.extend(a,
{readCookie:function(){return d.readCookie(a)},deleteCookie:function(){d.deleteCookie(a)},saveCookie:function(b,c){return d.saveCookie(a,b,c)},loadCookie:function(){return d.loadCookie(a)},loadState:function(b,c){d.loadState(a,b,c)},readState:function(b){return d.readState(a,b)},encodeJSON:d.encodeJSON,decodeJSON:d.decodeJSON});a.state.stateData={};if(c.autoLoad)if(b.isPlainObject(c.autoLoad))b.isEmptyObject(c.autoLoad)||a.loadState(c.autoLoad);else if(c.enabled)if(b.isFunction(c.autoLoad)){var f=
{};try{f=c.autoLoad(a,a.state,a.options,a.options.name||"")}catch(j){}f&&(b.isPlainObject(f)&&!b.isEmptyObject(f))&&a.loadState(f)}else a.loadCookie()},_unload:function(a){var d=a.options.stateManagement;if(d.enabled&&d.autoSave)if(b.isFunction(d.autoSave))try{d.autoSave(a,a.state,a.options,a.options.name||"")}catch(c){}else a.saveCookie()}};b.layout.onCreate.push(b.layout.state._create);b.layout.onUnload.push(b.layout.state._unload);b.layout.plugins.buttons=!0;b.layout.defaults.autoBindCustomButtons=
!1;b.layout.optionsMap.layout.push("autoBindCustomButtons");b.layout.buttons={init:function(a){var d=a.options.name||"",c;b.each("toggle open close pin toggle-slide open-slide".split(" "),function(f,j){b.each(b.layout.config.borderPanes,function(f,p){b(".ui-layout-button-"+j+"-"+p).each(function(){c=b(this).data("layoutName")||b(this).attr("layoutName");(void 0==c||c===d)&&a.bindButton(this,j,p)})})})},get:function(a,d,c,f){var j=b(d);a=a.options;var h=a.errors.addButtonError;j.length?0>b.inArray(c,
b.layout.config.borderPanes)?(b.layout.msg(h+" "+a.errors.pane+": "+c,!0),j=b("")):(d=a[c].buttonClass+"-"+f,j.addClass(d+" "+d+"-"+c).data("layoutName",a.name)):b.layout.msg(h+" "+a.errors.selector+": "+d,!0);return j},bind:function(a,d,c,f){var j=b.layout.buttons;switch(c.toLowerCase()){case "toggle":j.addToggle(a,d,f);break;case "open":j.addOpen(a,d,f);break;case "close":j.addClose(a,d,f);break;case "pin":j.addPin(a,d,f);break;case "toggle-slide":j.addToggle(a,d,f,!0);break;case "open-slide":j.addOpen(a,
d,f,!0)}return a},addToggle:function(a,d,c,f){b.layout.buttons.get(a,d,c,"toggle").click(function(b){a.toggle(c,!!f);b.stopPropagation()});return a},addOpen:function(a,d,c,f){b.layout.buttons.get(a,d,c,"open").attr("title",a.options[c].tips.Open).click(function(b){a.open(c,!!f);b.stopPropagation()});return a},addClose:function(a,d,c){b.layout.buttons.get(a,d,c,"close").attr("title",a.options[c].tips.Close).click(function(b){a.close(c);b.stopPropagation()});return a},addPin:function(a,d,c){var f=b.layout.buttons,
j=f.get(a,d,c,"pin");if(j.length){var h=a.state[c];j.click(function(d){f.setPinState(a,b(this),c,h.isSliding||h.isClosed);h.isSliding||h.isClosed?a.open(c):a.close(c);d.stopPropagation()});f.setPinState(a,j,c,!h.isClosed&&!h.isSliding);h.pins.push(d)}return a},setPinState:function(a,b,c,f){var j=b.attr("pin");if(!(j&&f===("down"==j))){a=a.options[c];var j=a.buttonClass+"-pin",h=j+"-"+c;c=j+"-up "+h+"-up";j=j+"-down "+h+"-down";b.attr("pin",f?"down":"up").attr("title",f?a.tips.Unpin:a.tips.Pin).removeClass(f?
c:j).addClass(f?j:c)}},syncPinBtns:function(a,d,c){b.each(a.state[d].pins,function(f,j){b.layout.buttons.setPinState(a,b(j),d,c)})},_load:function(a){var d=b.layout.buttons;b.extend(a,{bindButton:function(b,c,h){return d.bind(a,b,c,h)},addToggleBtn:function(b,c,h){return d.addToggle(a,b,c,h)},addOpenBtn:function(b,c,h){return d.addOpen(a,b,c,h)},addCloseBtn:function(b,c){return d.addClose(a,b,c)},addPinBtn:function(b,c){return d.addPin(a,b,c)}});for(var c=0;4>c;c++)a.state[b.layout.config.borderPanes[c]].pins=
[];a.options.autoBindCustomButtons&&d.init(a)},_unload:function(){}};b.layout.onLoad.push(b.layout.buttons._load);b.layout.plugins.browserZoom=!0;b.layout.defaults.browserZoomCheckInterval=1E3;b.layout.optionsMap.layout.push("browserZoomCheckInterval");b.layout.browserZoom={_init:function(a){!1!==b.layout.browserZoom.ratio()&&b.layout.browserZoom._setTimer(a)},_setTimer:function(a){if(!a.destroyed){var d=a.options,c=a.state,f=a.hasParentLayout?5E3:Math.max(d.browserZoomCheckInterval,100);setTimeout(function(){if(!a.destroyed&&
d.resizeWithWindow){var f=b.layout.browserZoom.ratio();f!==c.browserZoom&&(c.browserZoom=f,a.resizeAll());b.layout.browserZoom._setTimer(a)}},f)}},ratio:function(){function a(a,b){return(100*(parseInt(a,10)/parseInt(b,10))).toFixed()}var d=window,c=screen,f=document,j=f.documentElement||f.body,h=b.layout.browser,p=h.version,x,I,T;return h.msie&&8<p||!h.msie?!1:c.deviceXDPI&&c.systemXDPI?a(c.deviceXDPI,c.systemXDPI):h.webkit&&(x=f.body.getBoundingClientRect)?a(x.left-x.right,f.body.offsetWidth):h.webkit&&
(I=d.outerWidth)?a(I,d.innerWidth):(I=c.width)&&(T=j.clientWidth)?a(I,T):!1}};b.layout.onReady.push(b.layout.browserZoom._init)})(jQuery);

View File

@@ -14,7 +14,10 @@ if (PHP_VERSION_ID < 50600) {
echo $err;
}
}
throw new RuntimeException($err);
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';

View File

@@ -27,64 +27,6 @@ return array(
'AsyncSendEmail' => $baseDir . '/core/asynctask.class.inc.php',
'AsyncSendNewsroom' => $baseDir . '/core/asynctask.class.inc.php',
'AsyncTask' => $baseDir . '/core/asynctask.class.inc.php',
'AttributeApplicationLanguage' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeArchiveDate' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeArchiveFlag' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeBlob' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeBoolean' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeCaseLog' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClass' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClassAttCodeSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeClassState' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeCustomFields' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDBField' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDBFieldVoid' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDashboard' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDate' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDateTime' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDeadline' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDecimal' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDefinition' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeDuration' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeEmailAddress' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeEncryptedString' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeEnum' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeEnumSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeExternalField' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeExternalKey' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeFinalClass' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeFriendlyName' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeHTML' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeHierarchicalKey' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeIPAddress' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeImage' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeInteger' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeLinkedSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeLinkedSetIndirect' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeLongText' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeMetaEnum' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeOQL' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeObjectKey' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeObsolescenceDate' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeObsolescenceFlag' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeOneWayPassword' => $baseDir . '/core/attributedef.class.inc.php',
'AttributePassword' => $baseDir . '/core/attributedef.class.inc.php',
'AttributePercentage' => $baseDir . '/core/attributedef.class.inc.php',
'AttributePhoneNumber' => $baseDir . '/core/attributedef.class.inc.php',
'AttributePropertySet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeQueryAttCodeSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeRedundancySettings' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeStopWatch' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeString' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeSubItem' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTable' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTagSet' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTemplateHTML' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTemplateString' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeTemplateText' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeText' => $baseDir . '/core/attributedef.class.inc.php',
'AttributeURL' => $baseDir . '/core/attributedef.class.inc.php',
'AuditCategory' => $baseDir . '/application/audit.category.class.inc.php',
'AuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
'AuditRule' => $baseDir . '/application/audit.rule.class.inc.php',
@@ -405,6 +347,66 @@ return array(
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => $baseDir . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
'Combodo\\iTop\\Controller\\WelcomePopupController' => $baseDir . '/sources/Controller/WelcomePopupController.php',
'Combodo\\iTop\\Controller\\iController' => $baseDir . '/sources/Controller/iController.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeApplicationLanguage' => $baseDir . '/sources/Core/AttributeDefinition/AttributeApplicationLanguage.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeArchiveDate' => $baseDir . '/sources/Core/AttributeDefinition/AttributeArchiveDate.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeArchiveFlag' => $baseDir . '/sources/Core/AttributeDefinition/AttributeArchiveFlag.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeBlob' => $baseDir . '/sources/Core/AttributeDefinition/AttributeBlob.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeBoolean' => $baseDir . '/sources/Core/AttributeDefinition/AttributeBoolean.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeCaseLog' => $baseDir . '/sources/Core/AttributeDefinition/AttributeCaseLog.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeClass' => $baseDir . '/sources/Core/AttributeDefinition/AttributeClass.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeClassAttCodeSet' => $baseDir . '/sources/Core/AttributeDefinition/AttributeClassAttCodeSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeClassState' => $baseDir . '/sources/Core/AttributeDefinition/AttributeClassState.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeCustomFields' => $baseDir . '/sources/Core/AttributeDefinition/AttributeCustomFields.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDBField' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDBField.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDBFieldVoid' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDBFieldVoid.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDashboard' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDashboard.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDate' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDate.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDateTime' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDateTime.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDeadline' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDeadline.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDecimal' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDecimal.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDefinition' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDefinition.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDuration' => $baseDir . '/sources/Core/AttributeDefinition/AttributeDuration.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEmailAddress' => $baseDir . '/sources/Core/AttributeDefinition/AttributeEmailAddress.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEncryptedString' => $baseDir . '/sources/Core/AttributeDefinition/AttributeEncryptedString.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEnum' => $baseDir . '/sources/Core/AttributeDefinition/AttributeEnum.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEnumSet' => $baseDir . '/sources/Core/AttributeDefinition/AttributeEnumSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeExternalField' => $baseDir . '/sources/Core/AttributeDefinition/AttributeExternalField.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeExternalKey' => $baseDir . '/sources/Core/AttributeDefinition/AttributeExternalKey.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeFinalClass' => $baseDir . '/sources/Core/AttributeDefinition/AttributeFinalClass.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeFriendlyName' => $baseDir . '/sources/Core/AttributeDefinition/AttributeFriendlyName.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeHTML' => $baseDir . '/sources/Core/AttributeDefinition/AttributeHTML.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeHierarchicalKey' => $baseDir . '/sources/Core/AttributeDefinition/AttributeHierarchicalKey.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeIPAddress' => $baseDir . '/sources/Core/AttributeDefinition/AttributeIPAddress.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeImage' => $baseDir . '/sources/Core/AttributeDefinition/AttributeImage.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeInteger' => $baseDir . '/sources/Core/AttributeDefinition/AttributeInteger.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeLinkedSet' => $baseDir . '/sources/Core/AttributeDefinition/AttributeLinkedSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeLinkedSetIndirect' => $baseDir . '/sources/Core/AttributeDefinition/AttributeLinkedSetIndirect.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeLongText' => $baseDir . '/sources/Core/AttributeDefinition/AttributeLongText.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeMetaEnum' => $baseDir . '/sources/Core/AttributeDefinition/AttributeMetaEnum.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeOQL' => $baseDir . '/sources/Core/AttributeDefinition/AttributeOQL.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeObjectKey' => $baseDir . '/sources/Core/AttributeDefinition/AttributeObjectKey.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeObsolescenceDate' => $baseDir . '/sources/Core/AttributeDefinition/AttributeObsolescenceDate.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeObsolescenceFlag' => $baseDir . '/sources/Core/AttributeDefinition/AttributeObsolescenceFlag.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeOneWayPassword' => $baseDir . '/sources/Core/AttributeDefinition/AttributeOneWayPassword.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePassword' => $baseDir . '/sources/Core/AttributeDefinition/AttributePassword.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePercentage' => $baseDir . '/sources/Core/AttributeDefinition/AttributePercentage.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePhoneNumber' => $baseDir . '/sources/Core/AttributeDefinition/AttributePhoneNumber.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePropertySet' => $baseDir . '/sources/Core/AttributeDefinition/AttributePropertySet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeQueryAttCodeSet' => $baseDir . '/sources/Core/AttributeDefinition/AttributeQueryAttCodeSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeRedundancySettings' => $baseDir . '/sources/Core/AttributeDefinition/AttributeRedundancySettings.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeSet' => $baseDir . '/sources/Core/AttributeDefinition/AttributeSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeStopWatch' => $baseDir . '/sources/Core/AttributeDefinition/AttributeStopWatch.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeString' => $baseDir . '/sources/Core/AttributeDefinition/AttributeString.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeSubItem' => $baseDir . '/sources/Core/AttributeDefinition/AttributeSubItem.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTable' => $baseDir . '/sources/Core/AttributeDefinition/AttributeTable.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTagSet' => $baseDir . '/sources/Core/AttributeDefinition/AttributeTagSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTemplateHTML' => $baseDir . '/sources/Core/AttributeDefinition/AttributeTemplateHTML.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTemplateString' => $baseDir . '/sources/Core/AttributeDefinition/AttributeTemplateString.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTemplateText' => $baseDir . '/sources/Core/AttributeDefinition/AttributeTemplateText.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeText' => $baseDir . '/sources/Core/AttributeDefinition/AttributeText.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeURL' => $baseDir . '/sources/Core/AttributeDefinition/AttributeURL.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\MissingColumnException' => $baseDir . '/sources/Core/AttributeDefinition/MissingColumnException.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\iAttributeNoGroupBy' => $baseDir . '/sources/Core/AttributeDefinition/iAttributeNoGroupBy.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php',
@@ -1116,7 +1118,6 @@ return array(
'MenuGroup' => $baseDir . '/application/menunode.class.inc.php',
'MenuNode' => $baseDir . '/application/menunode.class.inc.php',
'MetaModel' => $baseDir . '/core/metamodel.class.php',
'MissingColumnException' => $baseDir . '/core/attributedef.class.inc.php',
'MissingQueryArgument' => $baseDir . '/core/oql/expression.class.inc.php',
'ModelReflection' => $baseDir . '/core/modelreflection.class.inc.php',
'ModelReflectionRuntime' => $baseDir . '/core/modelreflection.class.inc.php',
@@ -3161,8 +3162,6 @@ return array(
'Twig\\Source' => $vendorDir . '/twig/twig/src/Source.php',
'Twig\\Template' => $vendorDir . '/twig/twig/src/Template.php',
'Twig\\TemplateWrapper' => $vendorDir . '/twig/twig/src/TemplateWrapper.php',
'Twig\\Test\\IntegrationTestCase' => $vendorDir . '/twig/twig/src/Test/IntegrationTestCase.php',
'Twig\\Test\\NodeTestCase' => $vendorDir . '/twig/twig/src/Test/NodeTestCase.php',
'Twig\\Token' => $vendorDir . '/twig/twig/src/Token.php',
'Twig\\TokenParser\\AbstractTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AbstractTokenParser.php',
'Twig\\TokenParser\\ApplyTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ApplyTokenParser.php',
@@ -3232,7 +3231,6 @@ return array(
'cmdbAbstractObject' => $baseDir . '/application/cmdbabstract.class.inc.php',
'cmdbDataGenerator' => $baseDir . '/core/data.generator.class.inc.php',
'iApplicationUIExtension' => $baseDir . '/application/applicationextension/backoffice/iApplicationUIExtension.php',
'iAttributeNoGroupBy' => $baseDir . '/core/attributedef.class.inc.php',
'iBackgroundProcess' => $baseDir . '/core/backgroundprocess.inc.php',
'iBackofficeDictEntriesExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php',
'iBackofficeDictEntriesPrefixesExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php',

View File

@@ -55,7 +55,7 @@ return array(
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'),
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'),
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'),
'Laminas\\Validator\\' => array($vendorDir . '/laminas/laminas-validator/src'),
'Laminas\\Stdlib\\' => array($vendorDir . '/laminas/laminas-stdlib/src'),
'Laminas\\ServiceManager\\' => array($vendorDir . '/laminas/laminas-servicemanager/src'),

View File

@@ -311,8 +311,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
),
'League\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/league/oauth2-google/src',
1 => __DIR__ . '/..' . '/league/oauth2-client/src',
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
1 => __DIR__ . '/..' . '/league/oauth2-google/src',
),
'Laminas\\Validator\\' =>
array (
@@ -399,64 +399,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'AsyncSendEmail' => __DIR__ . '/../..' . '/core/asynctask.class.inc.php',
'AsyncSendNewsroom' => __DIR__ . '/../..' . '/core/asynctask.class.inc.php',
'AsyncTask' => __DIR__ . '/../..' . '/core/asynctask.class.inc.php',
'AttributeApplicationLanguage' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeArchiveDate' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeArchiveFlag' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeBlob' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeBoolean' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeCaseLog' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClass' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClassAttCodeSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeClassState' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeCustomFields' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDBField' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDBFieldVoid' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDashboard' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDate' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDateTime' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDeadline' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDecimal' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDefinition' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeDuration' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeEmailAddress' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeEncryptedString' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeEnum' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeEnumSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeExternalField' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeExternalKey' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeFinalClass' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeFriendlyName' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeHTML' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeHierarchicalKey' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeIPAddress' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeImage' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeInteger' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeLinkedSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeLinkedSetIndirect' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeLongText' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeMetaEnum' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeOQL' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeObjectKey' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeObsolescenceDate' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeObsolescenceFlag' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeOneWayPassword' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributePassword' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributePercentage' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributePhoneNumber' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributePropertySet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeQueryAttCodeSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeRedundancySettings' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeStopWatch' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeString' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeSubItem' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTable' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTagSet' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTemplateHTML' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTemplateString' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeTemplateText' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeText' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AttributeURL' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'AuditCategory' => __DIR__ . '/../..' . '/application/audit.category.class.inc.php',
'AuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
'AuditRule' => __DIR__ . '/../..' . '/application/audit.rule.class.inc.php',
@@ -777,6 +719,66 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => __DIR__ . '/../..' . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
'Combodo\\iTop\\Controller\\WelcomePopupController' => __DIR__ . '/../..' . '/sources/Controller/WelcomePopupController.php',
'Combodo\\iTop\\Controller\\iController' => __DIR__ . '/../..' . '/sources/Controller/iController.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeApplicationLanguage' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeApplicationLanguage.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeArchiveDate' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeArchiveDate.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeArchiveFlag' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeArchiveFlag.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeBlob' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeBlob.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeBoolean' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeBoolean.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeCaseLog' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeCaseLog.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeClass' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeClass.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeClassAttCodeSet' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeClassAttCodeSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeClassState' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeClassState.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeCustomFields' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeCustomFields.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDBField' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDBField.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDBFieldVoid' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDBFieldVoid.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDashboard' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDashboard.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDate' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDate.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDateTime' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDateTime.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDeadline' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDeadline.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDecimal' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDecimal.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDefinition' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDefinition.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeDuration' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeDuration.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEmailAddress' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeEmailAddress.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEncryptedString' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeEncryptedString.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEnum' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeEnum.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeEnumSet' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeEnumSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeExternalField' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeExternalField.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeExternalKey' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeExternalKey.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeFinalClass' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeFinalClass.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeFriendlyName' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeFriendlyName.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeHTML' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeHTML.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeHierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeHierarchicalKey.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeIPAddress' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeIPAddress.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeImage' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeImage.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeInteger' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeInteger.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeLinkedSet' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeLinkedSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeLinkedSetIndirect' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeLinkedSetIndirect.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeLongText' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeLongText.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeMetaEnum' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeMetaEnum.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeOQL' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeOQL.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeObjectKey' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeObjectKey.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeObsolescenceDate' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeObsolescenceDate.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeObsolescenceFlag' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeObsolescenceFlag.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeOneWayPassword' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeOneWayPassword.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePassword' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributePassword.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePercentage' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributePercentage.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePhoneNumber' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributePhoneNumber.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributePropertySet' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributePropertySet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeQueryAttCodeSet' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeQueryAttCodeSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeRedundancySettings' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeRedundancySettings.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeSet' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeStopWatch' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeStopWatch.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeString' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeString.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeSubItem' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeSubItem.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTable' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeTable.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTagSet' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeTagSet.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTemplateHTML' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeTemplateHTML.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTemplateString' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeTemplateString.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeTemplateText' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeTemplateText.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeText' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeText.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\AttributeURL' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/AttributeURL.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\MissingColumnException' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/MissingColumnException.php',
'Combodo\\iTop\\Core\\AttributeDefinition\\iAttributeNoGroupBy' => __DIR__ . '/../..' . '/sources/Core/AttributeDefinition/iAttributeNoGroupBy.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php',
@@ -1488,7 +1490,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'MenuGroup' => __DIR__ . '/../..' . '/application/menunode.class.inc.php',
'MenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php',
'MetaModel' => __DIR__ . '/../..' . '/core/metamodel.class.php',
'MissingColumnException' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'MissingQueryArgument' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
'ModelReflection' => __DIR__ . '/../..' . '/core/modelreflection.class.inc.php',
'ModelReflectionRuntime' => __DIR__ . '/../..' . '/core/modelreflection.class.inc.php',
@@ -3533,8 +3534,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Twig\\Source' => __DIR__ . '/..' . '/twig/twig/src/Source.php',
'Twig\\Template' => __DIR__ . '/..' . '/twig/twig/src/Template.php',
'Twig\\TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/src/TemplateWrapper.php',
'Twig\\Test\\IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/IntegrationTestCase.php',
'Twig\\Test\\NodeTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/NodeTestCase.php',
'Twig\\Token' => __DIR__ . '/..' . '/twig/twig/src/Token.php',
'Twig\\TokenParser\\AbstractTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AbstractTokenParser.php',
'Twig\\TokenParser\\ApplyTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ApplyTokenParser.php',
@@ -3604,7 +3603,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'cmdbAbstractObject' => __DIR__ . '/../..' . '/application/cmdbabstract.class.inc.php',
'cmdbDataGenerator' => __DIR__ . '/../..' . '/core/data.generator.class.inc.php',
'iApplicationUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iApplicationUIExtension.php',
'iAttributeNoGroupBy' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php',
'iBackgroundProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
'iBackofficeDictEntriesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php',
'iBackofficeDictEntriesPrefixesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php',

View File

@@ -36,7 +36,8 @@ if ($issues) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

6
node_modules/.package-lock.json generated vendored
View File

@@ -22,6 +22,12 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@ungap/custom-elements": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/custom-elements/-/custom-elements-1.3.0.tgz",
"integrity": "sha512-f4q/s76+8nOy+fhrNHyetuoPDR01lmlZB5czfCG+OOnBw/Wf+x48DcCDPmMQY7oL8xYFL8qfenMoiS8DUkKBUw==",
"license": "ISC"
},
"node_modules/ace-builds": {
"version": "1.32.7",
"resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.32.7.tgz",

15
node_modules/@ungap/custom-elements/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2020, Andrea Giammarchi, @WebReflection
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

50
node_modules/@ungap/custom-elements/README.md generated vendored Normal file
View File

@@ -0,0 +1,50 @@
# Custom Elements Polyfill
This module provides the [Custom Elements V1 API](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-api) as defined by standards, including the ability to [extend builtin elements](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example), all in ~2K _minified_ and _gzipped_ / _brotlied_.
## Compatibility
The polyfill gracefully enhances the following minimum versions of at least these browsers, up to their latest version:
* Chrome 38
* Firefox 14
* Opera 25
* Internet Explorer 11 and Edge 12
* Safari 8 and WebKit based
* Samsung Internet 3
## How To
Either install this module via `npm i @ungap/custom-elements`, and include it in your project, or use a CDN such as [unpkg.com](https://unpkg.com/@ungap/custom-elements) to obtain the _minified_ version of this module.
```html
<!-- this should be on top of your HTML <head> scripts -->
<script src="//unpkg.com/@ungap/custom-elements"></script>
```
If targeted browsers are ES2015 compatible, the `es.js` file would provide the same polyfill, just lighter, as no transpilation is used.
```html
<script src="//unpkg.com/@ungap/custom-elements/es.js"></script>
```
If installed as module, please remember to include it on top of your main JS file.
```js
// ESM
import '@ungap/custom-elements';
// CJS
require('@ungap/custom-elements');
```
The module will incrementally patch the global `window`/`self` reference, adding a `customElements` object that is compatible with the API.
## Source Code
This module simply provides [@webreflection/custom-elements](https://github.com/WebReflection/custom-elements#readme) module under the [ungap](https://ungap.github.io/) umbrella.

2
node_modules/@ungap/custom-elements/es.js generated vendored Normal file

File diff suppressed because one or more lines are too long

554
node_modules/@ungap/custom-elements/index.js generated vendored Normal file
View File

@@ -0,0 +1,554 @@
/*! (c) Andrea Giammarchi @webreflection ISC */
(function () {
'use strict';
var attributesObserver = (function (whenDefined, MutationObserver) {
var attributeChanged = function attributeChanged(records) {
for (var i = 0, length = records.length; i < length; i++) dispatch(records[i]);
};
var dispatch = function dispatch(_ref) {
var target = _ref.target,
attributeName = _ref.attributeName,
oldValue = _ref.oldValue;
target.attributeChangedCallback(attributeName, oldValue, target.getAttribute(attributeName));
};
return function (target, is) {
var attributeFilter = target.constructor.observedAttributes;
if (attributeFilter) {
whenDefined(is).then(function () {
new MutationObserver(attributeChanged).observe(target, {
attributes: true,
attributeOldValue: true,
attributeFilter: attributeFilter
});
for (var i = 0, length = attributeFilter.length; i < length; i++) {
if (target.hasAttribute(attributeFilter[i])) dispatch({
target: target,
attributeName: attributeFilter[i],
oldValue: null
});
}
});
}
return target;
};
});
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (!it) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = it.call(o);
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
/*! (c) Andrea Giammarchi - ISC */
var TRUE = true,
FALSE = false,
QSA$1 = 'querySelectorAll';
/**
* Start observing a generic document or root element.
* @param {(node:Element, connected:boolean) => void} callback triggered per each dis/connected element
* @param {Document|Element} [root=document] by default, the global document to observe
* @param {Function} [MO=MutationObserver] by default, the global MutationObserver
* @param {string[]} [query=['*']] the selectors to use within nodes
* @returns {MutationObserver}
*/
var notify = function notify(callback) {
var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
var MO = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : MutationObserver;
var query = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ['*'];
var loop = function loop(nodes, selectors, added, removed, connected, pass) {
var _iterator = _createForOfIteratorHelper(nodes),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var node = _step.value;
if (pass || QSA$1 in node) {
if (connected) {
if (!added.has(node)) {
added.add(node);
removed["delete"](node);
callback(node, connected);
}
} else if (!removed.has(node)) {
removed.add(node);
added["delete"](node);
callback(node, connected);
}
if (!pass) loop(node[QSA$1](selectors), selectors, added, removed, connected, TRUE);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
};
var mo = new MO(function (records) {
if (query.length) {
var selectors = query.join(',');
var added = new Set(),
removed = new Set();
var _iterator2 = _createForOfIteratorHelper(records),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var _step2$value = _step2.value,
addedNodes = _step2$value.addedNodes,
removedNodes = _step2$value.removedNodes;
loop(removedNodes, selectors, added, removed, FALSE, FALSE);
loop(addedNodes, selectors, added, removed, TRUE, FALSE);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
});
var observe = mo.observe;
(mo.observe = function (node) {
return observe.call(mo, node, {
subtree: TRUE,
childList: TRUE
});
})(root);
return mo;
};
var QSA = 'querySelectorAll';
var _self$1 = self,
document$2 = _self$1.document,
Element$1 = _self$1.Element,
MutationObserver$2 = _self$1.MutationObserver,
Set$2 = _self$1.Set,
WeakMap$1 = _self$1.WeakMap;
var elements = function elements(element) {
return QSA in element;
};
var filter = [].filter;
var qsaObserver = (function (options) {
var live = new WeakMap$1();
var drop = function drop(elements) {
for (var i = 0, length = elements.length; i < length; i++) live["delete"](elements[i]);
};
var flush = function flush() {
var records = observer.takeRecords();
for (var i = 0, length = records.length; i < length; i++) {
parse(filter.call(records[i].removedNodes, elements), false);
parse(filter.call(records[i].addedNodes, elements), true);
}
};
var matches = function matches(element) {
return element.matches || element.webkitMatchesSelector || element.msMatchesSelector;
};
var notifier = function notifier(element, connected) {
var selectors;
if (connected) {
for (var q, m = matches(element), i = 0, length = query.length; i < length; i++) {
if (m.call(element, q = query[i])) {
if (!live.has(element)) live.set(element, new Set$2());
selectors = live.get(element);
if (!selectors.has(q)) {
selectors.add(q);
options.handle(element, connected, q);
}
}
}
} else if (live.has(element)) {
selectors = live.get(element);
live["delete"](element);
selectors.forEach(function (q) {
options.handle(element, connected, q);
});
}
};
var parse = function parse(elements) {
var connected = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
for (var i = 0, length = elements.length; i < length; i++) notifier(elements[i], connected);
};
var query = options.query;
var root = options.root || document$2;
var observer = notify(notifier, root, MutationObserver$2, query);
var attachShadow = Element$1.prototype.attachShadow;
if (attachShadow) Element$1.prototype.attachShadow = function (init) {
var shadowRoot = attachShadow.call(this, init);
observer.observe(shadowRoot);
return shadowRoot;
};
if (query.length) parse(root[QSA](query));
return {
drop: drop,
flush: flush,
observer: observer,
parse: parse
};
});
var _self = self,
document$1 = _self.document,
Map = _self.Map,
MutationObserver$1 = _self.MutationObserver,
Object$1 = _self.Object,
Set$1 = _self.Set,
WeakMap = _self.WeakMap,
Element = _self.Element,
HTMLElement = _self.HTMLElement,
Node = _self.Node,
Error = _self.Error,
TypeError$1 = _self.TypeError,
Reflect = _self.Reflect;
var defineProperty = Object$1.defineProperty,
keys = Object$1.keys,
getOwnPropertyNames = Object$1.getOwnPropertyNames,
setPrototypeOf = Object$1.setPrototypeOf;
var legacy = !self.customElements;
var expando = function expando(element) {
var key = keys(element);
var value = [];
var ignore = new Set$1();
var length = key.length;
for (var i = 0; i < length; i++) {
value[i] = element[key[i]];
try {
delete element[key[i]];
} catch (SafariTP) {
ignore.add(i);
}
}
return function () {
for (var _i = 0; _i < length; _i++) ignore.has(_i) || (element[key[_i]] = value[_i]);
};
};
if (legacy) {
var HTMLBuiltIn = function HTMLBuiltIn() {
var constructor = this.constructor;
if (!classes.has(constructor)) throw new TypeError$1('Illegal constructor');
var is = classes.get(constructor);
if (override) return augment(override, is);
var element = createElement.call(document$1, is);
return augment(setPrototypeOf(element, constructor.prototype), is);
};
var createElement = document$1.createElement;
var classes = new Map();
var defined = new Map();
var prototypes = new Map();
var registry = new Map();
var query = [];
var handle = function handle(element, connected, selector) {
var proto = prototypes.get(selector);
if (connected && !proto.isPrototypeOf(element)) {
var redefine = expando(element);
override = setPrototypeOf(element, proto);
try {
new proto.constructor();
} finally {
override = null;
redefine();
}
}
var method = "".concat(connected ? '' : 'dis', "connectedCallback");
if (method in proto) element[method]();
};
var _qsaObserver = qsaObserver({
query: query,
handle: handle
}),
parse = _qsaObserver.parse;
var override = null;
var whenDefined = function whenDefined(name) {
if (!defined.has(name)) {
var _,
$ = new Promise(function ($) {
_ = $;
});
defined.set(name, {
$: $,
_: _
});
}
return defined.get(name).$;
};
var augment = attributesObserver(whenDefined, MutationObserver$1);
self.customElements = {
define: function define(is, Class) {
if (registry.has(is)) throw new Error("the name \"".concat(is, "\" has already been used with this registry"));
classes.set(Class, is);
prototypes.set(is, Class.prototype);
registry.set(is, Class);
query.push(is);
whenDefined(is).then(function () {
parse(document$1.querySelectorAll(is));
});
defined.get(is)._(Class);
},
get: function get(is) {
return registry.get(is);
},
whenDefined: whenDefined
};
defineProperty(HTMLBuiltIn.prototype = HTMLElement.prototype, 'constructor', {
value: HTMLBuiltIn
});
self.HTMLElement = HTMLBuiltIn;
document$1.createElement = function (name, options) {
var is = options && options.is;
var Class = is ? registry.get(is) : registry.get(name);
return Class ? new Class() : createElement.call(document$1, name);
};
// in case ShadowDOM is used through a polyfill, to avoid issues
// with builtin extends within shadow roots
if (!('isConnected' in Node.prototype)) defineProperty(Node.prototype, 'isConnected', {
configurable: true,
get: function get() {
return !(this.ownerDocument.compareDocumentPosition(this) & this.DOCUMENT_POSITION_DISCONNECTED);
}
});
} else {
legacy = !self.customElements.get('extends-br');
if (legacy) {
try {
var BR = function BR() {
return self.Reflect.construct(HTMLBRElement, [], BR);
};
BR.prototype = HTMLLIElement.prototype;
var is = 'extends-br';
self.customElements.define('extends-br', BR, {
'extends': 'br'
});
legacy = document$1.createElement('br', {
is: is
}).outerHTML.indexOf(is) < 0;
var _self$customElements = self.customElements,
get = _self$customElements.get,
_whenDefined = _self$customElements.whenDefined;
self.customElements.whenDefined = function (is) {
var _this = this;
return _whenDefined.call(this, is).then(function (Class) {
return Class || get.call(_this, is);
});
};
} catch (o_O) {}
}
}
if (legacy) {
var _parseShadow = function _parseShadow(element) {
var root = shadowRoots.get(element);
_parse(root.querySelectorAll(this), element.isConnected);
};
var customElements = self.customElements;
var _createElement = document$1.createElement;
var define = customElements.define,
_get = customElements.get,
upgrade = customElements.upgrade;
var _ref = Reflect || {
construct: function construct(HTMLElement) {
return HTMLElement.call(this);
}
},
construct = _ref.construct;
var shadowRoots = new WeakMap();
var shadows = new Set$1();
var _classes = new Map();
var _defined = new Map();
var _prototypes = new Map();
var _registry = new Map();
var shadowed = [];
var _query = [];
var getCE = function getCE(is) {
return _registry.get(is) || _get.call(customElements, is);
};
var _handle = function _handle(element, connected, selector) {
var proto = _prototypes.get(selector);
if (connected && !proto.isPrototypeOf(element)) {
var redefine = expando(element);
_override = setPrototypeOf(element, proto);
try {
new proto.constructor();
} finally {
_override = null;
redefine();
}
}
var method = "".concat(connected ? '' : 'dis', "connectedCallback");
if (method in proto) element[method]();
};
var _qsaObserver2 = qsaObserver({
query: _query,
handle: _handle
}),
_parse = _qsaObserver2.parse;
var _qsaObserver3 = qsaObserver({
query: shadowed,
handle: function handle(element, connected) {
if (shadowRoots.has(element)) {
if (connected) shadows.add(element);else shadows["delete"](element);
if (_query.length) _parseShadow.call(_query, element);
}
}
}),
parseShadowed = _qsaObserver3.parse;
// qsaObserver also patches attachShadow
// be sure this runs *after* that
var attachShadow = Element.prototype.attachShadow;
if (attachShadow) Element.prototype.attachShadow = function (init) {
var root = attachShadow.call(this, init);
shadowRoots.set(this, root);
return root;
};
var _whenDefined2 = function _whenDefined2(name) {
if (!_defined.has(name)) {
var _,
$ = new Promise(function ($) {
_ = $;
});
_defined.set(name, {
$: $,
_: _
});
}
return _defined.get(name).$;
};
var _augment = attributesObserver(_whenDefined2, MutationObserver$1);
var _override = null;
getOwnPropertyNames(self).filter(function (k) {
return /^HTML.*Element$/.test(k);
}).forEach(function (k) {
var HTMLElement = self[k];
function HTMLBuiltIn() {
var constructor = this.constructor;
if (!_classes.has(constructor)) throw new TypeError$1('Illegal constructor');
var _classes$get = _classes.get(constructor),
is = _classes$get.is,
tag = _classes$get.tag;
if (is) {
if (_override) return _augment(_override, is);
var element = _createElement.call(document$1, tag);
element.setAttribute('is', is);
return _augment(setPrototypeOf(element, constructor.prototype), is);
} else return construct.call(this, HTMLElement, [], constructor);
}
defineProperty(HTMLBuiltIn.prototype = HTMLElement.prototype, 'constructor', {
value: HTMLBuiltIn
});
defineProperty(self, k, {
value: HTMLBuiltIn
});
});
document$1.createElement = function (name, options) {
var is = options && options.is;
if (is) {
var Class = _registry.get(is);
if (Class && _classes.get(Class).tag === name) return new Class();
}
var element = _createElement.call(document$1, name);
if (is) element.setAttribute('is', is);
return element;
};
customElements.get = getCE;
customElements.whenDefined = _whenDefined2;
customElements.upgrade = function (element) {
var is = element.getAttribute('is');
if (is) {
var _constructor = _registry.get(is);
if (_constructor) {
_augment(setPrototypeOf(element, _constructor.prototype), is);
// apparently unnecessary because this is handled by qsa observer
// if (element.isConnected && element.connectedCallback)
// element.connectedCallback();
return;
}
}
upgrade.call(customElements, element);
};
customElements.define = function (is, Class, options) {
if (getCE(is)) throw new Error("'".concat(is, "' has already been defined as a custom element"));
var selector;
var tag = options && options["extends"];
_classes.set(Class, tag ? {
is: is,
tag: tag
} : {
is: '',
tag: is
});
if (tag) {
selector = "".concat(tag, "[is=\"").concat(is, "\"]");
_prototypes.set(selector, Class.prototype);
_registry.set(is, Class);
_query.push(selector);
} else {
define.apply(customElements, arguments);
shadowed.push(selector = is);
}
_whenDefined2(is).then(function () {
if (tag) {
_parse(document$1.querySelectorAll(selector));
shadows.forEach(_parseShadow, [selector]);
} else parseShadowed(document$1.querySelectorAll(selector));
});
_defined.get(is)._(Class);
};
}
})();

3
node_modules/@ungap/custom-elements/min.js generated vendored Normal file

File diff suppressed because one or more lines are too long

30
node_modules/@ungap/custom-elements/package.json generated vendored Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "@ungap/custom-elements",
"version": "1.3.0",
"description": "All inclusive customElements polyfill for every browser",
"main": "index.js",
"module": "index.js",
"unpkg": "min.js",
"scripts": {
"build": "cp ./node_modules/@webreflection/custom-elements/*.js ./"
},
"keywords": [
"custom",
"elements",
"polyfill",
"customElements"
],
"author": "Andrea Giammarchi",
"license": "ISC",
"devDependencies": {
"@webreflection/custom-elements": "^1.3.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ungap/custom-elements.git"
},
"bugs": {
"url": "https://github.com/ungap/custom-elements/issues"
},
"homepage": "https://github.com/ungap/custom-elements#readme"
}

14
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"@cityssm/fa5-power-transforms-css": "github:cityssm/fa5-power-transforms-css",
"@fontsource/raleway": "^4.5.0",
"@popperjs/core": "^2.11.8",
"@ungap/custom-elements": "^1.3.0",
"ace-builds": "^1.32.7",
"blueimp-file-upload": "^10.32.0",
"bulma-scss": "^0.9.4",
@@ -56,6 +57,12 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@ungap/custom-elements": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/custom-elements/-/custom-elements-1.3.0.tgz",
"integrity": "sha512-f4q/s76+8nOy+fhrNHyetuoPDR01lmlZB5czfCG+OOnBw/Wf+x48DcCDPmMQY7oL8xYFL8qfenMoiS8DUkKBUw==",
"license": "ISC"
},
"node_modules/ace-builds": {
"version": "1.32.7",
"resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.32.7.tgz",
@@ -320,6 +327,11 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
},
"@ungap/custom-elements": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/custom-elements/-/custom-elements-1.3.0.tgz",
"integrity": "sha512-f4q/s76+8nOy+fhrNHyetuoPDR01lmlZB5czfCG+OOnBw/Wf+x48DcCDPmMQY7oL8xYFL8qfenMoiS8DUkKBUw=="
},
"ace-builds": {
"version": "1.32.7",
"resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.32.7.tgz",
@@ -368,7 +380,7 @@
},
"ckeditor5-itop-build": {
"version": "git+ssh://git@github.com/Combodo/ckeditor5-itop-build.git#303d2091805b12fce8807019f599b0a9601c9cfa",
"from": "ckeditor5-itop-build@https://github.com/Combodo/ckeditor5-itop-build.git"
"from": "ckeditor5-itop-build@github:Combodo/ckeditor5-itop-build"
},
"clipboard": {
"version": "2.0.11",

View File

@@ -4,6 +4,7 @@
"@cityssm/fa5-power-transforms-css": "github:cityssm/fa5-power-transforms-css",
"@fontsource/raleway": "^4.5.0",
"@popperjs/core": "^2.11.8",
"@ungap/custom-elements": "^1.3.0",
"ace-builds": "^1.32.7",
"blueimp-file-upload": "^10.32.0",
"bulma-scss": "^0.9.4",

View File

@@ -617,7 +617,7 @@ EOF;
}
if (is_null($iPort)) {
$iPort = CMDBSource::MYSQL_DEFAULT_PORT;
return '';
}
$sPortOption = self::GetMysqliCliSingleOption('port', $iPort);

View File

@@ -1,4 +1,8 @@
<?php
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
require_once(APPROOT.'/setup/parameters.class.inc.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
require_once(APPROOT.'/setup/modulediscovery.class.inc.php');

View File

@@ -20,6 +20,8 @@
*/
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php');

View File

@@ -1,6 +1,21 @@
<?php
namespace Combodo\iTop\Setup\ModuleDiscovery;
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use CoreException;
use Exception;
use ParseError;
use PhpParser\Error;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\If_;
use PhpParser\ParserFactory;
use PhpParser\Node\Expr\Assign;
use \PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Arg;
require_once __DIR__ . '/ModuleFileReaderException.php';
require_once APPROOT . 'sources/PhpParser/Evaluation/PhpExpressionEvaluator.php';
@@ -47,16 +62,16 @@ class ModuleFileReader {
{
try
{
$oParser = (new \PhpParser\ParserFactory())->createForNewestSupportedVersion();
$oParser = (new ParserFactory())->createForNewestSupportedVersion();
$aNodes = $oParser->parse(file_get_contents($sModuleFilePath));
}
catch (PhpParser\Error $e) {
throw new \ModuleFileReaderException($e->getMessage(), 0, $e, $sModuleFilePath);
catch (Error $e) {
throw new ModuleFileReaderException($e->getMessage(), 0, $e, $sModuleFilePath);
}
try {
foreach ($aNodes as $sKey => $oNode) {
if ($oNode instanceof \PhpParser\Node\Stmt\Expression) {
if ($oNode instanceof Expression) {
$aModuleInfo = $this->GetModuleInformationFromAddModuleCall($sModuleFilePath, $oNode);
if (! is_null($aModuleInfo)){
$this->CompleteModuleInfoWithFilePath($aModuleInfo);
@@ -64,7 +79,7 @@ class ModuleFileReader {
}
}
if ($oNode instanceof PhpParser\Node\Stmt\If_) {
if ($oNode instanceof If_) {
$aModuleInfo = $this->GetModuleInformationFromIf($sModuleFilePath, $oNode);
if (! is_null($aModuleInfo)){
$this->CompleteModuleInfoWithFilePath($aModuleInfo);
@@ -186,17 +201,17 @@ class ModuleFileReader {
* @param \PhpParser\Node\Expr\Assign $oAssignation
*
* @return array|null
* @throws \ModuleFileReaderException
* @throws ModuleFileReaderException
*/
private function GetModuleInformationFromAddModuleCall(string $sModuleFilePath, \PhpParser\Node\Stmt\Expression $oExpression) : ?array
{
/** @var Assign $oAssignation */
$oAssignation = $oExpression->expr;
if (false === ($oAssignation instanceof PhpParser\Node\Expr\StaticCall)) {
if (false === ($oAssignation instanceof StaticCall)) {
return null;
}
/** @var PhpParser\Node\Expr\StaticCall $oAssignation */
/** @var StaticCall $oAssignation */
if ("SetupWebPage" !== $oAssignation?->class?->name) {
return null;
@@ -212,24 +227,24 @@ class ModuleFileReader {
}
$oModuleId = $aArgs[1];
if (false === ($oModuleId instanceof PhpParser\Node\Arg)) {
if (false === ($oModuleId instanceof Arg)) {
throw new ModuleFileReaderException("2nd parameter to SetupWebPage::AddModule call issue: " . get_class($oModuleId), 0, null, $sModuleFilePath);
}
/** @var PhpParser\Node\Arg $oModuleId */
if (false === ($oModuleId->value instanceof PhpParser\Node\Scalar\String_)) {
/** @var Arg $oModuleId */
if (false === ($oModuleId->value instanceof String_)) {
throw new ModuleFileReaderException("2nd parameter to SetupWebPage::AddModule not a string: " . get_class($oModuleId->value), 0, null, $sModuleFilePath);
}
$sModuleId = $this->oPhpExpressionEvaluator->EvaluateExpression($oModuleId->value);
$oModuleConfigInfo = $aArgs[2];
if (false === ($oModuleConfigInfo instanceof PhpParser\Node\Arg)) {
if (false === ($oModuleConfigInfo instanceof Arg)) {
throw new ModuleFileReaderException("3rd parameter to SetupWebPage::AddModule call issue: " . get_class($oModuleConfigInfo), 0, null, $sModuleFilePath);
}
/** @var PhpParser\Node\Arg $oModuleConfigInfo */
if (false === ($oModuleConfigInfo->value instanceof PhpParser\Node\Expr\Array_)) {
/** @var Arg $oModuleConfigInfo */
if (false === ($oModuleConfigInfo->value instanceof Array_)) {
throw new ModuleFileReaderException("3rd parameter to SetupWebPage::AddModule not an array: " . get_class($oModuleConfigInfo->value), 0, null, $sModuleFilePath);
}
@@ -251,14 +266,14 @@ class ModuleFileReader {
* @param \PhpParser\Node\Stmt\If_ $oNode
*
* @return array|null
* @throws \ModuleFileReaderException
* @throws ModuleFileReaderException
*/
private function GetModuleInformationFromIf(string $sModuleFilePath, \PhpParser\Node\Stmt\If_ $oNode) : ?array
{
$bCondition = $this->oPhpExpressionEvaluator->EvaluateExpression($oNode->cond);
if ($bCondition) {
foreach ($oNode->stmts as $oSubNode) {
if ($oSubNode instanceof \PhpParser\Node\Stmt\Expression) {
if ($oSubNode instanceof Expression) {
$aModuleConfig = $this->GetModuleInformationFromAddModuleCall($sModuleFilePath, $oSubNode);
if (!is_null($aModuleConfig)) {
return $aModuleConfig;
@@ -271,7 +286,7 @@ class ModuleFileReader {
if (! is_null($oNode->elseifs)) {
foreach ($oNode->elseifs as $oElseIfSubNode) {
/** @var \PhpParser\Node\Stmt\ElseIf_ $oElseIfSubNode */
/** @var ElseIf_ $oElseIfSubNode */
$bCondition = $this->oPhpExpressionEvaluator->EvaluateExpression($oElseIfSubNode->cond);
if ($bCondition) {
return $this->GetModuleConfigurationFromStatement($sModuleFilePath, $oElseIfSubNode->stmts);
@@ -289,7 +304,7 @@ class ModuleFileReader {
private function GetModuleConfigurationFromStatement(string $sModuleFilePath, array $aStmts) : ?array
{
foreach ($aStmts as $oSubNode) {
if ($oSubNode instanceof \PhpParser\Node\Stmt\Expression) {
if ($oSubNode instanceof Expression) {
$aModuleConfig = $this->GetModuleInformationFromAddModuleCall($sModuleFilePath, $oSubNode);
if (!is_null($aModuleConfig)) {
return $aModuleConfig;

View File

@@ -1,5 +1,9 @@
<?php
namespace Combodo\iTop\Setup\ModuleDiscovery;
use Exception;
use SetupLog;
class ModuleFileReaderException extends Exception
{
/**
@@ -11,7 +15,7 @@ class ModuleFileReaderException extends Exception
*/
public function __construct($sMessage, $iHttpCode = 0, Exception $oPrevious = null, $sModuleFile = null)
{
$e = new \Exception("");
$e = new Exception("");
$aContext = ['previous' => $oPrevious?->getMessage(), 'stack' => $e->getTraceAsString()];
if (!is_null($sModuleFile)) {

View File

@@ -25,6 +25,8 @@
*/
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
require_once APPROOT."setup/modulediscovery.class.inc.php";
require_once APPROOT.'setup/modelfactory.class.inc.php';

View File

@@ -1,6 +1,7 @@
<?php
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/setup/setuppage.class.inc.php');

View File

@@ -41,6 +41,7 @@
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
require_once(APPROOT.'setup/setuputils.class.inc.php');
require_once(APPROOT.'setup/parameters.class.inc.php');

View File

@@ -55,7 +55,7 @@ class UIBlockParser extends AbstractTokenParser
$sType = $oStream->expect(Token::NAME_TYPE)->getValue();
$oParams = $this->parser->getExpressionParser()->parseExpression();
$oParams = $this->parser->parseExpression();
$oStream->expect(Token::BLOCK_END_TYPE);

View File

@@ -22,7 +22,7 @@ abstract class AbstractInput extends UIBlock
/**@var string */
protected $sPlaceholder;
public function GetName(): string
public function GetName(): ?string
{
return $this->sName;
}

View File

@@ -70,8 +70,6 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
];
/** @inheritDoc */
protected const COMPATIBILITY_DEPRECATED_LINKED_SCRIPTS_REL_PATH = [
'js/date.js',
'js/jquery.layout.min.js',
];
/** @inheritDoc */
protected const COMPATIBILITY_MOVED_LINKED_STYLESHEETS_REL_PATH = [

View File

@@ -0,0 +1,53 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Dict;
use ValueSetEnum;
/**
* An attibute that matches one of the language codes availables in the dictionnary
*
* @package iTopORM
*/
class AttributeApplicationLanguage extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
}
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
$aAvailableLanguages = Dict::GetLanguages();
$aLanguageCodes = array();
foreach ($aAvailableLanguages as $sLangCode => $aInfo) {
$aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')';
}
// N°6462 This should be sorted directly in \Dict during the compilation but we can't for 2 reasons:
// - Additional languages can be added on the fly even though it is not recommended
// - Formatting is done at run time (just above)
natcasesort($aLanguageCodes);
$aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes);
parent::__construct($sCode, $aParams);
}
public function RequiresIndex()
{
return true;
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Dict;
use Exception;
class AttributeArchiveDate extends AttributeDate
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetLabel($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeArchiveDate/Label', $sDefault);
return parent::GetLabel($sDefault);
}
public function GetDescription($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeArchiveDate/Label+', $sDefault);
return parent::GetDescription($sDefault);
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Dict;
class AttributeArchiveFlag extends AttributeBoolean
{
public function __construct($sCode)
{
parent::__construct($sCode, array(
"allowed_values" => null,
"sql" => $sCode,
"default_value" => false,
"is_null_allowed" => false,
"depends_on" => array(),
));
}
public function RequiresIndex()
{
return true;
}
public function CopyOnAllTables()
{
return true;
}
public function IsWritable()
{
return false;
}
public function IsMagic()
{
return true;
}
public function GetLabel($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeArchiveFlag/Label', $sDefault);
return parent::GetLabel($sDefault);
}
public function GetDescription($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeArchiveFlag/Label+', $sDefault);
return parent::GetDescription($sDefault);
}
}

View File

@@ -0,0 +1,386 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOp;
use CMDBChangeOpSetAttributeBlob;
use CMDBSource;
use DBObject;
use Exception;
use IssueLog;
use ormDocument;
use utils;
/**
* A blob is an ormDocument, it is stored as several columns in the database
*
* @package iTopORM
*/
class AttributeBlob extends AttributeDefinition
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("depends_on"));
}
public function GetEditClass()
{
return "Document";
}
public static function IsBasedOnDBColumns()
{
return true;
}
public static function IsScalar()
{
return true;
}
public function IsWritable()
{
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return new ormDocument('', '', '');
}
public function IsNullAllowed(DBObject $oHostObject = null)
{
return $this->GetOptional("is_null_allowed", false);
}
public function GetEditValue($sValue, $oHostObj = null)
{
return '';
}
/**
* {@inheritDoc}
*
* @see AttributeDefinition::MakeRealValue()
*
* @param string $proposedValue Can be an URL (including an URL to iTop itself), or a local path (CSV import)
*
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if ($proposedValue === null) {
return null;
}
if (is_object($proposedValue)) {
$proposedValue = clone $proposedValue;
} else {
try {
// Read the file from iTop, an URL (or the local file system - for admins only)
$proposedValue = utils::FileGetContentsAndMIMEType($proposedValue);
}
catch (Exception $e) {
IssueLog::Warning(get_class($this)."::MakeRealValue - ".$e->getMessage());
// Not a real document !! store is as text !!! (This was the default behavior before)
$proposedValue = new ormDocument($e->getMessage()." \n".$proposedValue, 'text/plain');
}
}
return $proposedValue;
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '') {
$sPrefix = $this->GetCode();
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
$aColumns[''] = $sPrefix.'_mimetype';
$aColumns['_data'] = $sPrefix.'_data';
$aColumns['_filename'] = $sPrefix.'_filename';
$aColumns['_downloads_count'] = $sPrefix.'_downloads_count';
return $aColumns;
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
if (!array_key_exists($sPrefix, $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
}
$sMimeType = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : '';
if (!array_key_exists($sPrefix.'_data', $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}");
}
$data = isset($aCols[$sPrefix.'_data']) ? $aCols[$sPrefix.'_data'] : null;
if (!array_key_exists($sPrefix.'_filename', $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}");
}
$sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : '';
if (!array_key_exists($sPrefix.'_downloads_count', $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_downloads_count' from {$sAvailable}");
}
$iDownloadsCount = isset($aCols[$sPrefix.'_downloads_count']) ? $aCols[$sPrefix.'_downloads_count'] : ormDocument::DEFAULT_DOWNLOADS_COUNT;
$value = new ormDocument($data, $sMimeType, $sFileName, $iDownloadsCount);
return $value;
}
public function GetSQLValues($value)
{
// #@# Optimization: do not load blobs anytime
// As per mySQL doc, selecting blob columns will prevent mySQL from
// using memory in case a temporary table has to be created
// (temporary tables created on disk)
// We will have to remove the blobs from the list of attributes when doing the select
// then the use of Get() should finalize the load
if ($value instanceof ormDocument) {
$aValues = array();
if (!$value->IsEmpty()) {
$aValues[$this->GetCode().'_data'] = $value->GetData();
} else {
$aValues[$this->GetCode().'_data'] = '';
}
$aValues[$this->GetCode().'_mimetype'] = $value->GetMimeType();
$aValues[$this->GetCode().'_filename'] = $value->GetFileName();
$aValues[$this->GetCode().'_downloads_count'] = $value->GetDownloadsCount();
} else {
$aValues = array();
$aValues[$this->GetCode().'_data'] = '';
$aValues[$this->GetCode().'_mimetype'] = '';
$aValues[$this->GetCode().'_filename'] = '';
$aValues[$this->GetCode().'_downloads_count'] = ormDocument::DEFAULT_DOWNLOADS_COUNT;
}
return $aValues;
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb)
$aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode().'_downloads_count'] = 'INT(11) UNSIGNED';
return $aColumns;
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return 'true';
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if (is_object($value)) {
return $value->GetAsHTML();
}
return '';
}
/**
* @param string $sValue
* @param string $sSeparator
* @param string $sTextQualifier
* @param DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return string
*/
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
$sAttCode = $this->GetCode();
if ($sValue instanceof ormDocument && !$sValue->IsEmpty()) {
return $sValue->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $sAttCode);
}
return ''; // Not exportable in CSV !
}
/**
* @param $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return mixed|string
*/
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
$sRet = '';
if (is_object($value)) {
/** @var ormDocument $value */
if (!$value->IsEmpty()) {
$sRet = '<mimetype>'.$value->GetMimeType().'</mimetype>';
$sRet .= '<filename>'.$value->GetFileName().'</filename>';
$sRet .= '<data>'.base64_encode($value->GetData()).'</data>';
$sRet .= '<downloads_count>'.$value->GetDownloadsCount().'</downloads_count>';
}
}
return $sRet;
}
public function GetForJSON($value)
{
if ($value instanceof ormDocument) {
$aValues = array();
$aValues['data'] = base64_encode($value->GetData());
$aValues['mimetype'] = $value->GetMimeType();
$aValues['filename'] = $value->GetFileName();
$aValues['downloads_count'] = $value->GetDownloadsCount();
} else {
$aValues = null;
}
return $aValues;
}
public function FromJSONToValue($json)
{
if (isset($json->data)) {
$data = base64_decode($json->data);
$value = new ormDocument($data, $json->mimetype, $json->filename, $json->downloads_count);
} else {
$value = null;
}
return $value;
}
public function Fingerprint($value)
{
$sFingerprint = '';
if ($value instanceof ormDocument) {
$sFingerprint = $value->GetSignature();
}
return $sFingerprint;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\BlobField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
/** @var $oFormField BlobField */
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
// Note: As of today we want this field to always be read-only
$oFormField->SetReadOnly(true);
// Calling parent before so current value is set, then proceed
parent::MakeFormField($oObject, $oFormField);
// Setting current value correctly as the default method returns an empty string when there is no file yet.
/** @var ormDocument $value */
$value = $oObject->Get($this->GetCode());
if (!is_object($value)) {
$oFormField->SetCurrentValue(new ormDocument());
}
// Generating urls
if (is_object($value) && !$value->IsEmpty()) {
$oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
$oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
}
return $oFormField;
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
if (false === ($proposedValue instanceof ormDocument)) {
return parent::HasAValue($proposedValue);
}
// Empty file (no content, just a filename) are supported since PR {@link https://github.com/Combodo/combodo-email-synchro/pull/17}, so we check for both empty content and empty filename to determine that a document has no value
return utils::IsNotNullOrEmptyString($proposedValue->GetData()) && utils::IsNotNullOrEmptyString($proposedValue->GetFileName());
}
/**
* @inheritDoc
*
* @param ormDocument $original
* @param ormDocument $value
*
* @since N°6502
*/
public function RecordAttChange(DBObject $oObject, $original, $value): void
{
// N°6502 Don't record history if only the download count has changed
if ((null !== $original) && (null !== $value) && $original->EqualsExceptDownloadsCount($value)) {
return;
}
parent::RecordAttChange($oObject, $original, $value);
}
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
{
if (is_null($original)) {
$original = new ormDocument();
}
$oMyChangeOp->Set("prevdata", $original);
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeBlob::class;
}
}

View File

@@ -0,0 +1,253 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOpSetAttributeScalar;
use Combodo\iTop\Form\Field\SelectField;
use DBObject;
use Dict;
/**
* Map a boolean column to an attribute
*
* @package iTopORM
*/
class AttributeBoolean extends AttributeInteger
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "Integer";
}
protected function GetSQLCol($bFullSpec = false)
{
return "TINYINT(1)".($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return null;
}
if ($proposedValue === '') {
return null;
}
if ((int)$proposedValue) {
return true;
}
return false;
}
public function ScalarToSQL($value)
{
if ($value) {
return 1;
}
return 0;
}
public function GetValueLabel($bValue)
{
if (is_null($bValue)) {
$sLabel = Dict::S('Core:'.get_class($this).'/Value:null');
} else {
$sValue = $bValue ? 'yes' : 'no';
$sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue);
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, true /*user lang*/);
}
return $sLabel;
}
public function GetValueDescription($bValue)
{
if (is_null($bValue)) {
$sDescription = Dict::S('Core:'.get_class($this).'/Value:null+');
} else {
$sValue = $bValue ? 'yes' : 'no';
$sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue.'+');
$sDescription = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue.'+', $sDefault,
true /*user lang*/);
}
return $sDescription;
}
public function GetAsHTML($bValue, $oHostObject = null, $bLocalize = true)
{
if (is_null($bValue)) {
$sRes = '';
} elseif ($bLocalize) {
$sLabel = $this->GetValueLabel($bValue);
$sDescription = $this->GetValueDescription($bValue);
// later, we could imagine a detailed description in the title
$sRes = "<span title=\"$sDescription\">".parent::GetAsHtml($sLabel)."</span>";
} else {
$sRes = $bValue ? 'yes' : 'no';
}
return $sRes;
}
public function GetAsXML($bValue, $oHostObject = null, $bLocalize = true)
{
if (is_null($bValue)) {
$sFinalValue = '';
} elseif ($bLocalize) {
$sFinalValue = $this->GetValueLabel($bValue);
} else {
$sFinalValue = $bValue ? 'yes' : 'no';
}
$sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize);
return $sRes;
}
public function GetAsCSV(
$bValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
if (is_null($bValue)) {
$sFinalValue = '';
} elseif ($bLocalize) {
$sFinalValue = $this->GetValueLabel($bValue);
} else {
$sFinalValue = $bValue ? 'yes' : 'no';
}
$sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize);
return $sRes;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\SelectField';
}
/**
* @param DBObject $oObject
* @param SelectField $oFormField
*
* @return SelectField
* @throws CoreException
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
$oFormField->SetChoices(array('yes' => $this->GetValueLabel(true), 'no' => $this->GetValueLabel(false)));
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
public function GetEditValue($value, $oHostObj = null)
{
if (is_null($value)) {
return '';
} else {
return $this->GetValueLabel($value);
}
}
public function GetForJSON($value)
{
return (bool)$value;
}
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
)
{
$sInput = mb_strtolower(trim($sProposedValue));
if ($bLocalizedValue) {
switch ($sInput) {
case '1': // backward compatibility
case $this->GetValueLabel(true):
$value = true;
break;
case '0': // backward compatibility
case 'no':
case $this->GetValueLabel(false):
$value = false;
break;
default:
$value = null;
}
} else {
switch ($sInput) {
case '1': // backward compatibility
case 'yes':
$value = true;
break;
case '0': // backward compatibility
case 'no':
$value = false;
break;
default:
$value = null;
}
}
return $value;
}
public function RecordAttChange(DBObject $oObject, $original, $value): void
{
parent::RecordAttChange($oObject, $original ? 1 : 0, $value ? 1 : 0);
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeScalar::class;
}
public function GetAllowedValues($aArgs = array(), $sContains = ''): array
{
return [
0 => $this->GetValueLabel(false),
1 => $this->GetValueLabel(true),
];
}
public function GetDisplayStyle()
{
return $this->GetOptional('display_style', 'select');
}
}

View File

@@ -0,0 +1,413 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOp;
use CMDBChangeOpSetAttributeCaseLog;
use CMDBSource;
use CoreWarning;
use DBObject;
use Dict;
use Exception;
use ormCaseLog;
use stdClass;
use UserRights;
use utils;
/**
* An attibute that stores a case log (i.e journal)
*
* @package iTopORM
*/
class AttributeCaseLog extends AttributeLongText
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetNullValue()
{
return '';
}
public function IsNull($proposedValue)
{
if (!($proposedValue instanceof ormCaseLog)) {
return ($proposedValue == '');
}
return ($proposedValue->GetText() == '');
}
/**
* @inheritDoc
*
* @param ormCaseLog $proposedValue
*/
public function HasAValue($proposedValue): bool
{
// Protection against wrong value type
if (false === ($proposedValue instanceof ormCaseLog)) {
return parent::HasAValue($proposedValue);
}
// We test if there is at least 1 entry in the log, not if the user is adding one
return $proposedValue->GetEntryCount() > 0;
}
public function ScalarToSQL($value)
{
if (!is_string($value) && !is_null($value)) {
throw new CoreWarning('Expected the attribute value to be a string', array(
'found_type' => gettype($value),
'value' => $value,
'class' => $this->GetCode(),
'attribute' => $this->GetHostClass(),
));
}
return $value;
}
public function GetEditClass()
{
return "CaseLog";
}
public function GetEditValue($sValue, $oHostObj = null)
{
if (!($sValue instanceof ormCaseLog)) {
return '';
}
return $sValue->GetModifiedEntry();
}
/**
* For fields containing a potential markup, return the value without this markup
*
* @param mixed $value
* @param \DBObject $oHostObj
*
* @return string
*/
public function GetAsPlainText($value, $oHostObj = null)
{
if ($value instanceof ormCaseLog) {
/** ormCaseLog $value */
return $value->GetAsPlainText();
} else {
return (string)$value;
}
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return new ormCaseLog();
}
public function Equals($val1, $val2)
{
return ($val1->GetText() == $val2->GetText());
}
/**
* Facilitate things: allow the user to Set the value from a string
*
* @param $proposedValue
* @param DBObject $oHostObj
*
* @return mixed|null|ormCaseLog|string
* @throws Exception
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if ($proposedValue instanceof ormCaseLog) {
// Passthrough
$ret = clone $proposedValue;
} else {
// Append the new value if an instance of the object is supplied
//
$oPreviousLog = null;
if ($oHostObj != null) {
$oPreviousLog = $oHostObj->Get($this->GetCode());
if (!is_object($oPreviousLog)) {
$oPreviousLog = $oHostObj->GetOriginal($this->GetCode());;
}
}
if (is_object($oPreviousLog)) {
$oCaseLog = clone($oPreviousLog);
} else {
$oCaseLog = new ormCaseLog();
}
if ($proposedValue instanceof stdClass) {
$oCaseLog->AddLogEntryFromJSON($proposedValue);
} else {
if (utils::StrLen($proposedValue) > 0) {
//N°5135 - add impersonation information in caselog
if (UserRights::IsImpersonated()) {
$sOnBehalfOf = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUserFriendlyName(), UserRights::GetUserFriendlyName());
$oCaseLog->AddLogEntry($proposedValue, $sOnBehalfOf, UserRights::GetConnectedUserId());
} else {
$oCaseLog->AddLogEntry($proposedValue);
}
}
}
$ret = $oCaseLog;
}
return $ret;
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '') {
$sPrefix = $this->Get('sql');
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
$aColumns[''] = $sPrefix;
$aColumns['_index'] = $sPrefix.'_index';
return $aColumns;
}
/**
* @param array $aCols
* @param string $sPrefix
*
* @return ormCaseLog
* @throws MissingColumnException
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
if (!array_key_exists($sPrefix, $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
}
$sLog = $aCols[$sPrefix];
if (isset($aCols[$sPrefix.'_index'])) {
$sIndex = $aCols[$sPrefix.'_index'];
} else {
// For backward compatibility, allow the current state to be: 1 log, no index
$sIndex = '';
}
if (strlen($sIndex) > 0) {
$aIndex = unserialize($sIndex);
$value = new ormCaseLog($sLog, $aIndex);
} else {
$value = new ormCaseLog($sLog);
}
return $value;
}
public function GetSQLValues($value)
{
if (!($value instanceof ormCaseLog)) {
$value = new ormCaseLog('');
}
$aValues = array();
$aValues[$this->GetCode()] = $value->GetText();
$aValues[$this->GetCode().'_index'] = serialize($value->GetIndex());
return $aValues;
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'LONGTEXT' // 2^32 (4 Gb)
.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode().'_index'] = 'BLOB';
return $aColumns;
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if ($value instanceof ormCaseLog) {
$sContent = $value->GetAsHTML(null, false, array(__class__, 'RenderWikiHtml'));
} else {
$sContent = '';
}
$aStyles = array();
if ($this->GetWidth() != '') {
$aStyles[] = 'width:'.$this->GetWidth();
}
if ($this->GetHeight() != '') {
$aStyles[] = 'height:'.$this->GetHeight();
}
$sStyle = '';
if (count($aStyles) > 0) {
$sStyle = 'style="'.implode(';', $aStyles).'"';
}
return "<div class=\"caselog\" $sStyle>".$sContent.'</div>';
}
public function GetAsCSV(
$value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
if ($value instanceof ormCaseLog) {
return parent::GetAsCSV($value->GetText($bConvertToPlainText), $sSeparator, $sTextQualifier, $oHostObject,
$bLocalize, $bConvertToPlainText);
} else {
return '';
}
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
if ($value instanceof ormCaseLog) {
return parent::GetAsXML($value->GetText(), $oHostObject, $bLocalize);
} else {
return '';
}
}
/**
* List the available verbs for 'GetForTemplate'
*/
public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text representation of all the log entries',
'head' => 'Plain text representation of the latest entry',
'head_html' => 'HTML representation of the latest entry',
'html' => 'HTML representation of all the log entries',
);
}
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param $value mixed The current value of the field
* @param $sVerb string The verb specifying the representation of the value
* @param $oHostObject DBObject The object
* @param $bLocalize bool Whether or not to localize the value
*
* @return mixed
* @throws \Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
switch ($sVerb) {
case '':
return $value->GetText(true);
case 'head':
return $value->GetLatestEntry('text');
case 'head_html':
return $value->GetLatestEntry('html');
case 'html':
return $value->GetAsEmailHtml();
default:
throw new \Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject));
}
}
public function GetForJSON($value)
{
return $value->GetForJSON();
}
public function FromJSONToValue($json)
{
if (is_string($json)) {
// Will be correctly handled in MakeRealValue
$ret = $json;
} else {
if (isset($json->add_item)) {
// Will be correctly handled in MakeRealValue
$ret = $json->add_item;
if (!isset($ret->message)) {
throw new Exception("Missing mandatory entry: 'message'");
}
} else {
$ret = ormCaseLog::FromJSON($json);
}
}
return $ret;
}
public function Fingerprint($value)
{
$sFingerprint = '';
if ($value instanceof ormCaseLog) {
$sFingerprint = $value->GetText();
}
return $sFingerprint;
}
/**
* The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
*
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'html'); // default format for case logs is now HTML
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\CaseLogField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
// First we call the parent so the field is build
$oFormField = parent::MakeFormField($oObject, $oFormField);
// Then only we set the value
$oFormField->SetCurrentValue($this->GetEditValue($oObject->Get($this->GetCode())));
// And we set the entries
$oFormField->SetEntries($oObject->Get($this->GetCode())->GetAsArray());
return $oFormField;
}
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
{
/** @var ormCaseLog $value */
$oMyChangeOp->Set("lastentry", $value->GetLatestEntryIndex());
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeCaseLog::class;
}
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
use MetaModel;
use ValueSetEnumClasses;
/**
* An attribute that matches an object class
*
* @package iTopORM
*/
class AttributeClass extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM;
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('class_category', 'more_values'));
}
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
$aParams["allowed_values"] = new ValueSetEnumClasses($aParams['class_category'], $aParams['more_values']);
parent::__construct($sCode, $aParams);
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
$sDefault = parent::GetDefaultValue($oHostObject);
if (!$this->IsNullAllowed() && $this->IsNull($sDefault)) {
// For this kind of attribute specifying null as default value
// is authorized even if null is not allowed
// Pick the first one...
$aClasses = $this->GetAllowedValues();
$sDefault = key($aClasses);
}
return $sDefault;
}
/**
* @param array $aArgs
* @param string $sContains
*
* @return array|null
* @throws \CoreException
*/
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$oValSetDef = $this->GetValuesDef();
if (!$oValSetDef) {
return null;
}
$aListClass = $oValSetDef->GetValues($aArgs, $sContains);
/* @since 3.3.0 remove elements in class_exclusion_list */
$sClassExclusionList = $this->GetOptional('class_exclusion_list', null);
if (!empty($sClassExclusionList)) {
foreach (explode(',', $sClassExclusionList) as $sClassName) {
unset($aListClass[trim($sClassName)]);
}
}
return $aListClass;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue)) {
return '';
}
return MetaModel::GetName($sValue);
}
public function RequiresIndex()
{
return true;
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
}

View File

@@ -0,0 +1,229 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CoreUnexpectedValue;
use Dict;
use Exception;
use MetaModel;
use ormSet;
use utils;
class AttributeClassAttCodeSet extends AttributeSet
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
const DEFAULT_PARAM_INCLUDE_CHILD_CLASSES_ATTRIBUTES = false;
public function __construct($sCode, array $aParams)
{
parent::__construct($sCode, $aParams);
$this->aCSSClasses[] = 'attribute-class-attcode-set';
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('class_field', 'attribute_definition_list', 'attribute_definition_exclusion_list'));
}
public function GetMaxSize()
{
return max(255, 15 * $this->GetMaxItems());
}
/**
* @param array $aArgs
* @param string $sContains
*
* @return array|null
* @throws \CoreException
*/
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
if (!isset($aArgs['this'])) {
return null;
}
$oHostObj = $aArgs['this'];
$sTargetClass = $this->Get('class_field');
$sRootClass = $oHostObj->Get($sTargetClass);
$bIncludeChildClasses = $this->GetOptional('include_child_classes_attributes', static::DEFAULT_PARAM_INCLUDE_CHILD_CLASSES_ATTRIBUTES);
$aExcludeDefs = array();
$sAttDefExclusionList = $this->Get('attribute_definition_exclusion_list');
if (!empty($sAttDefExclusionList)) {
foreach (explode(',', $sAttDefExclusionList) as $sAttDefName) {
$sAttDefName = trim($sAttDefName);
$aExcludeDefs[$sAttDefName] = $sAttDefName;
}
}
$aAllowedDefs = array();
$sAttDefList = $this->Get('attribute_definition_list');
if (!empty($sAttDefList)) {
foreach (explode(',', $sAttDefList) as $sAttDefName) {
$sAttDefName = trim($sAttDefName);
$aAllowedDefs[$sAttDefName] = $sAttDefName;
}
}
$aAllAttributes = array();
if (!empty($sRootClass)) {
$aClasses = array($sRootClass);
if ($bIncludeChildClasses === true) {
$aClasses = $aClasses + MetaModel::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_EXCLUDETOP);
}
foreach ($aClasses as $sClass) {
foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) {
// Add attribute only if not already there (can be in leaf classes but not the root)
if (!array_key_exists($sAttCode, $aAllAttributes)) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sAttDefClass = get_class($oAttDef);
// Skip excluded attdefs
if (isset($aExcludeDefs[$sAttDefClass])) {
continue;
}
// Skip not allowed attdefs only if list specified
if (!empty($aAllowedDefs) && !isset($aAllowedDefs[$sAttDefClass])) {
continue;
}
$aAllAttributes[$sAttCode] = array(
'classes' => array($sClass),
);
} else {
$aAllAttributes[$sAttCode]['classes'][] = $sClass;
}
}
}
}
$aAllowedAttributes = array();
foreach ($aAllAttributes as $sAttCode => $aAttData) {
$iAttClassesCount = count($aAttData['classes']);
$sAttFirstClass = $aAttData['classes'][0];
$sAttLabel = MetaModel::GetLabel($sAttFirstClass, $sAttCode);
if ($sAttFirstClass === $sRootClass) {
$sLabel = Dict::Format('Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass', $sAttCode, $sAttLabel);
} elseif ($iAttClassesCount === 1) {
$sLabel = Dict::Format('Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass', $sAttCode, $sAttLabel, MetaModel::GetName($sAttFirstClass));
} else {
$sLabel = Dict::Format('Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses', $sAttCode, $sAttLabel);
}
$aAllowedAttributes[$sAttCode] = $sLabel;
}
// N°6460 Always sort on the labels, not on the datamodel definition order
natcasesort($aAllowedAttributes);
return $aAllowedAttributes;
}
/**
* force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
*
* @param $proposedValue
* @param DBObject $oHostObj
*
* @param bool $bIgnoreErrors
*
* @return mixed
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws Exception
*/
public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false)
{
$oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
$aArgs = array();
if (!empty($oHostObj)) {
$aArgs['this'] = $oHostObj;
}
$aAllowedAttributes = $this->GetAllowedValues($aArgs);
$aInvalidAttCodes = array();
if (is_string($proposedValue) && !empty($proposedValue)) {
$aJsonFromWidget = json_decode($proposedValue, true);
if (is_null($aJsonFromWidget)) {
$proposedValue = trim($proposedValue);
$aProposedValues = $this->FromStringToArray($proposedValue);
$aValues = array();
foreach ($aProposedValues as $sValue) {
$sAttCode = trim($sValue);
if (empty($aAllowedAttributes) || isset($aAllowedAttributes[$sAttCode])) {
$aValues[$sAttCode] = $sAttCode;
} else {
$aInvalidAttCodes[] = $sAttCode;
}
}
$oSet->SetValues($aValues);
}
} elseif ($proposedValue instanceof ormSet) {
$oSet = $proposedValue;
}
if (!empty($aInvalidAttCodes) && !$bIgnoreErrors) {
$sTargetClass = $this->Get('class_field');
$sClass = $oHostObj->Get($sTargetClass);
throw new CoreUnexpectedValue("The attribute(s) ".implode(', ', $aInvalidAttCodes)." are invalid for class {$sClass}");
}
return $oSet;
}
/**
* @param $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string|null
*
* @throws Exception
*/
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if ($value instanceof ormSet) {
$value = $value->GetValues();
}
if (is_array($value)) {
if (!empty($oHostObject) && $bLocalize) {
$sTargetClass = $this->Get('class_field');
$sClass = $oHostObject->Get($sTargetClass);
$aLocalizedValues = array();
foreach ($value as $sAttCode) {
try {
$sAttClass = $sClass;
// Look for the first class (current or children) that have this attcode
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
if (MetaModel::IsValidAttCode($sChildClass, $sAttCode)) {
$sAttClass = $sChildClass;
break;
}
}
$sLabelForHtmlAttribute = utils::HtmlEntities(MetaModel::GetLabel($sAttClass, $sAttCode)." ($sAttCode)");
$aLocalizedValues[] = '<span class="attribute-set-item" data-code="'.$sAttCode.'" data-label="'.$sLabelForHtmlAttribute.'" data-description="" data-tooltip-content="'.$sLabelForHtmlAttribute.'">'.$sAttCode.'</span>';
}
catch (Exception $e) {
// Ignore bad values
}
}
$value = $aLocalizedValues;
}
$value = implode('', $value);
}
return '<span class="'.implode(' ', $this->aCSSClasses).'">'.$value.'</span>';
}
public function IsNull($proposedValue)
{
return (empty($proposedValue));
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use MetaModel;
use utils;
/**
* An attribute that matches a class state
*
* @package iTopORM
*/
class AttributeClassState extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('class_field'));
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
if (isset($aArgs['this'])) {
$oHostObj = $aArgs['this'];
$sTargetClass = $this->Get('class_field');
$sClass = $oHostObj->Get($sTargetClass);
$aAllowedStates = array();
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
$aValues = MetaModel::EnumStates($sChildClass);
foreach (array_keys($aValues) as $sState) {
$aAllowedStates[$sState] = $sState.' ('.MetaModel::GetStateLabel($sChildClass, $sState).')';
}
}
return $aAllowedStates;
}
return null;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue)) {
return '';
}
if (!empty($oHostObject)) {
$sTargetClass = $this->Get('class_field');
$sClass = $oHostObject->Get($sTargetClass);
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
$aValues = MetaModel::EnumStates($sChildClass);
if (in_array($sValue, $aValues)) {
$sLabelForHtmlAttribute = utils::EscapeHtml($sValue.' ('.MetaModel::GetStateLabel($sChildClass, $sValue).')');
$sHTML = '<span class="attribute-set-item" data-code="'.$sValue.'" data-label="'.$sLabelForHtmlAttribute.'" data-description="" data-tooltip-content="'.$sLabelForHtmlAttribute.'">'.$sValue.'</span>';
return $sHTML;
}
}
}
return $sValue;
}
}

View File

@@ -0,0 +1,471 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOp;
use CMDBChangeOpSetAttributeCustomFields;
use DBObject;
use Exception;
use ormCustomFieldsValue;
use Str;
use utils;
/**
* Custom fields managed by an external implementation
*
* @package iTopORM
*/
class AttributeCustomFields extends AttributeDefinition
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("handler_class"));
}
public function GetEditClass()
{
return "CustomFields";
}
public function IsWritable()
{
return true;
}
public static function LoadFromClassTables()
{
return false;
} // See ReadValue...
public function GetDefaultValue(DBObject $oHostObject = null)
{
return new ormCustomFieldsValue($oHostObject, $this->GetCode());
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return '';
}
/**
* @param array|null $aValues
*
* @return TemplateFieldsHandler
*/
public function GetHandler($aValues = null)
{
$sHandlerClass = $this->Get('handler_class');
/** @var TemplateFieldsHandler $oHandler */
$oHandler = new $sHandlerClass($this->GetCode());
if (!is_null($aValues)) {
$oHandler->SetCurrentValues($aValues);
}
return $oHandler;
}
public function GetPrerequisiteAttributes($sClass = null)
{
$sHandlerClass = $this->Get('handler_class');
return $sHandlerClass::GetPrerequisiteAttributes($sClass);
}
public function GetEditValue($sValue, $oHostObj = null)
{
return $this->GetForTemplate($sValue, '', $oHostObj, true);
}
/**
* Makes the string representation out of the values given by the form defined in GetDisplayForm
*/
public function ReadValueFromPostedForm($oHostObject, $sFormPrefix)
{
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true);
if ($aRawData != null) {
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
} else {
return null;
}
}
public function MakeRealValue($proposedValue, $oHostObject)
{
if (is_object($proposedValue) && ($proposedValue instanceof ormCustomFieldsValue)) {
if (false === $oHostObject->IsNew()) {
// In that case we need additional keys : see \TemplateFieldsHandler::DoBuildForm
$aRequestTemplateValues = $proposedValue->GetValues();
if (false === array_key_exists('current_template_id', $aRequestTemplateValues)) {
$aRequestTemplateValues['current_template_id'] = $aRequestTemplateValues['template_id'];
$aRequestTemplateValues['current_template_data'] = $aRequestTemplateValues['template_data'];
$proposedValue = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRequestTemplateValues);
}
}
if (is_null($proposedValue->GetHostObject())) {
// the object might not be set : for example in \AttributeCustomFields::FromJSONToValue we don't have the object available :(
$proposedValue->SetHostObject($oHostObject);
}
return $proposedValue;
}
if (is_string($proposedValue)) {
$aValues = json_decode($proposedValue, true);
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues);
}
if (is_array($proposedValue)) {
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $proposedValue);
}
if (is_null($proposedValue)) {
return new ormCustomFieldsValue($oHostObject, $this->GetCode());
}
throw new Exception('Unexpected type for the value of a custom fields attribute: '.gettype($proposedValue));
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\SubFormField';
}
/**
* Override to build the relevant form field
*
* When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the
* $oFormField is passed, MakeFormField behaves more like a Prepare.
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
$oFormField->SetForm($this->GetForm($oObject));
}
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
/**
* @param DBObject $oHostObject
* @param null $sFormPrefix
*
* @return Combodo\iTop\Form\Form
* @throws \Exception
*/
public function GetForm(DBObject $oHostObject, $sFormPrefix = null)
{
try {
$oValue = $oHostObject->Get($this->GetCode());
$oHandler = $this->GetHandler($oValue->GetValues());
$sFormId = utils::IsNullOrEmptyString($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode();
$oHandler->BuildForm($oHostObject, $sFormId);
$oForm = $oHandler->GetForm();
}
catch (Exception $e) {
$oForm = new \Combodo\iTop\Form\Form('');
$oField = new \Combodo\iTop\Form\Field\LabelField('');
$oField->SetLabel('Custom field error: '.$e->getMessage());
$oForm->AddField($oField);
$oForm->Finalize();
}
return $oForm;
}
/**
* Read the data from where it has been stored. This verb must be implemented as soon as LoadFromClassTables returns false
* and LoadInObject returns true
*
* @param DBObject $oHostObject
*
* @return mixed|null
* @since 3.1.0
*/
public function ReadExternalValues(DBObject $oHostObject)
{
try {
$oHandler = $this->GetHandler();
$aValues = $oHandler->ReadValues($oHostObject);
$oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues);
}
catch (Exception $e) {
$oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode());
}
return $oRet;
}
/**
* @inheritDoc
*
* @since 3.1.0 N°6043 Move code contained in \AttributeCustomFields::WriteValue to this generic method
*/
public function WriteExternalValues(DBObject $oHostObject): void
{
$oValue = $oHostObject->Get($this->GetCode());
if (!($oValue instanceof ormCustomFieldsValue)) {
$oHandler = $this->GetHandler();
$aValues = array();
} else {
// Pass the values through the form to make sure that they are correct
$oHandler = $this->GetHandler($oValue->GetValues());
$oHandler->BuildForm($oHostObject, '');
$oForm = $oHandler->GetForm();
$aValues = $oForm->GetCurrentValues();
}
$oHandler->WriteValues($oHostObject, $aValues);
}
/**
* The part of the current attribute in the object's signature, for the supplied value
*
* @param ormCustomFieldsValue $value The value of this attribute for the object
*
* @return string The "signature" for this field/attribute
*/
public function Fingerprint($value)
{
$oHandler = $this->GetHandler($value->GetValues());
return $oHandler->GetValueFingerprint();
}
/**
* Check the validity of the data
*
* @param DBObject $oHostObject
* @param $value
*
* @return bool|string true or error message
*/
public function CheckValue(DBObject $oHostObject, $value)
{
try {
$oHandler = $this->GetHandler($value->GetValues());
$oHandler->BuildForm($oHostObject, '');
$ret = $oHandler->Validate($oHostObject);
}
catch (Exception $e) {
$ret = $e->getMessage();
}
return $ret;
}
/**
* Cleanup data upon object deletion (object id still available here)
*
* @param DBObject $oHostObject
*
* @throws \CoreException
* @since 3.1.0
*/
public function DeleteExternalValues(DBObject $oHostObject): void
{
$oValue = $oHostObject->Get($this->GetCode());
$oHandler = $this->GetHandler($oValue->GetValues());
$oHandler->DeleteValues($oHostObject);
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
try {
/** @var ormCustomFieldsValue $value */
$sRet = $value->GetAsHTML($bLocalize);
}
catch (Exception $e) {
$sRet = 'Custom field error: '.utils::EscapeHtml($e->getMessage());
}
return $sRet;
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
try {
$sRet = $value->GetAsXML($bLocalize);
}
catch (Exception $e) {
$sRet = Str::pure2xml('Custom field error: '.$e->getMessage());
}
return $sRet;
}
/**
* @param ormCustomFieldsValue $value
* @param string $sSeparator
* @param string $sTextQualifier
* @param DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return string
* @noinspection PhpParameterNameChangedDuringInheritanceInspection
*/
public function GetAsCSV(
$value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
try {
$sRet = $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText);
}
catch (Exception $e) {
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, 'Custom field error: '.$e->getMessage());
$sRet = $sTextQualifier.$sEscaped.$sTextQualifier;
}
return $sRet;
}
/**
* List the available verbs for 'GetForTemplate'
*/
public function EnumTemplateVerbs()
{
$sHandlerClass = $this->Get('handler_class');
return $sHandlerClass::EnumTemplateVerbs();
}
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param $value mixed The current value of the field
* @param $sVerb string The verb specifying the representation of the value
* @param $oHostObject DBObject The object
* @param $bLocalize bool Whether or not to localize the value
*
* @return string
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
try {
$sRet = $value->GetForTemplate($sVerb, $bLocalize);
}
catch (Exception $e) {
$sRet = 'Custom field error: '.$e->getMessage();
}
return $sRet;
}
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
)
{
return null;
}
/**
* @inheritDoc
*
* @param ormCustomFieldsValue $value
*
* @return string|array
*
* @since 3.1.0 N°1150 now returns the value (was always returning null before)
*/
public function GetForJSON($value)
{
try {
$sRet = $value->GetForJSON();
}
catch (Exception $e) {
$sRet = 'Custom field error: '.$e->getMessage();
}
return $sRet;
}
/**
* @inheritDoc
*
* @return ?ormCustomFieldsValue with empty host object as we don't have it here (most consumers don't have an object in their context, for example in \RestUtils::GetObjectSetFromKey)
* The host object will be set in {@see MakeRealValue}
* All the necessary checks will be done in {@see CheckValue}
*/
public function FromJSONToValue($json)
{
return ormCustomFieldsValue::FromJSONToValue($json, $this);
}
public function Equals($val1, $val2)
{
try {
$bEquals = $val1->Equals($val2);
}
catch (Exception $e) {
$bEquals = false;
}
return $bEquals;
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
// Protection against wrong value type
if (false === ($proposedValue instanceof ormCustomFieldsValue)) {
return parent::HasAValue($proposedValue);
}
return count($proposedValue->GetValues()) > 0;
}
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
{
$oMyChangeOp->Set("prevdata", json_encode($original->GetValues()));
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeCustomFields::class;
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
/**
* Base class for all kind of DB attributes, with the exception of external keys
*
* @package iTopORM
*/
class AttributeDBField extends AttributeDBFieldVoid
{
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed"));
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->MakeRealValue($this->Get("default_value"), $oHostObject);
}
public function IsNullAllowed()
{
return $this->Get("is_null_allowed");
}
}

View File

@@ -0,0 +1,155 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use DBObject;
/**
* Abstract class implementing default filters for a DB column
*
* @package iTopORM
*/
class AttributeDBFieldVoid extends AttributeDefinition
{
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql"));
}
// To be overriden, used in GetSQLColumns
protected function GetSQLCol($bFullSpec = false)
{
return 'VARCHAR(255)'
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
protected function GetSQLColSpec()
{
$default = $this->ScalarToSQL($this->GetDefaultValue());
if (is_null($default)) {
$sRet = '';
} else {
if (is_numeric($default)) {
// Though it is a string in PHP, it will be considered as a numeric value in MySQL
// Then it must not be quoted here, to preserve the compatibility with the value returned by CMDBSource::GetFieldSpec
$sRet = " DEFAULT $default";
} else {
$sRet = " DEFAULT ".CMDBSource::Quote($default);
}
}
return $sRet;
}
public function GetEditClass()
{
return "String";
}
public function GetValuesDef()
{
return $this->Get("allowed_values");
}
public function GetPrerequisiteAttributes($sClass = null)
{
return $this->Get("depends_on");
}
public static function IsBasedOnDBColumns()
{
return true;
}
public static function IsScalar()
{
return true;
}
public function IsWritable()
{
return !$this->IsMagic();
}
public function GetSQLExpr()
{
return $this->Get("sql");
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->MakeRealValue("", $oHostObject);
}
public function IsNullAllowed()
{
return false;
}
//
protected function ScalarToSQL($value)
{
return $value;
} // format value as a valuable SQL literal (quoted outside)
public function GetSQLExpressions($sPrefix = '')
{
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
$aColumns[''] = $this->Get("sql");
return $aColumns;
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
$value = $this->MakeRealValue($aCols[$sPrefix.''], null);
return $value;
}
public function GetSQLValues($value)
{
$aValues = array();
$aValues[$this->Get("sql")] = $this->ScalarToSQL($value);
return $aValues;
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->Get("sql")] = $this->GetSQLCol($bFullSpec);
return $aColumns;
}
public function GetBasicFilterOperators()
{
return array("=" => "equals", "!=" => "differs from");
}
public function GetBasicFilterLooseOperator()
{
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode) {
case '!=':
return $this->GetSQLExpr()." != $sQValue";
break;
case '=':
default:
return $this->GetSQLExpr()." = $sQValue";
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
use MetaModel;
use RuntimeDashboard;
use utils;
class AttributeDashboard extends AttributeDefinition
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(),
array("definition_file", "is_user_editable"));
}
public function GetDashboard()
{
$sAttCode = $this->GetCode();
$sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $sAttCode);
$sFilePath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$this->Get('definition_file');
return RuntimeDashboard::GetDashboard($sFilePath, $sClass.'__'.$sAttCode);
}
public function IsUserEditable()
{
return $this->Get('is_user_editable');
}
public function IsWritable()
{
return false;
}
public function GetEditClass()
{
return "";
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return null;
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return '';
}
/**
* @inheritdoc
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
return null;
}
// if this verb returns false, then GetValue must be implemented
public static function LoadInObject()
{
return false;
}
public function GetValue($oHostObject)
{
return '';
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
// Always return false for now, we don't consider a custom version of a dashboard
return false;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use DateTimeFormat;
use DBObject;
/**
* Map a date+time column to an attribute
*
* @package iTopORM
*/
class AttributeDate extends AttributeDateTime
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE;
public static $oDateFormat = null;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function GetFormat()
{
if (self::$oDateFormat == null) {
AttributeDateTime::LoadFormatFromConfig();
}
return self::$oDateFormat;
}
public static function SetFormat(DateTimeFormat $oDateFormat)
{
self::$oDateFormat = $oDateFormat;
}
/**
* Returns the format string used for the date & time stored in memory
*
* @return string
*/
public static function GetInternalFormat()
{
return 'Y-m-d';
}
/**
* Returns the format string used for the date & time written to MySQL
*
* @return string
*/
public static function GetSQLFormat()
{
return 'Y-m-d';
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "Date";
}
protected function GetSQLCol($bFullSpec = false)
{
return "DATE";
}
public function GetImportColumns()
{
// Allow an empty string to be a valid value (synonym for "reset")
$aColumns = array();
$aColumns[$this->GetCode()] = 'VARCHAR(10)'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}
/**
* Override to specify Field class
*
* When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the
* $oFormField is passed, MakeFormField behave more like a Prepare.
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
$oFormField = parent::MakeFormField($oObject, $oFormField);
$oFormField->SetDateOnly(true);
return $oFormField;
}
}

View File

@@ -0,0 +1,519 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use BinaryExpression;
use CMDBSource;
use CoreUnexpectedValue;
use DateTime;
use DateTimeFormat;
use DateTimeImmutable;
use DBObject;
use Dict;
use Exception;
use Expression;
use FieldExpression;
use IssueLog;
use MetaModel;
use Str;
use utils;
use VariableExpression;
/**
* Map a date+time column to an attribute
*
* @package iTopORM
*/
class AttributeDateTime extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE_TIME;
public static $oFormat = null;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
/**
*
* @return DateTimeFormat
*/
public static function GetFormat()
{
if (self::$oFormat == null) {
static::LoadFormatFromConfig();
}
return self::$oFormat;
}
/**
* Load the 3 settings: date format, time format and data_time format from the configuration
*/
public static function LoadFormatFromConfig()
{
$aFormats = MetaModel::GetConfig()->Get('date_and_time_format');
$sLang = Dict::GetUserLanguage();
$sDateFormat = isset($aFormats[$sLang]['date']) ? $aFormats[$sLang]['date'] : (isset($aFormats['default']['date']) ? $aFormats['default']['date'] : 'Y-m-d');
$sTimeFormat = isset($aFormats[$sLang]['time']) ? $aFormats[$sLang]['time'] : (isset($aFormats['default']['time']) ? $aFormats['default']['time'] : 'H:i:s');
$sDateAndTimeFormat = isset($aFormats[$sLang]['date_time']) ? $aFormats[$sLang]['date_time'] : (isset($aFormats['default']['date_time']) ? $aFormats['default']['date_time'] : '$date $time');
$sFullFormat = str_replace(array('$date', '$time'), array($sDateFormat, $sTimeFormat), $sDateAndTimeFormat);
self::SetFormat(new DateTimeFormat($sFullFormat));
AttributeDate::SetFormat(new DateTimeFormat($sDateFormat));
}
/**
* Returns the format string used for the date & time stored in memory
*
* @return string
*/
public static function GetInternalFormat()
{
return 'Y-m-d H:i:s';
}
/**
* Returns the format string used for the date & time written to MySQL
*
* @return string
*/
public static function GetSQLFormat()
{
return 'Y-m-d H:i:s';
}
public static function SetFormat(DateTimeFormat $oDateTimeFormat)
{
self::$oFormat = $oDateTimeFormat;
}
public static function GetSQLTimeFormat()
{
return 'H:i:s';
}
/**
* Parses a search string coming from user input
*
* @param string $sSearchString
*
* @return string
*/
public function ParseSearchString($sSearchString)
{
try {
$oDateTime = $this->GetFormat()->Parse($sSearchString);
$sSearchString = $oDateTime->format($this->GetInternalFormat());
}
catch (Exception $e) {
$sFormatString = '!'.(string)AttributeDate::GetFormat(); // BEWARE: ! is needed to set non-parsed fields to zero !!!
$oDateTime = DateTime::createFromFormat($sFormatString, $sSearchString);
if ($oDateTime !== false) {
$sSearchString = $oDateTime->format($this->GetInternalFormat());
}
}
return $sSearchString;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\DateTimeField';
}
/**
* Override to specify Field class
*
* When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the
* $oFormField is passed, MakeFormField behave more like a Prepare.
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
$oFormField->SetPHPDateTimeFormat((string)$this->GetFormat());
$oFormField->SetJSDateTimeFormat($this->GetFormat()->ToMomentJS());
$oFormField = parent::MakeFormField($oObject, $oFormField);
// After call to the parent as it sets the current value
$oValue = $oObject->Get($this->GetCode());
if ($oValue === $this->GetNullValue()) {
$oValue = $this->GetDefaultValue($oObject);
}
$oFormField->SetCurrentValue($this->GetFormat()->Format($oValue));
return $oFormField;
}
/**
* @inheritdoc
*/
public function EnumTemplateVerbs()
{
return array(
'' => 'Formatted representation',
'raw' => 'Not formatted representation',
);
}
/**
* @inheritdoc
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
switch ($sVerb) {
case '':
case 'text':
return static::GetFormat()->format($value);
break;
case 'html':
// Note: Not passing formatted value as the method will format it.
return $this->GetAsHTML($value);
break;
case 'raw':
return $value;
break;
default:
return parent::GetForTemplate($value, $sVerb, $oHostObject, $bLocalize);
break;
}
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "DateTime";
}
public function GetEditValue($sValue, $oHostObj = null)
{
return (string)static::GetFormat()->format($sValue);
}
public function GetValueLabel($sValue, $oHostObj = null)
{
return (string)static::GetFormat()->format($sValue);
}
protected function GetSQLCol($bFullSpec = false)
{
return "DATETIME";
}
public function GetImportColumns()
{
// Allow an empty string to be a valid value (synonym for "reset")
$aColumns = array();
$aColumns[$this->GetCode()] = 'VARCHAR(19)'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}
public static function GetAsUnixSeconds($value)
{
$oDeadlineDateTime = new DateTime($value);
$iUnixSeconds = $oDeadlineDateTime->format('U');
return $iUnixSeconds;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
$sDefaultValue = $this->Get('default_value');
if (utils::IsNotNullOrEmptyString($sDefaultValue)) {
try {
$sDefaultDate = Expression::FromOQL($sDefaultValue)->Evaluate([]);
}
catch (Exception $e) {
try {
$sDefaultDate = Expression::FromOQL('"'.$sDefaultValue.'"')->Evaluate([]);
}
catch (Exception $e) {
IssueLog::Error("Invalid default value '$sDefaultValue' for field '{$this->GetCode()}' on class '{$this->GetHostClass()}', defaulting to null");
return $this->GetNullValue();
}
}
try {
$oDate = new DateTimeImmutable($sDefaultDate);
}
catch (Exception $e) {
IssueLog::Error("Invalid default value '$sDefaultValue' for field '{$this->GetCode()}' on class '{$this->GetHostClass()}', defaulting to null");
return $this->GetNullValue();
}
return $oDate->format($this->GetInternalFormat());
}
return $this->GetNullValue();
}
public function GetValidationPattern()
{
return static::GetFormat()->ToRegExpr();
}
public function GetBasicFilterOperators()
{
return array(
"=" => "equals",
"!=" => "differs from",
"<" => "before",
"<=" => "before",
">" => "after (strictly)",
">=" => "after",
"SameDay" => "same day (strip time)",
"SameMonth" => "same year/month",
"SameYear" => "same year",
"Today" => "today",
">|" => "after today + N days",
"<|" => "before today + N days",
"=|" => "equals today + N days",
);
}
public function GetBasicFilterLooseOperator()
{
// Unless we implement a "same xxx, depending on given precision" !
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode) {
case '=':
case '!=':
case '<':
case '<=':
case '>':
case '>=':
return $this->GetSQLExpr()." $sOpCode $sQValue";
case 'SameDay':
return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)";
case 'SameMonth':
return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')";
case 'SameYear':
return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)";
case 'Today':
return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()";
case '>|':
return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
case '<|':
return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
case '=|':
return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
default:
return $this->GetSQLExpr()." = $sQValue";
}
}
/**
* @inheritDoc
*
* @param int|DateTime|string $proposedValue possible values :
* - timestamp ({@see DateTime::getTimestamp())
* - {@see \DateTime} PHP object
* - string, following the {@see GetInternalFormat} format.
*
* @throws CoreUnexpectedValue if invalid value type or the string passed cannot be converted
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return null;
}
if (is_numeric($proposedValue)) {
return date(static::GetInternalFormat(), $proposedValue);
}
if (is_object($proposedValue) && ($proposedValue instanceof DateTime)) {
return $proposedValue->format(static::GetInternalFormat());
}
if (is_string($proposedValue)) {
if (($proposedValue === '') && $this->IsNullAllowed()) {
return null;
}
try {
$oFormat = new DateTimeFormat(static::GetInternalFormat());
$oFormat->Parse($proposedValue);
}
catch (Exception $e) {
throw new CoreUnexpectedValue('Wrong format for date attribute '.$this->GetCode().', expecting "'.$this->GetInternalFormat().'" and got "'.$proposedValue.'"');
}
return $proposedValue;
}
throw new CoreUnexpectedValue('Wrong format for date attribute '.$this->GetCode());
}
public function ScalarToSQL($value)
{
if (empty($value)) {
return null;
}
return $value;
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
return Str::pure2html(static::GetFormat()->format($value));
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
return Str::pure2xml($value);
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
if (empty($sValue) || ($sValue === '0000-00-00 00:00:00') || ($sValue === '0000-00-00')) {
return '';
} else {
if ((string)static::GetFormat() !== static::GetInternalFormat()) {
// Format conversion
$oDate = new DateTime($sValue);
if ($oDate !== false) {
$sValue = static::GetFormat()->format($oDate);
}
}
}
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
/**
* Parses a string to find some smart search patterns and build the corresponding search/OQL condition
* Each derived class is reponsible for defining and processing their own smart patterns, the base class
* does nothing special, and just calls the default (loose) operator
*
* @param string $sSearchText The search string to analyze for smart patterns
* @param FieldExpression $oField The FieldExpression representing the atttribute code in this OQL query
* @param array $aParams Values of the query parameters
* @param bool $bParseSearchString
*
* @return Expression The search condition to be added (AND) to the current search
* @throws CoreException
*/
public function GetSmartConditionExpression(
$sSearchText, FieldExpression $oField, &$aParams, $bParseSearchString = false
)
{
// Possible smart patterns
$aPatterns = array(
'between' => array('pattern' => '/^\[(.*),(.*)\]$/', 'operator' => 'n/a'),
'greater than or equal' => array('pattern' => '/^>=(.*)$/', 'operator' => '>='),
'greater than' => array('pattern' => '/^>(.*)$/', 'operator' => '>'),
'less than or equal' => array('pattern' => '/^<=(.*)$/', 'operator' => '<='),
'less than' => array('pattern' => '/^<(.*)$/', 'operator' => '<'),
);
$sPatternFound = '';
$aMatches = array();
foreach ($aPatterns as $sPatName => $sPattern) {
if (preg_match($sPattern['pattern'], $sSearchText, $aMatches)) {
$sPatternFound = $sPatName;
break;
}
}
switch ($sPatternFound) {
case 'between':
$sParamName1 = $oField->GetParent().'_'.$oField->GetName().'_1';
$oRightExpr = new VariableExpression($sParamName1);
if ($bParseSearchString) {
$aParams[$sParamName1] = $this->ParseSearchString($aMatches[1]);
} else {
$aParams[$sParamName1] = $aMatches[1];
}
$oCondition1 = new BinaryExpression($oField, '>=', $oRightExpr);
$sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2';
$oRightExpr = new VariableExpression($sParamName2);
if ($bParseSearchString) {
$aParams[$sParamName2] = $this->ParseSearchString($aMatches[2]);
} else {
$aParams[$sParamName2] = $aMatches[2];
}
$oCondition2 = new BinaryExpression($oField, '<=', $oRightExpr);
$oNewCondition = new BinaryExpression($oCondition1, 'AND', $oCondition2);
break;
case 'greater than':
case 'greater than or equal':
case 'less than':
case 'less than or equal':
$sSQLOperator = $aPatterns[$sPatternFound]['operator'];
$sParamName = $oField->GetParent().'_'.$oField->GetName();
$oRightExpr = new VariableExpression($sParamName);
if ($bParseSearchString) {
$aParams[$sParamName] = $this->ParseSearchString($aMatches[1]);
} else {
$aParams[$sParamName] = $aMatches[1];
}
$oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr);
break;
default:
$oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams);
}
return $oNewCondition;
}
public function GetHelpOnSmartSearch()
{
$sDict = parent::GetHelpOnSmartSearch();
$oFormat = static::GetFormat();
$sExample = $oFormat->Format(new DateTime('2015-07-19 18:40:00'));
return vsprintf($sDict, array($oFormat->ToPlaceholder(), $sExample));
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Dict;
use MetaModel;
/**
* A dead line stored as a date & time
* The only difference with the DateTime attribute is the display:
* relative to the current time
*/
class AttributeDeadline extends AttributeDateTime
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
$sResult = self::FormatDeadline($value);
return $sResult;
}
public static function FormatDeadline($value)
{
$sResult = '';
if ($value !== null) {
$iValue = AttributeDateTime::GetAsUnixSeconds($value);
$sDate = AttributeDateTime::GetFormat()->Format($value);
$difference = $iValue - time();
if ($difference >= 0) {
$sDifference = self::FormatDuration($difference);
} else {
$sDifference = Dict::Format('UI:DeadlineMissedBy_duration', self::FormatDuration(-$difference));
}
$sFormat = MetaModel::GetConfig()->Get('deadline_format');
$sResult = str_replace(array('$date$', '$difference$'), array($sDate, $sDifference), $sFormat);
}
return $sResult;
}
static function FormatDuration($duration)
{
$days = floor($duration / 86400);
$hours = floor(($duration - (86400 * $days)) / 3600);
$minutes = floor(($duration - (86400 * $days + 3600 * $hours)) / 60);
if ($duration < 60) {
// Less than 1 min
$sResult = Dict::S('UI:Deadline_LessThan1Min');
} else {
if ($duration < 3600) {
// less than 1 hour, display it in minutes
$sResult = Dict::Format('UI:Deadline_Minutes', $minutes);
} else {
if ($duration < 86400) {
// Less that 1 day, display it in hours/minutes
$sResult = Dict::Format('UI:Deadline_Hours_Minutes', $hours, $minutes);
} else {
// Less that 1 day, display it in hours/minutes
$sResult = Dict::Format('UI:Deadline_Days_Hours_Minutes', $days, $hours, $minutes);
}
}
}
return $sResult;
}
}

View File

@@ -0,0 +1,169 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use CoreException;
use utils;
/**
* Map a decimal value column (suitable for financial computations) to an attribute
* internally in PHP such numbers are represented as string. Should you want to perform
* a calculation on them, it is recommended to use the BC Math functions in order to
* retain the precision
*
* @package iTopORM
*/
class AttributeDecimal extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('digits', 'decimals' /* including precision */));
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return "DECIMAL(".$this->Get('digits').",".$this->Get('decimals').")".($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetValidationPattern()
{
$iNbDigits = $this->Get('digits');
$iPrecision = $this->Get('decimals');
$iNbIntegerDigits = $iNbDigits - $iPrecision;
return "^[\-\+]?\d{1,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$";
}
/**
* @inheritDoc
* @since 3.2.0
*/
public function CheckFormat($value)
{
$sRegExp = $this->GetValidationPattern();
return preg_match("/$sRegExp/", $value);
}
public function GetBasicFilterOperators()
{
return array(
"!=" => "differs from",
"=" => "equals",
">" => "greater (strict) than",
">=" => "greater than",
"<" => "less (strict) than",
"<=" => "less than",
"in" => "in",
);
}
public function GetBasicFilterLooseOperator()
{
// Unless we implement an "equals approximately..." or "same order of magnitude"
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode) {
case '!=':
return $this->GetSQLExpr()." != $sQValue";
break;
case '>':
return $this->GetSQLExpr()." > $sQValue";
break;
case '>=':
return $this->GetSQLExpr()." >= $sQValue";
break;
case '<':
return $this->GetSQLExpr()." < $sQValue";
break;
case '<=':
return $this->GetSQLExpr()." <= $sQValue";
break;
case 'in':
if (!is_array($value)) {
throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
}
return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')";
break;
case '=':
default:
return $this->GetSQLExpr()." = \"$value\"";
}
}
public function GetNullValue()
{
return null;
}
public function IsNull($proposedValue)
{
return is_null($proposedValue);
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
return utils::IsNotNullOrEmptyString($proposedValue);
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return null;
}
if ($proposedValue === '') {
return null;
}
return $this->ScalarToSQL($proposedValue);
}
public function ScalarToSQL($value)
{
assert(is_null($value) || preg_match('/'.$this->GetValidationPattern().'/', $value));
if (!is_null($value) && ($value !== '')) {
$value = sprintf("%1.".$this->Get('decimals')."F", $value);
}
return $value; // null or string
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
use Dict;
use Str;
/**
* Store a duration as a number of seconds
*
* @package iTopORM
*/
class AttributeDuration extends AttributeInteger
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetEditClass()
{
return "Duration";
}
protected function GetSQLCol($bFullSpec = false)
{
return "INT(11) UNSIGNED";
}
public function GetNullValue()
{
return '0';
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return null;
}
if (!is_numeric($proposedValue)) {
return null;
}
if (((int)$proposedValue) < 0) {
return null;
}
return (int)$proposedValue;
}
public function ScalarToSQL($value)
{
if (is_null($value)) {
return null;
}
return $value;
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
return Str::pure2html(self::FormatDuration($value));
}
public static function FormatDuration($duration)
{
$aDuration = self::SplitDuration($duration);
if ($duration < 60) {
// Less than 1 min
$sResult = Dict::Format('Core:Duration_Seconds', $aDuration['seconds']);
} else {
if ($duration < 3600) {
// less than 1 hour, display it in minutes/seconds
$sResult = Dict::Format('Core:Duration_Minutes_Seconds', $aDuration['minutes'], $aDuration['seconds']);
} else {
if ($duration < 86400) {
// Less than 1 day, display it in hours/minutes/seconds
$sResult = Dict::Format('Core:Duration_Hours_Minutes_Seconds', $aDuration['hours'],
$aDuration['minutes'], $aDuration['seconds']);
} else {
// more than 1 day, display it in days/hours/minutes/seconds
$sResult = Dict::Format('Core:Duration_Days_Hours_Minutes_Seconds', $aDuration['days'],
$aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']);
}
}
}
return $sResult;
}
static function SplitDuration($duration)
{
$duration = (int)$duration;
$days = floor($duration / 86400);
$hours = floor(($duration - (86400 * $days)) / 3600);
$minutes = floor(($duration - (86400 * $days + 3600 * $hours)) / 60);
$seconds = ($duration % 60); // modulo
return array('days' => $days, 'hours' => $hours, 'minutes' => $minutes, 'seconds' => $seconds);
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\DurationField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
parent::MakeFormField($oObject, $oFormField);
// Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition
$sAttCode = $this->GetCode();
$oFormField->SetCurrentValue($oObject->Get($sAttCode));
$oFormField->SetReadOnly(true);
return $oFormField;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use utils;
/**
* Specialization of a string: email
*
* @package iTopORM
*/
class AttributeEmailAddress extends AttributeString
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetValidationPattern()
{
return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('email_validation_pattern').'$');
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\EmailField';
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue)) {
return '';
}
$sUrlDecorationClass = utils::GetConfig()->Get('email_decoration_class');
return '<a class="mailto" href="mailto:'.$sValue.'"><span class="text_decoration '.$sUrlDecorationClass.'"></span>'.parent::GetAsHTML($sValue).'</a>';
}
}

View File

@@ -0,0 +1,96 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOp;
use CMDBChangeOpSetAttributeEncrypted;
use DBObject;
use MetaModel;
use SimpleCrypt;
/**
* Map a text column (size < 255) to an attribute that is encrypted in the database
* The encryption is based on a key set per iTop instance. Thus if you export your
* database (in SQL) to someone else without providing the key at the same time
* the encrypted fields will remain encrypted
*
* @package iTopORM
*/
class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
protected function GetSQLCol($bFullSpec = false)
{
return "TINYBLOB";
}
public function GetMaxSize()
{
return 255;
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return null;
}
return (string)$proposedValue;
}
/**
* Decrypt the value when reading from the database
*
* @param array $aCols
* @param string $sPrefix
*
* @return string
* @throws Exception
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
$oSimpleCrypt = new SimpleCrypt(MetaModel::GetConfig()->GetEncryptionLibrary());
$sValue = $oSimpleCrypt->Decrypt(MetaModel::GetConfig()->GetEncryptionKey(), $aCols[$sPrefix]);
return $sValue;
}
/**
* Encrypt the value before storing it in the database
*
* @param $value
*
* @return array
* @throws Exception
*/
public function GetSQLValues($value)
{
$oSimpleCrypt = new SimpleCrypt(MetaModel::GetConfig()->GetEncryptionLibrary());
$encryptedValue = $oSimpleCrypt->Encrypt(MetaModel::GetConfig()->GetEncryptionKey(), $value);
$aValues = array();
$aValues[$this->Get("sql")] = $encryptedValue;
return $aValues;
}
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
{
if (is_null($original)) {
$original = '';
}
$oMyChangeOp->Set("prevstring", $original);
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeEncrypted::class;
}
}

View File

@@ -0,0 +1,419 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory;
use Combodo\iTop\Renderer\BlockRenderer;
use DBObject;
use Dict;
use MetaModel;
use MySQLException;
use ormStyle;
/**
* Map a enum column to an attribute
*
* @package iTopORM
*/
class AttributeEnum extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array('styled_values'));
}
public function GetEditClass()
{
return "String";
}
/**
* @param string|null $sValue
*
* @return ormStyle|null
*/
public function GetStyle(?string $sValue): ?ormStyle
{
if ($this->IsParam('styled_values')) {
$aStyles = $this->Get('styled_values');
if (array_key_exists($sValue, $aStyles)) {
return $aStyles[$sValue];
}
}
if ($this->IsParam('default_style')) {
return $this->Get('default_style');
}
return null;
}
protected function GetSQLCol($bFullSpec = false)
{
// Get the definition of the column, including the actual values present in the table
return $this->GetSQLColHelper($bFullSpec, true);
}
/**
* A more versatile version of GetSQLCol
*
* @param bool $bFullSpec
* @param bool $bIncludeActualValues
* @param string $sSQLTableName The table where to look for the actual values (may be useful for data synchro tables)
*
* @return string
* @since 3.0.0
*/
protected function GetSQLColHelper($bFullSpec = false, $bIncludeActualValues = false, $sSQLTableName = null)
{
$oValDef = $this->GetValuesDef();
if ($oValDef) {
$aValues = CMDBSource::Quote(array_keys($oValDef->GetValues(array(), "")), true);
} else {
$aValues = array();
}
// Preserve the values already present in the database to ease migrations
if ($bIncludeActualValues) {
if ($sSQLTableName == null) {
// No SQL table given, use the one of the attribute
$sHostClass = $this->GetHostClass();
$sSQLTableName = MetaModel::DBGetTable($sHostClass, $this->GetCode());
}
$aValues = array_unique(array_merge($aValues, $this->GetActualValuesInDB($sSQLTableName)));
}
if (count($aValues) > 0) {
// The syntax used here do matters
// In particular, I had to remove unnecessary spaces to
// make sure that this string will match the field type returned by the DB
// (used to perform a comparison between the current DB format and the data model)
return "ENUM(".implode(",", $aValues).")"
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
} else {
return "VARCHAR(255)"
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? " DEFAULT ''" : ""); // ENUM() is not an allowed syntax!
}
}
/**
* @see AttributeDefinition::GetImportColumns()
* @since 3.0.0
* {@inheritDoc}
*/
public function GetImportColumns()
{
// Note: this is used by the Data Synchro to build the "data" table
// Right now the function is not passed the "target" SQL table, but if we improve this in the future
// we may call $this->GetSQLColHelper(true, true, $sDBTable); to take into account the actual 'enum' values
// in this table
return array($this->GetCode() => $this->GetSQLColHelper(false, false));
}
/**
* Get the list of the actual 'enum' values present in the database
*
* @return string[]
* @since 3.0.0
*/
protected function GetActualValuesInDB(string $sDBTable)
{
$aValues = array();
try {
$sSQL = "SELECT DISTINCT `".$this->GetSQLExpr()."` AS value FROM `$sDBTable`;";
$aValuesInDB = CMDBSource::QueryToArray($sSQL);
foreach ($aValuesInDB as $aRow) {
if ($aRow['value'] !== null) {
$aValues[] = $aRow['value'];
}
}
}
catch (MySQLException $e) {
// Never mind, maybe the table does not exist yet (new installation from scratch)
// It seems more efficient to try and ignore errors than to test if the table & column really exists
}
return CMDBSource::Quote($aValues);
}
protected function GetSQLColSpec()
{
$default = $this->ScalarToSQL($this->GetDefaultValue());
if (is_null($default)) {
$sRet = '';
} else {
// ENUMs values are strings so the default value must be a string as well,
// otherwise MySQL interprets the number as the zero-based index of the value in the list (i.e. the nth value in the list)
$sRet = " DEFAULT ".CMDBSource::Quote($default);
}
return $sRet;
}
public function ScalarToSQL($value)
{
// Note: for strings, the null value is an empty string and it is recorded as such in the DB
// but that wasn't working for enums, because '' is NOT one of the allowed values
// that's why a null value must be forced to a real null
$value = parent::ScalarToSQL($value);
if ($this->IsNull($value)) {
return null;
} else {
return $value;
}
}
public function RequiresIndex()
{
return false;
}
public function GetBasicFilterOperators()
{
return parent::GetBasicFilterOperators();
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return parent::GetBasicFilterSQLExpr($sOpCode, $value);
}
public function GetValueLabel($sValue)
{
if (is_null($sValue)) {
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
$sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue,
Dict::S('Enum:Undefined'));
} else {
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, true /*user lang*/);
if (is_null($sLabel)) {
$sDefault = str_replace('_', ' ', $sValue);
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, false);
}
}
return $sLabel;
}
public function GetValueDescription($sValue)
{
if (is_null($sValue)) {
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+',
Dict::S('Enum:Undefined'));
} else {
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+',
'', true /* user language only */);
if (strlen($sDescription) == 0) {
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass) {
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode)) {
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sDescription = $oAttDef->GetValueDescription($sValue);
}
}
}
}
return $sDescription;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if ($bLocalize) {
$sLabel = $this->GetValueLabel($sValue);
// $sDescription = $this->GetValueDescription($sValue);
$oStyle = $this->GetStyle($sValue);
// later, we could imagine a detailed description in the title
// $sRes = "<span title=\"$sDescription\">".parent::GetAsHtml($sLabel)."</span>";
$oBadge = FieldBadgeUIBlockFactory::MakeForField($sLabel, $oStyle);
$oRenderer = new BlockRenderer($oBadge);
$sRes = $oRenderer->RenderHtml();
} else {
$sRes = parent::GetAsHtml($sValue, $oHostObject, $bLocalize);
}
return $sRes;
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
if (is_null($value)) {
$sFinalValue = '';
} elseif ($bLocalize) {
$sFinalValue = $this->GetValueLabel($value);
} else {
$sFinalValue = $value;
}
$sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize);
return $sRes;
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
if (is_null($sValue)) {
$sFinalValue = '';
} elseif ($bLocalize) {
$sFinalValue = $this->GetValueLabel($sValue);
} else {
$sFinalValue = $sValue;
}
$sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize);
return $sRes;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\SelectField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
// Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
$oFormField->SetChoices($this->GetAllowedValues($oObject->ToArgsForQuery()));
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
public function GetEditValue($sValue, $oHostObj = null)
{
if (is_null($sValue)) {
return '';
} else {
return $this->GetValueLabel($sValue);
}
}
public function GetForJSON($value)
{
return $value;
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$aRawValues = parent::GetAllowedValues($aArgs, $sContains);
if (is_null($aRawValues)) {
return null;
}
$aLocalizedValues = array();
foreach ($aRawValues as $sKey => $sValue) {
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
}
// Sort by label only if necessary
// See N°1646 and {@see \MFCompiler::CompileAttributeEnumValues()} for complete information as for why sort on labels is done at runtime while other sorting are done at compile time
/** @var \ValueSetEnum $oValueSetDef */
$oValueSetDef = $this->GetValuesDef();
if ($oValueSetDef->IsSortedByValues()) {
asort($aLocalizedValues);
}
return $aLocalizedValues;
}
public function GetMaxSize()
{
return null;
}
/**
* An enum can be localized
*/
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
)
{
if ($bLocalizedValue) {
// Lookup for the value matching the input
//
$sFoundValue = null;
$aRawValues = parent::GetAllowedValues();
if (!is_null($aRawValues)) {
foreach ($aRawValues as $sKey => $sValue) {
$sRefValue = $this->GetValueLabel($sKey);
if ($sProposedValue == $sRefValue) {
$sFoundValue = $sKey;
break;
}
}
}
if (is_null($sFoundValue)) {
return null;
}
return $this->MakeRealValue($sFoundValue, null);
} else {
return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue,
$sAttributeQualifier);
}
}
/**
* Processes the input value to align it with the values supported
* by this type of attribute. In this case: turns empty strings into nulls
*
* @param mixed $proposedValue The value to be set for the attribute
*
* @return mixed The actual value that will be set
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if ($proposedValue == '') {
return null;
}
return parent::MakeRealValue($proposedValue, $oHostObj);
}
public function GetOrderByHint()
{
$aValues = $this->GetAllowedValues();
return Dict::Format('UI:OrderByHint_Values', implode(', ', $aValues));
}
}

View File

@@ -0,0 +1,243 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Dict;
use MetaModel;
use ormSet;
use ValueSetEnumPadded;
/**
* @since 2.7.0 N°985
*/
class AttributeEnumSet extends AttributeSet
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_TAG_SET;
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('possible_values', 'is_null_allowed', 'max_items'));
}
public function GetMaxSize()
{
$aRawValues = $this->GetRawPossibleValues();
$iMaxItems = $this->GetMaxItems();
$aLengths = array();
foreach (array_keys($aRawValues) as $sKey) {
$aLengths[] = strlen($sKey);
}
rsort($aLengths, SORT_NUMERIC);
$iMaxSize = 2;
for ($i = 0; $i < min($iMaxItems, count($aLengths)); $i++) {
$iMaxSize += $aLengths[$i] + 1;
}
return max(255, $iMaxSize);
}
private function GetRawPossibleValues($aArgs = array(), $sContains = '')
{
/** @var ValueSetEnumPadded $oValSetDef */
$oValSetDef = $this->Get('possible_values');
if (!$oValSetDef) {
return array();
}
return $oValSetDef->GetValues($aArgs, $sContains);
}
public function GetPossibleValues($aArgs = array(), $sContains = '')
{
$aRawValues = $this->GetRawPossibleValues($aArgs, $sContains);
$aLocalizedValues = array();
foreach ($aRawValues as $sKey => $sValue) {
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
}
return $aLocalizedValues;
}
public function GetValueLabel($sValue)
{
if ($sValue instanceof ormSet) {
$sValue = implode(', ', $sValue->GetValues());
}
$aValues = $this->GetRawPossibleValues();
if (is_array($aValues) && is_string($sValue) && isset($aValues[$sValue])) {
$sValue = $aValues[$sValue];
}
if (is_null($sValue)) {
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
$sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue,
Dict::S('Enum:Undefined'));
} else {
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, true /*user lang*/);
if (is_null($sLabel)) {
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, false);
if (is_null($sLabel)) {
$sDefault = trim(str_replace('_', ' ', $sValue));
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sDefault, $sDefault, false);
}
}
}
return $sLabel;
}
public function GetValueDescription($sValue)
{
if (is_null($sValue)) {
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+',
Dict::S('Enum:Undefined'));
} else {
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+',
'', true /* user language only */);
if (strlen($sDescription) == 0) {
$sParentClass = MetaModel::GetParentClass($this->m_sHostClass);
if ($sParentClass) {
if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode)) {
$oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode);
$sDescription = $oAttDef->GetValueDescription($sValue);
}
}
}
}
return $sDescription;
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if ($bLocalize) {
if ($value instanceof ormSet) {
$sRes = $this->GenerateViewHtmlForValues($value->GetValues());
} else {
$sLabel = $this->GetValueLabel($value);
$sDescription = $this->GetValueDescription($value);
$sRes = "<span title=\"$sDescription\">".parent::GetAsHtml($sLabel)."</span>";
}
} else {
$sRes = parent::GetAsHtml($value, $oHostObject, $bLocalize);
}
return $sRes;
}
/**
* @param ormSet $value
* @param string $sSeparator
* @param string $sTextQualifier
* @param DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return mixed|string
* @throws Exception
*/
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
if (is_object($value) && ($value instanceof ormSet)) {
$aValues = $value->GetValues();
if ($bLocalize) {
$aLocalizedValues = array();
foreach ($aValues as $sValue) {
$aLocalizedValues[] = $this->GetValueLabel($sValue);
}
$aValues = $aLocalizedValues;
}
$sRes = implode($sSepItem, $aValues);
} else {
$sRes = '';
}
return "{$sTextQualifier}{$sRes}{$sTextQualifier}";
}
/**
* Get the value from a given string (plain text, CSV import)
*
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return mixed null if no match could be found
* @throws Exception
*/
public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
{
if ($bLocalizedValue) {
// Lookup for the values matching the input
//
$aValues = $this->FromStringToArray($sProposedValue);
$aFoundValues = array();
$aRawValues = $this->GetPossibleValues();
foreach ($aValues as $sValue) {
$bFound = false;
foreach ($aRawValues as $sCode => $sRawValue) {
if ($sValue == $sRawValue) {
$aFoundValues[] = $sCode;
$bFound = true;
break;
}
}
if (!$bFound) {
// Not found, break the import
return null;
}
}
return $this->MakeRealValue(implode(',', $aFoundValues), null);
} else {
return $this->MakeRealValue($sProposedValue, null, false);
}
}
/**
* @param string $proposedValue Search string used for MATCHES
*
* @param string $sDefaultSepItem word separator to extract items
*
* @return array of EnumSet codes
* @throws Exception
*/
public function FromStringToArray($proposedValue, $sDefaultSepItem = ',')
{
$aValues = array();
if (!empty($proposedValue)) {
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
// convert also other separators
if ($sSepItem !== $sDefaultSepItem) {
$proposedValue = str_replace($sDefaultSepItem, $sSepItem, $proposedValue);
}
foreach (explode($sSepItem, $proposedValue) as $sCode) {
$sValue = trim($sCode);
if (strlen($sValue) > 2) {
$sLabel = $this->GetValueLabel($sValue);
$aValues[$sLabel] = $sValue;
}
}
}
return $aValues;
}
public function Equals($val1, $val2)
{
return $val1->Equals($val2);
}
}

View File

@@ -0,0 +1,525 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Combodo\iTop\Form\Field\Field;
use CoreException;
use DBObject;
use MetaModel;
/**
* An attribute which corresponds to an external key (direct or indirect)
*
* @package iTopORM
*/
class AttributeExternalField extends AttributeDefinition
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
/**
* Return the search widget type corresponding to this attribute
*
* @return string
* @throws CoreException
*/
public function GetSearchType()
{
// Not necessary the external key is already present
if ($this->IsFriendlyName()) {
return self::SEARCH_WIDGET_TYPE_RAW;
}
try {
$oRemoteAtt = $this->GetFinalAttDef();
switch (true) {
case ($oRemoteAtt instanceof AttributeString):
return self::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD;
case ($oRemoteAtt instanceof AttributeExternalKey):
return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
}
}
catch (CoreException $e) {
}
return self::SEARCH_WIDGET_TYPE_RAW;
}
function IsSearchable()
{
if ($this->IsFriendlyName()) {
return true;
}
return parent::IsSearchable();
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode"));
}
public function GetEditClass()
{
return "ExtField";
}
/**
* @return AttributeDefinition
* @throws CoreException
*/
public function GetFinalAttDef()
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetFinalAttDef();
}
protected function GetSQLCol($bFullSpec = false)
{
// throw new CoreException("external attribute: does it make any sense to request its type ?");
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetSQLCol($bFullSpec);
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '') {
return array('' => $this->GetCode()); // Warning: Use GetCode() since AttributeExternalField does not have any 'sql' property
} else {
return $sPrefix;
}
}
/**
* @param string $sDefault
*
* @return string dict entry if defined, otherwise :
* <ul>
* <li>if field is a friendlyname then display the label of the ExternalKey
* <li>the class hierarchy -> field name
*
* <p>For example, having this :
*
* <pre>
* +---------------------+ +--------------------+ +--------------+
* | Class A | | Class B | | Class C |
* +---------------------+ +--------------------+ +--------------+
* | foo <ExternalField>-------->c_id_friendly_name--------->friendlyname |
* +---------------------+ +--------------------+ +--------------+
* </pre>
*
* <p>The ExternalField foo points to a magical field that is brought by c_id ExternalKey in class B.
*
* <p>In the normal case the foo label would be : B -> C -> friendlyname<br>
* But as foo is a friendlyname its label will be the same as the one on A.b_id field
* This can be overrided with dict key Class:ClassA/Attribute:foo
*
* @throws CoreException
* @throws Exception
*/
public function GetLabel($sDefault = null)
{
$sLabelDefaultValue = '';
$sLabel = parent::GetLabel($sLabelDefaultValue);
if ($sLabelDefaultValue !== $sLabel) {
return $sLabel;
}
if ($this->IsFriendlyName() && ($this->Get("target_attcode") === "friendlyname")) {
// This will be used even if we are pointing to a friendlyname in a distance > 1
// For example we can link to a magic friendlyname (like org_id_friendlyname)
// If a specific label is needed, use a Dict key !
// See N°2174
$sKeyAttCode = $this->Get("extkey_attcode");
$oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
$sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode);
return $sLabel;
}
$oRemoteAtt = $this->GetExtAttDef();
$sLabel = $oRemoteAtt->GetLabel($this->m_sCode);
$oKeyAtt = $this->GetKeyAttDef();
$sKeyLabel = $oKeyAtt->GetLabel($this->GetKeyAttCode());
$sLabel = "{$sKeyLabel}->{$sLabel}";
return $sLabel;
}
public function GetLabelForSearchField()
{
$sLabel = parent::GetLabel('');
if (strlen($sLabel) == 0) {
$sKeyAttCode = $this->Get("extkey_attcode");
$oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
$sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode);
$oRemoteAtt = $this->GetExtAttDef();
$sLabel .= '->'.$oRemoteAtt->GetLabel($this->m_sCode);
}
return $sLabel;
}
public function GetDescription($sDefault = null)
{
$sLabel = parent::GetDescription('');
if (strlen($sLabel) == 0) {
$oRemoteAtt = $this->GetExtAttDef();
$sLabel = $oRemoteAtt->GetDescription('');
}
return $sLabel;
}
public function GetHelpOnEdition($sDefault = null)
{
$sLabel = parent::GetHelpOnEdition('');
if (strlen($sLabel) == 0) {
$oRemoteAtt = $this->GetExtAttDef();
$sLabel = $oRemoteAtt->GetHelpOnEdition('');
}
return $sLabel;
}
public function IsExternalKey($iType = EXTKEY_RELATIVE)
{
switch ($iType) {
case EXTKEY_ABSOLUTE:
// see further
$oRemoteAtt = $this->GetExtAttDef();
return $oRemoteAtt->IsExternalKey($iType);
case EXTKEY_RELATIVE:
return false;
default:
throw new CoreException("Unexpected value for argument iType: '$iType'");
}
}
/**
* @return bool
* @throws CoreException
*/
public function IsFriendlyName()
{
$oRemoteAtt = $this->GetExtAttDef();
if ($oRemoteAtt instanceof AttributeExternalField) {
$bRet = $oRemoteAtt->IsFriendlyName();
} elseif ($oRemoteAtt instanceof AttributeFriendlyName) {
$bRet = true;
} else {
$bRet = false;
}
return $bRet;
}
public function GetTargetClass($iType = EXTKEY_RELATIVE)
{
return $this->GetKeyAttDef($iType)->GetTargetClass();
}
public static function IsExternalField()
{
return true;
}
public function GetKeyAttCode()
{
return $this->Get("extkey_attcode");
}
public function GetExtAttCode()
{
return $this->Get("target_attcode");
}
/**
* @param int $iType
*
* @return AttributeExternalKey
* @throws CoreException
* @throws Exception
*/
public function GetKeyAttDef($iType = EXTKEY_RELATIVE)
{
switch ($iType) {
case EXTKEY_ABSOLUTE:
// see further
/** @var AttributeExternalKey $oRemoteAtt */
$oRemoteAtt = $this->GetExtAttDef();
if ($oRemoteAtt->IsExternalField()) {
return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE);
} else {
if ($oRemoteAtt->IsExternalKey()) {
return $oRemoteAtt;
}
}
return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter !
case EXTKEY_RELATIVE:
/** @var AttributeExternalKey $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode"));
return $oAttDef;
default:
throw new CoreException("Unexpected value for argument iType: '$iType'");
}
}
public function GetPrerequisiteAttributes($sClass = null)
{
return array($this->Get("extkey_attcode"));
}
/**
* @return AttributeExternalField
* @throws CoreException
* @throws Exception
*/
public function GetExtAttDef()
{
$oKeyAttDef = $this->GetKeyAttDef();
/** @var AttributeExternalField $oExtAttDef */
$oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->GetTargetClass(), $this->Get("target_attcode"));
if (!is_object($oExtAttDef)) {
throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->GetTargetClass()." has no attribute ".$this->Get("target_attcode"));
}
return $oExtAttDef;
}
/**
* @return mixed
* @throws CoreException
*/
public function GetSQLExpr()
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetSQLExpr();
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetDefaultValue();
}
public function IsNullAllowed()
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->IsNullAllowed();
}
public static function IsScalar()
{
return true;
}
public function GetBasicFilterOperators()
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetBasicFilterOperators();
}
public function GetBasicFilterLooseOperator()
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetBasicFilterLooseOperator();
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value);
}
public function GetNullValue()
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetNullValue();
}
public function IsNull($proposedValue)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->IsNull($proposedValue);
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->HasAValue($proposedValue);
}
public function MakeRealValue($proposedValue, $oHostObj)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj);
}
/**
* @inheritDoc
* @since 3.1.0 N°6271 Delegate to remote attribute to ensure cascading computed values
*/
public function GetSQLValues($value)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetSQLValues($value);
}
public function ScalarToSQL($value)
{
// This one could be used in case of filtering only
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->ScalarToSQL($value);
}
// Do not overload GetSQLExpression here because this is handled in the joins
//public function GetSQLExpressions($sPrefix = '') {return array();}
// Here, we get the data...
public function FromSQLToValue($aCols, $sPrefix = '')
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->FromSQLToValue($aCols, $sPrefix);
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetAsHTML($value, null, $bLocalize);
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetAsXML($value, null, $bLocalize);
}
public function GetAsCSV(
$value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier, null, $bLocalize, $bConvertToPlainText);
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\LabelField';
}
/**
* @param DBObject $oObject
* @param Field $oFormField
*
* @return null
* @throws CoreException
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
// Retrieving AttDef from the remote attribute
$oRemoteAttDef = $this->GetExtAttDef();
if ($oFormField === null) {
// ExternalField's FormField are actually based on the FormField from the target attribute.
// Except for the AttributeExternalKey because we have no OQL and stuff
if ($oRemoteAttDef instanceof AttributeExternalKey) {
$sFormFieldClass = static::GetFormFieldClass();
} else {
$sFormFieldClass = $oRemoteAttDef::GetFormFieldClass();
}
/** @var Field $oFormField */
$oFormField = new $sFormFieldClass($this->GetCode());
switch ($sFormFieldClass) {
case '\Combodo\iTop\Form\Field\SelectField':
$oFormField->SetChoices($oRemoteAttDef->GetAllowedValues($oObject->ToArgsForQuery()));
break;
default:
break;
}
}
parent::MakeFormField($oObject, $oFormField);
if ($oFormField instanceof \Combodo\iTop\Form\Field\TextAreaField) {
if (method_exists($oRemoteAttDef, 'GetFormat')) {
/** @var TextAreaField $oFormField */
$oFormField->SetFormat($oRemoteAttDef->GetFormat());
}
}
// Manually setting for remote ExternalKey, otherwise, the id would be displayed.
if ($oRemoteAttDef instanceof AttributeExternalKey) {
$oFormField->SetCurrentValue($oObject->Get($this->GetCode().'_friendlyname'));
}
// Readonly field because we can't update external fields
$oFormField->SetReadOnly(true);
return $oFormField;
}
public function IsPartOfFingerprint()
{
return false;
}
public function GetFormat()
{
$oRemoteAttDef = $this->GetExtAttDef();
if (method_exists($oRemoteAttDef, 'GetFormat')) {
/** @var TextAreaField $oFormField */
return $oRemoteAttDef->GetFormat();
}
return 'text';
}
}

View File

@@ -0,0 +1,354 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CoreException;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use DBSearch;
use Exception;
use MetaModel;
use TemporaryObjectDescriptor;
use ValueSetObjects;
/**
* Map a foreign key to an attribute
* AttributeExternalKey and AttributeExternalField may be an external key
* the difference is that AttributeExternalKey corresponds to a column into the defined table
* where an AttributeExternalField corresponds to a column into another table (class)
*
* @package iTopORM
*/
class AttributeExternalKey extends AttributeDBFieldVoid
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
/**
* Return the search widget type corresponding to this attribute
*
* @return string
*/
public function GetSearchType()
{
try {
$oRemoteAtt = $this->GetFinalAttDef();
$sTargetClass = $oRemoteAtt->GetTargetClass();
if (MetaModel::IsHierarchicalClass($sTargetClass)) {
return self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY;
}
return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
}
catch (CoreException $e) {
}
return self::SEARCH_WIDGET_TYPE_RAW;
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete"));
}
public function GetEditClass()
{
return "ExtKey";
}
protected function GetSQLCol($bFullSpec = false)
{
return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");
}
public function RequiresIndex()
{
return true;
}
public function IsExternalKey($iType = EXTKEY_RELATIVE)
{
return true;
}
public function GetTargetClass($iType = EXTKEY_RELATIVE)
{
return $this->Get("targetclass");
}
public function GetKeyAttDef($iType = EXTKEY_RELATIVE)
{
return $this;
}
public function GetKeyAttCode()
{
return $this->GetCode();
}
public function GetDisplayStyle()
{
return $this->GetOptional('display_style', 'select');
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return 0;
}
public function IsNullAllowed()
{
if (MetaModel::GetConfig()->Get('disable_mandatory_ext_keys')) {
return true;
}
return $this->Get("is_null_allowed");
}
public function GetBasicFilterOperators()
{
return parent::GetBasicFilterOperators();
}
public function GetBasicFilterLooseOperator()
{
return parent::GetBasicFilterLooseOperator();
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return parent::GetBasicFilterSQLExpr($sOpCode, $value);
}
// overloaded here so that an ext key always have the answer to
// "what are your possible values?"
public function GetValuesDef()
{
$oValSetDef = $this->Get("allowed_values");
if (!$oValSetDef) {
// Let's propose every existing value
$oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass());
}
return $oValSetDef;
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
//throw new Exception("GetAllowedValues on ext key has been deprecated");
try {
return parent::GetAllowedValues($aArgs, $sContains);
}
catch (Exception $e) {
// Some required arguments could not be found, enlarge to any existing value
$oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass());
return $oValSetDef->GetValues($aArgs, $sContains);
}
}
public function GetAllowedValuesForSelect($aArgs = array(), $sContains = '')
{
//$this->GetValuesDef();
$oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass());
return $oValSetDef->GetValuesForAutocomplete($aArgs, $sContains);
}
public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
$oValSetDef = $this->GetValuesDef();
$oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue);
return $oSet;
}
public function GetAllowedValuesAsFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
return DBObjectSearch::FromOQL($this->GetValuesDef()->GetFilterExpression());
}
public function GetDeletionPropagationOption()
{
return $this->Get("on_target_delete");
}
public function GetNullValue()
{
return 0;
}
public function IsNull($proposedValue)
{
return ($proposedValue == 0);
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
return ((int)$proposedValue) !== 0;
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return 0;
}
if ($proposedValue === '') {
return 0;
}
if (MetaModel::IsValidObject($proposedValue)) {
return $proposedValue->GetKey();
}
return (int)$proposedValue;
}
/** @inheritdoc @since 3.1 */
public function WriteExternalValues(DBObject $oHostObject): void
{
$sTargetKey = $oHostObject->Get($this->GetCode());
$oFilter = DBSearch::FromOQL('SELECT `'.TemporaryObjectDescriptor::class.'` WHERE item_class=:class AND item_id=:id');
$oSet = new DBObjectSet($oFilter, [], ['class' => $this->GetTargetClass(), 'id' => $sTargetKey]);
while ($oTemporaryObjectDescriptor = $oSet->Fetch()) {
$oTemporaryObjectDescriptor->Set('host_class', get_class($oHostObject));
$oTemporaryObjectDescriptor->Set('host_id', $oHostObject->GetKey());
$oTemporaryObjectDescriptor->Set('host_att_code', $this->GetCode());
$oTemporaryObjectDescriptor->DBUpdate();
}
}
public function GetMaximumComboLength()
{
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
}
public function GetMinAutoCompleteChars()
{
return $this->GetOptional('min_autocomplete_chars', MetaModel::GetConfig()->Get('min_autocomplete_chars'));
}
/**
* @return int
* @since 3.0.0
*/
public function GetMaxAutoCompleteResults(): int
{
return MetaModel::GetConfig()->Get('max_autocomplete_results');
}
public function AllowTargetCreation()
{
return $this->GetOptional('allow_target_creation', MetaModel::GetConfig()->Get('allow_target_creation'));
}
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
* @throws \CoreException
*/
public function GetMirrorLinkAttribute()
{
$oRet = null;
$sRemoteClass = $this->GetTargetClass();
foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) {
if (!$oRemoteAttDef->IsLinkSet()) {
continue;
}
if (!is_subclass_of($this->GetHostClass(),
$oRemoteAttDef->GetLinkedClass()) && $oRemoteAttDef->GetLinkedClass() != $this->GetHostClass()) {
continue;
}
if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetCode()) {
continue;
}
$oRet = $oRemoteAttDef;
break;
}
return $oRet;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\SelectObjectField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
/** @var \Combodo\iTop\Form\Field\Field $oFormField */
if ($oFormField === null) {
// Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
// Setting params
$oFormField->SetMaximumComboLength($this->GetMaximumComboLength());
$oFormField->SetMinAutoCompleteChars($this->GetMinAutoCompleteChars());
$oFormField->SetMaxAutoCompleteResults($this->GetMaxAutoCompleteResults());
$oFormField->SetHierarchical(MetaModel::IsHierarchicalClass($this->GetTargetClass()));
// Setting choices regarding the field dependencies
$aFieldDependencies = $this->GetPrerequisiteAttributes();
if (!empty($aFieldDependencies)) {
$oTmpAttDef = $this;
$oTmpField = $oFormField;
$oFormField->SetOnFinalizeCallback(function () use ($oTmpField, $oTmpAttDef, $oObject) {
/** @var $oTmpField \Combodo\iTop\Form\Field\Field */
/** @var $oTmpAttDef \AttributeDefinition */
/** @var $oObject \DBObject */
// We set search object only if it has not already been set (overrided)
if ($oTmpField->GetSearch() === null) {
$oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression());
$oSearch->SetInternalParams(array('this' => $oObject));
$oTmpField->SetSearch($oSearch);
}
});
} else {
$oSearch = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression());
$oSearch->SetInternalParams(array('this' => $oObject));
$oFormField->SetSearch($oSearch);
}
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (!is_null($oHostObject)) {
return $oHostObject->GetAsHTML($this->GetCode(), $oHostObject);
}
return DBObject::MakeHyperLink($this->GetTargetClass(), $sValue);
}
}

View File

@@ -0,0 +1,208 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
use MetaModel;
use Str;
/**
* The attribute dedicated to the finalclass automatic attribute
*
* @package iTopORM
*/
class AttributeFinalClass extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public $m_sValue;
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
$aParams["allowed_values"] = null;
parent::__construct($sCode, $aParams);
$this->m_sValue = $this->Get("default_value");
}
public function IsWritable()
{
return false;
}
public function IsMagic()
{
return true;
}
public function RequiresIndex()
{
return true;
}
public function SetFixedValue($sValue)
{
$this->m_sValue = $sValue;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->m_sValue;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue)) {
return '';
}
if ($bLocalize) {
return MetaModel::GetName($sValue);
} else {
return $sValue;
}
}
/**
* An enum can be localized
*
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return mixed|null|string
* @throws CoreException
* @throws OQLException
*/
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
)
{
if ($bLocalizedValue) {
// Lookup for the value matching the input
//
$sFoundValue = null;
$aRawValues = self::GetAllowedValues();
if (!is_null($aRawValues)) {
foreach ($aRawValues as $sKey => $sValue) {
if ($sProposedValue == $sValue) {
$sFoundValue = $sKey;
break;
}
}
}
if (is_null($sFoundValue)) {
return null;
}
return $this->MakeRealValue($sFoundValue, null);
} else {
return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue,
$sAttributeQualifier);
}
}
// Because this is sometimes used to get a localized/string version of an attribute...
public function GetEditValue($sValue, $oHostObj = null)
{
if (empty($sValue)) {
return '';
}
return MetaModel::GetName($sValue);
}
public function GetForJSON($value)
{
// JSON values are NOT localized
return $value;
}
/**
* @param $value
* @param string $sSeparator
* @param string $sTextQualifier
* @param DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return string
* @throws CoreException
* @throws DictExceptionMissingString
*/
public function GetAsCSV(
$value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
if ($bLocalize && $value != '') {
$sRawValue = MetaModel::GetName($value);
} else {
$sRawValue = $value;
}
return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false, $bConvertToPlainText);
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
if (empty($value)) {
return '';
}
if ($bLocalize) {
$sRawValue = MetaModel::GetName($value);
} else {
$sRawValue = $value;
}
return Str::pure2xml($sRawValue);
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetValueLabel($sValue)
{
if (empty($sValue)) {
return '';
}
return MetaModel::GetName($sValue);
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$aRawValues = MetaModel::EnumChildClasses($this->GetHostClass(), ENUM_CHILD_CLASSES_ALL);
$aLocalizedValues = array();
foreach ($aRawValues as $sClass) {
$aLocalizedValues[$sClass] = MetaModel::GetName($sClass);
}
return $aLocalizedValues;
}
/**
* @return bool
* @since 2.7.0 N°2272 OQL perf finalclass in all intermediary tables
*/
public function CopyOnAllTables()
{
$sClass = self::GetHostClass();
if (MetaModel::IsLeafClass($sClass)) {
// Leaf class, no finalclass
return false;
}
return true;
}
}

View File

@@ -0,0 +1,211 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use DBObject;
use Dict;
use MetaModel;
use Str;
/**
* The attribute dedicated to the friendly name automatic attribute (not written)
*
* @package iTopORM
*/
class AttributeFriendlyName extends AttributeDefinition
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public $m_sValue;
public function __construct($sCode)
{
$this->m_sCode = $sCode;
$aParams = array();
$aParams["default_value"] = '';
parent::__construct($sCode, $aParams);
$this->m_sValue = $this->Get("default_value");
}
public function GetEditClass()
{
return "";
}
public function GetValuesDef()
{
return null;
}
public function GetPrerequisiteAttributes($sClass = null)
{
// Code duplicated with AttributeObsolescenceFlag
$aAttributes = $this->GetOptional("depends_on", array());
$oExpression = $this->GetOQLExpression();
foreach ($oExpression->ListRequiredFields() as $sAttCode) {
if (!in_array($sAttCode, $aAttributes)) {
$aAttributes[] = $sAttCode;
}
}
return $aAttributes;
}
public static function IsScalar()
{
return true;
}
public function IsNullAllowed()
{
return false;
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '') {
$sPrefix = $this->GetCode(); // Warning AttributeComputedFieldVoid does not have any sql property
}
return array('' => $sPrefix);
}
public static function IsBasedOnOQLExpression()
{
return true;
}
public function GetOQLExpression()
{
return MetaModel::GetNameExpression($this->GetHostClass());
}
public function GetLabel($sDefault = null)
{
$sLabel = parent::GetLabel('');
if (strlen($sLabel) == 0) {
$sLabel = Dict::S('Core:FriendlyName-Label');
}
return $sLabel;
}
public function GetDescription($sDefault = null)
{
$sLabel = parent::GetDescription('');
if (strlen($sLabel) == 0) {
$sLabel = Dict::S('Core:FriendlyName-Description');
}
return $sLabel;
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
$sValue = $aCols[$sPrefix];
return $sValue;
}
public function IsWritable()
{
return false;
}
public function IsMagic()
{
return true;
}
public static function IsBasedOnDBColumns()
{
return false;
}
public function SetFixedValue($sValue)
{
$this->m_sValue = $sValue;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->m_sValue;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
return Str::pure2html((string)$sValue);
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\StringField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
$oFormField->SetReadOnly(true);
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
// Do not display friendly names in the history of change
public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null)
{
return '';
}
public function GetBasicFilterOperators()
{
return array("=" => "equals", "!=" => "differs from");
}
public function GetBasicFilterLooseOperator()
{
return "Contains";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode) {
case '=':
case '!=':
return $this->GetSQLExpr()." $sOpCode $sQValue";
case 'Contains':
return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%");
case 'NotLike':
return $this->GetSQLExpr()." NOT LIKE $sQValue";
case 'Like':
default:
return $this->GetSQLExpr()." LIKE $sQValue";
}
}
public function IsPartOfFingerprint()
{
return false;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
/**
* Map a text column (size > ?), containing HTML code, to an attribute
*
* @package iTopORM
*/
class AttributeHTML extends AttributeLongText
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->Get('sql')] = $this->GetSQLCol();
if ($this->GetOptional('format', null) != null) {
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
if ($bFullSpec) {
$aColumns[$this->Get('sql').'_format'] .= " DEFAULT 'html'"; // default 'html' is for migrating old records
}
}
return $aColumns;
}
/**
* The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
*
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'html'); // Defaults to HTML
}
}

View File

@@ -0,0 +1,194 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObjectSearch;
/**
* Special kind of External Key to manage a hierarchy of objects
*/
class AttributeHierarchicalKey extends AttributeExternalKey
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY;
protected $m_sTargetClass;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
$aParams = parent::ListExpectedParams();
$idx = array_search('targetclass', $aParams);
unset($aParams[$idx]);
$idx = array_search('jointype', $aParams);
unset($aParams[$idx]);
return $aParams; // Later: mettre les bons parametres ici !!
}
public function GetEditClass()
{
return "ExtKey";
}
public function RequiresIndex()
{
return true;
}
/*
* The target class is the class for which the attribute has been defined first
*/
public function SetHostClass($sHostClass)
{
if (!isset($this->m_sTargetClass)) {
$this->m_sTargetClass = $sHostClass;
}
parent::SetHostClass($sHostClass);
}
public static function IsHierarchicalKey()
{
return true;
}
public function GetTargetClass($iType = EXTKEY_RELATIVE)
{
return $this->m_sTargetClass;
}
public function GetKeyAttDef($iType = EXTKEY_RELATIVE)
{
return $this;
}
public function GetKeyAttCode()
{
return $this->GetCode();
}
public function GetBasicFilterOperators()
{
return parent::GetBasicFilterOperators();
}
public function GetBasicFilterLooseOperator()
{
return parent::GetBasicFilterLooseOperator();
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : '');
$aColumns[$this->GetSQLLeft()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : '');
$aColumns[$this->GetSQLRight()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : '');
return $aColumns;
}
public function GetSQLRight()
{
return $this->GetCode().'_right';
}
public function GetSQLLeft()
{
return $this->GetCode().'_left';
}
public function GetSQLValues($value)
{
if (!is_array($value)) {
$aValues[$this->GetCode()] = $value;
} else {
$aValues = array();
$aValues[$this->GetCode()] = $value[$this->GetCode()];
$aValues[$this->GetSQLRight()] = $value[$this->GetSQLRight()];
$aValues[$this->GetSQLLeft()] = $value[$this->GetSQLLeft()];
}
return $aValues;
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$oFilter = $this->GetHierachicalFilter($aArgs, $sContains);
if ($oFilter) {
$oValSetDef = $this->GetValuesDef();
$oValSetDef->SetCondition($oFilter);
return $oValSetDef->GetValues($aArgs, $sContains);
} else {
return parent::GetAllowedValues($aArgs, $sContains);
}
}
public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
$oValSetDef = $this->GetValuesDef();
$oFilter = $this->GetHierachicalFilter($aArgs, $sContains, $iAdditionalValue);
if ($oFilter) {
$oValSetDef->SetCondition($oFilter);
}
$oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue);
return $oSet;
}
public function GetAllowedValuesAsFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
$oFilter = $this->GetHierachicalFilter($aArgs, $sContains, $iAdditionalValue);
if ($oFilter) {
return $oFilter;
}
return parent::GetAllowedValuesAsFilter($aArgs, $sContains, $iAdditionalValue);
}
private function GetHierachicalFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
if (array_key_exists('this', $aArgs)) {
// Hierarchical keys have one more constraint: the "parent value" cannot be
// "under" themselves
$iRootId = $aArgs['this']->GetKey();
if ($iRootId > 0) // ignore objects that do no exist in the database...
{
$sClass = $this->m_sTargetClass;
return DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId");
}
}
return false;
}
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
*/
public function GetMirrorLinkAttribute()
{
return null;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
/**
* Specialization of a string: IP address
*
* @package iTopORM
*/
class AttributeIPAddress extends AttributeString
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetValidationPattern()
{
$sNum = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])';
return "^($sNum\\.$sNum\\.$sNum\\.$sNum)$";
}
public function GetOrderBySQLExpressions($sClassAlias)
{
// Note: This is the responsibility of this function to place backticks around column aliases
return array('INET_ATON(`'.$sClassAlias.$this->GetCode().'`)');
}
}

View File

@@ -0,0 +1,207 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
use HTMLSanitizer;
use ormDocument;
use utils;
/**
* An image is a specific type of document, it is stored as several columns in the database
*
* @package iTopORM
*/
class AttributeImage extends AttributeBlob
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function Get($sParamName)
{
$oParamValue = parent::Get($sParamName);
if ($sParamName === 'default_image') {
/** @noinspection NestedPositiveIfStatementsInspection */
if (!empty($oParamValue)) {
return utils::GetAbsoluteUrlModulesRoot().$oParamValue;
}
}
return $oParamValue;
}
public function GetEditClass()
{
return "Image";
}
/**
* {@inheritDoc}
* @see AttributeBlob::MakeRealValue()
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
$oDoc = parent::MakeRealValue($proposedValue, $oHostObj);
if (($oDoc instanceof ormDocument)
&& (false === $oDoc->IsEmpty())
&& ($oDoc->GetMimeType() === 'image/svg+xml')) {
$sCleanSvg = HTMLSanitizer::Sanitize($oDoc->GetData(), 'svg_sanitizer');
$oDoc = new ormDocument($sCleanSvg, $oDoc->GetMimeType(), $oDoc->GetFileName());
}
// The validation of the MIME Type is done by CheckFormat below
return $oDoc;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return new ormDocument('', '', '');
}
/**
* Check that the supplied ormDocument actually contains an image
* {@inheritDoc}
*
* @see AttributeDefinition::CheckFormat()
*/
public function CheckFormat($value)
{
if ($value instanceof ormDocument && !$value->IsEmpty()) {
return ($value->GetMainMimeType() == 'image');
}
return true;
}
/**
* @see edit_image.js for JS generated markup in form edition
*
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @param ormDocument $value
*
* @return string
*
*/
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
$sRet = '';
$bIsCustomImage = false;
$iMaxWidth = $this->Get('display_max_width');
$sMaxWidthPx = $iMaxWidth.'px';
$iMaxHeight = $this->Get('display_max_height');
$sMaxHeightPx = $iMaxHeight.'px';
$sDefaultImageUrl = $this->Get('default_image');
if ($sDefaultImageUrl !== null) {
$sRet = $this->GetHtmlForImageUrl($sDefaultImageUrl, $sMaxWidthPx, $sMaxHeightPx);
}
$sCustomImageUrl = $this->GetAttributeImageFileUrl($value, $oHostObject);
if ($sCustomImageUrl !== null) {
$bIsCustomImage = true;
$sRet = $this->GetHtmlForImageUrl($sCustomImageUrl, $sMaxWidthPx, $sMaxHeightPx);
}
$sCssClasses = 'ibo-input-image--image-view attribute-image';
$sCssClasses .= ' '.(($bIsCustomImage) ? 'attribute-image-custom' : 'attribute-image-default');
// Important: If you change this, mind updating edit_image.js as well
return '<div class="'.$sCssClasses.'" style="max-width: min('.$sMaxWidthPx.',100%); max-height: min('.$sMaxHeightPx.',100%); aspect-ratio: '.$iMaxWidth.' / '.$iMaxHeight.'">'.$sRet.'</div>';
}
/**
* @param string $sUrl
* @param int $iMaxWidthPx
* @param int $iMaxHeightPx
*
* @return string
*
* @since 2.6.0 new private method
* @since 2.7.0 change visibility to protected
*/
protected function GetHtmlForImageUrl($sUrl, $iMaxWidthPx, $iMaxHeightPx)
{
return '<img src="'.$sUrl.'" style="max-width: min('.$iMaxWidthPx.',100%); max-height: min('.$iMaxHeightPx.',100%)">';
}
/**
* @param ormDocument $value
* @param DBObject $oHostObject
*
* @return null|string
*
* @since 2.6.0 new private method
* @since 2.7.0 change visibility to protected
*/
protected function GetAttributeImageFileUrl($value, $oHostObject)
{
if (!is_object($value)) {
return null;
}
if ($value->IsEmpty()) {
return null;
}
$bExistingImageModified = ($oHostObject->IsModified() && (array_key_exists($this->GetCode(), $oHostObject->ListChanges())));
if ($oHostObject->IsNew() || ($bExistingImageModified)) {
// If the object is modified (or not yet stored in the database) we must serve the content of the image directly inline
// otherwise (if we just give an URL) the browser will be given the wrong content... and may cache it
return 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
}
return $value->GetDisplayURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode());
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\ImageField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
parent::MakeFormField($oObject, $oFormField);
// Generating urls
$value = $oObject->Get($this->GetCode());
if (is_object($value) && !$value->IsEmpty()) {
$oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
$oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
} else {
$oDefaultImage = $this->Get('default_image');
if (is_object($oDefaultImage) && !$oDefaultImage->IsEmpty()) {
$oFormField->SetDownloadUrl($oDefaultImage);
$oFormField->SetDisplayUrl($oDefaultImage);
}
}
return $oFormField;
}
}

View File

@@ -0,0 +1,148 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use CoreException;
use utils;
/**
* Map an integer column to an attribute
*
* @package iTopORM
*/
class AttributeInteger extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return "INT(11)".($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetValidationPattern()
{
return "^[0-9]+$";
}
public function GetBasicFilterOperators()
{
return array(
"!=" => "differs from",
"=" => "equals",
">" => "greater (strict) than",
">=" => "greater than",
"<" => "less (strict) than",
"<=" => "less than",
"in" => "in",
);
}
public function GetBasicFilterLooseOperator()
{
// Unless we implement an "equals approximately..." or "same order of magnitude"
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode) {
case '!=':
return $this->GetSQLExpr()." != $sQValue";
break;
case '>':
return $this->GetSQLExpr()." > $sQValue";
break;
case '>=':
return $this->GetSQLExpr()." >= $sQValue";
break;
case '<':
return $this->GetSQLExpr()." < $sQValue";
break;
case '<=':
return $this->GetSQLExpr()." <= $sQValue";
break;
case 'in':
if (!is_array($value)) {
throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
}
return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')";
break;
case '=':
default:
return $this->GetSQLExpr()." = \"$value\"";
}
}
public function GetNullValue()
{
return null;
}
public function IsNull($proposedValue)
{
return is_null($proposedValue);
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
return utils::IsNotNullOrEmptyString($proposedValue);
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return null;
}
if ($proposedValue === '') {
return null;
} // 0 is transformed into '' !
return (int)$proposedValue;
}
public function ScalarToSQL($value)
{
assert(is_numeric($value) || is_null($value));
return $value; // supposed to be an int
}
}

View File

@@ -0,0 +1,940 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use Combodo\iTop\Application\UI\Links\Set\BlockLinkSetDisplayAsProperty;
use Combodo\iTop\Form\Field\LinkedSetField;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Service\Links\LinkSetModel;
use CoreException;
use CSVParser;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use Dict;
use Exception;
use ExceptionLog;
use IssueLog;
use MetaModel;
use ormLinkSet;
use ValueSetObjects;
/**
* Set of objects directly linked to an object, and being part of its definition
*
* @package iTopORM
*/
class AttributeLinkedSet extends AttributeDefinition
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
$this->aCSSClasses[] = 'attribute-set';
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(),
array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max"));
}
public function GetEditClass()
{
return "LinkedSet";
}
/** @inheritDoc */
public static function IsBulkModifyCompatible(): bool
{
return false;
}
public function IsWritable()
{
return true;
}
public static function IsLinkSet()
{
return true;
}
public function IsIndirect()
{
return false;
}
public function GetValuesDef()
{
$oValSetDef = $this->Get("allowed_values");
if (!$oValSetDef) {
// Let's propose every existing value
$oValSetDef = new ValueSetObjects('SELECT '.LinkSetModel::GetTargetClass($this));
}
return $oValSetDef;
}
public function GetEditValue($value, $oHostObj = null)
{
/** @var ormLinkSet $value * */
if ($value->Count() === 0) {
return '';
}
/** Return linked objects key as string **/
$aValues = $value->GetValues();
return implode(' ', $aValues);
}
public function GetPrerequisiteAttributes($sClass = null)
{
return $this->Get("depends_on");
}
/**
* @param \DBObject|null $oHostObject
*
* @return \ormLinkSet
*
* @throws Exception
* @throws CoreException
* @throws CoreWarning
*/
public function GetDefaultValue(DBObject $oHostObject = null)
{
if ($oHostObject === null) {
return null;
}
$sLinkClass = $this->GetLinkedClass();
$sExtKeyToMe = $this->GetExtKeyToMe();
// The class to target is not the current class, because if this is a derived class,
// it may differ from the target class, then things start to become confusing
/** @var AttributeExternalKey $oRemoteExtKeyAtt */
$oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe);
$sMyClass = $oRemoteExtKeyAtt->GetTargetClass();
$oMyselfSearch = new DBObjectSearch($sMyClass);
if ($oHostObject !== null) {
$oMyselfSearch->AddCondition('id', $oHostObject->GetKey(), '=');
}
$oLinkSearch = new DBObjectSearch($sLinkClass);
$oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe);
if ($this->IsIndirect()) {
// Join the remote class so that the archive flag will be taken into account
/** @var AttributeLinkedSetIndirect $this */
$sExtKeyToRemote = $this->GetExtKeyToRemote();
/** @var AttributeExternalKey $oExtKeyToRemote */
$oExtKeyToRemote = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToRemote);
$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
if (MetaModel::IsArchivable($sRemoteClass)) {
$oRemoteSearch = new DBObjectSearch($sRemoteClass);
/** @var \AttributeLinkedSetIndirect $this */
$oLinkSearch->AddCondition_PointingTo($oRemoteSearch, $this->GetExtKeyToRemote());
}
}
$oLinks = new DBObjectSet($oLinkSearch);
$oLinkSet = new ormLinkSet($this->GetHostClass(), $this->GetCode(), $oLinks);
return $oLinkSet;
}
public function GetTrackingLevel()
{
return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_default'));
}
/**
* @return string see LINKSET_EDITMODE_* constants
*/
public function GetEditMode()
{
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
}
/**
* @return int see LINKSET_EDITWHEN_* constants
* @since 3.1.1 3.2.0 N°6385
*/
public function GetEditWhen(): int
{
return $this->GetOptional('edit_when', LINKSET_EDITWHEN_ALWAYS);
}
/**
* @return string see LINKSET_DISPLAY_STYLE_* constants
* @since 3.1.0 N°3190
*/
public function GetDisplayStyle()
{
$sDisplayStyle = $this->GetOptional('display_style', LINKSET_DISPLAY_STYLE_TAB);
if ($sDisplayStyle === '') {
$sDisplayStyle = LINKSET_DISPLAY_STYLE_TAB;
}
return $sDisplayStyle;
}
/**
* Indicates if the current Attribute has constraints (php constraints or datamodel constraints)
*
* @return bool true if Attribute has constraints
* @since 3.1.0 N°6228
*/
public function HasPHPConstraint(): bool
{
return $this->GetOptional('with_php_constraint', false);
}
/**
* @return bool true if Attribute has computation (DB_LINKS_CHANGED event propagation, `with_php_computation` attribute xml property), false otherwise
* @since 3.1.1 3.2.0 N°6228
*/
public function HasPHPComputation(): bool
{
return $this->GetOptional('with_php_computation', false);
}
public function GetLinkedClass()
{
return $this->Get('linked_class');
}
public function GetExtKeyToMe()
{
return $this->Get('ext_key_to_me');
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return '';
}
/** @inheritDoc * */
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if ($this->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_TAB) {
return $this->GetAsHTMLForTab($sValue, $oHostObject, $bLocalize);
} else {
return $this->GetAsHTMLForProperty($sValue, $oHostObject, $bLocalize);
}
}
public function GetAsHTMLForTab($sValue, $oHostObject = null, $bLocalize = true)
{
if (is_object($sValue) && ($sValue instanceof ormLinkSet)) {
$sValue->Rewind();
$aItems = array();
while ($oObj = $sValue->Fetch()) {
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach (MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef) {
if ($sAttCode == $this->GetExtKeyToMe()) {
continue;
}
if ($oAttDef->IsExternalField()) {
continue;
}
$sAttValue = $oObj->GetAsHTML($sAttCode);
if (strlen($sAttValue) > 0) {
$aAttributes[] = $sAttValue;
}
}
$sAttributes = implode(', ', $aAttributes);
$aItems[] = $sAttributes;
}
return implode('<br/>', $aItems);
}
return null;
}
public function GetAsHTMLForProperty($sValue, $oHostObject = null, $bLocalize = true): string
{
try {
/** @var ormLinkSet $sValue */
if (is_null($sValue) || $sValue->Count() === 0) {
return '';
}
$oLinkSetBlock = new BlockLinkSetDisplayAsProperty($this->GetCode(), $this, $sValue);
return ConsoleBlockRenderer::RenderBlockTemplates($oLinkSetBlock);
}
catch (Exception $e) {
$sMessage = "Error while displaying attribute {$this->GetCode()}";
IssueLog::Error($sMessage, IssueLog::CHANNEL_DEFAULT, [
'host_object_class' => $this->GetHostClass(),
'host_object_key' => $oHostObject->GetKey(),
'attribute' => $this->GetCode(),
]);
return $sMessage;
}
}
/**
* @param string $sValue
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string
*
* @throws CoreException
*/
public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true)
{
if (is_object($sValue) && ($sValue instanceof ormLinkSet)) {
$sValue->Rewind();
$sRes = "<Set>\n";
while ($oObj = $sValue->Fetch()) {
$sObjClass = get_class($oObj);
$sRes .= "<$sObjClass id=\"".$oObj->GetKey()."\">\n";
// Show only relevant information (hide the external key to the current object)
foreach (MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) {
if ($sAttCode == 'finalclass') {
if ($sObjClass == $this->GetLinkedClass()) {
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe()) {
continue;
}
if ($oAttDef->IsExternalField()) {
/** @var \AttributeExternalField $oAttDef */
if ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe()) {
continue;
}
/** @var AttributeExternalField $oAttDef */
if ($oAttDef->IsFriendlyName()) {
continue;
}
}
if ($oAttDef instanceof AttributeFriendlyName) {
continue;
}
if (!$oAttDef->IsScalar()) {
continue;
}
$sAttValue = $oObj->GetAsXML($sAttCode, $bLocalize);
$sRes .= "<$sAttCode>$sAttValue</$sAttCode>\n";
}
$sRes .= "</$sObjClass>\n";
}
$sRes .= "</Set>\n";
} else {
$sRes = '';
}
return $sRes;
}
/**
* @param $sValue
* @param string $sSeparator
* @param string $sTextQualifier
* @param DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return mixed|string
* @throws CoreException
*/
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
$sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator');
$sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator');
$sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator');
$sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier');
if (is_object($sValue) && ($sValue instanceof ormLinkSet)) {
$sValue->Rewind();
$aItems = array();
while ($oObj = $sValue->Fetch()) {
$sObjClass = get_class($oObj);
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach (MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) {
if ($sAttCode == 'finalclass') {
if ($sObjClass == $this->GetLinkedClass()) {
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe()) {
continue;
}
if ($oAttDef->IsExternalField()) {
continue;
}
if (!$oAttDef->IsBasedOnDBColumns()) {
continue;
}
if (!$oAttDef->IsScalar()) {
continue;
}
$sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize);
if (strlen($sAttValue) > 0) {
$sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier,
$sAttCode.$sSepValue.$sAttValue);
$aAttributes[] = $sAttributeQualifier.$sAttributeData.$sAttributeQualifier;
}
}
$sAttributes = implode($sSepAttribute, $aAttributes);
$aItems[] = $sAttributes;
}
$sRes = implode($sSepItem, $aItems);
} else {
$sRes = '';
}
$sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes);
$sRes = $sTextQualifier.$sRes.$sTextQualifier;
return $sRes;
}
/**
* List the available verbs for 'GetForTemplate'
*/
public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text (unlocalized) representation',
'html' => 'HTML representation (unordered list)',
);
}
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param mixed $value The current value of the field
* @param string $sVerb The verb specifying the representation of the value
* @param DBObject $oHostObject The object
* @param bool $bLocalize Whether or not to localize the value
*
* @return string
* @throws Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
$sRemoteName = $this->IsIndirect() ?
/** @var AttributeLinkedSetIndirect $this */
$this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname';
$oLinkSet = clone $value; // Workaround/Safety net for Trac #887
$iLimit = MetaModel::GetConfig()->Get('max_linkset_output');
$iCount = 0;
$aNames = array();
foreach ($oLinkSet as $oItem) {
if (($iLimit > 0) && ($iCount == $iLimit)) {
$iTotal = $oLinkSet->Count();
$aNames[] = '... '.Dict::Format('UI:TruncatedResults', $iCount, $iTotal);
break;
}
$aNames[] = $oItem->Get($sRemoteName);
$iCount++;
}
switch ($sVerb) {
case '':
return implode("\n", $aNames);
case 'html':
return '<ul><li>'.implode("</li><li>", $aNames).'</li></ul>';
default:
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject));
}
}
public function DuplicatesAllowed()
{
return false;
} // No duplicates for 1:n links, never
public function GetImportColumns()
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'MEDIUMTEXT'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}
/**
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return DBObjectSet
* @throws CSVParserException
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws MissingQueryArgument
* @throws MySQLException
* @throws MySQLHasGoneAwayException
* @throws Exception
*/
public function MakeValueFromString(
$sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null,
$sAttributeQualifier = null
)
{
if (is_null($sSepItem) || empty($sSepItem)) {
$sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator');
}
if (is_null($sSepAttribute) || empty($sSepAttribute)) {
$sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator');
}
if (is_null($sSepValue) || empty($sSepValue)) {
$sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator');
}
if (is_null($sAttributeQualifier) || empty($sAttributeQualifier)) {
$sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier');
}
$sTargetClass = $this->Get('linked_class');
$sInput = str_replace($sSepItem, "\n", $sProposedValue);
$oCSVParser = new CSVParser($sInput, $sSepAttribute, $sAttributeQualifier);
$aInput = $oCSVParser->ToArray(0 /* do not skip lines */);
$aLinks = array();
foreach ($aInput as $aRow) {
// 1st - get the values, split the extkey->searchkey specs, and eventually get the finalclass value
$aExtKeys = array();
$aValues = array();
foreach ($aRow as $sCell) {
$iSepPos = strpos($sCell, $sSepValue);
if ($iSepPos === false) {
// Houston...
throw new CoreException('Wrong format for link attribute specification', array('value' => $sCell));
}
$sAttCode = trim(substr($sCell, 0, $iSepPos));
$sValue = substr($sCell, $iSepPos + strlen($sSepValue));
if (preg_match('/^(.+)->(.+)$/', $sAttCode, $aMatches)) {
$sKeyAttCode = $aMatches[1];
$sRemoteAttCode = $aMatches[2];
$aExtKeys[$sKeyAttCode][$sRemoteAttCode] = $sValue;
if (!MetaModel::IsValidAttCode($sTargetClass, $sKeyAttCode)) {
throw new CoreException('Wrong attribute code for link attribute specification',
array('class' => $sTargetClass, 'attcode' => $sKeyAttCode));
}
/** @var \AttributeExternalKey $oKeyAttDef */
$oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode);
$sRemoteClass = $oKeyAttDef->GetTargetClass();
if (!MetaModel::IsValidAttCode($sRemoteClass, $sRemoteAttCode)) {
throw new CoreException('Wrong attribute code for link attribute specification',
array('class' => $sRemoteClass, 'attcode' => $sRemoteAttCode));
}
} else {
if (!MetaModel::IsValidAttCode($sTargetClass, $sAttCode)) {
throw new CoreException('Wrong attribute code for link attribute specification',
array('class' => $sTargetClass, 'attcode' => $sAttCode));
}
$oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttCode);
$aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem,
$sSepAttribute, $sSepValue, $sAttributeQualifier);
}
}
// 2nd - Instanciate the object and set the value
if (isset($aValues['finalclass'])) {
$sLinkClass = $aValues['finalclass'];
if (!is_subclass_of($sLinkClass, $sTargetClass)) {
throw new CoreException('Wrong class for link attribute specification',
array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
}
} elseif (MetaModel::IsAbstract($sTargetClass)) {
throw new CoreException('Missing finalclass for link attribute specification');
} else {
$sLinkClass = $sTargetClass;
}
$oLink = MetaModel::NewObject($sLinkClass);
foreach ($aValues as $sAttCode => $sValue) {
$oLink->Set($sAttCode, $sValue);
}
// 3rd - Set external keys from search conditions
foreach ($aExtKeys as $sKeyAttCode => $aReconciliation) {
$oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode);
$sKeyClass = $oKeyAttDef->GetTargetClass();
$oExtKeyFilter = new DBObjectSearch($sKeyClass);
$aReconciliationDesc = array();
foreach ($aReconciliation as $sRemoteAttCode => $sValue) {
$oExtKeyFilter->AddCondition($sRemoteAttCode, $sValue, '=');
$aReconciliationDesc[] = "$sRemoteAttCode=$sValue";
}
$oExtKeySet = new DBObjectSet($oExtKeyFilter);
switch ($oExtKeySet->Count()) {
case 0:
$sReconciliationDesc = implode(', ', $aReconciliationDesc);
throw new CoreException("Found no match",
array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc));
break;
case 1:
$oRemoteObj = $oExtKeySet->Fetch();
$oLink->Set($sKeyAttCode, $oRemoteObj->GetKey());
break;
default:
$sReconciliationDesc = implode(', ', $aReconciliationDesc);
throw new CoreException("Found several matches",
array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc));
// Found several matches, ambiguous
}
}
// Check (roughly) if such a link is valid
$aErrors = array();
foreach (MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef) {
if ($oAttDef->IsExternalKey()) {
/** @var \AttributeExternalKey $oAttDef */
if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(),
$oAttDef->GetTargetClass()))) {
continue; // Don't check the key to self
}
}
if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) {
$aErrors[] = $sAttCode;
}
}
if (count($aErrors) > 0) {
throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
}
$aLinks[] = $oLink;
}
$oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
return $oSet;
}
/**
* @inheritDoc
*
* @param ormLinkSet $value
*/
public function GetForJSON($value)
{
$aRet = array();
if (is_object($value) && ($value instanceof ormLinkSet)) {
$value->Rewind();
while ($oObj = $value->Fetch()) {
$sObjClass = get_class($oObj);
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach (MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) {
if ($sAttCode == 'finalclass') {
if ($sObjClass == $this->GetLinkedClass()) {
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe()) {
continue;
}
if ($oAttDef->IsExternalField()) {
continue;
}
if (!$oAttDef->IsBasedOnDBColumns()) {
continue;
}
if (!$oAttDef->IsScalar()) {
continue;
}
$attValue = $oObj->Get($sAttCode);
$aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue);
}
$aRet[] = $aAttributes;
}
}
return $aRet;
}
/**
* @inheritDoc
*
* @return DBObjectSet
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws Exception
*/
public function FromJSONToValue($json)
{
$sTargetClass = $this->Get('linked_class');
$aLinks = array();
foreach ($json as $aValues) {
if (isset($aValues['finalclass'])) {
$sLinkClass = $aValues['finalclass'];
if (!is_subclass_of($sLinkClass, $sTargetClass)) {
throw new CoreException('Wrong class for link attribute specification',
array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
}
} elseif (MetaModel::IsAbstract($sTargetClass)) {
throw new CoreException('Missing finalclass for link attribute specification');
} else {
$sLinkClass = $sTargetClass;
}
$oLink = MetaModel::NewObject($sLinkClass);
foreach ($aValues as $sAttCode => $sValue) {
$oLink->Set($sAttCode, $sValue);
}
// Check (roughly) if such a link is valid
$aErrors = array();
foreach (MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef) {
if ($oAttDef->IsExternalKey()) {
/** @var AttributeExternalKey $oAttDef */
if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(),
$oAttDef->GetTargetClass()))) {
continue; // Don't check the key to self
}
}
if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) {
$aErrors[] = $sAttCode;
}
}
if (count($aErrors) > 0) {
throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
}
$aLinks[] = $oLink;
}
$oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
return $oSet;
}
/**
* @param $proposedValue
* @param $oHostObj
*
* @return mixed
* @throws Exception
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if ($proposedValue === null) {
$sLinkedClass = $this->GetLinkedClass();
$aLinkedObjectsArray = array();
$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
return new ormLinkSet(
get_class($oHostObj),
$this->GetCode(),
$oSet
);
}
return $proposedValue;
}
/**
* @param ormLinkSet $val1
* @param ormLinkSet $val2
*
* @return bool
*/
public function Equals($val1, $val2)
{
if ($val1 === $val2) {
$bAreEquivalent = true;
} else {
$bAreEquivalent = ($val2->HasDelta() === false);
}
return $bAreEquivalent;
}
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
* @throws Exception
*/
public function GetMirrorLinkAttribute()
{
$oRemoteAtt = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToMe());
return $oRemoteAtt;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\LinkedSetField';
}
/**
* @param DBObject $oObject
* @param LinkedSetField $oFormField
*
* @return LinkedSetField
* @throws CoreException
* @throws DictExceptionMissingString
* @throws Exception
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
// Setting target class
if (!$this->IsIndirect()) {
$sTargetClass = $this->GetLinkedClass();
} else {
/** @var \AttributeExternalKey $oRemoteAttDef */
/** @var \AttributeLinkedSetIndirect $this */
$oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
$sTargetClass = $oRemoteAttDef->GetTargetClass();
/** @var \AttributeLinkedSetIndirect $this */
$oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote());
}
$oFormField->SetTargetClass($sTargetClass);
$oFormField->SetLinkedClass($this->GetLinkedClass());
$oFormField->SetIndirect($this->IsIndirect());
// Setting attcodes to display
$aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list'));
// - Adding friendlyname attribute to the list is not already in it
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass);
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay)) {
$aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
}
// - Adding attribute properties
$aAttributesToDisplay = array();
foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) {
$oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
$aAttributesToDisplay[$sAttCodeToDisplay] = [
'att_code' => $sAttCodeToDisplay,
'label' => $oAttDefToDisplay->GetLabel(),
];
}
$oFormField->SetAttributesToDisplay($aAttributesToDisplay);
// Append lnk attributes (filtered from zlist)
if ($this->IsIndirect()) {
$aLnkAttDefToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($this->m_sHostClass, $this->m_sCode);
$aLnkAttributesToDisplay = array();
foreach ($aLnkAttDefToDisplay as $oLnkAttDefToDisplay) {
$aLnkAttributesToDisplay[$oLnkAttDefToDisplay->GetCode()] = [
'att_code' => $oLnkAttDefToDisplay->GetCode(),
'label' => $oLnkAttDefToDisplay->GetLabel(),
'mandatory' => !$oLnkAttDefToDisplay->IsNullAllowed(),
];
}
$oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay);
}
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
public function IsPartOfFingerprint()
{
return false;
}
/**
* @inheritDoc
*
* @param ormLinkSet $proposedValue
*/
public function HasAValue($proposedValue): bool
{
// Protection against wrong value type
if (false === ($proposedValue instanceof ormLinkSet)) {
return parent::HasAValue($proposedValue);
}
// We test if there is at least 1 item in the linkset (new or existing), not if an item is being added to it.
return $proposedValue->Count() > 0;
}
/**
* SearchSpecificLabel.
*
* @param string $sDictEntrySuffix
* @param string $sDefault
* @param bool $bUserLanguageOnly
* @param ...$aArgs
*
* @return string
* @since 3.1.0
*/
public function SearchSpecificLabel(string $sDictEntrySuffix, string $sDefault, bool $bUserLanguageOnly, ...$aArgs): string
{
try {
$sNextClass = $this->m_sHostClass;
do {
$sKey = "Class:{$sNextClass}/Attribute:{$this->m_sCode}/{$sDictEntrySuffix}";
if (Dict::S($sKey, null, $bUserLanguageOnly) !== $sKey) {
return Dict::Format($sKey, ...$aArgs);
}
$sNextClass = MetaModel::GetParentClass($sNextClass);
} while ($sNextClass !== null);
if (Dict::S($sDictEntrySuffix, null, $bUserLanguageOnly) !== $sKey) {
return Dict::Format($sDictEntrySuffix, ...$aArgs);
} else {
return $sDefault;
}
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return $sDefault;
}
}
}

View File

@@ -0,0 +1,106 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CoreException;
use Exception;
use MetaModel;
/**
* Set of objects linked to an object (n-n), and being part of its definition
*
* @package iTopORM
*/
class AttributeLinkedSetIndirect extends AttributeLinkedSet
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote"));
}
public function IsIndirect()
{
return true;
}
public function GetExtKeyToRemote()
{
return $this->Get('ext_key_to_remote');
}
public function GetEditClass()
{
return "LinkedSet";
}
public function DuplicatesAllowed()
{
return $this->GetOptional("duplicates", false);
} // The same object may be linked several times... or not...
public function GetTrackingLevel()
{
return $this->GetOptional('tracking_level',
MetaModel::GetConfig()->Get('tracking_level_linked_set_indirect_default'));
}
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
* @throws CoreException
*/
public function GetMirrorLinkAttribute()
{
$oRet = null;
/** @var \AttributeExternalKey $oExtKeyToRemote */
$oExtKeyToRemote = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) {
if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect) {
continue;
}
if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass()) {
continue;
}
if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote()) {
continue;
}
if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe()) {
continue;
}
$oRet = $oRemoteAttDef;
break;
}
return $oRet;
}
/** @inheritDoc */
public static function IsBulkModifyCompatible(): bool
{
return true;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOpSetAttributeHTML;
use CMDBChangeOpSetAttributeLongText;
use CMDBSource;
use Exception;
/**
* Map a log to an attribute
*
* @package iTopORM
*/
class AttributeLongText extends AttributeText
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
protected function GetSQLCol($bFullSpec = false)
{
return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition();
}
public function GetMaxSize()
{
// Is there a way to know the current limitation for mysql?
// See mysql_field_len()
return 65535 * 1024; // Limited... still 64 MB!
}
protected function GetChangeRecordClassName(): string
{
return ($this->GetFormat() === 'html')
? CMDBChangeOpSetAttributeHTML::class
: CMDBChangeOpSetAttributeLongText::class;
}
}

View File

@@ -0,0 +1,146 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
use MetaModel;
/**
* A meta enum is an aggregation of enum from subclasses into an enum of a base class
* It has been designed is to cope with the fact that statuses must be defined in leaf classes, while it makes sense to
* have a superstatus available on the root classe(s)
*
* @package iTopORM
*/
class AttributeMetaEnum extends AttributeEnum
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array('allowed_values', 'sql', 'default_value', 'mapping');
}
public function IsNullAllowed()
{
return false; // Well... this actually depends on the mapping
}
public function IsWritable()
{
return false;
}
public function RequiresIndex()
{
return true;
}
public function GetPrerequisiteAttributes($sClass = null)
{
if (is_null($sClass)) {
$sClass = $this->GetHostClass();
}
$aMappingData = $this->GetMapRule($sClass);
if ($aMappingData == null) {
$aRet = array();
} else {
$aRet = array($aMappingData['attcode']);
}
return $aRet;
}
/**
* Overload the standard so as to leave the data unsorted
*
* @param array $aArgs
* @param string $sContains
*
* @return array|null
*/
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$oValSetDef = $this->GetValuesDef();
if (!$oValSetDef) {
return null;
}
$aRawValues = $oValSetDef->GetValueList();
if (is_null($aRawValues)) {
return null;
}
$aLocalizedValues = array();
foreach ($aRawValues as $sKey => $sValue) {
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
}
return $aLocalizedValues;
}
/**
* Returns the meta value for the given object.
* See also MetaModel::RebuildMetaEnums() that must be maintained when MapValue changes
*
* @param $oObject
*
* @return mixed
* @throws Exception
*/
public function MapValue($oObject)
{
$aMappingData = $this->GetMapRule(get_class($oObject));
if ($aMappingData == null) {
$sRet = $this->GetDefaultValue();
} else {
$sAttCode = $aMappingData['attcode'];
$value = $oObject->Get($sAttCode);
if (array_key_exists($value, $aMappingData['values'])) {
$sRet = $aMappingData['values'][$value];
} elseif ($this->GetDefaultValue() != '') {
$sRet = $this->GetDefaultValue();
} else {
throw new Exception('AttributeMetaEnum::MapValue(): mapping not found for value "'.$value.'" in '.get_class($oObject).', on attribute '.MetaModel::GetAttributeOrigin($this->GetHostClass(),
$this->GetCode()).'::'.$this->GetCode());
}
}
return $sRet;
}
public function GetMapRule($sClass)
{
$aMappings = $this->Get('mapping');
if (array_key_exists($sClass, $aMappings)) {
$aMappingData = $aMappings[$sClass];
} else {
$sParent = MetaModel::GetParentClass($sClass);
if (is_null($sParent)) {
$aMappingData = null;
} else {
$aMappingData = $this->GetMapRule($sParent);
}
}
return $aMappingData;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
/**
* Specialization of a string: OQL expression
*
* @package iTopORM
*/
class AttributeOQL extends AttributeText
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetEditClass()
{
return "OQLExpression";
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
use Exception;
use MetaModel;
/**
* An external key for which the class is defined as the value of another attribute
*
* @package iTopORM
*/
class AttributeObjectKey extends AttributeDBFieldVoid
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('class_attcode', 'is_null_allowed'));
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return 0;
}
public function IsNullAllowed()
{
return $this->Get("is_null_allowed");
}
public function GetBasicFilterOperators()
{
return parent::GetBasicFilterOperators();
}
public function GetBasicFilterLooseOperator()
{
return parent::GetBasicFilterLooseOperator();
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return parent::GetBasicFilterSQLExpr($sOpCode, $value);
}
public function GetNullValue()
{
return 0;
}
public function IsNull($proposedValue)
{
return ($proposedValue == 0);
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
return ((int)$proposedValue) !== 0;
}
/**
* @inheritDoc
*
* @param int|DBObject $proposedValue Object key or valid ({@see MetaModel::IsValidObject()}) datamodel object
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return 0;
}
if ($proposedValue === '') {
return 0;
}
if (MetaModel::IsValidObject($proposedValue)) {
return $proposedValue->GetKey();
}
return (int)$proposedValue;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Dict;
use Exception;
class AttributeObsolescenceDate extends AttributeDate
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetLabel($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeObsolescenceDate/Label', $sDefault);
return parent::GetLabel($sDefault);
}
public function GetDescription($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeObsolescenceDate/Label+', $sDefault);
return parent::GetDescription($sDefault);
}
}

View File

@@ -0,0 +1,134 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use DBObject;
use Dict;
use MetaModel;
class AttributeObsolescenceFlag extends AttributeBoolean
{
public function __construct($sCode)
{
parent::__construct($sCode, array(
"allowed_values" => null,
"sql" => $sCode,
"default_value" => "",
"is_null_allowed" => false,
"depends_on" => array(),
));
}
public function IsWritable()
{
return false;
}
public function IsMagic()
{
return true;
}
public static function IsBasedOnDBColumns()
{
return false;
}
/**
* Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via
* GetOQLExpression)
*
* @return bool
*/
public static function IsBasedOnOQLExpression()
{
return true;
}
public function GetOQLExpression()
{
return MetaModel::GetObsolescenceExpression($this->GetHostClass());
}
public function GetSQLExpressions($sPrefix = '')
{
return array();
}
public function GetSQLColumns($bFullSpec = false)
{
return array();
} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation)
public function GetSQLValues($value)
{
return array();
} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update)
public function GetEditClass()
{
return "";
}
public function GetValuesDef()
{
return null;
}
public function GetPrerequisiteAttributes($sClass = null)
{
// Code duplicated with AttributeFriendlyName
$aAttributes = $this->GetOptional("depends_on", array());
$oExpression = $this->GetOQLExpression();
foreach ($oExpression->ListRequiredFields() as $sClass => $sAttCode) {
if (!in_array($sAttCode, $aAttributes)) {
$aAttributes[] = $sAttCode;
}
}
return $aAttributes;
}
public function IsDirectField()
{
return true;
}
public static function IsScalar()
{
return true;
}
public function GetSQLExpr()
{
return null;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->MakeRealValue(false, $oHostObject);
}
public function IsNullAllowed()
{
return false;
}
public function GetLabel($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label', $sDefault);
return parent::GetLabel($sDefault);
}
public function GetDescription($sDefault = null)
{
$sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label+', $sDefault);
return parent::GetDescription($sDefault);
}
}

View File

@@ -0,0 +1,247 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOp;
use CMDBChangeOpSetAttributeOneWayPassword;
use CMDBSource;
use DBObject;
use Exception;
use ormPassword;
use utils;
/**
* One way encrypted (hashed) password
*/
class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("depends_on"));
}
public function GetEditClass()
{
return "One Way Password";
}
public static function IsBasedOnDBColumns()
{
return true;
}
public static function IsScalar()
{
return true;
}
public function IsWritable()
{
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return "";
}
public function IsNullAllowed()
{
return $this->GetOptional("is_null_allowed", false);
}
// Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted)
public function MakeRealValue($proposedValue, $oHostObj)
{
$oPassword = $proposedValue;
if (is_object($oPassword)) {
$oPassword = clone $proposedValue;
} else {
$oPassword = new ormPassword('', '');
$oPassword->SetPassword($proposedValue);
}
return $oPassword;
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '') {
$sPrefix = $this->GetCode(); // Warning: AttributeOneWayPassword does not have any sql property so code = sql !
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
$aColumns[''] = $sPrefix.'_hash';
$aColumns['_salt'] = $sPrefix.'_salt';
return $aColumns;
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
if (!array_key_exists($sPrefix, $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
}
$hashed = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : '';
if (!array_key_exists($sPrefix.'_salt', $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}");
}
$sSalt = isset($aCols[$sPrefix.'_salt']) ? $aCols[$sPrefix.'_salt'] : '';
$value = new ormPassword($hashed, $sSalt);
return $value;
}
public function GetSQLValues($value)
{
// #@# Optimization: do not load blobs anytime
// As per mySQL doc, selecting blob columns will prevent mySQL from
// using memory in case a temporary table has to be created
// (temporary tables created on disk)
// We will have to remove the blobs from the list of attributes when doing the select
// then the use of Get() should finalize the load
if ($value instanceof ormPassword) {
$aValues = array();
$aValues[$this->GetCode().'_hash'] = $value->GetHash();
$aValues[$this->GetCode().'_salt'] = $value->GetSalt();
} else {
$aValues = array();
$aValues[$this->GetCode().'_hash'] = '';
$aValues[$this->GetCode().'_salt'] = '';
}
return $aValues;
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->GetCode().'_hash'] = 'TINYBLOB';
$aColumns[$this->GetCode().'_salt'] = 'TINYBLOB';
return $aColumns;
}
public function GetImportColumns()
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'TINYTEXT'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}
public function FromImportToValue($aCols, $sPrefix = '')
{
if (!isset($aCols[$sPrefix])) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
}
$sClearPwd = $aCols[$sPrefix];
$oPassword = new ormPassword('', '');
$oPassword->SetPassword($sClearPwd);
return $oPassword;
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return 'true';
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if (is_object($value)) {
return $value->GetAsHTML();
}
return '';
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
return ''; // Not exportable in CSV
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
return ''; // Not exportable in XML
}
public function GetValueLabel($sValue, $oHostObj = null)
{
// Don't display anything in "group by" reports
return '*****';
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
// Protection against wrong value type
if (false === ($proposedValue instanceof ormPassword)) {
// On object creation, the attribute value is "" instead of an ormPassword...
if (is_string($proposedValue)) {
return utils::IsNotNullOrEmptyString($proposedValue);
}
return parent::HasAValue($proposedValue);
}
return $proposedValue->IsEmpty() === false;
}
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
{
if (is_null($original)) {
$original = '';
}
$oMyChangeOp->Set("prev_pwd", $original);
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeOneWayPassword::class;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use Exception;
use utils;
/**
* Map a varchar column (size < ?) to an attribute that must never be shown to the user
*
* @package iTopORM
*/
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "Password";
}
protected function GetSQLCol($bFullSpec = false)
{
return "VARCHAR(64)"
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetMaxSize()
{
return 64;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (utils::IsNullOrEmptyString($sValue)) {
return '';
} else {
return '******';
}
}
public function IsPartOfFingerprint()
{
return false;
} // Cannot reliably compare two encrypted passwords since the same password will be encrypted in diffferent manners depending on the random 'salt'
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
/**
* Display an integer between 0 and 100 as a percentage / horizontal bar graph
*
* @package iTopORM
*/
class AttributePercentage extends AttributeInteger
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
$iWidth = 5; // Total width of the percentage bar graph, in em...
$iValue = (int)$sValue;
if ($iValue > 100) {
$iValue = 100;
} else {
if ($iValue < 0) {
$iValue = 0;
}
}
if ($iValue > 90) {
$sColor = "#cc3300";
} else {
if ($iValue > 50) {
$sColor = "#cccc00";
} else {
$sColor = "#33cc00";
}
}
$iPercentWidth = ($iWidth * $iValue) / 100;
return "<div style=\"width:{$iWidth}em;-moz-border-radius: 3px;-webkit-border-radius: 3px;border-radius: 3px;display:inline-block;border: 1px #ccc solid;\"><div style=\"width:{$iPercentWidth}em; display:inline-block;background-color:$sColor;\">&nbsp;</div></div>&nbsp;$sValue %";
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
use utils;
/**
* Specialization of a string: phone number
*
* @package iTopORM
*/
class AttributePhoneNumber extends AttributeString
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetValidationPattern()
{
return $this->GetOptional('validation_pattern',
'^'.utils::GetConfig()->Get('phone_number_validation_pattern').'$');
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\PhoneField';
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (empty($sValue)) {
return '';
}
$sUrlDecorationClass = utils::GetConfig()->Get('phone_number_decoration_class');
$sUrlPattern = utils::GetConfig()->Get('phone_number_url_pattern');
$sUrl = sprintf($sUrlPattern, $sValue);
return '<a class="tel" href="'.$sUrl.'"><span class="text_decoration '.$sUrlDecorationClass.'"></span>'.parent::GetAsHTML($sValue).'</a>';
}
}

View File

@@ -0,0 +1,118 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CoreException;
use Exception;
use Str;
class AttributePropertySet extends AttributeTable
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetEditClass()
{
return "PropertySet";
}
// Facilitate things: allow the user to Set the value from a string
public function MakeRealValue($proposedValue, $oHostObj)
{
if (!is_array($proposedValue)) {
return array('?' => (string)$proposedValue);
}
return $proposedValue;
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if (!is_array($value)) {
throw new CoreException('Expecting an array', array('found' => get_class($value)));
}
if (count($value) == 0) {
return "";
}
$sRes = "<TABLE class=\"listResults\">";
$sRes .= "<TBODY>";
foreach ($value as $sProperty => $sValue) {
if ($sProperty == 'auth_pwd') {
$sValue = '*****';
}
$sRes .= "<TR>";
$sCell = str_replace("\n", "<br>\n", Str::pure2html(@(string)$sValue));
$sRes .= "<TD class=\"label\">$sProperty</TD><TD>$sCell</TD>";
$sRes .= "</TR>";
}
$sRes .= "</TBODY>";
$sRes .= "</TABLE>";
return $sRes;
}
public function GetAsCSV(
$value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
if (!is_array($value) || count($value) == 0) {
return "";
}
$aRes = array();
foreach ($value as $sProperty => $sValue) {
if ($sProperty == 'auth_pwd') {
$sValue = '*****';
}
$sFrom = array(',', '=');
$sTo = array('\,', '\=');
$aRes[] = $sProperty.'='.str_replace($sFrom, $sTo, (string)$sValue);
}
$sRaw = implode(',', $aRes);
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, $sRaw);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
if (!is_array($value) || count($value) == 0) {
return "";
}
$sRes = "";
foreach ($value as $sProperty => $sValue) {
if ($sProperty == 'auth_pwd') {
$sValue = '*****';
}
$sRes .= "<property id=\"$sProperty\">";
$sRes .= Str::pure2xml((string)$sValue);
$sRes .= "</property>";
}
return $sRes;
}
}

View File

@@ -0,0 +1,191 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use CoreException;
use CoreUnexpectedValue;
use DBSearch;
use Exception;
use IssueLog;
use MetaModel;
use OQLException;
use ormSet;
use utils;
class AttributeQueryAttCodeSet extends AttributeSet
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public function __construct($sCode, array $aParams)
{
parent::__construct($sCode, $aParams);
$this->aCSSClasses[] = 'attribute-query-attcode-set';
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('query_field'));
}
protected function GetSQLCol($bFullSpec = false)
{
return "TEXT".CMDBSource::GetSqlStringColumnDefinition();
}
public function GetMaxSize()
{
return 65535;
}
/**
* Get a class array indexed by alias
*
* @param $oHostObj
*
* @return array
*/
private function GetClassList($oHostObj)
{
try {
$sQueryField = $this->Get('query_field');
$sQuery = $oHostObj->Get($sQueryField);
if (empty($sQuery)) {
return array();
}
$oFilter = DBSearch::FromOQL($sQuery);
return $oFilter->GetSelectedClasses();
}
catch (OQLException $e) {
IssueLog::Warning($e->getMessage());
}
return array();
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
if (isset($aArgs['this'])) {
$oHostObj = $aArgs['this'];
$aClasses = $this->GetClassList($oHostObj);
$aAllowedAttributes = array();
$aAllAttributes = array();
if ((count($aClasses) == 1) && (array_keys($aClasses)[0] == array_values($aClasses)[0])) {
$sClass = reset($aClasses);
$aAttributes = MetaModel::GetAttributesList($sClass);
foreach ($aAttributes as $sAttCode) {
$aAllowedAttributes[$sAttCode] = "$sAttCode (".MetaModel::GetLabel($sClass, $sAttCode).')';
}
} else {
if (!empty($aClasses)) {
ksort($aClasses);
foreach ($aClasses as $sAlias => $sClass) {
$aAttributes = MetaModel::GetAttributesList($sClass);
foreach ($aAttributes as $sAttCode) {
$aAllAttributes[] = array('alias' => $sAlias, 'class' => $sClass, 'att_code' => $sAttCode);
}
}
}
foreach ($aAllAttributes as $aFullAttCode) {
$sAttCode = $aFullAttCode['alias'].'.'.$aFullAttCode['att_code'];
$sClass = $aFullAttCode['class'];
$sLabel = "$sAttCode (".MetaModel::GetLabel($sClass, $aFullAttCode['att_code']).')';
$aAllowedAttributes[$sAttCode] = $sLabel;
}
}
return $aAllowedAttributes;
}
return null;
}
/**
* force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
*
* @param $proposedValue
* @param DBObject $oHostObj
*
* @param bool $bIgnoreErrors
*
* @return mixed
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws OQLException
* @throws Exception
*/
public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false)
{
$oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
$aArgs = array();
if (!empty($oHostObj)) {
$aArgs['this'] = $oHostObj;
}
$aAllowedAttributes = $this->GetAllowedValues($aArgs);
$aInvalidAttCodes = array();
if (is_string($proposedValue) && !empty($proposedValue)) {
$proposedValue = trim($proposedValue);
$aProposedValues = $this->FromStringToArray($proposedValue);
$aValues = array();
foreach ($aProposedValues as $sValue) {
$sAttCode = trim($sValue);
if (empty($aAllowedAttributes) || isset($aAllowedAttributes[$sAttCode])) {
$aValues[$sAttCode] = $sAttCode;
} else {
$aInvalidAttCodes[] = $sAttCode;
}
}
$oSet->SetValues($aValues);
} elseif ($proposedValue instanceof ormSet) {
$oSet = $proposedValue;
}
if (!empty($aInvalidAttCodes) && !$bIgnoreErrors) {
throw new CoreUnexpectedValue("The attribute(s) ".implode(', ', $aInvalidAttCodes)." are invalid");
}
return $oSet;
}
/**
* @param $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string|null
*
* @throws Exception
*/
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if ($value instanceof ormSet) {
$value = $value->GetValues();
}
if (is_array($value)) {
if (!empty($oHostObject) && $bLocalize) {
$aArgs['this'] = $oHostObject;
$aAllowedAttributes = $this->GetAllowedValues($aArgs);
$aLocalizedValues = array();
foreach ($value as $sAttCode) {
if (isset($aAllowedAttributes[$sAttCode])) {
$sLabelForHtmlAttribute = utils::HtmlEntities($aAllowedAttributes[$sAttCode]);
$aLocalizedValues[] = '<span class="attribute-set-item" data-code="'.$sAttCode.'" data-label="'.$sLabelForHtmlAttribute.'" data-description="" data-tooltip-content="'.$sLabelForHtmlAttribute.'">'.$sAttCode.'</span>';
}
}
$value = $aLocalizedValues;
}
$value = implode('', $value);
}
return '<span class="'.implode(' ', $this->aCSSClasses).'">'.$value.'</span>';
}
}

View File

@@ -0,0 +1,460 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use Combodo\iTop\Application\WebPage\WebPage;
use CoreException;
use DBObject;
use DictExceptionMissingString;
use Exception;
use MetaModel;
use utils;
/**
* Holds the setting for the redundancy on a specific relation
* Its value is a string, containing either:
* - 'disabled'
* - 'n', where n is a positive integer value giving the minimum count of items upstream
* - 'n%', where n is a positive integer value, giving the minimum as a percentage of the total count of items upstream
*
* @package iTopORM
*/
class AttributeRedundancySettings extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return array(
'sql',
'relation_code',
'from_class',
'neighbour_id',
'enabled',
'enabled_mode',
'min_up',
'min_up_type',
'min_up_mode',
);
}
public function GetValuesDef()
{
return null;
}
public function GetPrerequisiteAttributes($sClass = null)
{
return array();
}
public function GetEditClass()
{
return "RedundancySetting";
}
protected function GetSQLCol($bFullSpec = false)
{
return "VARCHAR(20)"
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetValidationPattern()
{
return "^[0-9]{1,3}|[0-9]{1,2}%|disabled$";
}
public function GetMaxSize()
{
return 20;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
$sRet = 'disabled';
if ($this->Get('enabled')) {
if ($this->Get('min_up_type') == 'count') {
$sRet = (string)$this->Get('min_up');
} else // percent
{
$sRet = $this->Get('min_up').'%';
}
}
return $sRet;
}
public function IsNullAllowed()
{
return false;
}
public function GetNullValue()
{
return '';
}
public function IsNull($proposedValue)
{
return ($proposedValue == '');
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return '';
}
return (string)$proposedValue;
}
public function ScalarToSQL($value)
{
if (!is_string($value)) {
throw new CoreException('Expected the attribute value to be a string', array(
'found_type' => gettype($value),
'value' => $value,
'class' => $this->GetHostClass(),
'attribute' => $this->GetCode(),
));
}
return $value;
}
public function GetRelationQueryData()
{
foreach (MetaModel::EnumRelationQueries($this->GetHostClass(), $this->Get('relation_code'),
false) as $sDummy => $aQueryInfo) {
if ($aQueryInfo['sFromClass'] == $this->Get('from_class')) {
if ($aQueryInfo['sNeighbour'] == $this->Get('neighbour_id')) {
return $aQueryInfo;
}
}
}
return array();
}
/**
* Find the user option label
*
* @param string $sUserOption possible values : disabled|cout|percent
* @param string $sDefault
*
* @return string
* @throws Exception
*/
public function GetUserOptionFormat($sUserOption, $sDefault = null)
{
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, null, true /*user lang*/);
if (is_null($sLabel)) {
// If no default value is specified, let's define the most relevant one for developping purposes
if (is_null($sDefault)) {
$sDefault = str_replace('_', ' ', $this->m_sCode.':'.$sUserOption.'(%1$s)');
}
// Browse the hierarchy again, accepting default (english) translations
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, $sDefault, false);
}
return $sLabel;
}
/**
* Override to display the value in the GUI
*
* @param string $sValue
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string
* @throws CoreException
* @throws DictExceptionMissingString
*/
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
$sCurrentOption = $this->GetCurrentOption($sValue);
$sClass = $oHostObject ? get_class($oHostObject) : $this->m_sHostClass;
return sprintf($this->GetUserOptionFormat($sCurrentOption), $this->GetMinUpValue($sValue),
MetaModel::GetName($sClass));
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
/**
* Helper to interpret the value, given the current settings and string representation of the attribute
*/
public function IsEnabled($sValue)
{
if ($this->get('enabled_mode') == 'fixed') {
$bRet = $this->get('enabled');
} else {
$bRet = ($sValue != 'disabled');
}
return $bRet;
}
/**
* Helper to interpret the value, given the current settings and string representation of the attribute
*/
public function GetMinUpType($sValue)
{
if ($this->get('min_up_mode') == 'fixed') {
$sRet = $this->get('min_up_type');
} else {
$sRet = 'count';
if (substr(trim($sValue), -1, 1) == '%') {
$sRet = 'percent';
}
}
return $sRet;
}
/**
* Helper to interpret the value, given the current settings and string representation of the attribute
*/
public function GetMinUpValue($sValue)
{
if ($this->get('min_up_mode') == 'fixed') {
$iRet = (int)$this->Get('min_up');
} else {
$sRefValue = $sValue;
if (substr(trim($sValue), -1, 1) == '%') {
$sRefValue = substr(trim($sValue), 0, -1);
}
$iRet = (int)trim($sRefValue);
}
return $iRet;
}
/**
* Helper to determine if the redundancy can be viewed/edited by the end-user
*/
public function IsVisible()
{
$bRet = false;
if ($this->Get('enabled_mode') == 'fixed') {
$bRet = $this->Get('enabled');
} elseif ($this->Get('enabled_mode') == 'user') {
$bRet = true;
}
return $bRet;
}
public function IsWritable()
{
if (($this->Get('enabled_mode') == 'fixed') && ($this->Get('min_up_mode') == 'fixed')) {
return false;
}
return true;
}
/**
* Returns an HTML form that can be read by ReadValueFromPostedForm
*/
public function GetDisplayForm($sCurrentValue, $oPage, $bEditMode = false, $sFormPrefix = '')
{
$sRet = '';
$aUserOptions = $this->GetUserOptions($sCurrentValue);
if (count($aUserOptions) < 2) {
$bEditOption = false;
} else {
$bEditOption = $bEditMode;
}
$sCurrentOption = $this->GetCurrentOption($sCurrentValue);
foreach ($aUserOptions as $sUserOption) {
$bSelected = ($sUserOption == $sCurrentOption);
$sRet .= '<div>';
$sRet .= $this->GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditOption, $sUserOption,
$bSelected);
$sRet .= '</div>';
}
return $sRet;
}
const USER_OPTION_DISABLED = 'disabled';
const USER_OPTION_ENABLED_COUNT = 'count';
const USER_OPTION_ENABLED_PERCENT = 'percent';
/**
* Depending on the xxx_mode parameters, build the list of options that are allowed to the end-user
*/
protected function GetUserOptions($sValue)
{
$aRet = array();
if ($this->Get('enabled_mode') == 'user') {
$aRet[] = self::USER_OPTION_DISABLED;
}
if ($this->Get('min_up_mode') == 'user') {
$aRet[] = self::USER_OPTION_ENABLED_COUNT;
$aRet[] = self::USER_OPTION_ENABLED_PERCENT;
} else {
if ($this->GetMinUpType($sValue) == 'count') {
$aRet[] = self::USER_OPTION_ENABLED_COUNT;
} else {
$aRet[] = self::USER_OPTION_ENABLED_PERCENT;
}
}
return $aRet;
}
/**
* Convert the string representation into one of the existing options
*/
protected function GetCurrentOption($sValue)
{
$sRet = self::USER_OPTION_DISABLED;
if ($this->IsEnabled($sValue)) {
if ($this->GetMinUpType($sValue) == 'count') {
$sRet = self::USER_OPTION_ENABLED_COUNT;
} else {
$sRet = self::USER_OPTION_ENABLED_PERCENT;
}
}
return $sRet;
}
/**
* Display an option (form, or current value)
*
* @param string $sCurrentValue
* @param WebPage $oPage
* @param string $sFormPrefix
* @param bool $bEditMode
* @param string $sUserOption
* @param bool $bSelected
*
* @return string
* @throws CoreException
* @throws DictExceptionMissingString
* @throws Exception
*/
protected function GetDisplayOption(
$sCurrentValue, $oPage, $sFormPrefix, $bEditMode, $sUserOption, $bSelected = true
)
{
$sRet = '';
$iCurrentValue = $this->GetMinUpValue($sCurrentValue);
if ($bEditMode) {
$sValue = null;
$sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id');
switch ($sUserOption) {
case self::USER_OPTION_DISABLED:
$sValue = ''; // Empty placeholder
break;
case self::USER_OPTION_ENABLED_COUNT:
if ($bEditMode) {
$sName = $sHtmlNamesPrefix.'_min_up_count';
$sEditValue = $bSelected ? $iCurrentValue : '';
$sValue = '<input class="redundancy-min-up-count" type="string" size="3" name="'.$sName.'" value="'.$sEditValue.'">';
// To fix an issue on Firefox: focus set to the option (because the input is within the label for the option)
$oPage->add_ready_script("\$('[name=\"$sName\"]').on('click', function(){var me=this; setTimeout(function(){\$(me).trigger('focus');}, 100);});");
} else {
$sValue = $iCurrentValue;
}
break;
case self::USER_OPTION_ENABLED_PERCENT:
if ($bEditMode) {
$sName = $sHtmlNamesPrefix.'_min_up_percent';
$sEditValue = $bSelected ? $iCurrentValue : '';
$sValue = '<input class="redundancy-min-up-percent" type="string" size="3" name="'.$sName.'" value="'.$sEditValue.'">';
// To fix an issue on Firefox: focus set to the option (because the input is within the label for the option)
$oPage->add_ready_script("\$('[name=\"$sName\"]').on('click', function(){var me=this; setTimeout(function(){\$(me).trigger('focus');}, 100);});");
} else {
$sValue = $iCurrentValue;
}
break;
}
$sLabel = sprintf($this->GetUserOptionFormat($sUserOption), $sValue,
MetaModel::GetName($this->GetHostClass()));
$sOptionName = $sHtmlNamesPrefix.'_user_option';
$sOptionId = $sOptionName.'_'.$sUserOption;
$sChecked = $bSelected ? 'checked' : '';
$sRet = '<input type="radio" name="'.$sOptionName.'" id="'.$sOptionId.'" value="'.$sUserOption.'" '.$sChecked.'> <label for="'.$sOptionId.'">'.$sLabel.'</label>';
} else {
// Read-only: display only the currently selected option
if ($bSelected) {
$sRet = sprintf($this->GetUserOptionFormat($sUserOption), $iCurrentValue,
MetaModel::GetName($this->GetHostClass()));
}
}
return $sRet;
}
/**
* Makes the string representation out of the values given by the form defined in GetDisplayForm
*/
public function ReadValueFromPostedForm($sFormPrefix)
{
$sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id');
$iMinUpCount = (int)utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_count', null, 'raw_data');
$iMinUpPercent = (int)utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_percent', null, 'raw_data');
$sSelectedOption = utils::ReadPostedParam($sHtmlNamesPrefix.'_user_option', null, 'raw_data');
switch ($sSelectedOption) {
case self::USER_OPTION_ENABLED_COUNT:
$sRet = $iMinUpCount;
break;
case self::USER_OPTION_ENABLED_PERCENT:
$sRet = $iMinUpPercent.'%';
break;
case self::USER_OPTION_DISABLED:
default:
$sRet = 'disabled';
break;
}
return $sRet;
}
}

View File

@@ -0,0 +1,479 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use ApplicationContext;
use cmdbAbstractObject;
use CMDBChangeOpSetAttributeTagSet;
use CMDBSource;
use CoreException;
use CoreUnexpectedValue;
use DBObject;
use DBSearch;
use Exception;
use MetaModel;
use ormSet;
use utils;
/**
* An unordered multi values attribute
* Allowed values are mandatory for this attribute to be modified
*
* Class AttributeSet
*/
abstract class AttributeSet extends AttributeDBFieldVoid
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
const EDITABLE_INPUT_ID_SUFFIX = '-setwidget-values'; // used client side, see js/jquery.itop-set-widget.js
protected $bDisplayLink; // Display search link in readonly mode
public function __construct($sCode, array $aParams)
{
parent::__construct($sCode, $aParams);
$this->aCSSClasses[] = 'attribute-set';
$this->bDisplayLink = true;
}
/**
* @param bool $bDisplayLink
*/
public function setDisplayLink($bDisplayLink)
{
$this->bDisplayLink = $bDisplayLink;
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('is_null_allowed', 'max_items'));
}
/**
* Allowed different values for the set values are mandatory for this attribute to be modified
*
* @param array $aArgs
* @param string $sContains
*
* @return array|null
* @throws \CoreException
* @throws \OQLException
*/
public function GetPossibleValues($aArgs = array(), $sContains = '')
{
return $this->GetAllowedValues($aArgs, $sContains);
}
/**
* @param \ormSet $oValue
*
* @param $aArgs
*
* @return string JSON to be used in the itop.set_widget JQuery widget
* @throws \CoreException
* @throws \OQLException
*/
public function GetJsonForWidget($oValue, $aArgs = array())
{
$aJson = array();
// possible_values
$aAllowedValues = $this->GetPossibleValues($aArgs);
$aSetKeyValData = array();
foreach ($aAllowedValues as $sCode => $sLabel) {
$aSetKeyValData[] = [
'code' => $sCode,
'label' => $sLabel,
];
}
$aJson['possible_values'] = $aSetKeyValData;
$aRemoved = array();
if (is_null($oValue)) {
$aJson['partial_values'] = array();
$aJson['orig_value'] = array();
} else {
$aPartialValues = $oValue->GetModified();
foreach ($aPartialValues as $key => $value) {
if (!isset($aAllowedValues[$value])) {
unset($aPartialValues[$key]);
}
}
$aJson['partial_values'] = array_values($aPartialValues);
$aOrigValues = array_merge($oValue->GetValues(), $oValue->GetModified());
foreach ($aOrigValues as $key => $value) {
if (!isset($aAllowedValues[$value])) {
// Remove unwanted values
$aRemoved[] = $value;
unset($aOrigValues[$key]);
}
}
$aJson['orig_value'] = array_values($aOrigValues);
}
$aJson['added'] = array();
$aJson['removed'] = $aRemoved;
$iMaxTags = $this->GetMaxItems();
$aJson['max_items_allowed'] = $iMaxTags;
return json_encode($aJson);
}
public function RequiresIndex()
{
return true;
}
public function RequiresFullTextIndex()
{
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return null;
}
public function IsNullAllowed()
{
return $this->Get("is_null_allowed");
}
public function GetEditClass()
{
return "Set";
}
public function GetEditValue($value, $oHostObj = null)
{
if (is_string($value)) {
return $value;
}
if ($value instanceof ormSet) {
$value = $value->GetValues();
}
if (is_array($value)) {
return implode(', ', $value);
}
return '';
}
protected function GetSQLCol($bFullSpec = false)
{
$iLen = $this->GetMaxSize();
return "VARCHAR($iLen)"
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetMaxSize()
{
return 255;
}
public function FromStringToArray($proposedValue, $sDefaultSepItem = ',')
{
$aValues = array();
if (!empty($proposedValue)) {
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
// convert also , separated strings
if ($sSepItem !== $sDefaultSepItem) {
$proposedValue = str_replace($sDefaultSepItem, $sSepItem, $proposedValue);
}
foreach (explode($sSepItem, $proposedValue) as $sCode) {
$sValue = trim($sCode);
if ($sValue !== '') {
$aValues[] = $sValue;
}
}
}
return $aValues;
}
/**
* @param array $aCols
* @param string $sPrefix
*
* @return mixed
* @throws Exception
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
$sValue = $aCols["$sPrefix"];
return $this->MakeRealValue($sValue, null, true);
}
/**
* force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
*
* @param $proposedValue
* @param DBObject $oHostObj
*
* @param bool $bIgnoreErrors
*
* @return mixed
* @throws CoreException
* @throws CoreUnexpectedValue
*/
public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false)
{
$oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
$aAllowedValues = $this->GetPossibleValues();
if (is_string($proposedValue) && !empty($proposedValue)) {
$proposedValue = trim("$proposedValue");
$aValues = $this->FromStringToArray($proposedValue);
foreach ($aValues as $i => $sValue) {
if (!isset($aAllowedValues[$sValue])) {
unset($aValues[$i]);
}
}
$oSet->SetValues($aValues);
} elseif ($proposedValue instanceof ormSet) {
$oSet = $proposedValue;
}
return $oSet;
}
/**
* Get the value from a given string (plain text, CSV import)
*
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return mixed null if no match could be found
* @throws Exception
*/
public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
{
return $this->MakeRealValue($sProposedValue, null);
}
/**
* @return null|ormSet
* @throws CoreException
* @throws Exception
*/
public function GetNullValue()
{
return new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
}
public function IsNull($proposedValue)
{
if (empty($proposedValue)) {
return true;
}
/** @var ormSet $proposedValue */
return $proposedValue->Count() == 0;
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
if (false === ($proposedValue instanceof ormSet)) {
return parent::HasAValue($proposedValue);
}
return $proposedValue->Count() > 0;
}
/**
* To be overloaded for localized enums
*
* @param $sValue
*
* @return string label corresponding to the given value (in plain text)
* @throws Exception
*/
public function GetValueLabel($sValue)
{
if ($sValue instanceof ormSet) {
$sValue = $sValue->GetValues();
}
if (is_array($sValue)) {
return implode(', ', $sValue);
}
return $sValue;
}
/**
* @param string $sValue
* @param null $oHostObj
*
* @return string
* @throws Exception
*/
public function GetAsPlainText($sValue, $oHostObj = null)
{
return $this->GetValueLabel($sValue);
}
/**
* @param string $value
*
* @return string
*/
public function ScalarToSQL($value)
{
if (empty($value)) {
return '';
}
if ($value instanceof ormSet) {
$value = $value->GetValues();
}
if (is_array($value)) {
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
$sRes = implode($sSepItem, $value);
if (!empty($sRes)) {
$value = "{$sSepItem}{$sRes}{$sSepItem}";
} else {
$value = '';
}
}
return $value;
}
/**
* @param $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string|null
*
* @throws \Exception
*/
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if ($value instanceof ormSet) {
$aValues = $value->GetValues();
return $this->GenerateViewHtmlForValues($aValues);
}
if (is_array($value)) {
return implode(', ', $value);
}
return $value;
}
/**
* HTML representation of a list of values (read-only)
* accept a list of strings
*
* @param array $aValues
* @param string $sCssClass
* @param bool $bWithLink if true will generate a link, otherwise just a "a" tag without href
*
* @return string
* @throws CoreException
* @throws OQLException
*/
public function GenerateViewHtmlForValues($aValues, $sCssClass = '', $bWithLink = true)
{
if (empty($aValues)) {
return '';
}
$sHtml = '<span class="'.$sCssClass.' '.implode(' ', $this->aCSSClasses).'">';
foreach ($aValues as $sValue) {
$sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode());
$sAttCode = $this->GetCode();
$sLabel = utils::EscapeHtml($this->GetValueLabel($sValue));
$sDescription = utils::EscapeHtml($this->GetValueDescription($sValue));
$oFilter = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sValue'");
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink(true);
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($oFilter->GetClass());
$sFilter = rawurlencode($oFilter->serialize());
$sLink = '';
if ($bWithLink && $this->bDisplayLink) {
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter.$sContext;
$sLink = ' href="'.$sUrl.'"';
}
// Prepare tooltip
if (empty($sDescription)) {
$sTooltipContent = $sLabel;
$sTooltipHtmlEnabled = 'false';
} else {
$sTooltipContent = <<<HTML
<h4>$sLabel</h4>
<div>$sDescription</div>
HTML;
$sTooltipHtmlEnabled = 'true';
}
$sTooltipContent = utils::EscapeHtml($sTooltipContent);
$sHtml .= '<a'.$sLink.' class="attribute-set-item attribute-set-item-'.$sValue.'" data-code="'.$sValue.'" data-label="'.$sLabel.'" data-description="'.$sDescription.'" data-tooltip-content="'.$sTooltipContent.'" data-tooltip-html-enabled="'.$sTooltipHtmlEnabled.'">'.$sLabel.'</a>';
}
$sHtml .= '</span>';
return $sHtml;
}
/**
* @param $value
* @param string $sSeparator
* @param string $sTextQualifier
* @param DBObject $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return mixed|string
*/
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
{
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
if (is_object($value) && ($value instanceof ormSet)) {
if ($bLocalize) {
$aValues = $value->GetLabels();
} else {
$aValues = $value->GetValues();
}
$sRes = implode($sSepItem, $aValues);
} else {
$sRes = '';
}
return "{$sTextQualifier}{$sRes}{$sTextQualifier}";
}
public function GetMaxItems()
{
return $this->Get('max_items');
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\SetField';
}
public function RecordAttChange(DBObject $oObject, $original, $value): void
{
/** @var \ormSet $original */
/** @var \ormSet $value */
parent::RecordAttChange($oObject,
implode(' ', $original->GetValues()),
implode(' ', $value->GetValues())
);
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeTagSet::class;
}
}

View File

@@ -0,0 +1,824 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOpSetAttributeScalar;
use CoreException;
use DateTime;
use DBObject;
use Dict;
use Exception;
use MetaModel;
use ormStopWatch;
use Str;
/**
* A stop watch is an ormStopWatch object, it is stored as several columns in the database
*
* @package iTopORM
*/
class AttributeStopWatch extends AttributeDefinition
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
// The list of thresholds must be an array of iPercent => array of 'option' => value
return array_merge(parent::ListExpectedParams(),
array("states", "goal_computing", "working_time_computing", "thresholds"));
}
public function GetEditClass()
{
return "StopWatch";
}
public static function IsBasedOnDBColumns()
{
return true;
}
public static function IsScalar()
{
return true;
}
public function IsWritable()
{
return true;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->NewStopWatch();
}
/**
* @param ormStopWatch $value
* @param DBObject $oHostObj
*
* @return string
*/
public function GetEditValue($value, $oHostObj = null)
{
return $value->GetTimeSpent();
}
public function GetStates()
{
return $this->Get('states');
}
public function AlwaysLoadInTables()
{
// Each and every stop watch is accessed for computing the highlight code (DBObject::GetHighlightCode())
return true;
}
/**
* Construct a brand new (but configured) stop watch
*/
public function NewStopWatch()
{
$oSW = new ormStopWatch();
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$oSW->DefineThreshold($iThreshold);
}
return $oSW;
}
// Facilitate things: allow the user to Set the value from a string
public function MakeRealValue($proposedValue, $oHostObj)
{
if (!$proposedValue instanceof ormStopWatch) {
return $this->NewStopWatch();
}
return $proposedValue;
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '') {
$sPrefix = $this->GetCode(); // Warning: a stopwatch does not have any 'sql' property, so its SQL column is equal to its attribute code !!
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
$aColumns[''] = $sPrefix.'_timespent';
$aColumns['_started'] = $sPrefix.'_started';
$aColumns['_laststart'] = $sPrefix.'_laststart';
$aColumns['_stopped'] = $sPrefix.'_stopped';
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = '_'.$iThreshold;
$aColumns[$sThPrefix.'_deadline'] = $sPrefix.$sThPrefix.'_deadline';
$aColumns[$sThPrefix.'_passed'] = $sPrefix.$sThPrefix.'_passed';
$aColumns[$sThPrefix.'_triggered'] = $sPrefix.$sThPrefix.'_triggered';
$aColumns[$sThPrefix.'_overrun'] = $sPrefix.$sThPrefix.'_overrun';
}
return $aColumns;
}
public static function DateToSeconds($sDate)
{
if (is_null($sDate)) {
return null;
}
$oDateTime = new DateTime($sDate);
$iSeconds = $oDateTime->format('U');
return $iSeconds;
}
public static function SecondsToDate($iSeconds)
{
if (is_null($iSeconds)) {
return null;
}
return date("Y-m-d H:i:s", $iSeconds);
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
$aExpectedCols = array($sPrefix, $sPrefix.'_started', $sPrefix.'_laststart', $sPrefix.'_stopped');
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = '_'.$iThreshold;
$aExpectedCols[] = $sPrefix.$sThPrefix.'_deadline';
$aExpectedCols[] = $sPrefix.$sThPrefix.'_passed';
$aExpectedCols[] = $sPrefix.$sThPrefix.'_triggered';
$aExpectedCols[] = $sPrefix.$sThPrefix.'_overrun';
}
foreach ($aExpectedCols as $sExpectedCol) {
if (!array_key_exists($sExpectedCol, $aCols)) {
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sExpectedCol' from {$sAvailable}");
}
}
$value = new ormStopWatch(
$aCols[$sPrefix],
self::DateToSeconds($aCols[$sPrefix.'_started']),
self::DateToSeconds($aCols[$sPrefix.'_laststart']),
self::DateToSeconds($aCols[$sPrefix.'_stopped'])
);
foreach ($this->ListThresholds() as $iThreshold => $aDefinition) {
$sThPrefix = '_'.$iThreshold;
$value->DefineThreshold(
$iThreshold,
self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']),
(bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1),
(bool)($aCols[$sPrefix.$sThPrefix.'_triggered'] == 1),
$aCols[$sPrefix.$sThPrefix.'_overrun'],
array_key_exists('highlight', $aDefinition) ? $aDefinition['highlight'] : null
);
}
return $value;
}
public function GetSQLValues($value)
{
if ($value instanceof ormStopWatch) {
$aValues = array();
$aValues[$this->GetCode().'_timespent'] = $value->GetTimeSpent();
$aValues[$this->GetCode().'_started'] = self::SecondsToDate($value->GetStartDate());
$aValues[$this->GetCode().'_laststart'] = self::SecondsToDate($value->GetLastStartDate());
$aValues[$this->GetCode().'_stopped'] = self::SecondsToDate($value->GetStopDate());
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sPrefix = $this->GetCode().'_'.$iThreshold;
$aValues[$sPrefix.'_deadline'] = self::SecondsToDate($value->GetThresholdDate($iThreshold));
$aValues[$sPrefix.'_passed'] = $value->IsThresholdPassed($iThreshold) ? '1' : '0';
$aValues[$sPrefix.'_triggered'] = $value->IsThresholdTriggered($iThreshold) ? '1' : '0';
$aValues[$sPrefix.'_overrun'] = $value->GetOverrun($iThreshold);
}
} else {
$aValues = array();
$aValues[$this->GetCode().'_timespent'] = '';
$aValues[$this->GetCode().'_started'] = '';
$aValues[$this->GetCode().'_laststart'] = '';
$aValues[$this->GetCode().'_stopped'] = '';
}
return $aValues;
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->GetCode().'_timespent'] = 'INT(11) UNSIGNED';
$aColumns[$this->GetCode().'_started'] = 'DATETIME';
$aColumns[$this->GetCode().'_laststart'] = 'DATETIME';
$aColumns[$this->GetCode().'_stopped'] = 'DATETIME';
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sPrefix = $this->GetCode().'_'.$iThreshold;
$aColumns[$sPrefix.'_deadline'] = 'DATETIME';
$aColumns[$sPrefix.'_passed'] = 'TINYINT(1) UNSIGNED';
$aColumns[$sPrefix.'_triggered'] = 'TINYINT(1)';
$aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED';
}
return $aColumns;
}
public function GetMagicFields()
{
$aRes = [
$this->GetCode().'_started',
$this->GetCode().'_laststart',
$this->GetCode().'_stopped',
];
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sPrefix = $this->GetCode().'_'.$iThreshold;
$aRes[] = $sPrefix.'_deadline';
$aRes[] = $sPrefix.'_passed';
$aRes[] = $sPrefix.'_triggered';
$aRes[] = $sPrefix.'_overrun';
}
return $aRes;
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return '=';
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
return 'true';
}
/**
* @param ormStopWatch $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string
*/
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if (is_object($value)) {
return $value->GetAsHTML($this, $oHostObject);
}
return '';
}
/**
* @param ormStopWatch $value
* @param string $sSeparator
* @param string $sTextQualifier
* @param null $oHostObject
* @param bool $bLocalize
* @param bool $bConvertToPlainText
*
* @return string
*/
public function GetAsCSV(
$value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
return $value->GetTimeSpent();
}
/**
* @param ormStopWatch $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return mixed
*/
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
return $value->GetTimeSpent();
}
public function ListThresholds()
{
return $this->Get('thresholds');
}
public function Fingerprint($value)
{
$sFingerprint = '';
if (is_object($value)) {
$sFingerprint = $value->GetAsHTML($this);
}
return $sFingerprint;
}
/**
* To expose internal values: Declare an attribute AttributeSubItem
* and implement the GetSubItemXXXX verbs
*
* @param string $sItemCode
*
* @return array
* @throws CoreException
*/
public function GetSubItemSQLExpression($sItemCode)
{
$sPrefix = $this->GetCode();
switch ($sItemCode) {
case 'timespent':
return array('' => $sPrefix.'_timespent');
case 'started':
return array('' => $sPrefix.'_started');
case 'laststart':
return array('' => $sPrefix.'_laststart');
case 'stopped':
return array('' => $sPrefix.'_stopped');
}
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
return array('' => $sPrefix.'_'.$iThreshold.'_deadline');
case 'passed':
return array('' => $sPrefix.'_'.$iThreshold.'_passed');
case 'triggered':
return array('' => $sPrefix.'_'.$iThreshold.'_triggered');
case 'overrun':
return array('' => $sPrefix.'_'.$iThreshold.'_overrun');
}
}
}
throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode());
}
/**
* @param string $sItemCode
* @param ormStopWatch $value
* @param DBObject $oHostObject
*
* @return mixed
* @throws CoreException
*/
public function GetSubItemValue($sItemCode, $value, $oHostObject = null)
{
$oStopWatch = $value;
switch ($sItemCode) {
case 'timespent':
return $oStopWatch->GetTimeSpent();
case 'started':
return $oStopWatch->GetStartDate();
case 'laststart':
return $oStopWatch->GetLastStartDate();
case 'stopped':
return $oStopWatch->GetStopDate();
}
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
return $oStopWatch->GetThresholdDate($iThreshold);
case 'passed':
return $oStopWatch->IsThresholdPassed($iThreshold);
case 'triggered':
return $oStopWatch->IsThresholdTriggered($iThreshold);
case 'overrun':
return $oStopWatch->GetOverrun($iThreshold);
}
}
}
throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode());
}
public function GetSubItemSearchType($sItemCode)
{
switch ($sItemCode) {
case 'timespent':
return static::SEARCH_WIDGET_TYPE_NUMERIC; //seconds
case 'started':
case 'laststart':
case 'stopped':
return static::SEARCH_WIDGET_TYPE_DATE_TIME; //timestamp
}
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
return static::SEARCH_WIDGET_TYPE_DATE_TIME; //timestamp
case 'passed':
case 'triggered':
return static::SEARCH_WIDGET_TYPE_ENUM; //booleans, used in conjuction with GetSubItemAllowedValues and IsSubItemNullAllowed
case 'overrun':
return static::SEARCH_WIDGET_TYPE_NUMERIC; //seconds
}
}
}
return static::SEARCH_WIDGET_TYPE_RAW;
}
public function GetSubItemAllowedValues($sItemCode, $aArgs = array(), $sContains = '')
{
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'passed':
case 'triggered':
return array(
0 => $this->GetBooleanLabel(0),
1 => $this->GetBooleanLabel(1),
);
}
}
}
return null;
}
public function IsSubItemNullAllowed($sItemCode, $bDefaultValue)
{
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'passed':
case 'triggered':
return false;
}
}
}
return $bDefaultValue;
}
protected function GetBooleanLabel($bValue)
{
$sDictKey = $bValue ? 'yes' : 'no';
return Dict::S('BooleanLabel:'.$sDictKey, 'def:'.$sDictKey);
}
public function GetSubItemAsHTMLForHistory($sItemCode, $sValue)
{
$sHtml = null;
switch ($sItemCode) {
case 'timespent':
$sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null;
break;
case 'started':
case 'laststart':
case 'stopped':
$sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null;
break;
default:
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
$sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(),
(int)$sValue) : null;
break;
case 'passed':
case 'triggered':
$sHtml = $this->GetBooleanLabel((int)$sValue);
break;
case 'overrun':
$sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : '';
}
}
}
}
return $sHtml;
}
public function GetSubItemAsPlainText($sItemCode, $value)
{
$sRet = $value;
switch ($sItemCode) {
case 'timespent':
$sRet = AttributeDuration::FormatDuration($value);
break;
case 'started':
case 'laststart':
case 'stopped':
if (is_null($value)) {
$sRet = ''; // Undefined
} else {
$oDateTime = new DateTime();
$oDateTime->setTimestamp($value);
$oDateTimeFormat = AttributeDateTime::GetFormat();
$sRet = $oDateTimeFormat->Format($oDateTime);
}
break;
default:
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
if ($value) {
if (is_int($value)) {
$sDate = date(AttributeDateTime::GetInternalFormat(), $value);
$sRet = AttributeDeadline::FormatDeadline($sDate);
} else {
$sRet = $value;
}
} else {
$sRet = '';
}
break;
case 'passed':
case 'triggered':
$sRet = $this->GetBooleanLabel($value);
break;
case 'overrun':
$sRet = AttributeDuration::FormatDuration($value);
break;
}
}
}
}
return $sRet;
}
public function GetSubItemAsHTML($sItemCode, $value)
{
$sHtml = $value;
switch ($sItemCode) {
case 'timespent':
$sHtml = Str::pure2html(AttributeDuration::FormatDuration($value));
break;
case 'started':
case 'laststart':
case 'stopped':
if (is_null($value)) {
$sHtml = ''; // Undefined
} else {
$oDateTime = new DateTime();
$oDateTime->setTimestamp($value);
$oDateTimeFormat = AttributeDateTime::GetFormat();
$sHtml = Str::pure2html($oDateTimeFormat->Format($oDateTime));
}
break;
default:
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
if ($value) {
$sDate = date(AttributeDateTime::GetInternalFormat(), $value);
$sHtml = Str::pure2html(AttributeDeadline::FormatDeadline($sDate));
} else {
$sHtml = '';
}
break;
case 'passed':
case 'triggered':
$sHtml = $this->GetBooleanLabel($value);
break;
case 'overrun':
$sHtml = Str::pure2html(AttributeDuration::FormatDuration($value));
break;
}
}
}
}
return $sHtml;
}
public function GetSubItemAsCSV(
$sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false
)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$value);
$sRet = $sTextQualifier.$sEscaped.$sTextQualifier;
switch ($sItemCode) {
case 'timespent':
$sRet = $sTextQualifier.AttributeDuration::FormatDuration($value).$sTextQualifier;
break;
case 'started':
case 'laststart':
case 'stopped':
if ($value !== null) {
$oDateTime = new DateTime();
$oDateTime->setTimestamp($value);
$oDateTimeFormat = AttributeDateTime::GetFormat();
$sRet = $sTextQualifier.$oDateTimeFormat->Format($oDateTime).$sTextQualifier;
}
break;
default:
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
if ($value != '') {
$oDateTime = new DateTime();
$oDateTime->setTimestamp($value);
$oDateTimeFormat = AttributeDateTime::GetFormat();
$sRet = $sTextQualifier.$oDateTimeFormat->Format($oDateTime).$sTextQualifier;
}
break;
case 'passed':
case 'triggered':
$sRet = $sTextQualifier.$this->GetBooleanLabel($value).$sTextQualifier;
break;
case 'overrun':
$sRet = $sTextQualifier.AttributeDuration::FormatDuration($value).$sTextQualifier;
break;
}
}
}
}
return $sRet;
}
public function GetSubItemAsXML($sItemCode, $value)
{
$sRet = Str::pure2xml((string)$value);
switch ($sItemCode) {
case 'timespent':
case 'started':
case 'laststart':
case 'stopped':
break;
default:
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
case 'overrun':
break;
case 'triggered':
case 'passed':
$sRet = $this->GetBooleanLabel($value);
break;
}
}
}
}
return $sRet;
}
/**
* Implemented for the HTML spreadsheet format!
*
* @param string $sItemCode
* @param ormStopWatch $value
*
* @return false|string
*/
public function GetSubItemAsEditValue($sItemCode, $value)
{
$sRet = $value;
switch ($sItemCode) {
case 'timespent':
break;
case 'started':
case 'laststart':
case 'stopped':
if (is_null($value)) {
$sRet = ''; // Undefined
} else {
$sRet = date((string)AttributeDateTime::GetFormat(), $value);
}
break;
default:
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
$sThPrefix = $iThreshold.'_';
if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) {
// The current threshold is concerned
$sThresholdCode = substr($sItemCode, strlen($sThPrefix));
switch ($sThresholdCode) {
case 'deadline':
if ($value) {
$sRet = date((string)AttributeDateTime::GetFormat(), $value);
} else {
$sRet = '';
}
break;
case 'passed':
case 'triggered':
$sRet = $this->GetBooleanLabel($value);
break;
case 'overrun':
break;
}
}
}
}
return $sRet;
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
// A stopwatch always has a value
return true;
}
public function RecordAttChange(DBObject $oObject, $original, $value): void
{
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
//
foreach ($this->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef) {
$item_value = $this->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $oObject);
$item_original = $this->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $oObject);
if ($item_value != $item_original) {
$oMyChangeOp = MetaModel::NewObject(CMDBChangeOpSetAttributeScalar::class);
$oMyChangeOp->Set("objclass", get_class($oObject));
$oMyChangeOp->Set("objkey", $oObject->GetKey());
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
$oMyChangeOp->Set("oldvalue", $item_original);
$oMyChangeOp->Set("newvalue", $item_value);
$oMyChangeOp->DBInsertNoReload();
}
}
}
}

View File

@@ -0,0 +1,199 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use CoreWarning;
use DBObject;
use Exception;
use utils;
/**
* Map a varchar column (size < ?) to an attribute
*
* @package iTopORM
*/
class AttributeString extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
return parent::ListExpectedParams();
//return array_merge(parent::ListExpectedParams(), array());
}
public function GetEditClass()
{
return "String";
}
protected function GetSQLCol($bFullSpec = false)
{
return 'VARCHAR(255)'
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetValidationPattern()
{
$sPattern = $this->GetOptional('validation_pattern', '');
if (empty($sPattern)) {
return parent::GetValidationPattern();
} else {
return $sPattern;
}
}
public function CheckFormat($value)
{
$sRegExp = $this->GetValidationPattern();
if (empty($sRegExp)) {
return true;
} else {
$sRegExp = str_replace('/', '\\/', $sRegExp);
return preg_match("/$sRegExp/", $value);
}
}
public function GetMaxSize()
{
return 255;
}
public function GetBasicFilterOperators()
{
return array(
"=" => "equals",
"!=" => "differs from",
"Like" => "equals (no case)",
"NotLike" => "differs from (no case)",
"Contains" => "contains",
"Begins with" => "begins with",
"Finishes with" => "finishes with",
);
}
public function GetBasicFilterLooseOperator()
{
return "Contains";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode) {
case '=':
case '!=':
return $this->GetSQLExpr()." $sOpCode $sQValue";
case 'Begins with':
return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%");
case 'Finishes with':
return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value");
case 'Contains':
return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%");
case 'NotLike':
return $this->GetSQLExpr()." NOT LIKE $sQValue";
case 'Like':
default:
return $this->GetSQLExpr()." LIKE $sQValue";
}
}
public function GetNullValue()
{
return '';
}
public function IsNull($proposedValue)
{
return ($proposedValue == '');
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
return utils::IsNotNullOrEmptyString($proposedValue);
}
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return '';
}
return (string)$proposedValue;
}
public function ScalarToSQL($value)
{
if (!is_string($value) && !is_null($value)) {
throw new CoreWarning('Expected the attribute value to be a string', array(
'found_type' => gettype($value),
'value' => $value,
'class' => $this->GetHostClass(),
'attribute' => $this->GetCode(),
));
}
return $value;
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
}
public function GetDisplayStyle()
{
return $this->GetOptional('display_style', 'select');
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\StringField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
}

View File

@@ -0,0 +1,273 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use DBObject;
use Exception;
use MetaModel;
/**
* View of a subvalue of another attribute
* If an attribute implements the verbs GetSubItem.... then it can expose
* internal values, each of them being an attribute and therefore they
* can be displayed at different times in the object lifecycle, and used for
* reporting (as a condition in OQL, or as an additional column in an export)
* Known usages: Stop Watches can expose threshold statuses
*/
class AttributeSubItem extends AttributeDefinition
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
/**
* Return the search widget type corresponding to this attribute
* the computation is made by AttributeStopWatch::GetSubItemSearchType
*
* @return string
*/
public function GetSearchType()
{
/** @var AttributeStopWatch $oParent */
$oParent = $this->GetTargetAttDef();
return $oParent->GetSubItemSearchType($this->Get('item_code'));
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
/** @var AttributeStopWatch $oParent */
$oParent = $this->GetTargetAttDef();
return $oParent->GetSubItemAllowedValues($this->Get('item_code'), $aArgs, $sContains);
}
public function IsNullAllowed()
{
/** @var AttributeStopWatch $oParent */
$oParent = $this->GetTargetAttDef();
$bDefaultValue = parent::IsNullAllowed();
return $oParent->IsSubItemNullAllowed($this->Get('item_code'), $bDefaultValue);
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('target_attcode', 'item_code'));
}
public function GetParentAttCode()
{
return $this->Get("target_attcode");
}
/**
* Helper : get the attribute definition to which the execution will be forwarded
*/
public function GetTargetAttDef()
{
$sClass = $this->GetHostClass();
$oParentAttDef = MetaModel::GetAttributeDef($sClass, $this->Get('target_attcode'));
return $oParentAttDef;
}
public function GetEditClass()
{
return "";
}
public function GetValuesDef()
{
return null;
}
public static function IsBasedOnDBColumns()
{
return true;
}
public static function IsScalar()
{
return true;
}
public function IsWritable()
{
return false;
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
return null;
}
// public function IsNullAllowed() {return false;}
public static function LoadInObject()
{
return false;
} // if this verb returns false, then GetValues must be implemented
/**
* Used by DBOBject::Get()
*
* @param DBObject $oHostObject
*
* @return AttributeSubItem
* @throws CoreException
*/
public function GetValue($oHostObject)
{
/** @var AttributeStopWatch $oParent */
$oParent = $this->GetTargetAttDef();
$parentValue = $oHostObject->GetStrict($oParent->GetCode());
$res = $oParent->GetSubItemValue($this->Get('item_code'), $parentValue, $oHostObject);
return $res;
}
//
// protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside)
public function FromSQLToValue($aCols, $sPrefix = '')
{
}
public function GetSQLColumns($bFullSpec = false)
{
return array();
}
public function GetBasicFilterOperators()
{
return array();
}
public function GetBasicFilterLooseOperator()
{
return "=";
}
public function GetBasicFilterSQLExpr($sOpCode, $value)
{
$sQValue = CMDBSource::Quote($value);
switch ($sOpCode) {
case '!=':
return $this->GetSQLExpr()." != $sQValue";
break;
case '=':
default:
return $this->GetSQLExpr()." = $sQValue";
}
}
public function GetSQLExpressions($sPrefix = '')
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemSQLExpression($this->Get('item_code'));
return $res;
}
public function GetAsPlainText($value, $oHostObject = null, $bLocalize = true)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsPlainText($this->Get('item_code'), $value);
return $res;
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsHTML($this->Get('item_code'), $value);
return $res;
}
public function GetAsHTMLForHistory($value, $oHostObject = null, $bLocalize = true)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsHTMLForHistory($this->Get('item_code'), $value);
return $res;
}
public function GetAsCSV(
$value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier,
$bConvertToPlainText);
return $res;
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsXML($this->Get('item_code'), $value);
return $res;
}
/**
* As of now, this function must be implemented to have the value in spreadsheet format
*/
public function GetEditValue($value, $oHostObj = null)
{
$oParent = $this->GetTargetAttDef();
$res = $oParent->GetSubItemAsEditValue($this->Get('item_code'), $value);
return $res;
}
public function IsPartOfFingerprint()
{
return false;
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\LabelField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
parent::MakeFormField($oObject, $oFormField);
// Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition
$sAttCode = $this->GetCode();
$oFormField->SetCurrentValue(html_entity_decode($oObject->GetAsHTML($sAttCode), ENT_QUOTES, 'UTF-8'));
$oFormField->SetReadOnly(true);
return $oFormField;
}
}

View File

@@ -0,0 +1,176 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBSource;
use CoreException;
use Exception;
class AttributeTable extends AttributeDBField
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetEditClass()
{
return "Table";
}
protected function GetSQLCol($bFullSpec = false)
{
return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition();
}
public function GetMaxSize()
{
return null;
}
public function GetNullValue()
{
return array();
}
public function IsNull($proposedValue)
{
return (count($proposedValue) == 0);
}
/**
* @inheritDoc
*/
public function HasAValue($proposedValue): bool
{
return count($proposedValue) > 0;
}
public function GetEditValue($sValue, $oHostObj = null)
{
return '';
}
// Facilitate things: allow the user to Set the value from a string
public function MakeRealValue($proposedValue, $oHostObj)
{
if (is_null($proposedValue)) {
return array();
} else {
if (!is_array($proposedValue)) {
return array(0 => array(0 => $proposedValue));
}
}
return $proposedValue;
}
public function FromSQLToValue($aCols, $sPrefix = '')
{
try {
$value = @unserialize($aCols[$sPrefix.'']);
if ($value === false) {
$value = @json_decode($aCols[$sPrefix.''], true);
if (is_null($value)) {
$value = false;
}
}
if ($value === false) {
$value = $this->MakeRealValue($aCols[$sPrefix.''], null);
}
}
catch (Exception $e) {
$value = $this->MakeRealValue($aCols[$sPrefix.''], null);
}
return $value;
}
public function GetSQLValues($value)
{
$aValues = array();
try {
$sSerializedValue = serialize($value);
}
catch (Exception $e) {
$sSerializedValue = json_encode($value);
}
$aValues[$this->Get("sql")] = $sSerializedValue;
return $aValues;
}
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if (!is_array($value)) {
throw new CoreException('Expecting an array', array('found' => get_class($value)));
}
if (count($value) == 0) {
return "";
}
$sRes = "<TABLE class=\"listResults\">";
$sRes .= "<TBODY>";
foreach ($value as $iRow => $aRawData) {
$sRes .= "<TR>";
foreach ($aRawData as $iCol => $cell) {
// Note: avoid the warning in case the cell is made of an array
$sCell = @Str::pure2html((string)$cell);
$sCell = str_replace("\n", "<br>\n", $sCell);
$sRes .= "<TD>$sCell</TD>";
}
$sRes .= "</TR>";
}
$sRes .= "</TBODY>";
$sRes .= "</TABLE>";
return $sRes;
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
// Not implemented
return '';
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
if (!is_array($value) || count($value) == 0) {
return "";
}
$sRes = "";
foreach ($value as $iRow => $aRawData) {
$sRes .= "<row>";
foreach ($aRawData as $iCol => $cell) {
$sCell = Str::pure2xml((string)$cell);
$sRes .= "<cell icol=\"$iCol\">$sCell</cell>";
}
$sRes .= "</row>";
}
return $sRes;
}
}

View File

@@ -0,0 +1,662 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CoreException;
use CoreUnexpectedValue;
use CoreWarning;
use DBObject;
use DBSearch;
use Dict;
use Exception;
use MetaModel;
use OQLException;
use ormTagSet;
use TagSetFieldData;
use utils;
/**
* Multi value list of tags
*
* @see TagSetFieldData
* @since 2.6.0 N°931 tag fields
*/
class AttributeTagSet extends AttributeSet
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_TAG_SET;
public function __construct($sCode, array $aParams)
{
parent::__construct($sCode, $aParams);
$this->aCSSClasses[] = 'attribute-tag-set';
}
public function GetEditClass()
{
return 'TagSet';
}
public static function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('tag_code_max_len'));
}
/**
* @param ormTagSet $oValue
*
* @param $aArgs
*
* @return string JSON to be used in the itop.tagset_widget JQuery widget
*/
public function GetJsonForWidget($oValue, $aArgs = array())
{
$aJson = array();
// possible_values
$aTagSetObjectData = $this->GetAllowedValues($aArgs);
$aTagSetKeyValData = array();
foreach ($aTagSetObjectData as $sTagCode => $sTagLabel) {
$aTagSetKeyValData[] = [
'code' => $sTagCode,
'label' => $sTagLabel,
];
}
$aJson['possible_values'] = $aTagSetKeyValData;
if (is_null($oValue)) {
$aJson['partial_values'] = array();
$aJson['orig_value'] = array();
$aJson['added'] = array();
$aJson['removed'] = array();
} else {
$aJson['orig_value'] = array_merge($oValue->GetValues(), $oValue->GetModified());
$aJson['added'] = $oValue->GetAdded();
$aJson['removed'] = $oValue->GetRemoved();
if ($oValue->DisplayPartial()) {
// For bulk updates
$aJson['partial_values'] = $oValue->GetModified();
} else {
// For simple updates
$aJson['partial_values'] = array();
}
}
$iMaxTags = $this->GetMaxItems();
$aJson['max_items_allowed'] = $iMaxTags;
return json_encode($aJson);
}
public function FromStringToArray($proposedValue, $sDefaultSepItem = ',')
{
$aValues = array();
if (!empty($proposedValue)) {
foreach (explode(' ', $proposedValue) as $sCode) {
$sValue = trim($sCode);
$aValues[] = $sValue;
}
}
return $aValues;
}
/**
* Extract all existing tags from a string and ignore bad tags
*
* @param $sValue
* @param bool $bNoLimit : don't apply the maximum tag limit
*
* @return ormTagSet
* @throws CoreException
* @throws CoreUnexpectedValue
*/
public function GetExistingTagsFromString($sValue, $bNoLimit = false)
{
$aTagCodes = $this->FromStringToArray("$sValue");
$sAttCode = $this->GetCode();
$sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $sAttCode);
if ($bNoLimit) {
$oTagSet = new ormTagSet($sClass, $sAttCode, 0);
} else {
$oTagSet = new ormTagSet($sClass, $sAttCode, $this->GetMaxItems());
}
$aGoodTags = array();
foreach ($aTagCodes as $sTagCode) {
if ($sTagCode === '') {
continue;
}
if ($oTagSet->IsValidTag($sTagCode)) {
$aGoodTags[] = $sTagCode;
if (!$bNoLimit && (count($aGoodTags) === $this->GetMaxItems())) {
// extra and bad tags are ignored
break;
}
}
}
$oTagSet->SetValues($aGoodTags);
return $oTagSet;
}
public function GetTagCodeMaxLength()
{
return $this->Get('tag_code_max_len');
}
public function GetEditValue($value, $oHostObj = null)
{
if (empty($value)) {
return '';
}
if ($value instanceof ormTagSet) {
$aValues = $value->GetValues();
return implode(' ', $aValues);
}
return '';
}
public function GetMaxSize()
{
return max(255, ($this->GetMaxItems() * $this->GetTagCodeMaxLength()) + 1);
}
public function Equals($val1, $val2)
{
if (($val1 instanceof ormTagSet) && ($val2 instanceof ormTagSet)) {
return $val1->Equals($val2);
}
return ($val1 == $val2);
}
public function GetAllowedValues($aArgs = array(), $sContains = '')
{
$sAttCode = $this->GetCode();
$sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $sAttCode);
$aAllowedTags = TagSetFieldData::GetAllowedValues($sClass, $sAttCode);
$aAllowedValues = array();
foreach ($aAllowedTags as $oAllowedTag) {
$aAllowedValues[$oAllowedTag->Get('code')] = $oAllowedTag->Get('label');
}
return $aAllowedValues;
}
/**
* @param array $aCols
* @param string $sPrefix
*
* @return mixed
* @throws CoreException
* @throws Exception
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
$sValue = $aCols["$sPrefix"];
return $this->GetExistingTagsFromString($sValue);
}
/**
* force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
*
* @param $proposedValue
* @param $oHostObj
*
* @param bool $bIgnoreErrors
*
* @return mixed
* @throws CoreException
* @throws CoreUnexpectedValue
*/
public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false)
{
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
if (is_string($proposedValue) && !empty($proposedValue)) {
$sJsonFromWidget = json_decode($proposedValue, true);
if (is_null($sJsonFromWidget)) {
$proposedValue = trim("$proposedValue");
$aTagCodes = $this->FromStringToArray($proposedValue);
$oTagSet->SetValues($aTagCodes);
}
} elseif ($proposedValue instanceof ormTagSet) {
$oTagSet = $proposedValue;
}
return $oTagSet;
}
/**
* Get the value from a given string (plain text, CSV import)
*
* @param string $sProposedValue
* @param bool $bLocalizedValue
* @param string $sSepItem
* @param string $sSepAttribute
* @param string $sSepValue
* @param string $sAttributeQualifier
*
* @return mixed null if no match could be found
* @throws Exception
*/
public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
{
if (is_null($sSepItem) || empty($sSepItem)) {
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
}
if (!empty($sProposedValue)) {
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()),
$this->GetCode(), $this->GetMaxItems());
$aLabels = explode($sSepItem, $sProposedValue);
$aCodes = array();
foreach ($aLabels as $sTagLabel) {
if (!empty($sTagLabel)) {
$aCodes[] = ($bLocalizedValue) ? $oTagSet->GetTagFromLabel($sTagLabel) : $sTagLabel;
}
}
$sProposedValue = implode(' ', $aCodes);
}
return $this->MakeRealValue($sProposedValue, null);
}
public function GetNullValue()
{
return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
}
public function GetDefaultValue(DBObject $oHostObject = null)
{
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
$oTagSet->SetValues([]);
return $oTagSet;
}
public function IsNull($proposedValue)
{
if (is_null($proposedValue)) {
return true;
}
/** @var ormTagSet $proposedValue */
return count($proposedValue->GetValues()) == 0;
}
/**
* To be overloaded for localized enums
*
* @param $sValue
*
* @return string label corresponding to the given value (in plain text)
* @throws CoreWarning
* @throws Exception
*/
public function GetValueLabel($sValue)
{
if (empty($sValue)) {
return '';
}
if (is_string($sValue)) {
$sValue = $this->GetExistingTagsFromString($sValue);
}
if ($sValue instanceof ormTagSet) {
$aValues = $sValue->GetLabels();
return implode(', ', $aValues);
}
throw new CoreWarning('Expected the attribute value to be a TagSet', array(
'found_type' => gettype($sValue),
'value' => $sValue,
'class' => $this->GetHostClass(),
'attribute' => $this->GetCode(),
));
}
/**
* @param $value
*
* @return string
* @throws CoreWarning
*/
public function ScalarToSQL($value)
{
if (empty($value)) {
return '';
}
if ($value instanceof ormTagSet) {
$aValues = $value->GetValues();
return implode(' ', $aValues);
}
throw new CoreWarning('Expected the attribute value to be a TagSet', array(
'found_type' => gettype($value),
'value' => $value,
'class' => $this->GetHostClass(),
'attribute' => $this->GetCode(),
));
}
/**
* @param $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string|null
*
* @throws CoreException
* @throws Exception
*/
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
if ($value instanceof ormTagSet) {
if ($bLocalize) {
$aValues = $value->GetTags();
} else {
$aValues = $value->GetValues();
}
if (empty($aValues)) {
return '';
}
return $this->GenerateViewHtmlForValues($aValues);
}
if (is_string($value)) {
try {
$oValue = $this->MakeRealValue($value, $oHostObject);
return $this->GetAsHTML($oValue, $oHostObject, $bLocalize);
}
catch (Exception $e) {
// unknown tags are present display the code instead
}
$aTagCodes = $this->FromStringToArray($value);
$aValues = array();
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()),
$this->GetCode(), $this->GetMaxItems());
foreach ($aTagCodes as $sTagCode) {
try {
$oTagSet->Add($sTagCode);
}
catch (Exception $e) {
$aValues[] = $sTagCode;
}
}
$sHTML = '';
if (!empty($aValues)) {
$sHTML .= $this->GenerateViewHtmlForValues($aValues, 'attribute-set-item-undefined');
}
$aValues = $oTagSet->GetTags();
if (!empty($aValues)) {
$sHTML .= $this->GenerateViewHtmlForValues($aValues);
}
return $sHTML;
}
return parent::GetAsHTML($value, $oHostObject, $bLocalize);
}
// Do not display friendly names in the history of change
public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null)
{
$sResult = Dict::Format('Change:AttName_Changed', $this->GetLabel()).", ";
$aNewValues = $this->FromStringToArray($sNewValue);
$aOldValues = $this->FromStringToArray($sOldValue);
$aDelta['removed'] = array_diff($aOldValues, $aNewValues);
$aDelta['added'] = array_diff($aNewValues, $aOldValues);
$aAllowedTags = TagSetFieldData::GetAllowedValues(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode());
if (!empty($aDelta['removed'])) {
$aRemoved = array();
foreach ($aDelta['removed'] as $idx => $sTagCode) {
if (empty($sTagCode)) {
continue;
}
$sTagLabel = $sTagCode;
foreach ($aAllowedTags as $oTag) {
if ($sTagCode === $oTag->Get('code')) {
$sTagLabel = $oTag->Get('label');
}
}
$aRemoved[] = $sTagLabel;
}
$sRemoved = $this->GenerateViewHtmlForValues($aRemoved, 'history-removed');
if (!empty($sRemoved)) {
$sResult .= Dict::Format('Change:LinkSet:Removed', $sRemoved);
}
}
if (!empty($aDelta['added'])) {
if (!empty($sRemoved)) {
$sResult .= ', ';
}
$aAdded = array();
foreach ($aDelta['added'] as $idx => $sTagCode) {
if (empty($sTagCode)) {
continue;
}
$sTagLabel = $sTagCode;
foreach ($aAllowedTags as $oTag) {
if ($sTagCode === $oTag->Get('code')) {
$sTagLabel = $oTag->Get('label');
}
}
$aAdded[] = $sTagLabel;
}
$sAdded = $this->GenerateViewHtmlForValues($aAdded, 'history-added');
if (!empty($sAdded)) {
$sResult .= Dict::Format('Change:LinkSet:Added', $sAdded);
}
}
return $sResult;
}
/**
* HTML representation of a list of tags (read-only)
* accept a list of strings or a list of TagSetFieldData
*
* @param array $aValues
* @param string $sCssClass
* @param bool $bWithLink if true will generate a link, otherwise just a "a" tag without href
*
* @return string
* @throws CoreException
* @throws OQLException
*/
public function GenerateViewHtmlForValues($aValues, $sCssClass = '', $bWithLink = true)
{
if (empty($aValues)) {
return '';
}
$sHtml = '<span class="'.$sCssClass.' '.implode(' ', $this->aCSSClasses).'">';
foreach ($aValues as $oTag) {
if ($oTag instanceof TagSetFieldData) {
$sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode());
$sAttCode = $this->GetCode();
$sTagCode = $oTag->Get('code');
$sTagLabel = $oTag->Get('label');
$sTagDescription = $oTag->Get('description');
$oFilter = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'");
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink(true);
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($oFilter->GetClass());
$sFilter = rawurlencode($oFilter->serialize());
$sLink = '';
if ($bWithLink && $this->bDisplayLink) {
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter.$sContext;
$sLink = ' href="'.$sUrl.'"';
}
$sLabelForHtml = utils::EscapeHtml($sTagLabel);
$sDescriptionForHtml = utils::EscapeHtml($sTagDescription);
if (empty($sTagDescription)) {
$sTooltipContent = $sTagLabel;
$sTooltipHtmlEnabled = 'false';
} else {
$sTagLabelEscaped = utils::EscapeHtml($sTagLabel);
$sTooltipContent = <<<HTML
<h4>$sTagLabelEscaped</h4>
<div>$sTagDescription</div>
HTML;
$sTooltipHtmlEnabled = 'true';
}
$sTooltipContent = utils::HtmlEntities($sTooltipContent);
$sHtml .= '<a'.$sLink.' class="attribute-set-item attribute-set-item-'.$sTagCode.'" data-code="'.$sTagCode.'" data-label="'.$sLabelForHtml.'" data-description="'.$sDescriptionForHtml.'" data-tooltip-content="'.$sTooltipContent.'" data-tooltip-html-enabled="'.$sTooltipHtmlEnabled.'">'.$sLabelForHtml.'</a>';
} else {
$sHtml .= '<span class="attribute-set-item">'.utils::EscapeHtml($oTag).'</span>';
}
}
$sHtml .= '</span>';
return $sHtml;
}
/**
* @param $value
* @param DBObject $oHostObject
* @param bool $bLocalize
*
* @return string
*
*/
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
if (is_object($value) && ($value instanceof ormTagSet)) {
$sRes = "<Set>\n";
if ($bLocalize) {
$aValues = $value->GetLabels();
} else {
$aValues = $value->GetValues();
}
if (!empty($aValues)) {
$sRes .= '<Tag>'.implode('</Tag><Tag>', $aValues).'</Tag>';
}
$sRes .= "</Set>\n";
} else {
$sRes = '';
}
return $sRes;
}
/**
* List the available verbs for 'GetForTemplate'
*/
public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text representation',
'html' => 'HTML representation (unordered list)',
);
}
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param mixed $value The current value of the field
* @param string $sVerb The verb specifying the representation of the value
* @param DBObject $oHostObject The object
* @param bool $bLocalize Whether or not to localize the value
*
* @return string
* @throws Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
if (is_object($value) && ($value instanceof ormTagSet)) {
if ($bLocalize) {
$aValues = $value->GetLabels();
$sSep = ', ';
} else {
$aValues = $value->GetValues();
$sSep = ' ';
}
switch ($sVerb) {
case '':
return implode($sSep, $aValues);
case 'html':
return '<ul><li>'.implode("</li><li>", $aValues).'</li></ul>';
default:
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject));
}
}
throw new CoreUnexpectedValue("Bad value '$value' for attribute ".$this->GetCode().' in class '.get_class($oHostObject));
}
/**
* @inheritDoc
*
* @param ormTagSet $value
*
* @return array
*/
public function GetForJSON($value)
{
$aRet = array();
if (is_object($value) && ($value instanceof ormTagSet)) {
$aRet = $value->GetValues();
}
return $aRet;
}
/**
* @inheritDoc
*
* @return ormTagSet
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws Exception
*/
public function FromJSONToValue($json)
{
$oSet = new ormTagSet($this->GetHostClass(), $this->GetCode(), $this->GetMaxItems());
$oSet->SetValues($json);
return $oSet;
}
/**
* The part of the current attribute in the object's signature, for the supplied value
*
* @param mixed $value The value of this attribute for the object
*
* @return string The "signature" for this field/attribute
*/
public function Fingerprint($value)
{
if ($value instanceof ormTagSet) {
$aValues = $value->GetValues();
return implode(' ', $aValues);
}
return parent::Fingerprint($value);
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\TagSetField';
}
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
/**
* Specialization of a HTML: template (contains iTop placeholders like $current_contact_id$ or $this->name$)
*
* @package iTopORM
*/
class AttributeTemplateHTML extends AttributeText
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->Get('sql')] = $this->GetSQLCol();
if ($this->GetOptional('format', null) != null) {
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')";
if ($bFullSpec) {
$aColumns[$this->Get('sql').'_format'] .= " DEFAULT 'html'"; // default 'html' is for migrating old records
}
}
return $aColumns;
}
/**
* The actual formatting of the text: either text (=plain text) or html (= text with HTML markup)
*
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'html'); // Defaults to HTML
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
/**
* Specialization of a string: template (contains iTop placeholders like $current_contact_id$ or $this->name$)
*
* @package iTopORM
*/
class AttributeTemplateString extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
/**
* Specialization of a text: template (contains iTop placeholders like $current_contact_id$ or $this->name$)
*
* @package iTopORM
*/
class AttributeTemplateText extends AttributeText
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
}

View File

@@ -0,0 +1,408 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOp;
use CMDBChangeOpSetAttributeHTML;
use CMDBChangeOpSetAttributeText;
use CMDBSource;
use Combodo\iTop\Form\Field\TextAreaField;
use CoreException;
use DBObject;
use Dict;
use Exception;
use HTMLSanitizer;
use InlineImage;
use MetaModel;
use ormCaseLog;
use Str;
use utils;
/**
* Map a text column (size > ?) to an attribute
*
* @package iTopORM
*/
class AttributeText extends AttributeString
{
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public function GetEditClass()
{
return ($this->GetFormat() == 'text') ? 'Text' : "HTML";
}
protected function GetSQLCol($bFullSpec = false)
{
return "TEXT".CMDBSource::GetSqlStringColumnDefinition();
}
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
$aColumns[$this->Get('sql')] = $this->GetSQLCol($bFullSpec);
if ($this->GetOptional('format', null) != null) {
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".CMDBSource::GetSqlStringColumnDefinition();
if ($bFullSpec) {
$aColumns[$this->Get('sql').'_format'] .= " DEFAULT 'text'"; // default 'text' is for migrating old records
}
}
return $aColumns;
}
public function GetSQLExpressions($sPrefix = '')
{
if ($sPrefix == '') {
$sPrefix = $this->Get('sql');
}
$aColumns = array();
// Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix
$aColumns[''] = $sPrefix;
if ($this->GetOptional('format', null) != null) {
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns['_format'] = $sPrefix.'_format';
}
return $aColumns;
}
public function GetMaxSize()
{
// Is there a way to know the current limitation for mysql?
// See mysql_field_len()
return 65535;
}
public static function RenderWikiHtml($sText, $bWikiOnly = false)
{
if (!$bWikiOnly) {
$sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i';
if (preg_match_all($sPattern, $sText, $aAllMatches,
PREG_SET_ORDER /* important !*/ | PREG_OFFSET_CAPTURE /* important ! */)) {
$i = count($aAllMatches);
// Replace the URLs by an actual hyperlink <a href="...">...</a>
// Let's do it backwards so that the initial positions are not modified by the replacement
// This works if the matches are captured: in the order they occur in the string AND
// with their offset (i.e. position) inside the string
while ($i > 0) {
$i--;
$sUrl = $aAllMatches[$i][0][0]; // String corresponding to the main pattern
$iPos = $aAllMatches[$i][0][1]; // Position of the main pattern
$sText = substr_replace($sText, "<a href=\"$sUrl\">$sUrl</a>", $iPos, strlen($sUrl));
}
}
}
if (preg_match_all(WIKI_OBJECT_REGEXP, $sText, $aAllMatches, PREG_SET_ORDER)) {
foreach ($aAllMatches as $iPos => $aMatches) {
$sClass = trim($aMatches[1]);
$sName = trim($aMatches[2]);
$sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null;
if (MetaModel::IsValidClass($sClass)) {
$bFound = false;
// Try to find by name, then by id
if (is_object($oObj = MetaModel::GetObjectByName($sClass, $sName, false /* MustBeFound */))) {
$bFound = true;
} elseif (is_object($oObj = MetaModel::GetObject($sClass, (int)$sName, false /* MustBeFound */, true))) {
$bFound = true;
}
if ($bFound === true) {
// Propose a std link to the object
$sHyperlinkLabel = (empty($sLabel)) ? $oObj->GetName() : $sLabel;
$sText = str_replace($aMatches[0], $oObj->GetHyperlink(null, true, $sHyperlinkLabel), $sText);
} else {
// Propose a std link to the object
$sClassLabel = MetaModel::GetName($sClass);
$sToolTipForHtml = utils::EscapeHtml(Dict::Format('Core:UnknownObjectLabel', $sClass, $sName));
$sReplacement = "<span class=\"wiki_broken_link ibo-is-broken-hyperlink\" data-tooltip-content=\"$sToolTipForHtml\">$sClassLabel:$sName".(!empty($sLabel) ? " ($sLabel)" : "")."</span>";
$sText = str_replace($aMatches[0], $sReplacement, $sText);
// Later: propose a link to create a new object
// Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name
//$sText = preg_replace('/\[\[(.+):(.+)\]\]/', '<a href="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$sClass.'&default[att1]=xxx&default[att2]=yyy">'.$sName.'</a>', $sText);
}
}
}
}
return $sText;
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
$aStyles = array();
if ($this->GetWidth() != '') {
$aStyles[] = 'width:'.$this->GetWidth();
}
if ($this->GetHeight() != '') {
$aStyles[] = 'height:'.$this->GetHeight();
}
$sStyle = '';
if (count($aStyles) > 0) {
$sStyle = 'style="'.implode(';', $aStyles).'"';
}
if ($this->GetFormat() == 'text') {
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
$sValue = self::RenderWikiHtml($sValue);
$sValue = nl2br($sValue);
return "<div $sStyle>$sValue</div>";
} else {
$sValue = self::RenderWikiHtml($sValue, true /* wiki only */);
return "<div class=\"HTML ibo-is-html-content\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
}
}
public function GetEditValue($sValue, $oHostObj = null)
{
// N°4517 - PHP 8.1 compatibility: str_replace call with null cause deprecated message
if ($sValue == null) {
return '';
}
if ($this->GetFormat() == 'text') {
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) {
foreach ($aAllMatches as $iPos => $aMatches) {
$sClass = trim($aMatches[1]);
$sName = trim($aMatches[2]);
$sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null;
if (MetaModel::IsValidClass($sClass)) {
$sClassLabel = MetaModel::GetName($sClass);
$sReplacement = "[[$sClassLabel:$sName".(!empty($sLabel) ? " | $sLabel" : "")."]]";
$sValue = str_replace($aMatches[0], $sReplacement, $sValue);
}
}
}
}
return $sValue;
}
/**
* For fields containing a potential markup, return the value without this markup
*
* @param string $sValue
* @param DBObject $oHostObj
*
* @return string
*/
public function GetAsPlainText($sValue, $oHostObj = null)
{
if ($this->GetFormat() == 'html') {
return (string)utils::HtmlToText($this->GetEditValue($sValue, $oHostObj));
} else {
return parent::GetAsPlainText($sValue, $oHostObj);
}
}
public function MakeRealValue($proposedValue, $oHostObj)
{
$sValue = $proposedValue;
// N°4517 - PHP 8.1 compatibility: str_replace call with null cause deprecated message
if ($sValue == null) {
return '';
}
switch ($this->GetFormat()) {
case 'html':
if (($sValue !== null) && ($sValue !== '')) {
$sValue = HTMLSanitizer::Sanitize($sValue);
}
break;
case 'text':
default:
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) {
foreach ($aAllMatches as $iPos => $aMatches) {
$sClassLabel = trim($aMatches[1]);
$sName = trim($aMatches[2]);
$sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null;
if (!MetaModel::IsValidClass($sClassLabel)) {
$sClass = MetaModel::GetClassFromLabel($sClassLabel);
if ($sClass) {
$sReplacement = "[[$sClassLabel:$sName".(!empty($sLabel) ? " | $sLabel" : "")."]]";
$sValue = str_replace($aMatches[0], $sReplacement, $sValue);
}
}
}
}
}
return $sValue;
}
public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
{
return Str::pure2xml($value);
}
public function GetWidth()
{
return $this->GetOptional('width', '');
}
public function GetHeight()
{
return $this->GetOptional('height', '');
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\TextAreaField';
}
/**
* @param DBObject $oObject
* @param TextAreaField $oFormField
*
* @return TextAreaField
* @throws CoreException
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
/** @var TextAreaField $oFormField */
$oFormField = new $sFormFieldClass($this->GetCode(), null, $oObject);
$oFormField->SetFormat($this->GetFormat());
}
parent::MakeFormField($oObject, $oFormField);
return $oFormField;
}
/**
* The actual formatting of the field: either text (=plain text) or html (= text with HTML markup)
*
* @return string
*/
public function GetFormat()
{
return $this->GetOptional('format', 'text');
}
/**
* Read the value from the row returned by the SQL query and transorms it to the appropriate
* internal format (either text or html)
*
* @see AttributeDBFieldVoid::FromSQLToValue()
*
* @param string $sPrefix
*
* @param array $aCols
*
* @return string
*/
public function FromSQLToValue($aCols, $sPrefix = '')
{
$value = $aCols[$sPrefix.''];
if ($this->GetOptional('format', null) != null) {
// Read from the extra column only if the property 'format' is specified for the attribute
$sFormat = $aCols[$sPrefix.'_format'];
} else {
$sFormat = $this->GetFormat();
}
switch ($sFormat) {
case 'text':
if ($this->GetFormat() == 'html') {
$value = utils::TextToHtml($value);
}
break;
case 'html':
if ($this->GetFormat() == 'text') {
$value = utils::HtmlToText($value);
} else {
$value = InlineImage::FixUrls((string)$value);
}
break;
default:
// unknown format ??
}
return $value;
}
public function GetSQLValues($value)
{
$aValues = array();
$aValues[$this->Get("sql")] = $this->ScalarToSQL($value);
if ($this->GetOptional('format', null) != null) {
// Add the extra column only if the property 'format' is specified for the attribute
$aValues[$this->Get("sql").'_format'] = $this->GetFormat();
}
return $aValues;
}
public function GetAsCSV(
$sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true,
$bConvertToPlainText = false
)
{
switch ($this->GetFormat()) {
case 'html':
if ($bConvertToPlainText) {
$sValue = utils::HtmlToText((string)$sValue);
}
$sFrom = array("\r\n", $sTextQualifier);
$sTo = array("\n", $sTextQualifier.$sTextQualifier);
$sEscaped = str_replace($sFrom, $sTo, (string)$sValue);
return $sTextQualifier.$sEscaped.$sTextQualifier;
break;
case 'text':
default:
return parent::GetAsCSV($sValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize,
$bConvertToPlainText);
}
}
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
{
/** @noinspection PhpConditionCheckedByNextConditionInspection */
if (false === is_null($original) && ($original instanceof ormCaseLog)) {
$original = $original->GetText();
}
$oMyChangeOp->Set("prevdata", $original);
}
protected function GetChangeRecordClassName(): string
{
return ($this->GetFormat() === 'html')
? CMDBChangeOpSetAttributeHTML::class
: CMDBChangeOpSetAttributeText::class;
}
}

View File

@@ -0,0 +1,125 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use CMDBChangeOpSetAttributeURL;
use CMDBSource;
use Combodo\iTop\Form\Field\UrlField;
use CoreException;
use DBObject;
use Exception;
use Str;
use utils;
/**
* Map a varchar column to an URL (formats the ouput in HMTL)
*
* @package iTopORM
*/
class AttributeURL extends AttributeString
{
/**
* @var string
* SCHEME....... USER....................... PASSWORD.......................... HOST/IP........... PORT.......... PATH......................... GET............................................ ANCHOR..........................
* Example: http://User:passWord@127.0.0.1:8888/patH/Page.php?arrayArgument[2]=something:blah20#myAnchor
* @link http://www.php.net/manual/fr/function.preg-match.php#93824 regexp source
* @since 3.0.1 N°4515 handle Alfresco and Sharepoint URLs
* @since 3.0.3 moved from Config to AttributeURL constant
*/
public const DEFAULT_VALIDATION_PATTERN = /** @lang RegExp */
'(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%@+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.,-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\$_.-]*)?';
/**
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
*
* @see https://www.php.net/manual/fr/language.oop5.decon.php states that child constructor can be ommited
* @see https://bugs.php.net/bug.php?id=79010 bug solved in PHP 7.4.9
*
* @param string $sCode
* @param array $aParams
*
* @throws Exception
* @noinspection SenselessProxyMethodInspection
*/
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
}
public static function ListExpectedParams()
{
//return parent::ListExpectedParams();
return array_merge(parent::ListExpectedParams(), array("target"));
}
protected function GetSQLCol($bFullSpec = false)
{
return "VARCHAR(2048)"
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
public function GetMaxSize()
{
return 2048;
}
public function GetEditClass()
{
return "String";
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
$sTarget = $this->Get("target");
if (empty($sTarget)) {
$sTarget = "_blank";
}
$sLabel = Str::pure2html($sValue);
if (strlen($sLabel) > 128) {
// Truncate the length to 128 characters, by removing the middle
$sLabel = substr($sLabel, 0, 100).'.....'.substr($sLabel, -20);
}
return "<a target=\"$sTarget\" href=\"$sValue\">$sLabel</a>";
}
public function GetValidationPattern()
{
return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('url_validation_pattern').'$');
}
public static function GetFormFieldClass()
{
return '\\Combodo\\iTop\\Form\\Field\\UrlField';
}
/**
* @param DBObject $oObject
* @param UrlField $oFormField
*
* @return null
* @throws CoreException
*/
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null) {
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
parent::MakeFormField($oObject, $oFormField);
$oFormField->SetTarget($this->Get('target'));
return $oFormField;
}
protected function GetChangeRecordClassName(): string
{
return CMDBChangeOpSetAttributeURL::class;
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
use Exception;
/**
* MissingColumnException - sent if an attribute is being created but the column is missing in the row
*
* @package iTopORM
*/
class MissingColumnException extends Exception
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\AttributeDefinition;
/**
* Attributes implementing this interface won't be accepted as `group by` field
*
* @since 2.7.4 N°3473
*/
interface iAttributeNoGroupBy
{
//no method, just a contract on implement
}

View File

@@ -2,12 +2,11 @@
namespace Combodo\iTop\PhpParser\Evaluation;
use ModuleFileParser;
use ModuleFileReaderException;
use \Combodo\iTop\Setup\ModuleDiscovery\ModuleFileParser;
use \Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
use PhpParser\ConstExprEvaluator;
use PhpParser\Node\Expr;
use PhpParser\ParserFactory;
/**
* Used at runtime/setup time
*/
@@ -31,7 +30,7 @@ class PhpExpressionEvaluator {
* @param string $sBooleanExpr
*
* @return bool
* @throws \ModuleFileReaderException
* @throws ModuleFileReaderException
*/
public function ParseAndEvaluateBooleanExpression(string $sBooleanExpr) : bool
{

View File

@@ -31,4 +31,66 @@ class_alias(\Combodo\iTop\Application\WebPage\PDFPage::class, 'PDFPage');
class_alias(\Combodo\iTop\Application\WebPage\TabManager::class, 'TabManager');
class_alias(\Combodo\iTop\Application\WebPage\UnauthenticatedWebPage::class, 'UnauthenticatedWebPage');
class_alias(\Combodo\iTop\Application\WebPage\WebPage::class, 'WebPage');
class_alias(\Combodo\iTop\Application\WebPage\XMLPage::class, 'XMLPage');
class_alias(\Combodo\iTop\Application\WebPage\XMLPage::class, 'XMLPage');
// attribute definitions
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeApplicationLanguage::class, 'AttributeApplicationLanguage');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeArchiveDate::class, 'AttributeArchiveDate');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeArchiveFlag::class, 'AttributeArchiveFlag');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeBlob::class, 'AttributeBlob');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeBoolean::class, 'AttributeBoolean');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeCaseLog::class, 'AttributeCaseLog');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeClass::class, 'AttributeClass');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeClassAttCodeSet::class, 'AttributeClassAttCodeSet');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeClassState::class, 'AttributeClassState');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeCustomFields::class, 'AttributeCustomFields');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDashboard::class, 'AttributeDashboard');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDate::class, 'AttributeDate');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDateTime::class, 'AttributeDateTime');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDBField::class, 'AttributeDBField');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDBFieldVoid::class, 'AttributeDBFieldVoid');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDeadline::class, 'AttributeDeadline');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDecimal::class, 'AttributeDecimal');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDefinition::class, 'AttributeDefinition');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeDuration::class, 'AttributeDuration');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeEmailAddress::class, 'AttributeEmailAddress');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeEncryptedString::class, 'AttributeEncryptedString');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeEnum::class, 'AttributeEnum');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeEnumSet::class, 'AttributeEnumSet');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeExternalField::class, 'AttributeExternalField');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey::class, 'AttributeExternalKey');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeFinalClass::class, 'AttributeFinalClass');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeFriendlyName::class, 'AttributeFriendlyName');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class, 'AttributeHierarchicalKey');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeHTML::class, 'AttributeHTML');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeImage::class, 'AttributeImage');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeInteger::class, 'AttributeInteger');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeIPAddress::class, 'AttributeIPAddress');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeLinkedSet::class, 'AttributeLinkedSet');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeLinkedSetIndirect::class, 'AttributeLinkedSetIndirect');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeLongText::class, 'AttributeLongText');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeMetaEnum::class, 'AttributeMetaEnum');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeObjectKey::class, 'AttributeObjectKey');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeObsolescenceDate::class, 'AttributeObsolescenceDate');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeObsolescenceFlag::class, 'AttributeObsolescenceFlag');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeOneWayPassword::class, 'AttributeOneWayPassword');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeOQL::class, 'AttributeOQL');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributePassword::class, 'AttributePassword');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributePercentage::class, 'AttributePercentage');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributePhoneNumber::class, 'AttributePhoneNumber');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributePropertySet::class, 'AttributePropertySet');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeQueryAttCodeSet::class, 'AttributeQueryAttCodeSet');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeRedundancySettings::class, 'AttributeRedundancySettings');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeSet::class, 'AttributeSet');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeStopWatch::class, 'AttributeStopWatch');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeString::class, 'AttributeString');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeSubItem::class, 'AttributeSubItem');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeTable::class, 'AttributeTable');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeTagSet::class, 'AttributeTagSet');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeTemplateHTML::class, 'AttributeTemplateHTML');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeTemplateString::class, 'AttributeTemplateString');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeTemplateText::class, 'AttributeTemplateText');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeText::class, 'AttributeText');
class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeURL::class, 'AttributeURL');
class_alias(\Combodo\iTop\Core\AttributeDefinition\iAttributeNoGroupBy::class, 'iAttributeNoGroupBy');
class_alias(\Combodo\iTop\Core\AttributeDefinition\MissingColumnException::class, 'MissingColumnException');

View File

@@ -3284,6 +3284,15 @@ span.search-button, span.refresh-button {
cursor: pointer !important;
}
.qtip-content {
font-size: 12px;
}
.qtip-content p:first-child {
margin-top: 0px;
}
.qtip-content p:last-child {
margin-bottom: 0px;
}
.synchro-source {
}
.synchro-source-title {

View File

@@ -130,16 +130,16 @@ class DBBackupTest extends ItopTestCase
'Localhost with port' => ['localhost', $iTestPort, $iTestPort, ' --protocol=tcp'],
// we want both port and protocol for 127.0.0.1, because it is an ip address so using tcp/ip stack !
'127.0.0.1 no port' => ['127.0.0.1', null, $iDefaultPort, ''],
'127.0.0.1 no port' => ['127.0.0.1', null, null, ''],
'127.0.0.1 with port' => ['127.0.0.1', $iTestPort, $iTestPort, ''],
'IP no port' => ['192.168.1.15', null, $iDefaultPort, ''],
'IP no port' => ['192.168.1.15', null, null, ''],
'IP with port' => ['192.168.1.15', $iTestPort, $iTestPort, ''],
'DNS no port' => ['dbserver.mycompany.com', null, $iDefaultPort, ''],
'DNS no port' => ['dbserver.mycompany.com', null, null, ''],
'DNS with port' => ['dbserver.mycompany.com', $iTestPort, $iTestPort, ''],
'Windows name no port' => ['dbserver', null, $iDefaultPort, ''],
'Windows name no port' => ['dbserver', null, null, ''],
'Windows name with port' => ['dbserver', $iTestPort, $iTestPort, ''],
];
}

View File

@@ -2,8 +2,9 @@
namespace Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ModuleFileReader;
class ModuleFileReaderTest extends ItopDataTestCase
{
@@ -59,7 +60,7 @@ class ModuleFileReaderTest extends ItopDataTestCase
{
$sModuleFilePath = __DIR__.'/resources/module.__MODULE__.php';
$this->expectException(\ModuleFileReaderException::class);
$this->expectException(ModuleFileReaderException::class);
$this->expectExceptionMessage("Syntax error, unexpected T_CONSTANT_ENCAPSED_STRING, expecting ',' or ']' or ')' on line 31");
ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);

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