diff --git a/application/utils.inc.php b/application/utils.inc.php
index 026da66f5..581b63089 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -1,1983 +1,1983 @@
-
-
-
-/**
- * Static class utils
- *
- * @copyright Copyright (C) 2010-2017 Combodo SARL
- * @license http://opensource.org/licenses/AGPL-3.0
- */
-
-require_once(APPROOT.'/core/config.class.inc.php');
-require_once(APPROOT.'/application/transaction.class.inc.php');
-require_once(APPROOT.'application/Html2Text.php');
-require_once(APPROOT.'application/Html2TextException.php');
-
-define('ITOP_CONFIG_FILE', 'config-itop.php');
-define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE);
-
-define('SERVER_NAME_PLACEHOLDER', '$SERVER_NAME$');
-
-define('SERVER_MAX_URL_LENGTH', 2048);
-
-class FileUploadException extends Exception
-{
-}
-
-
-/**
- * Helper functions to interact with forms: read parameters, upload files...
- * @package iTop
- */
-class utils
-{
- private static $oConfig = null;
- private static $m_bCASClient = false;
-
- // Parameters loaded from a file, parameters of the page/command line still have precedence
- private static $m_aParamsFromFile = null;
- private static $m_aParamSource = array();
-
- protected static function LoadParamFile($sParamFile)
- {
- if (!file_exists($sParamFile))
- {
- throw new Exception("Could not find the parameter file: '$sParamFile'");
- }
- if (!is_readable($sParamFile))
- {
- throw new Exception("Could not load parameter file: '$sParamFile'");
- }
- $sParams = file_get_contents($sParamFile);
-
- if (is_null(self::$m_aParamsFromFile))
- {
- self::$m_aParamsFromFile = array();
- }
-
- $aParamLines = explode("\n", $sParams);
- foreach ($aParamLines as $sLine)
- {
- $sLine = trim($sLine);
-
- // Ignore the line after a '#'
- if (($iCommentPos = strpos($sLine, '#')) !== false)
- {
- $sLine = substr($sLine, 0, $iCommentPos);
- $sLine = trim($sLine);
- }
-
- // Note: the line is supposed to be already trimmed
- if (preg_match('/^(\S*)\s*=(.*)$/', $sLine, $aMatches))
- {
- $sParam = $aMatches[1];
- $value = trim($aMatches[2]);
- self::$m_aParamsFromFile[$sParam] = $value;
- self::$m_aParamSource[$sParam] = $sParamFile;
- }
- }
- }
-
- public static function UseParamFile($sParamFileArgName = 'param_file', $bAllowCLI = true)
- {
- $sFileSpec = self::ReadParam($sParamFileArgName, '', $bAllowCLI, 'raw_data');
- foreach(explode(',', $sFileSpec) as $sFile)
- {
- $sFile = trim($sFile);
- if (!empty($sFile))
- {
- self::LoadParamFile($sFile);
- }
- }
- }
-
- /**
- * Return the source file from which the parameter has been found,
- * usefull when it comes to pass user credential to a process executed
- * in the background
- * @param $sName Parameter name
- * @return The file name if any, or null
- */
- public static function GetParamSourceFile($sName)
- {
- if (array_key_exists($sName, self::$m_aParamSource))
- {
- return self::$m_aParamSource[$sName];
- }
- else
- {
- return null;
- }
- }
-
- public static function IsModeCLI()
- {
- $sSAPIName = php_sapi_name();
- $sCleanName = strtolower(trim($sSAPIName));
- if ($sCleanName == 'cli')
- {
- return true;
- }
- else
- {
- return false;
- }
- }
-
- protected static $bPageMode = null;
- /**
- * @var boolean[]
- */
- protected static $aModes = array();
-
- public static function InitArchiveMode()
- {
- if (isset($_SESSION['archive_mode']))
- {
- $iDefault = $_SESSION['archive_mode'];
- }
- else
- {
- $iDefault = 0;
- }
- // Read and record the value for switching the archive mode
- $iCurrent = self::ReadParam('with-archive', $iDefault);
- if (isset($_SESSION))
- {
- $_SESSION['archive_mode'] = $iCurrent;
- }
- // Read and use the value for the current page (web services)
- $iCurrent = self::ReadParam('with_archive', $iCurrent, true);
- self::$bPageMode = ($iCurrent == 1);
- }
-
- /**
- * @param boolean $bMode if true then activate archive mode (archived objects are visible), otherwise archived objects are
- * hidden (archive = "soft deletion")
- */
- public static function PushArchiveMode($bMode)
- {
- array_push(self::$aModes, $bMode);
- }
-
- public static function PopArchiveMode()
- {
- array_pop(self::$aModes);
- }
-
- /**
- * @return boolean true if archive mode is enabled
- */
- public static function IsArchiveMode()
- {
- if (count(self::$aModes) > 0)
- {
- $bRet = end(self::$aModes);
- }
- else
- {
- if (self::$bPageMode === null)
- {
- self::InitArchiveMode();
- }
- $bRet = self::$bPageMode;
- }
- return $bRet;
- }
-
- /**
- * Helper to be called by the GUI and define if the user will see obsolete data (otherwise, the user will have to dig further)
- * @return bool
- */
- public static function ShowObsoleteData()
- {
- $bDefault = MetaModel::GetConfig()->Get('obsolescence.show_obsolete_data'); // default is false
- $bShow = appUserPreferences::GetPref('show_obsolete_data', $bDefault);
- if (static::IsArchiveMode())
- {
- $bShow = true;
- }
- return $bShow;
- }
-
- public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false, $sSanitizationFilter = 'parameter')
- {
- global $argv;
- $retValue = $defaultValue;
-
- if (!is_null(self::$m_aParamsFromFile))
- {
- if (isset(self::$m_aParamsFromFile[$sName]))
- {
- $retValue = self::$m_aParamsFromFile[$sName];
- }
- }
-
- if (isset($_REQUEST[$sName]))
- {
- $retValue = $_REQUEST[$sName];
- }
- elseif ($bAllowCLI && isset($argv))
- {
- foreach($argv as $iArg => $sArg)
- {
- if (preg_match('/^--'.$sName.'=(.*)$/', $sArg, $aMatches))
- {
- $retValue = $aMatches[1];
- }
- }
- }
- return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
- }
-
- public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
- {
- $retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
- return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
- }
-
- public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
- {
- if ($value === $defaultValue)
- {
- // Preserve the real default value (can be used to detect missing mandatory parameters)
- $retValue = $value;
- }
- else
- {
- $retValue = self::Sanitize_Internal($value, $sSanitizationFilter);
- if ($retValue === false)
- {
- $retValue = $defaultValue;
- }
- }
- return $retValue;
- }
-
- protected static function Sanitize_Internal($value, $sSanitizationFilter)
- {
- switch($sSanitizationFilter)
- {
- case 'integer':
- $retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT);
- break;
-
- case 'class':
- $retValue = $value;
- if (!MetaModel::IsValidClass($value))
- {
- $retValue = false;
- }
- break;
-
- case 'string':
- $retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
- break;
-
- case 'context_param':
- case 'parameter':
- case 'field_name':
- if (is_array($value))
- {
- $retValue = array();
- foreach($value as $key => $val)
- {
- $retValue[$key] = self::Sanitize_Internal($val, $sSanitizationFilter); // recursively check arrays
- if ($retValue[$key] === false)
- {
- $retValue = false;
- break;
- }
- }
- }
- else
- {
- switch($sSanitizationFilter)
- {
- case 'parameter':
- $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^([ A-Za-z0-9_=-]|%3D|%2B|%2F)*$/'))); // the '=', '%3D, '%2B', '%2F' characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
- break;
-
- case 'field_name':
- $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
- break;
-
- case 'context_param':
- $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[ A-Za-z0-9_=%:+-]*$/')));
- break;
-
- }
- }
- break;
-
- default:
- case 'raw_data':
- $retValue = $value;
- // Do nothing
- }
- return $retValue;
- }
-
- /**
- * Reads an uploaded file and turns it into an ormDocument object - Triggers an exception in case of error
- * @param string $sName Name of the input used from uploading the file
- * @param string $sIndex If Name is an array of posted files, then the index must be used to point out the file
- * @return ormDocument The uploaded file (can be 'empty' if nothing was uploaded)
- */
- public static function ReadPostedDocument($sName, $sIndex = null)
- {
- $oDocument = new ormDocument(); // an empty document
- if(isset($_FILES[$sName]))
- {
- $aFileInfo = $_FILES[$sName];
-
- $sError = is_null($sIndex) ? $aFileInfo['error'] : $aFileInfo['error'][$sIndex];
- switch($sError)
- {
- case UPLOAD_ERR_OK:
- $sTmpName = is_null($sIndex) ? $aFileInfo['tmp_name'] : $aFileInfo['tmp_name'][$sIndex];
- $sMimeType = is_null($sIndex) ? $aFileInfo['type'] : $aFileInfo['type'][$sIndex];
- $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
-
- $doc_content = file_get_contents($sTmpName);
- if (function_exists('finfo_file'))
- {
- // as of PHP 5.3 the fileinfo extension is bundled within PHP
- // in which case we don't trust the mime type provided by the browser
- $rInfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
- if ($rInfo !== false)
- {
- $sType = @finfo_file($rInfo, $sTmpName);
- if ( ($sType !== false)
- && is_string($sType)
- && (strlen($sType)>0))
- {
- $sMimeType = $sType;
- }
- }
- @finfo_close($rInfo);
- }
- $oDocument = new ormDocument($doc_content, $sMimeType, $sName);
- break;
-
- case UPLOAD_ERR_NO_FILE:
- // no file to load, it's a normal case, just return an empty document
- break;
-
- case UPLOAD_ERR_FORM_SIZE:
- case UPLOAD_ERR_INI_SIZE:
- throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
- break;
-
- case UPLOAD_ERR_PARTIAL:
- throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
- break;
-
- case UPLOAD_ERR_NO_TMP_DIR:
- throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
- break;
-
- case UPLOAD_ERR_CANT_WRITE:
- throw new FileUploadException(Dict::Format('UI:Error:CannotWriteToTmp_Dir', ini_get('upload_tmp_dir')));
- break;
-
- case UPLOAD_ERR_EXTENSION:
- $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
- throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
- break;
-
- default:
- throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
- break;
-
- }
- }
- return $oDocument;
- }
-
- /**
- * Interprets the results posted by a normal or paginated list (in multiple selection mode)
- *
- * @param $oFullSetFilter DBSearch The criteria defining the whole sets of objects being selected
- *
- * @return Array An array of object IDs corresponding to the objects selected in the set
- */
- public static function ReadMultipleSelection($oFullSetFilter)
- {
- $aSelectedObj = utils::ReadParam('selectObject', array());
- $sSelectionMode = utils::ReadParam('selectionMode', '');
- if ($sSelectionMode != '')
- {
- // Paginated selection
- $aExceptions = utils::ReadParam('storedSelection', array());
- if ($sSelectionMode == 'positive')
- {
- // Only the explicitely listed items are selected
- $aSelectedObj = $aExceptions;
- }
- else
- {
- // All items of the set are selected, except the one explicitely listed
- $aSelectedObj = array();
- $oFullSet = new DBObjectSet($oFullSetFilter);
- $sClassAlias = $oFullSetFilter->GetClassAlias();
- $oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
- while($oObj = $oFullSet->Fetch())
- {
- if (!in_array($oObj->GetKey(), $aExceptions))
- {
- $aSelectedObj[] = $oObj->GetKey();
- }
- }
- }
- }
- return $aSelectedObj;
- }
-
- /**
- * Interprets the results posted by a normal or paginated list (in multiple selection mode)
- *
- * @param DBSearch $oFullSetFilter The criteria defining the whole sets of objects being selected
- *
- * @return Array An array of object IDs:friendlyname corresponding to the objects selected in the set
- * @throws \CoreException
- */
- public static function ReadMultipleSelectionWithFriendlyname($oFullSetFilter)
- {
- $sSelectionMode = utils::ReadParam('selectionMode', '');
-
- if ($sSelectionMode === '')
- {
- throw new CoreException('selectionMode is mandatory');
- }
-
- // Paginated selection
- $aSelectedIds = utils::ReadParam('storedSelection', array());
- if (count($aSelectedIds) > 0 )
- {
- if ($sSelectionMode == 'positive')
- {
- // Only the explicitly listed items are selected
- $oFullSetFilter->AddCondition('id', $aSelectedIds, 'IN');
- }
- else
- {
- // All items of the set are selected, except the one explicitly listed
- $oFullSetFilter->AddCondition('id', $aSelectedIds, 'NOTIN');
- }
- }
-
- $aSelectedObj = array();
- $oFullSet = new DBObjectSet($oFullSetFilter);
- $sClassAlias = $oFullSetFilter->GetClassAlias();
- $oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
- while ($oObj = $oFullSet->Fetch())
- {
- $aSelectedObj[$oObj->GetKey()] = $oObj->Get('friendlyname');
- }
-
- return $aSelectedObj;
- }
-
- public static function GetNewTransactionId()
- {
- return privUITransaction::GetNewTransactionId();
- }
-
- public static function IsTransactionValid($sId, $bRemoveTransaction = true)
- {
- return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction);
- }
-
- public static function RemoveTransaction($sId)
- {
- return privUITransaction::RemoveTransaction($sId);
- }
-
- /**
- * Returns a unique tmp id for the current upload based on the transaction system (db).
- *
- * Build as session_id() . '_' . static::GetNewTransactionId()
- *
- * @return string
- */
- public static function GetUploadTempId($sTransactionId = null)
- {
- if ($sTransactionId === null)
- {
- $sTransactionId = static::GetNewTransactionId();
- }
- return session_id() . '_' . $sTransactionId;
- }
-
- public static function ReadFromFile($sFileName)
- {
- if (!file_exists($sFileName)) return false;
- return file_get_contents($sFileName);
- }
-
- /**
- * Helper function to convert a value expressed in a 'user friendly format'
- * as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes
- * @param mixed $value The value as read from php.ini
- * @return number
- */
- public static function ConvertToBytes( $value )
- {
- $iReturn = $value;
- if ( !is_numeric( $value ) )
- {
- $iLength = strlen( $value );
- $iReturn = substr( $value, 0, $iLength - 1 );
- $sUnit = strtoupper( substr( $value, $iLength - 1 ) );
- switch ( $sUnit )
- {
- case 'G':
- $iReturn *= 1024;
- case 'M':
- $iReturn *= 1024;
- case 'K':
- $iReturn *= 1024;
- }
- }
- return $iReturn;
- }
-
- /**
- * Checks if the memory limit is at least what is required
- *
- * @param int $memoryLimit set limit in bytes
- * @param int $requiredLimit required limit in bytes
- * @return bool
- */
- public static function IsMemoryLimitOk($memoryLimit, $requiredLimit)
- {
- return ($memoryLimit >= $requiredLimit) || ($memoryLimit == -1);
- }
-
- /**
- * Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
- *
- * @param type $value
- * @return string
- */
- public static function BytesToFriendlyFormat($value)
- {
- $sReturn = '';
- // Kilobytes
- if ($value >= 1024)
- {
- $sReturn = 'K';
- $value = $value / 1024;
- }
- // Megabytes
- if ($value >= 1024)
- {
- $sReturn = 'M';
- $value = $value / 1024;
- }
- // Gigabytes
- if ($value >= 1024)
- {
- $sReturn = 'G';
- $value = $value / 1024;
- }
- // Terabytes
- if ($value >= 1024)
- {
- $sReturn = 'T';
- $value = $value / 1024;
- }
-
- $value = round($value, 1);
-
- return $value . '' . $sReturn . 'B';
- }
-
- /**
- * Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for specifying a date in a french format (for instance)
- * Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
- * @param string $sDate
- * @param string $sFormat
- * @return timestamp or false if the input format is not correct
- */
- public static function StringToTime($sDate, $sFormat)
- {
- // Source: http://php.net/manual/fr/function.strftime.php
- // (alternative: http://www.php.net/manual/fr/datetime.formats.date.php)
- static $aDateTokens = null;
- static $aDateRegexps = null;
- if (is_null($aDateTokens))
- {
- $aSpec = array(
- '%d' =>'(?[0-9]{2})',
- '%m' => '(?[0-9]{2})',
- '%y' => '(?[0-9]{2})',
- '%Y' => '(?[0-9]{4})',
- '%H' => '(?[0-2][0-9])',
- '%i' => '(?[0-5][0-9])',
- '%s' => '(?[0-5][0-9])',
- );
- $aDateTokens = array_keys($aSpec);
- $aDateRegexps = array_values($aSpec);
- }
-
- $sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
-
- if (preg_match('!^(?)'.$sDateRegexp.'(?)$!', $sDate, $aMatches))
- {
- $sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
- $sMonth = isset($aMatches['month']) ? $aMatches['month'] : 1;
- $sDay = isset($aMatches['day']) ? $aMatches['day'] : 1;
- $sHour = isset($aMatches['hour']) ? $aMatches['hour'] : 0;
- $sMinute = isset($aMatches['minute']) ? $aMatches['minute'] : 0;
- $sSecond = isset($aMatches['second']) ? $aMatches['second'] : 0;
- return strtotime("$sYear-$sMonth-$sDay $sHour:$sMinute:$sSecond");
- }
- else
- {
- return false;
- }
- // http://www.spaweditor.com/scripts/regex/index.php
- }
-
- /**
- * Convert an old date/time format specifciation (using % placeholders)
- * to a format compatible with DateTime::createFromFormat
- * @param string $sOldDateTimeFormat
- * @return string
- */
- static public function DateTimeFormatToPHP($sOldDateTimeFormat)
- {
- $aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s');
- $aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's');
- return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
- }
-
- /**
- * @return \Config from the current environement, or if not existing from the production env, else new Config made from scratch
- * @uses \MetaModel::GetConfig() don't forget to add the needed require_once(APPROOT.'core/metamodel.class.php');
- */
- static public function GetConfig()
- {
- if (self::$oConfig == null)
- {
- self::$oConfig = MetaModel::GetConfig();
-
- if (self::$oConfig == null)
- {
- $sConfigFile = self::GetConfigFilePath();
- if (!file_exists($sConfigFile))
- {
- $sConfigFile = self::GetConfigFilePath('production');
- if (!file_exists($sConfigFile))
- {
- $sConfigFile = null;
- }
- }
-
- self::$oConfig = new Config($sConfigFile);
- }
- }
- return self::$oConfig;
- }
-
- public static function InitTimeZone() {
- $oConfig = self::GetConfig();
- $sItopTimeZone = $oConfig->Get('timezone');
-
- if (!empty($sItopTimeZone))
- {
- date_default_timezone_set($sItopTimeZone);
- }
- else
- {
- // Leave as is... up to the admin to set a value somewhere...
- // see http://php.net/manual/en/datetime.configuration.php#ini.date.timezone
- }
- }
-
- /**
- * Returns the absolute URL to the application root path
- *
- * @return string The absolute URL to the application root, without the first slash
- *
- * @throws \Exception
- */
- static public function GetAbsoluteUrlAppRoot()
- {
- static $sUrl = null;
- if ($sUrl === null)
- {
- $sUrl = self::GetConfig()->Get('app_root_url');
- if ($sUrl == '')
- {
- $sUrl = self::GetDefaultUrlAppRoot();
- }
- elseif (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
- {
- if (isset($_SERVER['SERVER_NAME']))
- {
- $sServerName = $_SERVER['SERVER_NAME'];
- }
- else
- {
- // CLI mode ?
- $sServerName = php_uname('n');
- }
- $sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl);
- }
- }
- return $sUrl;
- }
-
- /**
- * Builds an root url from the server's variables.
- * For most usages, when an root url is needed, use utils::GetAbsoluteUrlAppRoot() instead as uses this only as a fallback when the app_root_url conf parameter is not defined.
- *
- * @return string
- *
- * @throws \Exception
- */
- static public function GetDefaultUrlAppRoot()
- {
- // Build an absolute URL to this page on this server/port
- $sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
- $sProtocol = self::IsConnectionSecure() ? 'https' : 'http';
- $iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
- if ($sProtocol == 'http')
- {
- $sPort = ($iPort == 80) ? '' : ':'.$iPort;
- }
- else
- {
- $sPort = ($iPort == 443) ? '' : ':'.$iPort;
- }
- // $_SERVER['REQUEST_URI'] is empty when running on IIS
- // Let's use Ivan Tcholakov's fix (found on www.dokeos.com)
- if (!empty($_SERVER['REQUEST_URI']))
- {
- $sPath = $_SERVER['REQUEST_URI'];
- }
- else
- {
- $sPath = $_SERVER['SCRIPT_NAME'];
- if (!empty($_SERVER['QUERY_STRING']))
- {
- $sPath .= '?'.$_SERVER['QUERY_STRING'];
- }
- $_SERVER['REQUEST_URI'] = $sPath;
- }
- $sPath = $_SERVER['REQUEST_URI'];
-
- // remove all the parameters from the query string
- $iQuestionMarkPos = strpos($sPath, '?');
- if ($iQuestionMarkPos !== false)
- {
- $sPath = substr($sPath, 0, $iQuestionMarkPos);
- }
- $sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
-
- $sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
- $sCurrentScript = str_replace('\\', '/', $sCurrentScript); // canonical path
- $sAppRoot = str_replace('\\', '/', APPROOT); // canonical path
- $sCurrentRelativePath = str_replace($sAppRoot, '', $sCurrentScript);
-
- $sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
- if ($sAppRootPos !== false)
- {
- $sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
- }
- else
- {
- // Second attempt without index.php at the end...
- $sCurrentRelativePath = str_replace('index.php', '', $sCurrentRelativePath);
- $sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
- if ($sAppRootPos !== false)
- {
- $sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
- }
- else
- {
- // No luck...
- throw new Exception("Failed to determine application root path $sAbsoluteUrl ($sCurrentRelativePath) APPROOT:'$sAppRoot'");
- }
- }
- return $sAppRootUrl;
- }
-
- /**
- * Helper to handle the variety of HTTP servers
- * See #286 (fixed in [896]), and #634 (this fix)
- *
- * Though the official specs says 'a non empty string', some servers like IIS do set it to 'off' !
- * nginx set it to an empty string
- * Others might leave it unset (no array entry)
- */
- static public function IsConnectionSecure()
- {
- $bSecured = false;
-
- if (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
- {
- $bSecured = true;
- }
- return $bSecured;
- }
-
- /**
- * Tells whether or not log off operation is supported.
- * Actually in only one case:
- * 1) iTop is using an internal authentication
- * 2) the user did not log-in using the "basic" mode (i.e basic authentication) or by passing credentials in the URL
- * @return boolean True if logoff is supported, false otherwise
- */
- static function CanLogOff()
- {
- $bResult = false;
- if(isset($_SESSION['login_mode']))
- {
- $sLoginMode = $_SESSION['login_mode'];
- switch($sLoginMode)
- {
- case 'external':
- $bResult = false;
- break;
-
- case 'form':
- case 'basic':
- case 'url':
- case 'cas':
- default:
- $bResult = true;
-
- }
- }
- return $bResult;
- }
-
- /**
- * Initializes the CAS client
- */
- static function InitCASClient()
- {
- $sCASIncludePath = self::GetConfig()->Get('cas_include_path');
- include_once($sCASIncludePath.'/CAS.php');
-
- $bCASDebug = self::GetConfig()->Get('cas_debug');
- if ($bCASDebug)
- {
- phpCAS::setDebug(APPROOT.'log/error.log');
- }
-
- if (!self::$m_bCASClient)
- {
- // Initialize phpCAS
- $sCASVersion = self::GetConfig()->Get('cas_version');
- $sCASHost = self::GetConfig()->Get('cas_host');
- $iCASPort = self::GetConfig()->Get('cas_port');
- $sCASContext = self::GetConfig()->Get('cas_context');
- phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */);
- self::$m_bCASClient = true;
- $sCASCACertPath = self::GetConfig()->Get('cas_server_ca_cert_path');
- if (empty($sCASCACertPath))
- {
- // If no certificate authority is provided, do not attempt to validate
- // the server's certificate
- // THIS SETTING IS NOT RECOMMENDED FOR PRODUCTION.
- // VALIDATING THE CAS SERVER IS CRUCIAL TO THE SECURITY OF THE CAS PROTOCOL!
- phpCAS::setNoCasServerValidation();
- }
- else
- {
- phpCAS::setCasServerCACert($sCASCACertPath);
- }
- }
- }
-
- static function DebugBacktrace($iLimit = 5)
- {
- $aFullTrace = debug_backtrace();
- $aLightTrace = array();
- for($i=1; ($i<=$iLimit && $i < count($aFullTrace)); $i++) // Skip the last function call... which is the call to this function !
- {
- $aLightTrace[$i] = $aFullTrace[$i]['function'].'(), called from line '.$aFullTrace[$i]['line'].' in '.$aFullTrace[$i]['file'];
- }
- echo "".print_r($aLightTrace, true)."
\n";
- }
-
- /**
- * Execute the given iTop PHP script, passing it the current credentials
- * Only CLI mode is supported, because of the need to hand the credentials over to the next process
- * Throws an exception if the execution fails or could not be attempted (config issue)
- * @param string $sScript Name and relative path to the file (relative to the iTop root dir)
- * @param hash $aArguments Associative array of 'arg' => 'value'
- * @return array(iCode, array(output lines))
- */
- /**
- */
- static function ExecITopScript($sScriptName, $aArguments)
- {
- $aDisabled = explode(', ', ini_get('disable_functions'));
- if (in_array('exec', $aDisabled))
- {
- throw new Exception("The PHP exec() function has been disabled on this server");
- }
-
- $sPHPExec = trim(self::GetConfig()->Get('php_path'));
- if (strlen($sPHPExec) == 0)
- {
- throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file.");
- }
-
- $sAuthUser = self::ReadParam('auth_user', '', 'raw_data');
- $sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data');
- $sParamFile = self::GetParamSourceFile('auth_user');
- if (is_null($sParamFile))
- {
- $aArguments['auth_user'] = $sAuthUser;
- $aArguments['auth_pwd'] = $sAuthPwd;
- }
- else
- {
- $aArguments['param_file'] = $sParamFile;
- }
-
- $aArgs = array();
- foreach($aArguments as $sName => $value)
- {
- // Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation
- // It suggests to rely on pctnl_* function instead of using escapeshellargs
- $aArgs[] = "--$sName=".escapeshellarg($value);
- }
- $sArgs = implode(' ', $aArgs);
-
- $sScript = realpath(APPROOT.$sScriptName);
- if (!file_exists($sScript))
- {
- throw new Exception("Could not find the script file '$sScriptName' from the directory '".APPROOT."'");
- }
-
- $sCommand = '"'.$sPHPExec.'" '.escapeshellarg($sScript).' -- '.$sArgs;
-
- if (version_compare(phpversion(), '5.3.0', '<'))
- {
- if (substr(PHP_OS,0,3) == 'WIN')
- {
- // Under Windows, and for PHP 5.2.x, the whole command has to be quoted
- // Cf PHP doc: http://php.net/manual/fr/function.exec.php, comment from the 27-Dec-2010
- $sCommand = '"'.$sCommand.'"';
- }
- }
-
- $sLastLine = exec($sCommand, $aOutput, $iRes);
- if ($iRes == 1)
- {
- throw new Exception(Dict::S('Core:ExecProcess:Code1')." - ".$sCommand);
- }
- elseif ($iRes == 255)
- {
- $sErrors = implode("\n", $aOutput);
- throw new Exception(Dict::S('Core:ExecProcess:Code255')." - ".$sCommand.":\n".$sErrors);
- }
-
- //$aOutput[] = $sCommand;
- return array($iRes, $aOutput);
- }
-
- /**
- * Get the current environment
- */
- public static function GetCurrentEnvironment()
- {
- if (isset($_SESSION['itop_env']))
- {
- return $_SESSION['itop_env'];
- }
- else
- {
- return ITOP_DEFAULT_ENV;
- }
- }
-
- /**
- * Returns a path to a folder into which any module can store cache data
- * The corresponding folder is created or cleaned upon code compilation
- * @return string
- */
- public static function GetCachePath()
- {
- return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
- }
- /**
- * Merge standard menu items with plugin provided menus items
- */
- public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions, $sTableId = null, $sDataTableId = null)
- {
- // 1st - add standard built-in menu items
- //
- switch($iMenuId)
- {
- case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
- // $param is a DBObjectSet
- $oAppContext = new ApplicationContext();
- $sContext = $oAppContext->GetForLink();
- $sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
- $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass());
- $sOQL = addslashes($param->GetFilter()->ToOQL(true));
- $sFilter = urlencode($param->GetFilter()->serialize());
- $sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
- $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
- $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
- $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
-
- $aResult = array();
- if (strlen($sUrl) < SERVER_MAX_URL_LENGTH)
- {
- $aResult[] = new SeparatorPopupMenuItem();
- // Static menus: Email this page, CSV Export & Add to Dashboard
- $aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
- "mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
- );
- }
-
- if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO)
- {
- // Bulk export actions
- $aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
- $aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
- if (extension_loaded('gd'))
- {
- // PDF export requires GD
- $aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
- }
- }
- $aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
- $aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
-
- break;
-
- case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
- // $param is a DBObject
- $oObj = $param;
- $sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
- $oFilter = DBObjectSearch::FromOQL($sOQL);
- $sFilter = $oFilter->serialize();
- $sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
- $sUIPage = cmdbAbstractObject::ComputeStandardUIPage(get_class($oObj));
- $oAppContext = new ApplicationContext();
- $sContext = $oAppContext->GetForLink();
- $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
- $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
- $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
- $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
- $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
- $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
-
- $aResult = array(
- new SeparatorPopupMenuItem(),
- // Static menus: Email this page & CSV Export
- new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
- new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"),
- new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
- new SeparatorPopupMenuItem(),
- new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
- );
- break;
-
- case iPopupMenuExtension::MENU_DASHBOARD_ACTIONS:
- // $param is a Dashboard
- $oAppContext = new ApplicationContext();
- $aParams = $oAppContext->GetAsHash();
- $sMenuId = ApplicationMenu::GetActiveNodeId();
- $sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
- $sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
- $sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
- $aResult = array(
- new SeparatorPopupMenuItem(),
- new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sMenuId),
- new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sMenuId', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn' })"),
- );
- break;
-
- default:
- // Unknown type of menu, do nothing
- $aResult = array();
- }
- foreach($aResult as $oMenuItem)
- {
- $aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
- }
-
- // Invoke the plugins
- //
- foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
- {
- if (is_object($param) && !($param instanceof DBObject))
- {
- $tmpParam = clone $param; // In case the parameter is an DBObjectSet, clone it to prevent alterations
- }
- else
- {
- $tmpParam = $param;
- }
- foreach($oExtensionInstance->EnumItems($iMenuId, $tmpParam) as $oMenuItem)
- {
- if (is_object($oMenuItem))
- {
- $aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
-
- foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
- {
- $oPage->add_linked_script($sLinkedScript);
- }
- }
- }
- }
- }
- /**
- * Get target configuration file name (including full path)
- */
- public static function GetConfigFilePath($sEnvironment = null)
- {
- if (is_null($sEnvironment))
- {
- $sEnvironment = self::GetCurrentEnvironment();
- }
- return APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE;
- }
-
- /**
- * @return string the absolute URL to the modules root path
- */
- static public function GetAbsoluteUrlModulesRoot()
- {
- $sUrl = self::GetAbsoluteUrlAppRoot().'env-'.self::GetCurrentEnvironment().'/';
- return $sUrl;
- }
-
- /**
- * To be compatible with this mechanism, the called page must include approot with an absolute path OR not include
- * it at all (losing the direct access to the page) :
- *
- * ```php
- * if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
- * require_once(__DIR__.'/../../approot.inc.php');
- * ```
- *
- * @param string $sModule
- * @param string $sPage
- * @param string[] $aArguments
- * @param string $sEnvironment
- *
- * @return string the URL to a page that will execute the requested module page, with query string values url encoded
- *
- * @see GetExecPageArguments can be used to submit using the GET method (see bug in N.1108)
- * @see GetAbsoluteUrlExecPage
- */
- static public function GetAbsoluteUrlModulePage($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
- {
- $aArgs = self::GetExecPageArguments($sModule, $sPage, $aArguments, $sEnvironment);
- $sArgs = http_build_query($aArgs);
-
- return self::GetAbsoluteUrlExecPage()."?".$sArgs;
- }
-
- /**
- * @param string $sModule
- * @param string $sPage
- * @param string[] $aArguments
- * @param string $sEnvironment
- *
- * @return string[] key/value pair for the exec page query string. Warning : values are not url encoded !
- * @throws \Exception if one of the argument has a reserved name
- */
- static public function GetExecPageArguments($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
- {
- $sEnvironment = is_null($sEnvironment) ? self::GetCurrentEnvironment() : $sEnvironment;
- $aArgs = array();
- $aArgs['exec_module'] = $sModule;
- $aArgs['exec_page'] = $sPage;
- $aArgs['exec_env'] = $sEnvironment;
- foreach($aArguments as $sName => $sValue)
- {
- if (($sName == 'exec_module') || ($sName == 'exec_page') || ($sName == 'exec_env'))
- {
- throw new Exception("Module page: $sName is a reserved page argument name");
- }
- $aArgs[$sName] = $sValue;
- }
-
- return $aArgs;
- }
-
- /**
- * @return string
- */
- static public function GetAbsoluteUrlExecPage()
- {
- return self::GetAbsoluteUrlAppRoot().'pages/exec.php';
- }
-
- /**
- * Returns a name unique amongst the given list
- * @param string $sProposed The default value
- * @param array $aExisting An array of existing values (strings)
- */
- static public function MakeUniqueName($sProposed, $aExisting)
- {
- if (in_array($sProposed, $aExisting))
- {
- $i = 1;
- while (in_array($sProposed.$i, $aExisting) && ($i < 50))
- {
- $i++;
- }
- return $sProposed.$i;
- }
- else
- {
- return $sProposed;
- }
- }
-
- /**
- * Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
- * @param string $sId The ID to sanitize
- * @return string The sanitized ID
- */
- static public function GetSafeId($sId)
- {
- return str_replace(array(':', '[', ']', '+', '-'), '_', $sId);
- }
-
- /**
- * Helper to execute an HTTP POST request
- * Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
- * originaly named after do_post_request
- * Does not require cUrl but requires openssl for performing https POSTs.
- *
- * @param string $sUrl The URL to POST the data to
- * @param hash $aData The data to POST as an array('param_name' => value)
- * @param string $sOptionnalHeaders Additional HTTP headers as a string with newlines between headers
- * @param hash $aResponseHeaders An array to be filled with reponse headers: WARNING: the actual content of the array depends on the library used: cURL or fopen, test with both !! See: http://fr.php.net/manual/en/function.curl-getinfo.php
- * @param hash $aCurlOptions An (optional) array of options to pass to curl_init. The format is 'option_code' => 'value'. These values have precedence over the default ones. Example: CURLOPT_SSLVERSION => CURL_SSLVERSION_SSLv3
- * @return string The result of the POST request
- * @throws Exception
- */
- static public function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array())
- {
- // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
-
- if (function_exists('curl_init'))
- {
- // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options
- // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
- // by setting the SSLVERSION to 3 as done below.
- $aHeaders = explode("\n", $sOptionnalHeaders);
- $aHTTPHeaders = array();
- foreach($aHeaders as $sHeaderString)
- {
- if(preg_match('/^([^:]): (.+)$/', $sHeaderString, $aMatches))
- {
- $aHTTPHeaders[$aMatches[1]] = $aMatches[2];
- }
- }
- // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions
- $aOptions = array(
- CURLOPT_RETURNTRANSFER => true, // return the content of the request
- CURLOPT_HEADER => false, // don't return the headers in the output
- CURLOPT_FOLLOWLOCATION => true, // follow redirects
- CURLOPT_ENCODING => "", // handle all encodings
- CURLOPT_USERAGENT => "spider", // who am i
- CURLOPT_AUTOREFERER => true, // set referer on redirect
- CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect
- CURLOPT_TIMEOUT => 120, // timeout on response
- CURLOPT_MAXREDIRS => 10, // stop after 10 redirects
- CURLOPT_SSL_VERIFYPEER => false, // Disabled SSL Cert checks
- // SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why
- // but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
- // CURLOPT_SSLVERSION => 3,
- CURLOPT_POST => count($aData),
- CURLOPT_POSTFIELDS => http_build_query($aData),
- CURLOPT_HTTPHEADER => $aHTTPHeaders,
- );
-
- $aAllOptions = $aCurlOptions + $aOptions;
- $ch = curl_init($sUrl);
- curl_setopt_array($ch, $aAllOptions);
- $response = curl_exec($ch);
- $iErr = curl_errno($ch);
- $sErrMsg = curl_error( $ch );
- $aHeaders = curl_getinfo( $ch );
- if ($iErr !== 0)
- {
- throw new Exception("Problem opening URL: $sUrl, $sErrMsg");
- }
- if (is_array($aResponseHeaders))
- {
- $aHeaders = curl_getinfo($ch);
- foreach($aHeaders as $sCode => $sValue)
- {
- $sName = str_replace(' ' , '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type"
- $aResponseHeaders[$sName] = $sValue;
- }
- }
- curl_close( $ch );
- }
- else
- {
- // cURL is not available let's try with streams and fopen...
-
- $sData = http_build_query($aData);
- $aParams = array('http' => array(
- 'method' => 'POST',
- 'content' => $sData,
- 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n",
- ));
- if ($sOptionnalHeaders !== null)
- {
- $aParams['http']['header'] .= $sOptionnalHeaders;
- }
- $ctx = stream_context_create($aParams);
-
- $fp = @fopen($sUrl, 'rb', false, $ctx);
- if (!$fp)
- {
- global $php_errormsg;
- if (isset($php_errormsg))
- {
- throw new Exception("Wrong URL: $sUrl, $php_errormsg");
- }
- elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl'))
- {
- throw new Exception("Cannot connect to $sUrl: missing module 'openssl'");
- }
- else
- {
- throw new Exception("Wrong URL: $sUrl");
- }
- }
- $response = @stream_get_contents($fp);
- if ($response === false)
- {
- throw new Exception("Problem reading data from $sUrl, $php_errormsg");
- }
- if (is_array($aResponseHeaders))
- {
- $aMeta = stream_get_meta_data($fp);
- $aHeaders = $aMeta['wrapper_data'];
- foreach($aHeaders as $sHeaderString)
- {
- if(preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches))
- {
- $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]);
- }
- }
- }
- }
- return $response;
- }
-
- /**
- * Get a standard list of character sets
- *
- * @param array $aAdditionalEncodings Additional values
- * @return array of iconv code => english label, sorted by label
- */
- public static function GetPossibleEncodings($aAdditionalEncodings = array())
- {
- // Encodings supported:
- // ICONV_CODE => Display Name
- // Each iconv installation supports different encodings
- // Some reasonably common and useful encodings are listed here
- $aPossibleEncodings = array(
- 'UTF-8' => 'Unicode (UTF-8)',
- 'ISO-8859-1' => 'Western (ISO-8859-1)',
- 'WINDOWS-1251' => 'Cyrilic (Windows 1251)',
- 'WINDOWS-1252' => 'Western (Windows 1252)',
- 'ISO-8859-15' => 'Western (ISO-8859-15)',
- );
- $aPossibleEncodings = array_merge($aPossibleEncodings, $aAdditionalEncodings);
- asort($aPossibleEncodings);
- return $aPossibleEncodings;
- }
-
- /**
- * Convert a string containing some (valid) HTML markup to plain text
- * @param string $sHtml
- * @return string
- */
- public static function HtmlToText($sHtml)
- {
- try
- {
- //return ''.$sHtml;
- return \Html2Text\Html2Text::convert(''.$sHtml);
- }
- catch(Exception $e)
- {
- return $e->getMessage();
- }
- }
-
- /**
- * Convert (?) plain text to some HTML markup by replacing newlines by
tags
- * and escaping HTML entities
- * @param string $sText
- * @return string
- */
- public static function TextToHtml($sText)
- {
- $sText = str_replace("\r\n", "\n", $sText);
- $sText = str_replace("\r", "\n", $sText);
- return str_replace("\n", '
', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
- }
-
- /**
- * Eventually compiles the SASS (.scss) file into the CSS (.css) file
- *
- * @param string $sSassRelPath Relative path to the SCSS file (must have the extension .scss)
- * @param array $aImportPaths Array of absolute paths to load imports from
- * @return string Relative path to the CSS file (.css)
- */
- static public function GetCSSFromSASS($sSassRelPath, $aImportPaths = null)
- {
- // Avoiding compilation if file is already a css file.
- if (preg_match('/\.css$/', $sSassRelPath))
- {
- return $sSassRelPath;
- }
-
- // Setting import paths
- if ($aImportPaths === null)
- {
- $aImportPaths = array();
- }
- $aImportPaths[] = APPROOT . '/css';
-
- $sSassPath = APPROOT.$sSassRelPath;
- $sCssRelPath = preg_replace('/\.scss$/', '.css', $sSassRelPath);
- $sCssPath = APPROOT.$sCssRelPath;
- clearstatcache();
- if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSassPath))))
- {
- require_once(APPROOT.'lib/scssphp/scss.inc.php');
- $oScss = new Compiler();
- $oScss->setImportPaths($aImportPaths);
- $oScss->setFormatter('Leafo\\ScssPhp\\Formatter\\Expanded');
- // Temporary disabling max exec time while compiling
- $iCurrentMaxExecTime = (int) ini_get('max_execution_time');
- set_time_limit(0);
- $sCss = $oScss->compile(file_get_contents($sSassPath));
- set_time_limit($iCurrentMaxExecTime);
- file_put_contents($sCssPath, $sCss);
- }
- return $sCssRelPath;
- }
-
- static public function GetImageSize($sImageData)
- {
- if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
- {
- $aRet = @getimagesizefromstring($sImageData);
- }
- else if(ini_get('allow_url_fopen'))
- {
- // work around to avoid creating a tmp file
- $sUri = 'data://application/octet-stream;base64,'.base64_encode($sImageData);
- $aRet = @getimagesize($sUri);
- }
- else
- {
- // Damned, need to create a tmp file
- $sTempFile = tempnam(SetupUtils::GetTmpDir(), 'img-');
- @file_put_contents($sTempFile, $sImageData);
- $aRet = @getimagesize($sTempFile);
- @unlink($sTempFile);
- }
- return $aRet;
- }
-
- /**
- * Resize an image attachment so that it fits in the given dimensions
- * @param ormDocument $oImage The original image stored as an ormDocument
- * @param int $iWidth Image's original width
- * @param int $iHeight Image's original height
- * @param int $iMaxImageWidth Maximum width for the resized image
- * @param int $iMaxImageHeight Maximum height for the resized image
- * @return ormDocument The resampled image
- */
- public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
- {
- // If image size smaller than maximums, we do nothing
- if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
- {
- return $oImage;
- }
-
-
- // If gd extension is not loaded, we put a warning in the log and return the image as is
- if (extension_loaded('gd') === false)
- {
- IssueLog::Warning('Image could not be resized as the "gd" extension does not seem to be loaded. It will remain as ' . $iWidth . 'x' . $iHeight . ' instead of ' . $iMaxImageWidth . 'x' . $iMaxImageHeight);
- return $oImage;
- }
-
-
- switch($oImage->GetMimeType())
- {
- case 'image/gif':
- case 'image/jpeg':
- case 'image/png':
- $img = @imagecreatefromstring($oImage->GetData());
- break;
-
- default:
- // Unsupported image type, return the image as-is
- //throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
- return $oImage;
- }
- if ($img === false)
- {
- //throw new Exception("Warning: corrupted image: '".$oImage->GetFileName()." / ".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
- return $oImage;
- }
- else
- {
- // Let's scale the image, preserving the transparency for GIFs and PNGs
-
- $fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
-
- $iNewWidth = $iWidth * $fScale;
- $iNewHeight = $iHeight * $fScale;
-
- $new = imagecreatetruecolor($iNewWidth, $iNewHeight);
-
- // Preserve transparency
- if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
- {
- imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
- imagealphablending($new, false);
- imagesavealpha($new, true);
- }
-
- imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
-
- ob_start();
- switch ($oImage->GetMimeType())
- {
- case 'image/gif':
- imagegif($new); // send image to output buffer
- break;
-
- case 'image/jpeg':
- imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
- break;
-
- case 'image/png':
- imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
- break;
- }
- $oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
- @ob_end_clean();
-
- imagedestroy($img);
- imagedestroy($new);
-
- return $oResampledImage;
- }
-
- }
-
- /**
- * Create a 128 bit UUID in the format: {########-####-####-####-############}
- *
- * Note: this method can be run from the command line as well as from the web server.
- * Note2: this method is not cryptographically secure! If you need a cryptographically secure value
- * consider using open_ssl or PHP 7 methods.
- * @param string $sPrefix
- * @return string
- */
- static public function CreateUUID($sPrefix = '')
- {
- $uid = uniqid("", true);
- $data = $sPrefix;
- $data .= __FILE__;
- $data .= mt_rand();
- $hash = strtoupper(hash('ripemd128', $uid . md5($data)));
- $sUUID = '{' .
- substr($hash, 0, 8) .
- '-' .
- substr($hash, 8, 4) .
- '-' .
- substr($hash, 12, 4) .
- '-' .
- substr($hash, 16, 4) .
- '-' .
- substr($hash, 20, 12) .
- '}';
- return $sUUID;
- }
-
- /**
- * Returns the name of the module containing the file where the call to this function is made
- * or an empty string if no such module is found (or not called within a module file)
- * @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
- * @return string
- */
- static public function GetCurrentModuleName($iCallDepth = 0)
- {
- $sCurrentModuleName = '';
- $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
-
- foreach(GetModulesInfo() as $sModuleName => $aInfo)
- {
- if ($aInfo['root_dir'] !== '')
- {
- $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
-
- if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
- {
- $sCurrentModuleName = $sModuleName;
- break;
- }
- }
- }
- return $sCurrentModuleName;
- }
-
- /**
- * Returns the relative (to APPROOT) path of the root directory of the module containing the file where the call to this function is made
- * or an empty string if no such module is found (or not called within a module file)
- * @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
- * @return string
- */
- static public function GetCurrentModuleDir($iCallDepth)
- {
- $sCurrentModuleDir = '';
- $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
-
- foreach(GetModulesInfo() as $sModuleName => $aInfo)
- {
- if ($aInfo['root_dir'] !== '')
- {
- $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
-
- if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
- {
- $sCurrentModuleDir = basename($sRootDir);
- break;
- }
- }
- }
- return $sCurrentModuleDir;
- }
-
- /**
- * Returns the base URL for all files in the current module from which this method is called
- * or an empty string if no such module is found (or not called within a module file)
- * @return string
- */
- static public function GetCurrentModuleUrl()
- {
- $sDir = static::GetCurrentModuleDir(1);
- if ( $sDir !== '')
- {
- return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
- }
- return '';
- }
-
- /**
- * Get the value of a given setting for the current module
- * @param string $sProperty The name of the property to retrieve
- * @param mixed $defaultvalue
- * @return mixed
- */
- static public function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
- {
- $sModuleName = static::GetCurrentModuleName(1);
- return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
- }
-
- /**
- * Get the compiled version of a given module, as it was seen by the compiler
- * @param string $sModuleName
- * @return string|NULL
- */
- static public function GetCompiledModuleVersion($sModuleName)
- {
- $aModulesInfo = GetModulesInfo();
- if (array_key_exists($sModuleName, $aModulesInfo))
- {
- return $aModulesInfo[$sModuleName]['version'];
- }
- return null;
- }
-
- /**
- * Check if the given path/url is an http(s) URL
- * @param string $sPath
- * @return boolean
- */
- public static function IsURL($sPath)
- {
- $bRet = false;
- if ((substr($sPath, 0, 7) == 'http://') || (substr($sPath, 0, 8) == 'https://') || (substr($sPath, 0, 8) == 'ftp://'))
- {
- $bRet = true;
- }
- return $bRet;
- }
-
- /**
- * Check if the given URL is a link to download a document/image on the CURRENT iTop
- * In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
- * @param string $sPath
- * @return false|ormDocument
- * @throws Exception
- */
- public static function IsSelfURL($sPath)
- {
- $result = false;
- $sPageUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php';
- if (substr($sPath, 0, strlen($sPageUrl)) == $sPageUrl)
- {
- // If the URL is an URL pointing to this instance of iTop, then
- // extract the "query" part of the URL and analyze it
- $sQuery = parse_url($sPath, PHP_URL_QUERY);
- if ($sQuery !== null)
- {
- $aParams = array();
- foreach(explode('&', $sQuery) as $sChunk)
- {
- $aParts = explode('=', $sChunk);
- if (count($aParts) != 2) continue;
- $aParams[$aParts[0]] = urldecode($aParts[1]);
- }
- $result = array_key_exists('operation', $aParams) && array_key_exists('class', $aParams) && array_key_exists('id', $aParams) && array_key_exists('field', $aParams) && ($aParams['operation'] == 'download_document');
- if ($result)
- {
- // This is a 'download_document' operation, let's retrieve the document directly from the database
- $sClass = $aParams['class'];
- $iKey = $aParams['id'];
- $sAttCode = $aParams['field'];
-
- $oObj = MetaModel::GetObject($sClass, $iKey, false /* must exist */); // Users rights apply here !!
- if ($oObj)
- {
- /**
- * @var ormDocument $result
- */
- $result = clone $oObj->Get($sAttCode);
- return $result;
- }
- }
- }
- throw new Exception('Invalid URL. This iTop URL is not pointing to a valid Document/Image.');
- }
- return $result;
- }
-
- /**
- * Read the content of a file (and retrieve its MIME type) from either:
- * - an URL pointing to a blob (image/document) on the current iTop server
- * - an http(s) URL
- * - the local file system (but only if you are an administrator)
- * @param string $sPath
- * @return ormDocument|null
- * @throws Exception
- */
- public static function FileGetContentsAndMIMEType($sPath)
- {
- $oUploadedDoc = null;
- $aKnownExtensions = array(
- 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
- 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
- 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
- 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
- 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
- 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
- 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
- 'jpg' => 'image/jpeg',
- 'jpeg' => 'image/jpeg',
- 'gif' => 'image/gif',
- 'png' => 'image/png',
- 'pdf' => 'application/pdf',
- 'doc' => 'application/msword',
- 'dot' => 'application/msword',
- 'xls' => 'application/vnd.ms-excel',
- 'ppt' => 'application/vnd.ms-powerpoint',
- 'vsd' => 'application/x-visio',
- 'vdx' => 'application/visio.drawing',
- 'odt' => 'application/vnd.oasis.opendocument.text',
- 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
- 'odp' => 'application/vnd.oasis.opendocument.presentation',
- 'zip' => 'application/zip',
- 'txt' => 'text/plain',
- 'htm' => 'text/html',
- 'html' => 'text/html',
- 'exe' => 'application/octet-stream'
- );
-
- $sData = null;
- $sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
- $sFileName = 'uploaded-file'; // Default name for downloaded-files
- $sExtension = '.txt'; // Default file extension in case we don't know the MIME Type
-
- if(empty($sPath))
- {
- // Empty path (NULL or '') means that there is no input, making an empty document.
- $oUploadedDoc = new ormDocument('', '', '');
- }
- elseif (static::IsURL($sPath))
- {
- if ($oUploadedDoc = static::IsSelfURL($sPath))
- {
- // Nothing more to do, we've got it !!
- }
- else
- {
- // Remote file, let's use the HTTP headers to find the MIME Type
- $sData = @file_get_contents($sPath);
- if ($sData === false)
- {
- throw new Exception("Failed to load the file from the URL '$sPath'.");
- }
- else
- {
- if (isset($http_response_header))
- {
- $aHeaders = static::ParseHeaders($http_response_header);
- $sMimeType = array_key_exists('Content-Type', $aHeaders) ? strtolower($aHeaders['Content-Type']) : 'application/x-octet-stream';
- // Compute the file extension from the MIME Type
- foreach($aKnownExtensions as $sExtValue => $sMime)
- {
- if ($sMime === $sMimeType)
- {
- $sExtension = '.'.$sExtValue;
- break;
- }
- }
- }
- $sFileName .= $sExtension;
- }
- $oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
- }
- }
- else if (UserRights::IsAdministrator())
- {
- // Only administrators are allowed to read local files
- $sData = @file_get_contents($sPath);
- if ($sData === false)
- {
- throw new Exception("Failed to load the file '$sPath'. The file does not exist or the current process is not allowed to access it.");
- }
- $sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
- $sFileName = basename($sPath);
-
- if (array_key_exists($sExtension, $aKnownExtensions))
- {
- $sMimeType = $aKnownExtensions[$sExtension];
- }
- else if (extension_loaded('fileinfo'))
- {
- $finfo = new finfo(FILEINFO_MIME);
- $sMimeType = $finfo->file($sPath);
- }
- $oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
- }
- return $oUploadedDoc;
- }
-
- protected static function ParseHeaders($aHeaders)
- {
- $aCleanHeaders = array();
- foreach( $aHeaders as $sKey => $sValue )
- {
- $aTokens = explode(':', $sValue, 2);
- if(isset($aTokens[1]))
- {
- $aCleanHeaders[trim($aTokens[0])] = trim($aTokens[1]);
- }
- else
- {
- // The header is not in the form Header-Code: Value
- $aCleanHeaders[] = $sValue; // Store the value as-is
- $aMatches = array();
- // Check if it's not the HTTP response code
- if( preg_match("|HTTP/[0-9\.]+\s+([0-9]+)|", $sValue, $aMatches) )
- {
- $aCleanHeaders['reponse_code'] = intval($aMatches[1]);
- }
- }
- }
- return $aCleanHeaders;
- }
-
- /**
- * Return a string based on compilation time or (if not available because the datamodel has not been loaded)
- * the version of iTop. This string is useful to prevent browser side caching of content that may vary at each
- * (re)installation of iTop (especially during development).
- * @return string
- */
- public static function GetCacheBusterTimestamp()
- {
- if(!defined('COMPILATION_TIMESTAMP'))
- {
- return ITOP_VERSION;
- }
- return COMPILATION_TIMESTAMP;
- }
-
- /**
- * Check if the given class if configured as a high cardinality class.
- *
- * @param $sClass
- *
- * @return bool
- */
- public static function IsHighCardinality($sClass)
- {
- if (utils::GetConfig()->Get('search_manual_submit'))
- {
- return true;
- }
- $aHugeClasses = MetaModel::GetConfig()->Get('high_cardinality_classes');
- return in_array($sClass, $aHugeClasses);
- }
-
- /**
- * Check if iTop is in a development environment (VCS vs build number)
- *
- * @return bool
- */
- public static function IsDevelopmentEnvironment()
- {
- return ITOP_REVISION === 'svn';
- }
-}
+
+
+
+/**
+ * Static class utils
+ *
+ * @copyright Copyright (C) 2010-2017 Combodo SARL
+ * @license http://opensource.org/licenses/AGPL-3.0
+ */
+
+require_once(APPROOT.'/core/config.class.inc.php');
+require_once(APPROOT.'/application/transaction.class.inc.php');
+require_once(APPROOT.'application/Html2Text.php');
+require_once(APPROOT.'application/Html2TextException.php');
+
+define('ITOP_CONFIG_FILE', 'config-itop.php');
+define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE);
+
+define('SERVER_NAME_PLACEHOLDER', '$SERVER_NAME$');
+
+define('SERVER_MAX_URL_LENGTH', 2048);
+
+class FileUploadException extends Exception
+{
+}
+
+
+/**
+ * Helper functions to interact with forms: read parameters, upload files...
+ * @package iTop
+ */
+class utils
+{
+ private static $oConfig = null;
+ private static $m_bCASClient = false;
+
+ // Parameters loaded from a file, parameters of the page/command line still have precedence
+ private static $m_aParamsFromFile = null;
+ private static $m_aParamSource = array();
+
+ protected static function LoadParamFile($sParamFile)
+ {
+ if (!file_exists($sParamFile))
+ {
+ throw new Exception("Could not find the parameter file: '$sParamFile'");
+ }
+ if (!is_readable($sParamFile))
+ {
+ throw new Exception("Could not load parameter file: '$sParamFile'");
+ }
+ $sParams = file_get_contents($sParamFile);
+
+ if (is_null(self::$m_aParamsFromFile))
+ {
+ self::$m_aParamsFromFile = array();
+ }
+
+ $aParamLines = explode("\n", $sParams);
+ foreach ($aParamLines as $sLine)
+ {
+ $sLine = trim($sLine);
+
+ // Ignore the line after a '#'
+ if (($iCommentPos = strpos($sLine, '#')) !== false)
+ {
+ $sLine = substr($sLine, 0, $iCommentPos);
+ $sLine = trim($sLine);
+ }
+
+ // Note: the line is supposed to be already trimmed
+ if (preg_match('/^(\S*)\s*=(.*)$/', $sLine, $aMatches))
+ {
+ $sParam = $aMatches[1];
+ $value = trim($aMatches[2]);
+ self::$m_aParamsFromFile[$sParam] = $value;
+ self::$m_aParamSource[$sParam] = $sParamFile;
+ }
+ }
+ }
+
+ public static function UseParamFile($sParamFileArgName = 'param_file', $bAllowCLI = true)
+ {
+ $sFileSpec = self::ReadParam($sParamFileArgName, '', $bAllowCLI, 'raw_data');
+ foreach(explode(',', $sFileSpec) as $sFile)
+ {
+ $sFile = trim($sFile);
+ if (!empty($sFile))
+ {
+ self::LoadParamFile($sFile);
+ }
+ }
+ }
+
+ /**
+ * Return the source file from which the parameter has been found,
+ * usefull when it comes to pass user credential to a process executed
+ * in the background
+ * @param $sName Parameter name
+ * @return The file name if any, or null
+ */
+ public static function GetParamSourceFile($sName)
+ {
+ if (array_key_exists($sName, self::$m_aParamSource))
+ {
+ return self::$m_aParamSource[$sName];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public static function IsModeCLI()
+ {
+ $sSAPIName = php_sapi_name();
+ $sCleanName = strtolower(trim($sSAPIName));
+ if ($sCleanName == 'cli')
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ protected static $bPageMode = null;
+ /**
+ * @var boolean[]
+ */
+ protected static $aModes = array();
+
+ public static function InitArchiveMode()
+ {
+ if (isset($_SESSION['archive_mode']))
+ {
+ $iDefault = $_SESSION['archive_mode'];
+ }
+ else
+ {
+ $iDefault = 0;
+ }
+ // Read and record the value for switching the archive mode
+ $iCurrent = self::ReadParam('with-archive', $iDefault);
+ if (isset($_SESSION))
+ {
+ $_SESSION['archive_mode'] = $iCurrent;
+ }
+ // Read and use the value for the current page (web services)
+ $iCurrent = self::ReadParam('with_archive', $iCurrent, true);
+ self::$bPageMode = ($iCurrent == 1);
+ }
+
+ /**
+ * @param boolean $bMode if true then activate archive mode (archived objects are visible), otherwise archived objects are
+ * hidden (archive = "soft deletion")
+ */
+ public static function PushArchiveMode($bMode)
+ {
+ array_push(self::$aModes, $bMode);
+ }
+
+ public static function PopArchiveMode()
+ {
+ array_pop(self::$aModes);
+ }
+
+ /**
+ * @return boolean true if archive mode is enabled
+ */
+ public static function IsArchiveMode()
+ {
+ if (count(self::$aModes) > 0)
+ {
+ $bRet = end(self::$aModes);
+ }
+ else
+ {
+ if (self::$bPageMode === null)
+ {
+ self::InitArchiveMode();
+ }
+ $bRet = self::$bPageMode;
+ }
+ return $bRet;
+ }
+
+ /**
+ * Helper to be called by the GUI and define if the user will see obsolete data (otherwise, the user will have to dig further)
+ * @return bool
+ */
+ public static function ShowObsoleteData()
+ {
+ $bDefault = MetaModel::GetConfig()->Get('obsolescence.show_obsolete_data'); // default is false
+ $bShow = appUserPreferences::GetPref('show_obsolete_data', $bDefault);
+ if (static::IsArchiveMode())
+ {
+ $bShow = true;
+ }
+ return $bShow;
+ }
+
+ public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false, $sSanitizationFilter = 'parameter')
+ {
+ global $argv;
+ $retValue = $defaultValue;
+
+ if (!is_null(self::$m_aParamsFromFile))
+ {
+ if (isset(self::$m_aParamsFromFile[$sName]))
+ {
+ $retValue = self::$m_aParamsFromFile[$sName];
+ }
+ }
+
+ if (isset($_REQUEST[$sName]))
+ {
+ $retValue = $_REQUEST[$sName];
+ }
+ elseif ($bAllowCLI && isset($argv))
+ {
+ foreach($argv as $iArg => $sArg)
+ {
+ if (preg_match('/^--'.$sName.'=(.*)$/', $sArg, $aMatches))
+ {
+ $retValue = $aMatches[1];
+ }
+ }
+ }
+ return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
+ }
+
+ public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
+ {
+ $retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
+ return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
+ }
+
+ public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
+ {
+ if ($value === $defaultValue)
+ {
+ // Preserve the real default value (can be used to detect missing mandatory parameters)
+ $retValue = $value;
+ }
+ else
+ {
+ $retValue = self::Sanitize_Internal($value, $sSanitizationFilter);
+ if ($retValue === false)
+ {
+ $retValue = $defaultValue;
+ }
+ }
+ return $retValue;
+ }
+
+ protected static function Sanitize_Internal($value, $sSanitizationFilter)
+ {
+ switch($sSanitizationFilter)
+ {
+ case 'integer':
+ $retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT);
+ break;
+
+ case 'class':
+ $retValue = $value;
+ if (!MetaModel::IsValidClass($value))
+ {
+ $retValue = false;
+ }
+ break;
+
+ case 'string':
+ $retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
+ break;
+
+ case 'context_param':
+ case 'parameter':
+ case 'field_name':
+ if (is_array($value))
+ {
+ $retValue = array();
+ foreach($value as $key => $val)
+ {
+ $retValue[$key] = self::Sanitize_Internal($val, $sSanitizationFilter); // recursively check arrays
+ if ($retValue[$key] === false)
+ {
+ $retValue = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ switch($sSanitizationFilter)
+ {
+ case 'parameter':
+ $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^([ A-Za-z0-9_=-]|%3D|%2B|%2F)*$/'))); // the '=', '%3D, '%2B', '%2F' characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
+ break;
+
+ case 'field_name':
+ $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
+ break;
+
+ case 'context_param':
+ $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[ A-Za-z0-9_=%:+-]*$/')));
+ break;
+
+ }
+ }
+ break;
+
+ default:
+ case 'raw_data':
+ $retValue = $value;
+ // Do nothing
+ }
+ return $retValue;
+ }
+
+ /**
+ * Reads an uploaded file and turns it into an ormDocument object - Triggers an exception in case of error
+ * @param string $sName Name of the input used from uploading the file
+ * @param string $sIndex If Name is an array of posted files, then the index must be used to point out the file
+ * @return ormDocument The uploaded file (can be 'empty' if nothing was uploaded)
+ */
+ public static function ReadPostedDocument($sName, $sIndex = null)
+ {
+ $oDocument = new ormDocument(); // an empty document
+ if(isset($_FILES[$sName]))
+ {
+ $aFileInfo = $_FILES[$sName];
+
+ $sError = is_null($sIndex) ? $aFileInfo['error'] : $aFileInfo['error'][$sIndex];
+ switch($sError)
+ {
+ case UPLOAD_ERR_OK:
+ $sTmpName = is_null($sIndex) ? $aFileInfo['tmp_name'] : $aFileInfo['tmp_name'][$sIndex];
+ $sMimeType = is_null($sIndex) ? $aFileInfo['type'] : $aFileInfo['type'][$sIndex];
+ $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
+
+ $doc_content = file_get_contents($sTmpName);
+ if (function_exists('finfo_file'))
+ {
+ // as of PHP 5.3 the fileinfo extension is bundled within PHP
+ // in which case we don't trust the mime type provided by the browser
+ $rInfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
+ if ($rInfo !== false)
+ {
+ $sType = @finfo_file($rInfo, $sTmpName);
+ if ( ($sType !== false)
+ && is_string($sType)
+ && (strlen($sType)>0))
+ {
+ $sMimeType = $sType;
+ }
+ }
+ @finfo_close($rInfo);
+ }
+ $oDocument = new ormDocument($doc_content, $sMimeType, $sName);
+ break;
+
+ case UPLOAD_ERR_NO_FILE:
+ // no file to load, it's a normal case, just return an empty document
+ break;
+
+ case UPLOAD_ERR_FORM_SIZE:
+ case UPLOAD_ERR_INI_SIZE:
+ throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
+ break;
+
+ case UPLOAD_ERR_PARTIAL:
+ throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
+ break;
+
+ case UPLOAD_ERR_NO_TMP_DIR:
+ throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
+ break;
+
+ case UPLOAD_ERR_CANT_WRITE:
+ throw new FileUploadException(Dict::Format('UI:Error:CannotWriteToTmp_Dir', ini_get('upload_tmp_dir')));
+ break;
+
+ case UPLOAD_ERR_EXTENSION:
+ $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
+ throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
+ break;
+
+ default:
+ throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
+ break;
+
+ }
+ }
+ return $oDocument;
+ }
+
+ /**
+ * Interprets the results posted by a normal or paginated list (in multiple selection mode)
+ *
+ * @param $oFullSetFilter DBSearch The criteria defining the whole sets of objects being selected
+ *
+ * @return Array An array of object IDs corresponding to the objects selected in the set
+ */
+ public static function ReadMultipleSelection($oFullSetFilter)
+ {
+ $aSelectedObj = utils::ReadParam('selectObject', array());
+ $sSelectionMode = utils::ReadParam('selectionMode', '');
+ if ($sSelectionMode != '')
+ {
+ // Paginated selection
+ $aExceptions = utils::ReadParam('storedSelection', array());
+ if ($sSelectionMode == 'positive')
+ {
+ // Only the explicitely listed items are selected
+ $aSelectedObj = $aExceptions;
+ }
+ else
+ {
+ // All items of the set are selected, except the one explicitely listed
+ $aSelectedObj = array();
+ $oFullSet = new DBObjectSet($oFullSetFilter);
+ $sClassAlias = $oFullSetFilter->GetClassAlias();
+ $oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
+ while($oObj = $oFullSet->Fetch())
+ {
+ if (!in_array($oObj->GetKey(), $aExceptions))
+ {
+ $aSelectedObj[] = $oObj->GetKey();
+ }
+ }
+ }
+ }
+ return $aSelectedObj;
+ }
+
+ /**
+ * Interprets the results posted by a normal or paginated list (in multiple selection mode)
+ *
+ * @param DBSearch $oFullSetFilter The criteria defining the whole sets of objects being selected
+ *
+ * @return Array An array of object IDs:friendlyname corresponding to the objects selected in the set
+ * @throws \CoreException
+ */
+ public static function ReadMultipleSelectionWithFriendlyname($oFullSetFilter)
+ {
+ $sSelectionMode = utils::ReadParam('selectionMode', '');
+
+ if ($sSelectionMode === '')
+ {
+ throw new CoreException('selectionMode is mandatory');
+ }
+
+ // Paginated selection
+ $aSelectedIds = utils::ReadParam('storedSelection', array());
+ if (count($aSelectedIds) > 0 )
+ {
+ if ($sSelectionMode == 'positive')
+ {
+ // Only the explicitly listed items are selected
+ $oFullSetFilter->AddCondition('id', $aSelectedIds, 'IN');
+ }
+ else
+ {
+ // All items of the set are selected, except the one explicitly listed
+ $oFullSetFilter->AddCondition('id', $aSelectedIds, 'NOTIN');
+ }
+ }
+
+ $aSelectedObj = array();
+ $oFullSet = new DBObjectSet($oFullSetFilter);
+ $sClassAlias = $oFullSetFilter->GetClassAlias();
+ $oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
+ while ($oObj = $oFullSet->Fetch())
+ {
+ $aSelectedObj[$oObj->GetKey()] = $oObj->Get('friendlyname');
+ }
+
+ return $aSelectedObj;
+ }
+
+ public static function GetNewTransactionId()
+ {
+ return privUITransaction::GetNewTransactionId();
+ }
+
+ public static function IsTransactionValid($sId, $bRemoveTransaction = true)
+ {
+ return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction);
+ }
+
+ public static function RemoveTransaction($sId)
+ {
+ return privUITransaction::RemoveTransaction($sId);
+ }
+
+ /**
+ * Returns a unique tmp id for the current upload based on the transaction system (db).
+ *
+ * Build as session_id() . '_' . static::GetNewTransactionId()
+ *
+ * @return string
+ */
+ public static function GetUploadTempId($sTransactionId = null)
+ {
+ if ($sTransactionId === null)
+ {
+ $sTransactionId = static::GetNewTransactionId();
+ }
+ return session_id() . '_' . $sTransactionId;
+ }
+
+ public static function ReadFromFile($sFileName)
+ {
+ if (!file_exists($sFileName)) return false;
+ return file_get_contents($sFileName);
+ }
+
+ /**
+ * Helper function to convert a value expressed in a 'user friendly format'
+ * as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes
+ * @param mixed $value The value as read from php.ini
+ * @return number
+ */
+ public static function ConvertToBytes( $value )
+ {
+ $iReturn = $value;
+ if ( !is_numeric( $value ) )
+ {
+ $iLength = strlen( $value );
+ $iReturn = substr( $value, 0, $iLength - 1 );
+ $sUnit = strtoupper( substr( $value, $iLength - 1 ) );
+ switch ( $sUnit )
+ {
+ case 'G':
+ $iReturn *= 1024;
+ case 'M':
+ $iReturn *= 1024;
+ case 'K':
+ $iReturn *= 1024;
+ }
+ }
+ return $iReturn;
+ }
+
+ /**
+ * Checks if the memory limit is at least what is required
+ *
+ * @param int $memoryLimit set limit in bytes
+ * @param int $requiredLimit required limit in bytes
+ * @return bool
+ */
+ public static function IsMemoryLimitOk($memoryLimit, $requiredLimit)
+ {
+ return ($memoryLimit >= $requiredLimit) || ($memoryLimit == -1);
+ }
+
+ /**
+ * Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
+ *
+ * @param type $value
+ * @return string
+ */
+ public static function BytesToFriendlyFormat($value)
+ {
+ $sReturn = '';
+ // Kilobytes
+ if ($value >= 1024)
+ {
+ $sReturn = 'K';
+ $value = $value / 1024;
+ }
+ // Megabytes
+ if ($value >= 1024)
+ {
+ $sReturn = 'M';
+ $value = $value / 1024;
+ }
+ // Gigabytes
+ if ($value >= 1024)
+ {
+ $sReturn = 'G';
+ $value = $value / 1024;
+ }
+ // Terabytes
+ if ($value >= 1024)
+ {
+ $sReturn = 'T';
+ $value = $value / 1024;
+ }
+
+ $value = round($value, 1);
+
+ return $value . '' . $sReturn . 'B';
+ }
+
+ /**
+ * Helper function to convert a string to a date, given a format specification. It replaces strtotime which does not allow for specifying a date in a french format (for instance)
+ * Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s')
+ * @param string $sDate
+ * @param string $sFormat
+ * @return timestamp or false if the input format is not correct
+ */
+ public static function StringToTime($sDate, $sFormat)
+ {
+ // Source: http://php.net/manual/fr/function.strftime.php
+ // (alternative: http://www.php.net/manual/fr/datetime.formats.date.php)
+ static $aDateTokens = null;
+ static $aDateRegexps = null;
+ if (is_null($aDateTokens))
+ {
+ $aSpec = array(
+ '%d' =>'(?[0-9]{2})',
+ '%m' => '(?[0-9]{2})',
+ '%y' => '(?[0-9]{2})',
+ '%Y' => '(?[0-9]{4})',
+ '%H' => '(?[0-2][0-9])',
+ '%i' => '(?[0-5][0-9])',
+ '%s' => '(?[0-5][0-9])',
+ );
+ $aDateTokens = array_keys($aSpec);
+ $aDateRegexps = array_values($aSpec);
+ }
+
+ $sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
+
+ if (preg_match('!^(?)'.$sDateRegexp.'(?)$!', $sDate, $aMatches))
+ {
+ $sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
+ $sMonth = isset($aMatches['month']) ? $aMatches['month'] : 1;
+ $sDay = isset($aMatches['day']) ? $aMatches['day'] : 1;
+ $sHour = isset($aMatches['hour']) ? $aMatches['hour'] : 0;
+ $sMinute = isset($aMatches['minute']) ? $aMatches['minute'] : 0;
+ $sSecond = isset($aMatches['second']) ? $aMatches['second'] : 0;
+ return strtotime("$sYear-$sMonth-$sDay $sHour:$sMinute:$sSecond");
+ }
+ else
+ {
+ return false;
+ }
+ // http://www.spaweditor.com/scripts/regex/index.php
+ }
+
+ /**
+ * Convert an old date/time format specifciation (using % placeholders)
+ * to a format compatible with DateTime::createFromFormat
+ * @param string $sOldDateTimeFormat
+ * @return string
+ */
+ static public function DateTimeFormatToPHP($sOldDateTimeFormat)
+ {
+ $aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s');
+ $aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's');
+ return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
+ }
+
+ /**
+ * @return \Config from the current environement, or if not existing from the production env, else new Config made from scratch
+ * @uses \MetaModel::GetConfig() don't forget to add the needed require_once(APPROOT.'core/metamodel.class.php');
+ */
+ static public function GetConfig()
+ {
+ if (self::$oConfig == null)
+ {
+ self::$oConfig = MetaModel::GetConfig();
+
+ if (self::$oConfig == null)
+ {
+ $sConfigFile = self::GetConfigFilePath();
+ if (!file_exists($sConfigFile))
+ {
+ $sConfigFile = self::GetConfigFilePath('production');
+ if (!file_exists($sConfigFile))
+ {
+ $sConfigFile = null;
+ }
+ }
+
+ self::$oConfig = new Config($sConfigFile);
+ }
+ }
+ return self::$oConfig;
+ }
+
+ public static function InitTimeZone() {
+ $oConfig = self::GetConfig();
+ $sItopTimeZone = $oConfig->Get('timezone');
+
+ if (!empty($sItopTimeZone))
+ {
+ date_default_timezone_set($sItopTimeZone);
+ }
+ else
+ {
+ // Leave as is... up to the admin to set a value somewhere...
+ // see http://php.net/manual/en/datetime.configuration.php#ini.date.timezone
+ }
+ }
+
+ /**
+ * Returns the absolute URL to the application root path
+ *
+ * @return string The absolute URL to the application root, without the first slash
+ *
+ * @throws \Exception
+ */
+ static public function GetAbsoluteUrlAppRoot()
+ {
+ static $sUrl = null;
+ if ($sUrl === null)
+ {
+ $sUrl = self::GetConfig()->Get('app_root_url');
+ if ($sUrl == '')
+ {
+ $sUrl = self::GetDefaultUrlAppRoot();
+ }
+ elseif (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
+ {
+ if (isset($_SERVER['SERVER_NAME']))
+ {
+ $sServerName = $_SERVER['SERVER_NAME'];
+ }
+ else
+ {
+ // CLI mode ?
+ $sServerName = php_uname('n');
+ }
+ $sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl);
+ }
+ }
+ return $sUrl;
+ }
+
+ /**
+ * Builds an root url from the server's variables.
+ * For most usages, when an root url is needed, use utils::GetAbsoluteUrlAppRoot() instead as uses this only as a fallback when the app_root_url conf parameter is not defined.
+ *
+ * @return string
+ *
+ * @throws \Exception
+ */
+ static public function GetDefaultUrlAppRoot()
+ {
+ // Build an absolute URL to this page on this server/port
+ $sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
+ $sProtocol = self::IsConnectionSecure() ? 'https' : 'http';
+ $iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
+ if ($sProtocol == 'http')
+ {
+ $sPort = ($iPort == 80) ? '' : ':'.$iPort;
+ }
+ else
+ {
+ $sPort = ($iPort == 443) ? '' : ':'.$iPort;
+ }
+ // $_SERVER['REQUEST_URI'] is empty when running on IIS
+ // Let's use Ivan Tcholakov's fix (found on www.dokeos.com)
+ if (!empty($_SERVER['REQUEST_URI']))
+ {
+ $sPath = $_SERVER['REQUEST_URI'];
+ }
+ else
+ {
+ $sPath = $_SERVER['SCRIPT_NAME'];
+ if (!empty($_SERVER['QUERY_STRING']))
+ {
+ $sPath .= '?'.$_SERVER['QUERY_STRING'];
+ }
+ $_SERVER['REQUEST_URI'] = $sPath;
+ }
+ $sPath = $_SERVER['REQUEST_URI'];
+
+ // remove all the parameters from the query string
+ $iQuestionMarkPos = strpos($sPath, '?');
+ if ($iQuestionMarkPos !== false)
+ {
+ $sPath = substr($sPath, 0, $iQuestionMarkPos);
+ }
+ $sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
+
+ $sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
+ $sCurrentScript = str_replace('\\', '/', $sCurrentScript); // canonical path
+ $sAppRoot = str_replace('\\', '/', APPROOT); // canonical path
+ $sCurrentRelativePath = str_replace($sAppRoot, '', $sCurrentScript);
+
+ $sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
+ if ($sAppRootPos !== false)
+ {
+ $sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
+ }
+ else
+ {
+ // Second attempt without index.php at the end...
+ $sCurrentRelativePath = str_replace('index.php', '', $sCurrentRelativePath);
+ $sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
+ if ($sAppRootPos !== false)
+ {
+ $sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
+ }
+ else
+ {
+ // No luck...
+ throw new Exception("Failed to determine application root path $sAbsoluteUrl ($sCurrentRelativePath) APPROOT:'$sAppRoot'");
+ }
+ }
+ return $sAppRootUrl;
+ }
+
+ /**
+ * Helper to handle the variety of HTTP servers
+ * See #286 (fixed in [896]), and #634 (this fix)
+ *
+ * Though the official specs says 'a non empty string', some servers like IIS do set it to 'off' !
+ * nginx set it to an empty string
+ * Others might leave it unset (no array entry)
+ */
+ static public function IsConnectionSecure()
+ {
+ $bSecured = false;
+
+ if (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
+ {
+ $bSecured = true;
+ }
+ return $bSecured;
+ }
+
+ /**
+ * Tells whether or not log off operation is supported.
+ * Actually in only one case:
+ * 1) iTop is using an internal authentication
+ * 2) the user did not log-in using the "basic" mode (i.e basic authentication) or by passing credentials in the URL
+ * @return boolean True if logoff is supported, false otherwise
+ */
+ static function CanLogOff()
+ {
+ $bResult = false;
+ if(isset($_SESSION['login_mode']))
+ {
+ $sLoginMode = $_SESSION['login_mode'];
+ switch($sLoginMode)
+ {
+ case 'external':
+ $bResult = false;
+ break;
+
+ case 'form':
+ case 'basic':
+ case 'url':
+ case 'cas':
+ default:
+ $bResult = true;
+
+ }
+ }
+ return $bResult;
+ }
+
+ /**
+ * Initializes the CAS client
+ */
+ static function InitCASClient()
+ {
+ $sCASIncludePath = self::GetConfig()->Get('cas_include_path');
+ include_once($sCASIncludePath.'/CAS.php');
+
+ $bCASDebug = self::GetConfig()->Get('cas_debug');
+ if ($bCASDebug)
+ {
+ phpCAS::setDebug(APPROOT.'log/error.log');
+ }
+
+ if (!self::$m_bCASClient)
+ {
+ // Initialize phpCAS
+ $sCASVersion = self::GetConfig()->Get('cas_version');
+ $sCASHost = self::GetConfig()->Get('cas_host');
+ $iCASPort = self::GetConfig()->Get('cas_port');
+ $sCASContext = self::GetConfig()->Get('cas_context');
+ phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */);
+ self::$m_bCASClient = true;
+ $sCASCACertPath = self::GetConfig()->Get('cas_server_ca_cert_path');
+ if (empty($sCASCACertPath))
+ {
+ // If no certificate authority is provided, do not attempt to validate
+ // the server's certificate
+ // THIS SETTING IS NOT RECOMMENDED FOR PRODUCTION.
+ // VALIDATING THE CAS SERVER IS CRUCIAL TO THE SECURITY OF THE CAS PROTOCOL!
+ phpCAS::setNoCasServerValidation();
+ }
+ else
+ {
+ phpCAS::setCasServerCACert($sCASCACertPath);
+ }
+ }
+ }
+
+ static function DebugBacktrace($iLimit = 5)
+ {
+ $aFullTrace = debug_backtrace();
+ $aLightTrace = array();
+ for($i=1; ($i<=$iLimit && $i < count($aFullTrace)); $i++) // Skip the last function call... which is the call to this function !
+ {
+ $aLightTrace[$i] = $aFullTrace[$i]['function'].'(), called from line '.$aFullTrace[$i]['line'].' in '.$aFullTrace[$i]['file'];
+ }
+ echo "".print_r($aLightTrace, true)."
\n";
+ }
+
+ /**
+ * Execute the given iTop PHP script, passing it the current credentials
+ * Only CLI mode is supported, because of the need to hand the credentials over to the next process
+ * Throws an exception if the execution fails or could not be attempted (config issue)
+ * @param string $sScript Name and relative path to the file (relative to the iTop root dir)
+ * @param hash $aArguments Associative array of 'arg' => 'value'
+ * @return array(iCode, array(output lines))
+ */
+ /**
+ */
+ static function ExecITopScript($sScriptName, $aArguments)
+ {
+ $aDisabled = explode(', ', ini_get('disable_functions'));
+ if (in_array('exec', $aDisabled))
+ {
+ throw new Exception("The PHP exec() function has been disabled on this server");
+ }
+
+ $sPHPExec = trim(self::GetConfig()->Get('php_path'));
+ if (strlen($sPHPExec) == 0)
+ {
+ throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file.");
+ }
+
+ $sAuthUser = self::ReadParam('auth_user', '', 'raw_data');
+ $sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data');
+ $sParamFile = self::GetParamSourceFile('auth_user');
+ if (is_null($sParamFile))
+ {
+ $aArguments['auth_user'] = $sAuthUser;
+ $aArguments['auth_pwd'] = $sAuthPwd;
+ }
+ else
+ {
+ $aArguments['param_file'] = $sParamFile;
+ }
+
+ $aArgs = array();
+ foreach($aArguments as $sName => $value)
+ {
+ // Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation
+ // It suggests to rely on pctnl_* function instead of using escapeshellargs
+ $aArgs[] = "--$sName=".escapeshellarg($value);
+ }
+ $sArgs = implode(' ', $aArgs);
+
+ $sScript = realpath(APPROOT.$sScriptName);
+ if (!file_exists($sScript))
+ {
+ throw new Exception("Could not find the script file '$sScriptName' from the directory '".APPROOT."'");
+ }
+
+ $sCommand = '"'.$sPHPExec.'" '.escapeshellarg($sScript).' -- '.$sArgs;
+
+ if (version_compare(phpversion(), '5.3.0', '<'))
+ {
+ if (substr(PHP_OS,0,3) == 'WIN')
+ {
+ // Under Windows, and for PHP 5.2.x, the whole command has to be quoted
+ // Cf PHP doc: http://php.net/manual/fr/function.exec.php, comment from the 27-Dec-2010
+ $sCommand = '"'.$sCommand.'"';
+ }
+ }
+
+ $sLastLine = exec($sCommand, $aOutput, $iRes);
+ if ($iRes == 1)
+ {
+ throw new Exception(Dict::S('Core:ExecProcess:Code1')." - ".$sCommand);
+ }
+ elseif ($iRes == 255)
+ {
+ $sErrors = implode("\n", $aOutput);
+ throw new Exception(Dict::S('Core:ExecProcess:Code255')." - ".$sCommand.":\n".$sErrors);
+ }
+
+ //$aOutput[] = $sCommand;
+ return array($iRes, $aOutput);
+ }
+
+ /**
+ * Get the current environment
+ */
+ public static function GetCurrentEnvironment()
+ {
+ if (isset($_SESSION['itop_env']))
+ {
+ return $_SESSION['itop_env'];
+ }
+ else
+ {
+ return ITOP_DEFAULT_ENV;
+ }
+ }
+
+ /**
+ * Returns a path to a folder into which any module can store cache data
+ * The corresponding folder is created or cleaned upon code compilation
+ * @return string
+ */
+ public static function GetCachePath()
+ {
+ return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
+ }
+ /**
+ * Merge standard menu items with plugin provided menus items
+ */
+ public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions, $sTableId = null, $sDataTableId = null)
+ {
+ // 1st - add standard built-in menu items
+ //
+ switch($iMenuId)
+ {
+ case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
+ // $param is a DBObjectSet
+ $oAppContext = new ApplicationContext();
+ $sContext = $oAppContext->GetForLink();
+ $sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
+ $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass());
+ $sOQL = addslashes($param->GetFilter()->ToOQL(true));
+ $sFilter = urlencode($param->GetFilter()->serialize());
+ $sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
+ $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
+
+ $aResult = array();
+ if (strlen($sUrl) < SERVER_MAX_URL_LENGTH)
+ {
+ $aResult[] = new SeparatorPopupMenuItem();
+ // Static menus: Email this page, CSV Export & Add to Dashboard
+ $aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
+ "mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
+ );
+ }
+
+ if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO)
+ {
+ // Bulk export actions
+ $aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
+ $aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
+ if (extension_loaded('gd'))
+ {
+ // PDF export requires GD
+ $aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
+ }
+ }
+ $aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
+ $aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
+
+ break;
+
+ case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
+ // $param is a DBObject
+ $oObj = $param;
+ $sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
+ $oFilter = DBObjectSearch::FromOQL($sOQL);
+ $sFilter = $oFilter->serialize();
+ $sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
+ $sUIPage = cmdbAbstractObject::ComputeStandardUIPage(get_class($oObj));
+ $oAppContext = new ApplicationContext();
+ $sContext = $oAppContext->GetForLink();
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
+ $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js');
+ $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css');
+
+ $aResult = array(
+ new SeparatorPopupMenuItem(),
+ // Static menus: Email this page & CSV Export
+ new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
+ new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"),
+ new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
+ new SeparatorPopupMenuItem(),
+ new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
+ );
+ break;
+
+ case iPopupMenuExtension::MENU_DASHBOARD_ACTIONS:
+ // $param is a Dashboard
+ $oAppContext = new ApplicationContext();
+ $aParams = $oAppContext->GetAsHash();
+ $sMenuId = ApplicationMenu::GetActiveNodeId();
+ $sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
+ $sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
+ $sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
+ $aResult = array(
+ new SeparatorPopupMenuItem(),
+ new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sMenuId),
+ new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sMenuId', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn' })"),
+ );
+ break;
+
+ default:
+ // Unknown type of menu, do nothing
+ $aResult = array();
+ }
+ foreach($aResult as $oMenuItem)
+ {
+ $aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
+ }
+
+ // Invoke the plugins
+ //
+ foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
+ {
+ if (is_object($param) && !($param instanceof DBObject))
+ {
+ $tmpParam = clone $param; // In case the parameter is an DBObjectSet, clone it to prevent alterations
+ }
+ else
+ {
+ $tmpParam = $param;
+ }
+ foreach($oExtensionInstance->EnumItems($iMenuId, $tmpParam) as $oMenuItem)
+ {
+ if (is_object($oMenuItem))
+ {
+ $aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
+
+ foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
+ {
+ $oPage->add_linked_script($sLinkedScript);
+ }
+ }
+ }
+ }
+ }
+ /**
+ * Get target configuration file name (including full path)
+ */
+ public static function GetConfigFilePath($sEnvironment = null)
+ {
+ if (is_null($sEnvironment))
+ {
+ $sEnvironment = self::GetCurrentEnvironment();
+ }
+ return APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE;
+ }
+
+ /**
+ * @return string the absolute URL to the modules root path
+ */
+ static public function GetAbsoluteUrlModulesRoot()
+ {
+ $sUrl = self::GetAbsoluteUrlAppRoot().'env-'.self::GetCurrentEnvironment().'/';
+ return $sUrl;
+ }
+
+ /**
+ * To be compatible with this mechanism, the called page must include approot with an absolute path OR not include
+ * it at all (losing the direct access to the page) :
+ *
+ * ```php
+ * if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__));
+ * require_once(__DIR__.'/../../approot.inc.php');
+ * ```
+ *
+ * @param string $sModule
+ * @param string $sPage
+ * @param string[] $aArguments
+ * @param string $sEnvironment
+ *
+ * @return string the URL to a page that will execute the requested module page, with query string values url encoded
+ *
+ * @see GetExecPageArguments can be used to submit using the GET method (see bug in N.1108)
+ * @see GetAbsoluteUrlExecPage
+ */
+ static public function GetAbsoluteUrlModulePage($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
+ {
+ $aArgs = self::GetExecPageArguments($sModule, $sPage, $aArguments, $sEnvironment);
+ $sArgs = http_build_query($aArgs);
+
+ return self::GetAbsoluteUrlExecPage()."?".$sArgs;
+ }
+
+ /**
+ * @param string $sModule
+ * @param string $sPage
+ * @param string[] $aArguments
+ * @param string $sEnvironment
+ *
+ * @return string[] key/value pair for the exec page query string. Warning : values are not url encoded !
+ * @throws \Exception if one of the argument has a reserved name
+ */
+ static public function GetExecPageArguments($sModule, $sPage, $aArguments = array(), $sEnvironment = null)
+ {
+ $sEnvironment = is_null($sEnvironment) ? self::GetCurrentEnvironment() : $sEnvironment;
+ $aArgs = array();
+ $aArgs['exec_module'] = $sModule;
+ $aArgs['exec_page'] = $sPage;
+ $aArgs['exec_env'] = $sEnvironment;
+ foreach($aArguments as $sName => $sValue)
+ {
+ if (($sName == 'exec_module') || ($sName == 'exec_page') || ($sName == 'exec_env'))
+ {
+ throw new Exception("Module page: $sName is a reserved page argument name");
+ }
+ $aArgs[$sName] = $sValue;
+ }
+
+ return $aArgs;
+ }
+
+ /**
+ * @return string
+ */
+ static public function GetAbsoluteUrlExecPage()
+ {
+ return self::GetAbsoluteUrlAppRoot().'pages/exec.php';
+ }
+
+ /**
+ * Returns a name unique amongst the given list
+ * @param string $sProposed The default value
+ * @param array $aExisting An array of existing values (strings)
+ */
+ static public function MakeUniqueName($sProposed, $aExisting)
+ {
+ if (in_array($sProposed, $aExisting))
+ {
+ $i = 1;
+ while (in_array($sProposed.$i, $aExisting) && ($i < 50))
+ {
+ $i++;
+ }
+ return $sProposed.$i;
+ }
+ else
+ {
+ return $sProposed;
+ }
+ }
+
+ /**
+ * Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
+ * @param string $sId The ID to sanitize
+ * @return string The sanitized ID
+ */
+ static public function GetSafeId($sId)
+ {
+ return str_replace(array(':', '[', ']', '+', '-'), '_', $sId);
+ }
+
+ /**
+ * Helper to execute an HTTP POST request
+ * Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
+ * originaly named after do_post_request
+ * Does not require cUrl but requires openssl for performing https POSTs.
+ *
+ * @param string $sUrl The URL to POST the data to
+ * @param hash $aData The data to POST as an array('param_name' => value)
+ * @param string $sOptionnalHeaders Additional HTTP headers as a string with newlines between headers
+ * @param hash $aResponseHeaders An array to be filled with reponse headers: WARNING: the actual content of the array depends on the library used: cURL or fopen, test with both !! See: http://fr.php.net/manual/en/function.curl-getinfo.php
+ * @param hash $aCurlOptions An (optional) array of options to pass to curl_init. The format is 'option_code' => 'value'. These values have precedence over the default ones. Example: CURLOPT_SSLVERSION => CURL_SSLVERSION_SSLv3
+ * @return string The result of the POST request
+ * @throws Exception
+ */
+ static public function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array())
+ {
+ // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
+
+ if (function_exists('curl_init'))
+ {
+ // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options
+ // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
+ // by setting the SSLVERSION to 3 as done below.
+ $aHeaders = explode("\n", $sOptionnalHeaders);
+ $aHTTPHeaders = array();
+ foreach($aHeaders as $sHeaderString)
+ {
+ if(preg_match('/^([^:]): (.+)$/', $sHeaderString, $aMatches))
+ {
+ $aHTTPHeaders[$aMatches[1]] = $aMatches[2];
+ }
+ }
+ // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions
+ $aOptions = array(
+ CURLOPT_RETURNTRANSFER => true, // return the content of the request
+ CURLOPT_HEADER => false, // don't return the headers in the output
+ CURLOPT_FOLLOWLOCATION => true, // follow redirects
+ CURLOPT_ENCODING => "", // handle all encodings
+ CURLOPT_USERAGENT => "spider", // who am i
+ CURLOPT_AUTOREFERER => true, // set referer on redirect
+ CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect
+ CURLOPT_TIMEOUT => 120, // timeout on response
+ CURLOPT_MAXREDIRS => 10, // stop after 10 redirects
+ CURLOPT_SSL_VERIFYPEER => false, // Disabled SSL Cert checks
+ // SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why
+ // but it used to be a MUST to prevent a strange SSL error: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
+ // CURLOPT_SSLVERSION => 3,
+ CURLOPT_POST => count($aData),
+ CURLOPT_POSTFIELDS => http_build_query($aData),
+ CURLOPT_HTTPHEADER => $aHTTPHeaders,
+ );
+
+ $aAllOptions = $aCurlOptions + $aOptions;
+ $ch = curl_init($sUrl);
+ curl_setopt_array($ch, $aAllOptions);
+ $response = curl_exec($ch);
+ $iErr = curl_errno($ch);
+ $sErrMsg = curl_error( $ch );
+ $aHeaders = curl_getinfo( $ch );
+ if ($iErr !== 0)
+ {
+ throw new Exception("Problem opening URL: $sUrl, $sErrMsg");
+ }
+ if (is_array($aResponseHeaders))
+ {
+ $aHeaders = curl_getinfo($ch);
+ foreach($aHeaders as $sCode => $sValue)
+ {
+ $sName = str_replace(' ' , '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type"
+ $aResponseHeaders[$sName] = $sValue;
+ }
+ }
+ curl_close( $ch );
+ }
+ else
+ {
+ // cURL is not available let's try with streams and fopen...
+
+ $sData = http_build_query($aData);
+ $aParams = array('http' => array(
+ 'method' => 'POST',
+ 'content' => $sData,
+ 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n",
+ ));
+ if ($sOptionnalHeaders !== null)
+ {
+ $aParams['http']['header'] .= $sOptionnalHeaders;
+ }
+ $ctx = stream_context_create($aParams);
+
+ $fp = @fopen($sUrl, 'rb', false, $ctx);
+ if (!$fp)
+ {
+ global $php_errormsg;
+ if (isset($php_errormsg))
+ {
+ throw new Exception("Wrong URL: $sUrl, $php_errormsg");
+ }
+ elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl'))
+ {
+ throw new Exception("Cannot connect to $sUrl: missing module 'openssl'");
+ }
+ else
+ {
+ throw new Exception("Wrong URL: $sUrl");
+ }
+ }
+ $response = @stream_get_contents($fp);
+ if ($response === false)
+ {
+ throw new Exception("Problem reading data from $sUrl, $php_errormsg");
+ }
+ if (is_array($aResponseHeaders))
+ {
+ $aMeta = stream_get_meta_data($fp);
+ $aHeaders = $aMeta['wrapper_data'];
+ foreach($aHeaders as $sHeaderString)
+ {
+ if(preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches))
+ {
+ $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]);
+ }
+ }
+ }
+ }
+ return $response;
+ }
+
+ /**
+ * Get a standard list of character sets
+ *
+ * @param array $aAdditionalEncodings Additional values
+ * @return array of iconv code => english label, sorted by label
+ */
+ public static function GetPossibleEncodings($aAdditionalEncodings = array())
+ {
+ // Encodings supported:
+ // ICONV_CODE => Display Name
+ // Each iconv installation supports different encodings
+ // Some reasonably common and useful encodings are listed here
+ $aPossibleEncodings = array(
+ 'UTF-8' => 'Unicode (UTF-8)',
+ 'ISO-8859-1' => 'Western (ISO-8859-1)',
+ 'WINDOWS-1251' => 'Cyrilic (Windows 1251)',
+ 'WINDOWS-1252' => 'Western (Windows 1252)',
+ 'ISO-8859-15' => 'Western (ISO-8859-15)',
+ );
+ $aPossibleEncodings = array_merge($aPossibleEncodings, $aAdditionalEncodings);
+ asort($aPossibleEncodings);
+ return $aPossibleEncodings;
+ }
+
+ /**
+ * Convert a string containing some (valid) HTML markup to plain text
+ * @param string $sHtml
+ * @return string
+ */
+ public static function HtmlToText($sHtml)
+ {
+ try
+ {
+ //return ''.$sHtml;
+ return \Html2Text\Html2Text::convert(''.$sHtml);
+ }
+ catch(Exception $e)
+ {
+ return $e->getMessage();
+ }
+ }
+
+ /**
+ * Convert (?) plain text to some HTML markup by replacing newlines by
tags
+ * and escaping HTML entities
+ * @param string $sText
+ * @return string
+ */
+ public static function TextToHtml($sText)
+ {
+ $sText = str_replace("\r\n", "\n", $sText);
+ $sText = str_replace("\r", "\n", $sText);
+ return str_replace("\n", '
', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
+ }
+
+ /**
+ * Eventually compiles the SASS (.scss) file into the CSS (.css) file
+ *
+ * @param string $sSassRelPath Relative path to the SCSS file (must have the extension .scss)
+ * @param array $aImportPaths Array of absolute paths to load imports from
+ * @return string Relative path to the CSS file (.css)
+ */
+ static public function GetCSSFromSASS($sSassRelPath, $aImportPaths = null)
+ {
+ // Avoiding compilation if file is already a css file.
+ if (preg_match('/\.css$/', $sSassRelPath))
+ {
+ return $sSassRelPath;
+ }
+
+ // Setting import paths
+ if ($aImportPaths === null)
+ {
+ $aImportPaths = array();
+ }
+ $aImportPaths[] = APPROOT . '/css';
+
+ $sSassPath = APPROOT.$sSassRelPath;
+ $sCssRelPath = preg_replace('/\.scss$/', '.css', $sSassRelPath);
+ $sCssPath = APPROOT.$sCssRelPath;
+ clearstatcache();
+ if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSassPath))))
+ {
+ require_once(APPROOT.'lib/scssphp/scss.inc.php');
+ $oScss = new Compiler();
+ $oScss->setImportPaths($aImportPaths);
+ $oScss->setFormatter('Leafo\\ScssPhp\\Formatter\\Expanded');
+ // Temporary disabling max exec time while compiling
+ $iCurrentMaxExecTime = (int) ini_get('max_execution_time');
+ set_time_limit(0);
+ $sCss = $oScss->compile(file_get_contents($sSassPath));
+ set_time_limit($iCurrentMaxExecTime);
+ file_put_contents($sCssPath, $sCss);
+ }
+ return $sCssRelPath;
+ }
+
+ static public function GetImageSize($sImageData)
+ {
+ if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
+ {
+ $aRet = @getimagesizefromstring($sImageData);
+ }
+ else if(ini_get('allow_url_fopen'))
+ {
+ // work around to avoid creating a tmp file
+ $sUri = 'data://application/octet-stream;base64,'.base64_encode($sImageData);
+ $aRet = @getimagesize($sUri);
+ }
+ else
+ {
+ // Damned, need to create a tmp file
+ $sTempFile = tempnam(SetupUtils::GetTmpDir(), 'img-');
+ @file_put_contents($sTempFile, $sImageData);
+ $aRet = @getimagesize($sTempFile);
+ @unlink($sTempFile);
+ }
+ return $aRet;
+ }
+
+ /**
+ * Resize an image attachment so that it fits in the given dimensions
+ * @param ormDocument $oImage The original image stored as an ormDocument
+ * @param int $iWidth Image's original width
+ * @param int $iHeight Image's original height
+ * @param int $iMaxImageWidth Maximum width for the resized image
+ * @param int $iMaxImageHeight Maximum height for the resized image
+ * @return ormDocument The resampled image
+ */
+ public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight)
+ {
+ // If image size smaller than maximums, we do nothing
+ if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight))
+ {
+ return $oImage;
+ }
+
+
+ // If gd extension is not loaded, we put a warning in the log and return the image as is
+ if (extension_loaded('gd') === false)
+ {
+ IssueLog::Warning('Image could not be resized as the "gd" extension does not seem to be loaded. It will remain as ' . $iWidth . 'x' . $iHeight . ' instead of ' . $iMaxImageWidth . 'x' . $iMaxImageHeight);
+ return $oImage;
+ }
+
+
+ switch($oImage->GetMimeType())
+ {
+ case 'image/gif':
+ case 'image/jpeg':
+ case 'image/png':
+ $img = @imagecreatefromstring($oImage->GetData());
+ break;
+
+ default:
+ // Unsupported image type, return the image as-is
+ //throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
+ return $oImage;
+ }
+ if ($img === false)
+ {
+ //throw new Exception("Warning: corrupted image: '".$oImage->GetFileName()." / ".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
+ return $oImage;
+ }
+ else
+ {
+ // Let's scale the image, preserving the transparency for GIFs and PNGs
+
+ $fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
+
+ $iNewWidth = $iWidth * $fScale;
+ $iNewHeight = $iHeight * $fScale;
+
+ $new = imagecreatetruecolor($iNewWidth, $iNewHeight);
+
+ // Preserve transparency
+ if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
+ {
+ imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
+ imagealphablending($new, false);
+ imagesavealpha($new, true);
+ }
+
+ imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
+
+ ob_start();
+ switch ($oImage->GetMimeType())
+ {
+ case 'image/gif':
+ imagegif($new); // send image to output buffer
+ break;
+
+ case 'image/jpeg':
+ imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
+ break;
+
+ case 'image/png':
+ imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
+ break;
+ }
+ $oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
+ @ob_end_clean();
+
+ imagedestroy($img);
+ imagedestroy($new);
+
+ return $oResampledImage;
+ }
+
+ }
+
+ /**
+ * Create a 128 bit UUID in the format: {########-####-####-####-############}
+ *
+ * Note: this method can be run from the command line as well as from the web server.
+ * Note2: this method is not cryptographically secure! If you need a cryptographically secure value
+ * consider using open_ssl or PHP 7 methods.
+ * @param string $sPrefix
+ * @return string
+ */
+ static public function CreateUUID($sPrefix = '')
+ {
+ $uid = uniqid("", true);
+ $data = $sPrefix;
+ $data .= __FILE__;
+ $data .= mt_rand();
+ $hash = strtoupper(hash('ripemd128', $uid . md5($data)));
+ $sUUID = '{' .
+ substr($hash, 0, 8) .
+ '-' .
+ substr($hash, 8, 4) .
+ '-' .
+ substr($hash, 12, 4) .
+ '-' .
+ substr($hash, 16, 4) .
+ '-' .
+ substr($hash, 20, 12) .
+ '}';
+ return $sUUID;
+ }
+
+ /**
+ * Returns the name of the module containing the file where the call to this function is made
+ * or an empty string if no such module is found (or not called within a module file)
+ * @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
+ * @return string
+ */
+ static public function GetCurrentModuleName($iCallDepth = 0)
+ {
+ $sCurrentModuleName = '';
+ $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
+
+ foreach(GetModulesInfo() as $sModuleName => $aInfo)
+ {
+ if ($aInfo['root_dir'] !== '')
+ {
+ $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
+
+ if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
+ {
+ $sCurrentModuleName = $sModuleName;
+ break;
+ }
+ }
+ }
+ return $sCurrentModuleName;
+ }
+
+ /**
+ * Returns the relative (to APPROOT) path of the root directory of the module containing the file where the call to this function is made
+ * or an empty string if no such module is found (or not called within a module file)
+ * @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
+ * @return string
+ */
+ static public function GetCurrentModuleDir($iCallDepth)
+ {
+ $sCurrentModuleDir = '';
+ $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ $sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
+
+ foreach(GetModulesInfo() as $sModuleName => $aInfo)
+ {
+ if ($aInfo['root_dir'] !== '')
+ {
+ $sRootDir = realpath(APPROOT.$aInfo['root_dir']);
+
+ if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
+ {
+ $sCurrentModuleDir = basename($sRootDir);
+ break;
+ }
+ }
+ }
+ return $sCurrentModuleDir;
+ }
+
+ /**
+ * Returns the base URL for all files in the current module from which this method is called
+ * or an empty string if no such module is found (or not called within a module file)
+ * @return string
+ */
+ static public function GetCurrentModuleUrl()
+ {
+ $sDir = static::GetCurrentModuleDir(1);
+ if ( $sDir !== '')
+ {
+ return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
+ }
+ return '';
+ }
+
+ /**
+ * Get the value of a given setting for the current module
+ * @param string $sProperty The name of the property to retrieve
+ * @param mixed $defaultvalue
+ * @return mixed
+ */
+ static public function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
+ {
+ $sModuleName = static::GetCurrentModuleName(1);
+ return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
+ }
+
+ /**
+ * Get the compiled version of a given module, as it was seen by the compiler
+ * @param string $sModuleName
+ * @return string|NULL
+ */
+ static public function GetCompiledModuleVersion($sModuleName)
+ {
+ $aModulesInfo = GetModulesInfo();
+ if (array_key_exists($sModuleName, $aModulesInfo))
+ {
+ return $aModulesInfo[$sModuleName]['version'];
+ }
+ return null;
+ }
+
+ /**
+ * Check if the given path/url is an http(s) URL
+ * @param string $sPath
+ * @return boolean
+ */
+ public static function IsURL($sPath)
+ {
+ $bRet = false;
+ if ((substr($sPath, 0, 7) == 'http://') || (substr($sPath, 0, 8) == 'https://') || (substr($sPath, 0, 8) == 'ftp://'))
+ {
+ $bRet = true;
+ }
+ return $bRet;
+ }
+
+ /**
+ * Check if the given URL is a link to download a document/image on the CURRENT iTop
+ * In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
+ * @param string $sPath
+ * @return false|ormDocument
+ * @throws Exception
+ */
+ public static function IsSelfURL($sPath)
+ {
+ $result = false;
+ $sPageUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php';
+ if (substr($sPath, 0, strlen($sPageUrl)) == $sPageUrl)
+ {
+ // If the URL is an URL pointing to this instance of iTop, then
+ // extract the "query" part of the URL and analyze it
+ $sQuery = parse_url($sPath, PHP_URL_QUERY);
+ if ($sQuery !== null)
+ {
+ $aParams = array();
+ foreach(explode('&', $sQuery) as $sChunk)
+ {
+ $aParts = explode('=', $sChunk);
+ if (count($aParts) != 2) continue;
+ $aParams[$aParts[0]] = urldecode($aParts[1]);
+ }
+ $result = array_key_exists('operation', $aParams) && array_key_exists('class', $aParams) && array_key_exists('id', $aParams) && array_key_exists('field', $aParams) && ($aParams['operation'] == 'download_document');
+ if ($result)
+ {
+ // This is a 'download_document' operation, let's retrieve the document directly from the database
+ $sClass = $aParams['class'];
+ $iKey = $aParams['id'];
+ $sAttCode = $aParams['field'];
+
+ $oObj = MetaModel::GetObject($sClass, $iKey, false /* must exist */); // Users rights apply here !!
+ if ($oObj)
+ {
+ /**
+ * @var ormDocument $result
+ */
+ $result = clone $oObj->Get($sAttCode);
+ return $result;
+ }
+ }
+ }
+ throw new Exception('Invalid URL. This iTop URL is not pointing to a valid Document/Image.');
+ }
+ return $result;
+ }
+
+ /**
+ * Read the content of a file (and retrieve its MIME type) from either:
+ * - an URL pointing to a blob (image/document) on the current iTop server
+ * - an http(s) URL
+ * - the local file system (but only if you are an administrator)
+ * @param string $sPath
+ * @return ormDocument|null
+ * @throws Exception
+ */
+ public static function FileGetContentsAndMIMEType($sPath)
+ {
+ $oUploadedDoc = null;
+ $aKnownExtensions = array(
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 'jpg' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'gif' => 'image/gif',
+ 'png' => 'image/png',
+ 'pdf' => 'application/pdf',
+ 'doc' => 'application/msword',
+ 'dot' => 'application/msword',
+ 'xls' => 'application/vnd.ms-excel',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'vsd' => 'application/x-visio',
+ 'vdx' => 'application/visio.drawing',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'zip' => 'application/zip',
+ 'txt' => 'text/plain',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'exe' => 'application/octet-stream'
+ );
+
+ $sData = null;
+ $sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
+ $sFileName = 'uploaded-file'; // Default name for downloaded-files
+ $sExtension = '.txt'; // Default file extension in case we don't know the MIME Type
+
+ if(empty($sPath))
+ {
+ // Empty path (NULL or '') means that there is no input, making an empty document.
+ $oUploadedDoc = new ormDocument('', '', '');
+ }
+ elseif (static::IsURL($sPath))
+ {
+ if ($oUploadedDoc = static::IsSelfURL($sPath))
+ {
+ // Nothing more to do, we've got it !!
+ }
+ else
+ {
+ // Remote file, let's use the HTTP headers to find the MIME Type
+ $sData = @file_get_contents($sPath);
+ if ($sData === false)
+ {
+ throw new Exception("Failed to load the file from the URL '$sPath'.");
+ }
+ else
+ {
+ if (isset($http_response_header))
+ {
+ $aHeaders = static::ParseHeaders($http_response_header);
+ $sMimeType = array_key_exists('Content-Type', $aHeaders) ? strtolower($aHeaders['Content-Type']) : 'application/x-octet-stream';
+ // Compute the file extension from the MIME Type
+ foreach($aKnownExtensions as $sExtValue => $sMime)
+ {
+ if ($sMime === $sMimeType)
+ {
+ $sExtension = '.'.$sExtValue;
+ break;
+ }
+ }
+ }
+ $sFileName .= $sExtension;
+ }
+ $oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
+ }
+ }
+ else if (UserRights::IsAdministrator())
+ {
+ // Only administrators are allowed to read local files
+ $sData = @file_get_contents($sPath);
+ if ($sData === false)
+ {
+ throw new Exception("Failed to load the file '$sPath'. The file does not exist or the current process is not allowed to access it.");
+ }
+ $sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
+ $sFileName = basename($sPath);
+
+ if (array_key_exists($sExtension, $aKnownExtensions))
+ {
+ $sMimeType = $aKnownExtensions[$sExtension];
+ }
+ else if (extension_loaded('fileinfo'))
+ {
+ $finfo = new finfo(FILEINFO_MIME);
+ $sMimeType = $finfo->file($sPath);
+ }
+ $oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
+ }
+ return $oUploadedDoc;
+ }
+
+ protected static function ParseHeaders($aHeaders)
+ {
+ $aCleanHeaders = array();
+ foreach( $aHeaders as $sKey => $sValue )
+ {
+ $aTokens = explode(':', $sValue, 2);
+ if(isset($aTokens[1]))
+ {
+ $aCleanHeaders[trim($aTokens[0])] = trim($aTokens[1]);
+ }
+ else
+ {
+ // The header is not in the form Header-Code: Value
+ $aCleanHeaders[] = $sValue; // Store the value as-is
+ $aMatches = array();
+ // Check if it's not the HTTP response code
+ if( preg_match("|HTTP/[0-9\.]+\s+([0-9]+)|", $sValue, $aMatches) )
+ {
+ $aCleanHeaders['reponse_code'] = intval($aMatches[1]);
+ }
+ }
+ }
+ return $aCleanHeaders;
+ }
+
+ /**
+ * Return a string based on compilation time or (if not available because the datamodel has not been loaded)
+ * the version of iTop. This string is useful to prevent browser side caching of content that may vary at each
+ * (re)installation of iTop (especially during development).
+ * @return string
+ */
+ public static function GetCacheBusterTimestamp()
+ {
+ if(!defined('COMPILATION_TIMESTAMP'))
+ {
+ return ITOP_VERSION;
+ }
+ return COMPILATION_TIMESTAMP;
+ }
+
+ /**
+ * Check if the given class if configured as a high cardinality class.
+ *
+ * @param $sClass
+ *
+ * @return bool
+ */
+ public static function IsHighCardinality($sClass)
+ {
+ if (utils::GetConfig()->Get('search_manual_submit'))
+ {
+ return true;
+ }
+ $aHugeClasses = MetaModel::GetConfig()->Get('high_cardinality_classes');
+ return in_array($sClass, $aHugeClasses);
+ }
+
+ /**
+ * Check if iTop is in a development environment (VCS vs build number)
+ *
+ * @return bool
+ */
+ public static function IsDevelopmentEnvironment()
+ {
+ return ITOP_REVISION === 'svn';
+ }
+}
diff --git a/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php
index 37bcc1382..797dda9e9 100644
--- a/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php
@@ -1,1531 +1,1531 @@
-
-
-namespace Combodo\iTop\Portal\Controller;
-
-use Silex\Application;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\HttpKernelInterface;
-use Exception;
-use FileUploadException;
-use utils;
-use Dict;
-use IssueLog;
-use MetaModel;
-use DBObject;
-use DBSearch;
-use DBObjectSearch;
-use FalseExpression;
-use BinaryExpression;
-use FieldExpression;
-use VariableExpression;
-use ListExpression;
-use ScalarExpression;
-use DBObjectSet;
-use AttributeEnum;
-use AttributeImage;
-use AttributeFinalClass;
-use AttributeFriendlyName;
-use UserRights;
-use iPopupMenuExtension;
-use URLButtonItem;
-use JSButtonItem;
-use Combodo\iTop\Portal\Helper\ApplicationHelper;
-use Combodo\iTop\Portal\Helper\SecurityHelper;
-use Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
-use Combodo\iTop\Portal\Form\ObjectFormManager;
-use Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
-
-/**
- * Class ObjectController
- *
- * Controller to handle basic view / edit / create of cmdbAbstractObjectClass ManageBrickController
- *
- * @package Combodo\iTop\Portal\Controller
- * @author Guillaume Lajarige
- * @since 2.3.0
- */
-class ObjectController extends AbstractController
-{
-
- const ENUM_MODE_VIEW = 'view';
- const ENUM_MODE_EDIT = 'edit';
- const ENUM_MODE_CREATE = 'create';
-
- const DEFAULT_PAGE_NUMBER = 1;
- const DEFAULT_LIST_LENGTH = 10;
-
- /**
- * Displays an cmdbAbstractObject if the connected user is allowed to.
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sObjectClass (Class must be instance of cmdbAbstractObject)
- * @param string $sObjectId
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \ArchivedObjectException
- * @throws \CoreException
- * @throws \DictExceptionMissingString
- */
- public function ViewAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
- {
- // Checking parameters
- if ($sObjectClass === '' || $sObjectId === '')
- {
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
- $oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
- }
-
- // Checking security layers
- if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sObjectClass, $sObjectId))
- {
- IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . '::' . $sObjectId . ' object.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Retrieving object
- $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
- if ($oObject === null)
- {
- // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
-
- $aData = array('sMode' => 'view');
- $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
- $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:View:Title', MetaModel::GetName($sObjectClass), $oObject->GetName());
-
- // Add an edit button if user is allowed
- if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId))
- {
- $oModifyButton = new URLButtonItem(
- 'modify_object',
- Dict::S('UI:Menu:Modify'),
- $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
- );
- // Putting this one first
- $aData['form']['buttons']['links'][] = $oModifyButton->GetMenuItem();
- }
-
- // Preparing response
- if ($oRequest->isXmlHttpRequest())
- {
- // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
- if (empty($sOperation))
- {
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
- }
- else
- {
- $oResponse = $oApp->json($aData);
- }
- }
- else
- {
- // Adding brick if it was passed
- $sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
- if (!empty($sBrickId))
- {
- $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
- if ($oBrick !== null)
- {
- $aData['oBrick'] = $oBrick;
- }
- }
- $aData['sPageTitle'] = $aData['form']['title'];
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
- }
-
- return $oResponse;
- }
-
- /**
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param $sObjectClass
- * @param $sObjectId
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \ArchivedObjectException
- * @throws \CoreException
- * @throws \DictExceptionMissingString
- */
- public function EditAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
- {
- // Checking parameters
- if ($sObjectClass === '' || $sObjectId === '')
- {
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
- $oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
- }
-
- // Checking security layers
- // Warning : This is a dirty quick fix to allow editing its own contact information
- $bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
- if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite)
- {
- IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Retrieving object
- $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
- if ($oObject === null)
- {
- // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
-
- $aData = array('sMode' => 'edit');
- $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
- $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Edit:Title', MetaModel::GetName($sObjectClass), $aData['form']['object_name']);
-
- // Preparing response
- if ($oRequest->isXmlHttpRequest())
- {
- // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
- if (empty($sOperation))
- {
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
- }
- else
- {
- $oResponse = $oApp->json($aData);
- }
- }
- else
- {
- // Adding brick if it was passed
- $sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
- if (!empty($sBrickId))
- {
- $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
- if ($oBrick !== null)
- {
- $aData['oBrick'] = $oBrick;
- }
- }
- $aData['sPageTitle'] = $aData['form']['title'];
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
- }
-
- return $oResponse;
- }
-
- /**
- * Creates an cmdbAbstractObject of the $sObjectClass
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sObjectClass
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \CoreException
- * @throws \DictExceptionMissingString
- */
- public function CreateAction(Request $oRequest, Application $oApp, $sObjectClass)
- {
- // Checking security layers
- if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_CREATE, $sObjectClass))
- {
- IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to create ' . $sObjectClass . ' object.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
-
- $aData = array('sMode' => 'create');
- $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass);
- $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Create:Title', MetaModel::GetName($sObjectClass));
-
- // Preparing response
- if ($oRequest->isXmlHttpRequest())
- {
- // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
- if (empty($sOperation))
- {
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
- }
- else
- {
- $oResponse = $oApp->json($aData);
- }
- }
- else
- {
- // Adding brick if it was passed
- $sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
- if (!empty($sBrickId))
- {
- $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
- if ($oBrick !== null)
- {
- $aData['oBrick'] = $oBrick;
- }
- }
- $aData['sPageTitle'] = $aData['form']['title'];
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
- }
-
- return $oResponse;
- }
-
- /**
- * Creates an cmdbAbstractObject of a class determined by the method encoded in $sEncodedMethodName.
- * This method use an origin DBObject in order to determine the created cmdbAbstractObject.
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sObjectClass Class of the origin object
- * @param string $sObjectId ID of the origin object
- * @param string $sEncodedMethodName Base64 encoded factory method name
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \ArchivedObjectException
- * @throws \CoreException
- */
- public function CreateFromFactoryAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sEncodedMethodName)
- {
- $sMethodName = base64_decode($sEncodedMethodName);
-
- // Checking that the factory method is valid
- if (!is_callable($sMethodName))
- {
- IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Invalid factory method "' . $sMethodName . '" used when creating an object.');
- $oApp->abort(500, 'Invalid factory method "' . $sMethodName . '" used when creating an object');
- }
-
- // Retrieving origin object
- // Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
- $oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
-
- // Retrieving target object (We check if the method is a simple function or if it's part of a class in which case only static function are supported)
- if (!strpos($sMethodName, '::'))
- {
- $oTargetObject = $sMethodName($oOriginObject);
- }
- else
- {
- $aMethodNameParts = explode('::', $sMethodName);
- $sMethodClass = $aMethodNameParts[0];
- $sMethodName = $aMethodNameParts[1];
- $oTargetObject = $sMethodClass::$sMethodName($oOriginObject);
- }
-
- // Preparing redirection
- // - Route
- $aRouteParams = array(
- 'sObjectClass' => get_class($oTargetObject)
- );
- $sRedirectRoute = $oApp['url_generator']->generate('p_object_create', $aRouteParams);
- // - Request
- $oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
-
- return $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
- }
-
- /**
- * Applies a stimulus $sStimulus on an cmdbAbstractObject
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sObjectClass
- * @param string $sObjectId
- * @param string $sStimulusCode
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \ArchivedObjectException
- * @throws \CoreException
- */
- public function ApplyStimulusAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sStimulusCode)
- {
- // Checking parameters
- if ($sObjectClass === '' || $sObjectId === '' || $sStimulusCode === '')
- {
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, sObjectId and $sStimulusCode expected, "' . $sObjectClass . '", "' . $sObjectId . '" and "' . $sStimulusCode . '" given.');
- $oApp->abort(500, Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus'));
- }
-
- // Checking security layers
- if(!SecurityHelper::IsStimulusAllowed($oApp, $sStimulusCode, $sObjectClass))
- {
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Retrieving object
- $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
- if ($oObject === null)
- {
- // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Retrieving request parameters
- $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
-
- // Retrieving form properties
- $aStimuliForms = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, 'apply_stimulus');
- if(array_key_exists($sStimulusCode, $aStimuliForms))
- {
- $aFormProperties = $aStimuliForms[$sStimulusCode];
- }
- // Or preparing a default form for the stimulus application
- else
- {
- // Preparing default form
- $aFormProperties = array(
- 'id' => 'apply-stimulus',
- 'type' => 'custom_list',
- 'fields' => array(),
- 'layout' => null
- );
- }
-
- // Adding stimulus code to form
- $aFormProperties['stimulus_code'] = $sStimulusCode;
-
- // Adding target_state to current_values
- $oRequest->request->set('apply_stimulus', array('code' => $sStimulusCode));
-
- $aData = array('sMode' => 'apply_stimulus');
- $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
- $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Stimulus:Title');
- $aData['form']['validation']['redirection'] = array(
- 'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
- );
-
- // TODO : This is a ugly patch to avoid showing a modal with a readonly form to the user as it would prevent user from finishing the transition.
- // Instead, we apply the stimulus directly here and then go to the edited object.
- if (empty($sOperation))
- {
- if (isset($aData['form']['editable_fields_count']) && $aData['form']['editable_fields_count'] === 0)
- {
- $sOperation = 'redirect';
-
- $oSubRequest = $oRequest;
- $oSubRequest->request->set('operation', 'submit');
- $oSubRequest->request->set('stimulus_code', '');
-
- $aData = array('sMode' => 'apply_stimulus');
- $aData['form'] = $this->HandleForm($oSubRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
- // Redefining the array to be as simple as possible :
- $aData = array('redirection' =>
- array('url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId)))
- );
- }
- }
-
- // Preparing response
- if ($oRequest->isXmlHttpRequest())
- {
- // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
- if (empty($sOperation))
- {
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
- }
- elseif ($sOperation === 'redirect')
- {
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/modal/mode_loader.html.twig', $aData);
- }
- else
- {
- $oResponse = $oApp->json($aData);
- }
- }
- else
- {
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
- }
-
- return $oResponse;
- }
-
- /**
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sMode
- * @param string $sObjectClass
- * @param string $sObjectId
- * @param string $aFormProperties
- *
- * @return array
- *
- * @throws \Exception
- * @throws \ArchivedObjectException
- * @throws \CoreException
- * @throws \OQLException
- * @throws \Twig_Error_Loader
- * @throws \Twig_Error_Runtime
- * @throws \Twig_Error_Syntax
- */
- public static function HandleForm(Request $oRequest, Application $oApp, $sMode, $sObjectClass, $sObjectId = null, $aFormProperties = null)
- {
- $aFormData = array();
- $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
- $bModal = ($oRequest->isXmlHttpRequest() && empty($sOperation));
-
- // - Retrieve form properties
- if ($aFormProperties === null)
- {
- $aFormProperties = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, $sMode);
- }
-
- // - Create and
- if (empty($sOperation))
- {
- // Retrieving action rules
- //
- // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
- // But it would not be a security issue as it only presets values in the form.
- $sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
- $aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
-
- // Preparing object
- if ($sObjectId === null)
- {
- // Create new UserRequest
- $oObject = MetaModel::NewObject($sObjectClass);
-
- // Retrieve action rules information to auto-fill the form if available
- // Preparing object
- $oApp['context_manipulator']->PrepareObject($aActionRules, $oObject);
- $aPrefillFormParam = array( 'user' => $_SESSION["auth_user"],
- 'origin' => 'portal');
- $oObject->PrefillForm('creation_from_0', $aPrefillFormParam);
- }
- else
- {
- $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
- }
-
- // Preparing buttons
- $aFormData['buttons'] = array(
- 'transitions' => array(),
- 'actions' => array(),
- 'links' => array(),
- 'submit' => array(
- 'label' => Dict::S('Portal:Button:Submit'),
- ),
- );
- if ($sMode !== 'apply_stimulus')
- {
- // Add transition buttons
- $oSetToCheckRights = DBObjectSet::FromObject($oObject);
- $aStimuli = Metamodel::EnumStimuli($sObjectClass);
- foreach ($oObject->EnumTransitions() as $sStimulusCode => $aTransitionDef)
- {
- if(SecurityHelper::IsStimulusAllowed($oApp, $sStimulusCode, $sObjectClass, $oSetToCheckRights))
- {
- $aFormData['buttons']['transitions'][$sStimulusCode] = $aStimuli[$sStimulusCode]->GetLabel();
- }
- }
-
- // Add plugin buttons
- foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
- {
- foreach($oExtensionInstance->EnumItems(iPopupMenuExtension::PORTAL_OBJDETAILS_ACTIONS, array('portal_id' => $oApp['combodo.portal.instance.id'], 'object' => $oObject)) as $oMenuItem)
- {
- if (is_object($oMenuItem))
- {
- if($oMenuItem instanceof JSButtonItem)
- {
- $aFormData['buttons']['actions'][] = $oMenuItem->GetMenuItem() + array('js_files' => $oMenuItem->GetLinkedScripts());
- }
- elseif($oMenuItem instanceof URLButtonItem)
- {
- $aFormData['buttons']['links'][] = $oMenuItem->GetMenuItem();
- }
- }
- }
- }
-
- // Hiding submit button or changing its label if necessary
- if(!empty($aFormData['buttons']['transitions']) && isset($aFormProperties['properties']) &&$aFormProperties['properties']['always_show_submit'] === false)
- {
- unset($aFormData['buttons']['submit']);
- }
- elseif($sMode === static::ENUM_MODE_EDIT)
- {
- $aFormData['buttons']['submit']['label'] = Dict::S('Portal:Button:Apply');
- }
- }
- else
- {
- $aPrefillFormParam = array(
- 'user' => $_SESSION["auth_user"],
- 'origin' => 'portal',
- 'stimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)['code'],
- );
- $oObject->PrefillForm('state_change', $aPrefillFormParam);
- }
-
- // Preparing callback urls
- $aCallbackUrls = $oApp['context_manipulator']->GetCallbackUrls($oApp, $aActionRules, $oObject, $bModal);
- $aFormData['submit_callback'] = $aCallbackUrls['submit'];
- $aFormData['cancel_callback'] = $aCallbackUrls['cancel'];
-
- // Preparing renderer
- // Note : We might need to distinguish form & renderer endpoints
- if (in_array($sMode, array('create', 'edit', 'view')))
- {
- $sFormEndpoint = $oApp['url_generator']->generate('p_object_' . $sMode, array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId));
- }
- else
- {
- $sFormEndpoint = $_SERVER['REQUEST_URI'];
- }
- $oFormRenderer = new BsFormRenderer();
- $oFormRenderer->SetEndpoint($sFormEndpoint);
-
- $oFormManager = new ObjectFormManager();
- $oFormManager->SetApplication($oApp)
- ->SetObject($oObject)
- ->SetMode($sMode)
- ->SetActionRulesToken($sActionRulesToken)
- ->SetRenderer($oFormRenderer)
- ->SetFormProperties($aFormProperties);
-
- $oFormManager->Build();
-
- // Check the number of editable fields
- $aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
- }
- else
- {
- // Update / Submit / Cancel
- $sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
- $sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
- if ( empty($sFormManagerClass) || empty($sFormManagerData) )
- {
- IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameters formmanager_class and formamanager_data must be defined.');
- $oApp->abort(500, 'Parameters formmanager_class and formmanager_data must be defined.');
- }
-
- $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
- $oFormManager->SetApplication($oApp);
-
- // Applying action rules if present
- if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
- {
- $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
- $oObj = $oFormManager->GetObject();
- $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
- $oFormManager->SetObject($oObj);
- }
-
- switch ($sOperation)
- {
- case 'submit':
- // Applying modification to object
- $aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'attachmentIds' => $oApp['request_manipulator']->ReadParam('attachment_ids', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties, 'applyStimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)));
- if ($aFormData['validation']['valid'] === true)
- {
- // Note : We don't use $sObjectId there as it can be null if we are creating a new one. Instead we use the id from the created object once it has been seralized
- // Check if stimulus has to be applied
- $sStimulusCode = $oApp['request_manipulator']->ReadParam('stimulus_code', '');
- if (!empty($sStimulusCode))
- {
- $aFormData['validation']['redirection'] = array(
- 'url' => $oApp['url_generator']->generate('p_object_apply_stimulus', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey(), 'sStimulusCode' => $sStimulusCode)),
- 'ajax' => true
- );
- }
- // Otherwise, we show the object if there is no default
-// else
-// {
-// $aFormData['validation']['redirection'] = array(
-// 'alternative_url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
-// );
-// }
- }
- break;
-
- case 'update':
- $oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties));
- break;
-
- case 'cancel':
- $oFormManager->OnCancel();
- break;
- }
- }
-
- // Preparing field_set data
- $aFieldSetData = array(
- //'fields_list' => $oFormManager->GetRenderer()->Render(), // GLA : This should be done just after in the if statement.
- 'fields_impacts' => $oFormManager->GetForm()->GetFieldsImpacts(),
- 'form_path' => $oFormManager->GetForm()->GetId()
- );
-
- // Preparing fields list regarding the operation
- if ($sOperation === 'update')
- {
- $aRequestedFields = $oApp['request_manipulator']->ReadParam('requested_fields', array(), FILTER_UNSAFE_RAW);
- $sFormPath = $oApp['request_manipulator']->ReadParam('form_path', '');
-
- // Checking if the update was on a subform, if so we need to make the rendering for that part only
- if ( !empty($sFormPath) && $sFormPath !== $oFormManager->GetForm()->GetId() )
- {
- $oSubForm = $oFormManager->GetForm()->FindSubForm($sFormPath);
- $oSubFormRenderer = new BsFormRenderer($oSubForm);
- $oSubFormRenderer->SetEndpoint($oFormManager->GetRenderer()->GetEndpoint());
- $aFormData['updated_fields'] = $oSubFormRenderer->Render($aRequestedFields);
- }
- else
- {
- $aFormData['updated_fields'] = $oFormManager->GetRenderer()->Render($aRequestedFields);
- }
- }
- else
- {
- $aFieldSetData['fields_list'] = $oFormManager->GetRenderer()->Render();
- }
-
- // Preparing form data
- $aFormData['id'] = $oFormManager->GetForm()->GetId();
- $aFormData['transaction_id'] = $oFormManager->GetForm()->GetTransactionId();
- $aFormData['formmanager_class'] = $oFormManager->GetClass();
- $aFormData['formmanager_data'] = $oFormManager->ToJSON();
- $aFormData['renderer'] = $oFormManager->GetRenderer();
- $aFormData['object_name'] = $oFormManager->GetObject()->GetName();
- $aFormData['object_state'] = $oFormManager->GetObject()->GetState();
- $aFormData['fieldset'] = $aFieldSetData;
- $aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE;
-
- return $aFormData;
- }
-
- /**
- * Handles the autocomplete search
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
- * @param string $sHostObjectClass Class name of the host object
- * @param string $sHostObjectId Id of the host object
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \ArchivedObjectException
- * @throws \CoreException
- * @throws \OQLException
- */
- public function SearchAutocompleteAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
- {
- $aData = array(
- 'results' => array(
- 'count' => 0,
- 'items' => array()
- )
- );
-
- // Parsing parameters from request payload
- parse_str($oRequest->getContent(), $aRequestContent);
-
- // Checking parameters
- if (!isset($aRequestContent['sQuery']))
- {
- IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameter sQuery missing.');
- $oApp->abort(500, Dict::Format('UI:Error:ParameterMissing', 'sQuery'));
- }
-
- // Retrieving parameters
- $sQuery = $aRequestContent['sQuery'];
- $sFieldId = $aRequestContent['sFieldId'];
-
- // Checking security layers
- if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
- {
- IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sHostObjectClass . '::' . $sHostObjectId . '.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Retrieving host object for future DBSearch parameters
- if ($sHostObjectId !== null)
- {
- // Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
- $oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
- }
- else
- {
- $oHostObject = MetaModel::NewObject($sHostObjectClass);
- // Retrieving action rules
- //
- // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
- // But it would not be a security issue as it only presets values in the form.
- $sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
- $aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
- // Preparing object
- $oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
- }
-
- // Updating host object with form data / values
- $sFormManagerClass = $aRequestContent['formmanager_class'];
- $sFormManagerData = $aRequestContent['formmanager_data'];
- if (!empty($sFormManagerClass) && !empty($sFormManagerData))
- {
- $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
- $oFormManager->SetApplication($oApp);
- $oFormManager->SetObject($oHostObject);
-
- // Applying action rules if present
- if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
- {
- $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
- $oObj = $oFormManager->GetObject();
- $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
- $oFormManager->SetObject($oObj);
- }
-
- // Updating host object
- $oFormManager->OnUpdate(array('currentValues' => $aRequestContent['current_values']));
- $oHostObject = $oFormManager->GetObject();
- }
-
- // Building search query
- // - Retrieving target object class from attcode
- $oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
- if ($oTargetAttDef->GetEditClass() === 'CustomFields')
- {
- $oRequestTemplate = $oHostObject->Get($sTargetAttCode);
- $oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
- $sTargetObjectClass = $oTemplateFieldSearch->GetClass();
- }
- elseif ($oTargetAttDef->IsLinkSet())
- {
- throw new Exception('Search autocomplete cannot apply on AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' (' . $sHostObjectClass . '->' . $sTargetAttCode . ') given.');
- }
- else
- {
- $sTargetObjectClass = $oTargetAttDef->GetTargetClass();
- }
- // - Base query from meta model
- if ($oTargetAttDef->GetEditClass() === 'CustomFields')
- {
- $oSearch = $oTemplateFieldSearch;
- }
- else
- {
- $oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
- }
- // - Adding query condition
- $oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
- // - Intersecting with scope constraints
- // Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
- // It is the responsability of the template designer to write the right query so the user see only what he should.
- if ($oTargetAttDef->GetEditClass() !== 'CustomFields')
- {
- $oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
- $oSearch = $oSearch->Intersect($oScopeSearch);
- // - Allowing all data if necessary
- if ($oScopeSearch->IsAllDataAllowed())
- {
- $oSearch->AllowAllData();
- }
- }
-
- // Retrieving results
- // - Preparing object set
- $oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%' . $sQuery . '%'));
- $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
- // Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
- if ($oTargetAttDef->GetEditClass() === 'CustomFields')
- {
- $oSet->SetLimit(static::DEFAULT_LIST_LENGTH);
- }
- else
- {
- $oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
- }
- // - Retrieving objects
- while ($oItem = $oSet->Fetch())
- {
- $aData['results']['items'][] = array('id' => $oItem->GetKey(), 'name' => html_entity_decode($oItem->GetName(), ENT_QUOTES, 'UTF-8'));
- $aData['results']['count'] ++;
- }
-
- // Preparing response
- if ($oRequest->isXmlHttpRequest())
- {
- $oResponse = $oApp->json($aData);
- }
- else
- {
- $oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- return $oResponse;
- }
-
- /**
- * Handles the regular (table) search from an attribute
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
- * @param string $sHostObjectClass Class name of the host object
- * @param string $sHostObjectId Id of the host object
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \ArchivedObjectException
- * @throws \CoreException
- * @throws \DictExceptionMissingString
- * @throws \OQLException
- */
- public function SearchFromAttributeAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
- {
- $aData = array(
- 'sMode' => 'search_regular',
- 'sTargetAttCode' => $sTargetAttCode,
- 'sHostObjectClass' => $sHostObjectClass,
- 'sHostObjectId' => $sHostObjectId,
- 'sActionRulesToken' => $oApp['request_manipulator']->ReadParam('ar_token', ''),
- );
-
- // Checking security layers
- if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
- {
- IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sHostObjectClass . '::' . $sHostObjectId . ' object.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Retrieving host object for future DBSearch parameters
- if ($sHostObjectId !== null)
- {
- // Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
- $oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
- }
- else
- {
- $oHostObject = MetaModel::NewObject($sHostObjectClass);
- // Retrieving action rules
- //
- // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
- // But it would not be a security issue as it only presets values in the form.
- $aActionRules = !empty($aData['sActionRulesToken']) ? ContextManipulatorHelper::DecodeRulesToken($aData['sActionRulesToken']) : array();
- // Preparing object
- $oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
- }
-
- // Updating host object with form data / values
- $sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
- $sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
- if ( !empty($sFormManagerClass) && !empty($sFormManagerData) )
- {
- $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
- $oFormManager->SetApplication($oApp);
- $oFormManager->SetObject($oHostObject);
-
- // Applying action rules if present
- if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
- {
- $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
- $oObj = $oFormManager->GetObject();
- $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
- $oFormManager->SetObject($oObj);
- }
-
- // Updating host object
- $oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW)));
- $oHostObject = $oFormManager->GetObject();
- }
-
- // Retrieving request parameters
- $iPageNumber = $oApp['request_manipulator']->ReadParam('iPageNumber', static::DEFAULT_PAGE_NUMBER, FILTER_SANITIZE_NUMBER_INT);
- $iListLength = $oApp['request_manipulator']->ReadParam('iListLength', static::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT);
- $bInitalPass = $oApp['request_manipulator']->HasParam('draw') ? false : true;
- $sQuery = $oApp['request_manipulator']->ReadParam('sSearchValue', '');
- $sFormPath = $oApp['request_manipulator']->ReadParam('sFormPath', '');
- $sFieldId = $oApp['request_manipulator']->ReadParam('sFieldId', '');
- $aObjectIdsToIgnore = $oApp['request_manipulator']->ReadParam('aObjectIdsToIgnore', null, FILTER_UNSAFE_RAW);
-
- // Building search query
- // - Retrieving target object class from attcode
- $oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
- if ($oTargetAttDef->IsExternalKey())
- {
- $sTargetObjectClass = $oTargetAttDef->GetTargetClass();
- }
- elseif ($oTargetAttDef->IsLinkSet())
- {
- if (!$oTargetAttDef->IsIndirect())
- {
- $sTargetObjectClass = $oTargetAttDef->GetLinkedClass();
- }
- else
- {
- $oRemoteAttDef = MetaModel::GetAttributeDef($oTargetAttDef->GetLinkedClass(), $oTargetAttDef->GetExtKeyToRemote());
- $sTargetObjectClass = $oRemoteAttDef->GetTargetClass();
- }
- }
- elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
- {
- $oRequestTemplate = $oHostObject->Get($sTargetAttCode);
- $oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
- $sTargetObjectClass = $oTemplateFieldSearch->GetClass();
- }
- else
- {
- throw new Exception('Search from attribute can only apply on AttributeExternalKey or AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' given.');
- }
-
- // - Retrieving class attribute list
- $aAttCodes = ApplicationHelper::GetLoadedListFromClass($oApp, $sTargetObjectClass, 'list');
- // - Adding friendlyname attribute to the list is not already in it
- $sTitleAttCode = 'friendlyname';
- if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodes))
- {
- $aAttCodes = array_merge(array($sTitleAttCode), $aAttCodes);
- }
-
- // - Retrieving scope search
- // Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
- // It is the responsability of the template designer to write the right query so the user see only what he should.
- $oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
- $aInternalParams = array();
- if (($oScopeSearch === null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
- {
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $sTargetObjectClass . ' class.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // - Base query from meta model
- if ($oTargetAttDef->IsExternalKey())
- {
- $oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
- }
- elseif ($oTargetAttDef->IsLinkSet())
- {
- $oSearch = $oScopeSearch;
- }
- elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
- {
- // Note : $oTemplateFieldSearch has been defined in the "Retrieving target object class from attcode" part, it is not available otherwise
- $oSearch = $oTemplateFieldSearch;
- }
-
- // - Filtering objects to ignore
- if (($aObjectIdsToIgnore !== null) && (is_array($aObjectIdsToIgnore)))
- {
- //$oSearch->AddConditionExpression('id', $aObjectIdsToIgnore, 'NOT IN');
- $aExpressions = array();
- foreach ($aObjectIdsToIgnore as $sObjectIdToIgnore)
- {
- $aExpressions[] = new ScalarExpression($sObjectIdToIgnore);
- }
- $oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('id', $oSearch->GetClassAlias()), 'NOT IN', new ListExpression($aExpressions)));
- }
-
- // - Adding query condition
- $aInternalParams['this'] = $oHostObject;
- if (!empty($sQuery))
- {
- $oFullExpr = null;
- for ($i = 0; $i < count($aAttCodes); $i++)
- {
- // Checking if the current attcode is an external key in order to search on the friendlyname
- $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $aAttCodes[$i]);
- $sAttCode = (!$oAttDef->IsExternalKey()) ? $aAttCodes[$i] : $aAttCodes[$i] . '_friendlyname';
- // Building expression for the current attcode
- // - For attributes that need conversion from their display value to storage value
- // Note : This is dirty hack that will need to be refactored in the OQL core in order to be nicer and to be extended to other types such as dates etc...
- if (($oAttDef instanceof AttributeEnum) || ($oAttDef instanceof AttributeFinalClass))
- {
- // Looking up storage value
- $aMatchedCodes = array();
- foreach ($oAttDef->GetAllowedValues() as $sValueCode => $sValueLabel)
- {
- if (stripos($sValueLabel, $sQuery) !== false)
- {
- $aMatchedCodes[] = $sValueCode;
- }
- }
- // Building expression
- if (!empty($aMatchedCodes))
- {
- $oEnumeratedListExpr = ListExpression::FromScalars($aMatchedCodes);
- $oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'IN', $oEnumeratedListExpr);
- }
- else
- {
- $oBinExpr = new FalseExpression();
- }
- }
- // - For regular attributs
- else
- {
- $oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
- }
- // Adding expression to the full expression (all attcodes)
- if ($i === 0)
- {
- $oFullExpr = $oBinExpr;
- }
- else
- {
- $oFullExpr = new BinaryExpression($oFullExpr, 'OR', $oBinExpr);
- }
- }
- // Adding full expression to the search object
- $oSearch->AddConditionExpression($oFullExpr);
- $aInternalParams['re_query'] = '%' . $sQuery . '%';
- }
-
- // - Intersecting with scope constraints
- // Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
- // It is the responsability of the template designer to write the right query so the user see only what he should.
- if (($oScopeSearch !== null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
- {
- $oSearch = $oSearch->Intersect($oScopeSearch);
- // - Allowing all data if necessary
- if ($oScopeSearch->IsAllDataAllowed())
- {
- $oSearch->AllowAllData();
- }
- }
-
- // Retrieving results
- // - Preparing object set
- $oSet = new DBObjectSet($oSearch, array(), $aInternalParams);
- $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => $aAttCodes));
- $oSet->SetLimit($iListLength, $iListLength * ($iPageNumber - 1));
- // - Retrieving columns properties
- $aColumnProperties = array();
- foreach ($aAttCodes as $sAttCode)
- {
- $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $sAttCode);
- $aColumnProperties[$sAttCode] = array(
- 'title' => $oAttDef->GetLabel()
- );
- }
- // - Retrieving objects
- $aItems = array();
- while ($oItem = $oSet->Fetch())
- {
- $aItems[] = $this->PrepareObjectInformations($oApp, $oItem, $aAttCodes);
- }
-
- // Preparing response
- if ($bInitalPass)
- {
- $aData = $aData + array(
- 'form' => array(
- 'id' => 'object_search_form_' . time(),
- 'title' => Dict::Format('Brick:Portal:Object:Search:Regular:Title', $oTargetAttDef->GetLabel(), MetaModel::GetName($sTargetObjectClass))
- ),
- 'aColumnProperties' => json_encode($aColumnProperties),
- 'aResults' => array(
- 'aItems' => json_encode($aItems),
- 'iCount' => count($aItems)
- ),
- 'bMultipleSelect' => $oTargetAttDef->IsLinkSet(),
- 'aSource' => array(
- 'sFormPath' => $sFormPath,
- 'sFieldId' => $sFieldId,
- 'aObjectIdsToIgnore' => $aObjectIdsToIgnore,
- 'sFormManagerClass' => $sFormManagerClass,
- 'sFormManagerData' => $sFormManagerData
- )
- );
-
- if ($oRequest->isXmlHttpRequest())
- {
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
- }
- else
- {
- //$oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
- }
- }
- else
- {
- $aData = $aData + array(
- 'levelsProperties' => $aColumnProperties,
- 'data' => $aItems,
- 'recordsTotal' => $oSet->Count(),
- 'recordsFiltered' => $oSet->Count()
- );
-
- $oResponse = $oApp->json($aData);
- }
-
- return $oResponse;
- }
-
- /**
- * Handles the hierarchical search from an attribute
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
- * @param string $sHostObjectClass Class name of the host object
- * @param string $sHostObjectId Id of the host object
- *
- * @return void
- *
- */
- public function SearchHierarchyAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
- {
- // TODO
- }
-
- /**
- * Handles ormDocument display / download from an object
- *
- * Note: This is inspired from pages/ajax.document.php, but duplicated as there is no secret mecanism for ormDocument yet.
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sOperation
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \ArchivedObjectException
- * @throws \CoreException
- */
- public function DocumentAction(Request $oRequest, Application $oApp, $sOperation = null)
- {
- // Setting default operation
- if($sOperation === null)
- {
- $sOperation = 'display';
- }
-
- // Retrieving ormDocument's host object
- $sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
- $sObjectId = $oApp['request_manipulator']->ReadParam('sObjectId', '');
- $sObjectField = $oApp['request_manipulator']->ReadParam('sObjectField', '');
-
- // When reaching to an Attachment, we have to check security on its host object instead of the Attachment itself
- if($sObjectClass === 'Attachment')
- {
- $oAttachment = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
- $sHostClass = $oAttachment->Get('item_class');
- $sHostId = $oAttachment->Get('item_id');
- }
- else
- {
- $sHostClass = $sObjectClass;
- $sHostId = $sObjectId;
- }
-
- // Checking security layers
- if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostClass, $sHostId))
- {
- IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to retrieve document from attribute ' . $sObjectField . ' as it not allowed to read ' . $sHostClass . '::' . $sHostId . ' object.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Retrieving object
- $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* Must not be found */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sHostClass));
- if ($oObject === null)
- {
- // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
- $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
- }
-
- // Setting cache timeout
- // Note: Attachment download should be handle through AttachmentAction()
- if($sObjectClass === 'Attachment')
- {
- // One year ahead: an attachement cannot change
- $iCacheSec = 31556926;
- }
- else
- {
- $iCacheSec = $oApp['request_manipulator']->ReadParam('cache', 0, FILTER_SANITIZE_NUMBER_INT);
- }
-
- $aHeaders = array();
- if($iCacheSec > 0)
- {
- $aHeaders['Expires'] = '';
- $aHeaders['Cache-Control'] = 'no-transform, public,max-age='.$iCacheSec.',s-maxage='.$iCacheSec;
- // Reset the value set previously
- $aHeaders['Pragma'] = 'cache';
- // An arbitrary date in the past is ok
- $aHeaders['Last-Modified'] = 'Wed, 15 Jun 2015 13:21:15 GMT';
- }
-
- /** @var \ormDocument $oDocument */
- $oDocument = $oObject->Get($sObjectField);
- $aHeaders['Content-Type'] = $oDocument->GetMimeType();
- $aHeaders['Content-Disposition'] = (($sOperation === 'display') ? 'inline' : 'attachment') . ';filename="'.$oDocument->GetFileName().'"';
-
- return new Response($oDocument->GetData(), Response::HTTP_OK, $aHeaders);
- }
-
- /**
- * Handles attachment add/remove on an object
- *
- * Note: This is inspired from itop-attachment/ajax.attachment.php
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- * @param string $sOperation
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \Exception
- * @throws \CoreException
- * @throws \CoreUnexpectedValue
- */
- public function AttachmentAction(Request $oRequest, Application $oApp, $sOperation = null)
- {
- $aData = array(
- 'att_id' => 0,
- 'preview' => false,
- 'msg' => ''
- );
-
- // Retrieving sOperation from request only if it wasn't forced (determined by the route)
- if ($sOperation === null)
- {
- $sOperation = $oApp['request_manipulator']->ReadParam('operation', null);
- }
- switch ($sOperation)
- {
- case 'add':
- $sFieldName = $oApp['request_manipulator']->ReadParam('field_name', '');
- $sObjectClass = $oApp['request_manipulator']->ReadParam('object_class', '');
- $sTempId = $oApp['request_manipulator']->ReadParam('temp_id', '');
-
- if (empty($sObjectClass) || empty($sTempId))
- {
- $aData['error'] = Dict::Format('UI:Error:2ParametersMissing', 'object_class', 'temp_id');
- }
- else
- {
- try
- {
- $oDocument = utils::ReadPostedDocument($sFieldName);
- $oAttachment = MetaModel::NewObject('Attachment');
- $oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime')); // one hour...
- $oAttachment->Set('temp_id', $sTempId);
- $oAttachment->Set('item_class', $sObjectClass);
- $oAttachment->SetDefaultOrgId();
- $oAttachment->Set('contents', $oDocument);
- $iAttId = $oAttachment->DBInsert();
-
- $aData['msg'] = htmlentities($oDocument->GetFileName(), ENT_QUOTES, 'UTF-8');
- // TODO : Change icon location when itop-attachment is refactored
- //$aData['icon'] = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($oDoc->GetFileName());
- $aData['icon'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-attachments/icons/image.png';
- $aData['att_id'] = $iAttId;
- $aData['preview'] = $oDocument->IsPreviewAvailable() ? 'true' : 'false';
- }
- catch (FileUploadException $e)
- {
- $aData['error'] = $e->GetMessage();
- }
- }
-
- // Note : The Content-Type header is set to 'text/plain' in order to be IE9 compatible. Otherwise ('application/json') IE9 will download the response as a JSON file to the user computer...
- $oResponse = $oApp->json($aData, 200, array('Content-Type' => 'text/plain'));
- break;
-
- case 'download':
- // Preparing redirection
- // - Route
- $aRouteParams = array(
- 'sObjectClass' => 'Attachment',
- 'sObjectId' => $oApp['request_manipulator']->ReadParam('sAttachmentId', null),
- 'sObjectField' => 'contents',
- );
- $sRedirectRoute = $oApp['url_generator']->generate('p_object_document_download', $aRouteParams);
- // - Request
- $oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
-
- $oResponse = $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
- break;
-
- default:
- $oApp->abort(403);
- break;
- }
-
- return $oResponse;
- }
-
- /**
- * Returns a json response containing an array of objects informations.
- *
- * The service must be given 3 parameters :
- * - sObjectClass : The class of objects to retrieve information from
- * - aObjectIds : An array of object ids
- * - aObjectAttCodes : An array of attribute codes to retrieve
- *
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- *
- * @return \Symfony\Component\HttpFoundation\Response
- *
- * @throws \OQLException
- * @throws \CoreException
- */
- public function GetInformationsAsJsonAction(Request $oRequest, Application $oApp)
- {
- $aData = array();
-
- // Retrieving parameters
- $sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
- $aObjectIds = $oApp['request_manipulator']->ReadParam('aObjectIds', array(), FILTER_UNSAFE_RAW);
- $aObjectAttCodes = $oApp['request_manipulator']->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
- if ( empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes) )
- {
- IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, aObjectIds and aObjectAttCodes expected, "' . $sObjectClass . '", "' . implode('/', $aObjectIds) . '" given.');
- $oApp->abort(500, 'Invalid request data, some informations are missing');
- }
-
- // Checking that id is in the AttCodes
- if (!in_array('id', $aObjectAttCodes))
- {
- $aObjectAttCodes = array_merge(array('id'), $aObjectAttCodes);
- }
-
- // Building the search
- $bIgnoreSilos = $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
- $oSearch = DBObjectSearch::FromOQL("SELECT " . $sObjectClass . " WHERE id IN ('" . implode("','", $aObjectIds) . "')");
- if ($bIgnoreSilos === true)
- {
- $oSearch->AllowAllData();
- }
- $oSet = new DBObjectSet($oSearch);
- $oSet->OptimizeColumnLoad($aObjectAttCodes);
-
- // Retrieving objects
- while ($oObject = $oSet->Fetch())
- {
- $aData['items'][] = $this->PrepareObjectInformations($oApp, $oObject, $aObjectAttCodes);
- }
-
- return $oApp->json($aData);
- }
-
- /**
- * Prepare a DBObject informations as an array for a client side usage (typically, add a row in a table)
- *
- * @param \Silex\Application $oApp
- * @param \DBObject $oObject
- * @param array $aAttCodes
- *
- * @return array
- *
- * @throws \Exception
- * @throws \CoreException
- */
- protected function PrepareObjectInformations(Application $oApp, DBObject $oObject, $aAttCodes = array())
- {
- $sObjectClass = get_class($oObject);
- $aObjectData = array(
- 'id' => $oObject->GetKey(),
- 'name' => $oObject->GetName(),
- 'attributes' => array(),
- );
-
- // Retrieving attributes definitions
- $aAttDefs = array();
- foreach ($aAttCodes as $sAttCode)
- {
- if ($sAttCode === 'id')
- continue;
-
- $aAttDefs[$sAttCode] = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
- }
-
- // Preparing attribute data
- foreach ($aAttDefs as $oAttDef)
- {
- $aAttData = array(
- 'att_code' => $oAttDef->GetCode()
- );
-
- if ($oAttDef->IsExternalKey())
- {
- $aAttData['value'] = $oObject->GetAsHTML($oAttDef->GetCode() . '_friendlyname');
-
- // Checking if user can access object's external key
- if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass()))
- {
- $aAttData['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oObject->Get($oAttDef->GetCode())));
- }
- }
- elseif ($oAttDef->IsLinkSet())
- {
- // We skip it
- continue;
- }
- elseif ($oAttDef instanceof AttributeImage)
- {
- $oOrmDoc = $oObject->Get($oAttDef->GetCode());
- if (is_object($oOrmDoc) && !$oOrmDoc->IsEmpty())
- {
- $sUrl = $oApp['url_generator']->generate('p_object_document_display', array('sObjectClass' => get_class($oObject), 'sObjectId' => $oObject->GetKey(), 'sObjectField' => $oAttDef->GetCode(), 'cache' => 86400));
- }
- else
- {
- $sUrl = $oAttDef->Get('default_image');
- }
- $aAttData['value'] = '
';
- }
- else
- {
- $aAttData['value'] = $oAttDef->GetAsHTML($oObject->Get($oAttDef->GetCode()));
-
- if ($oAttDef instanceof AttributeFriendlyName)
- {
- // Checking if user can access object
- if(SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sObjectClass))
- {
- $aAttData['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oObject->GetKey()));
- }
- }
- }
-
- $aObjectData['attributes'][$oAttDef->GetCode()] = $aAttData;
- }
-
- return $aObjectData;
- }
-
-}
+
+
+namespace Combodo\iTop\Portal\Controller;
+
+use Silex\Application;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Exception;
+use FileUploadException;
+use utils;
+use Dict;
+use IssueLog;
+use MetaModel;
+use DBObject;
+use DBSearch;
+use DBObjectSearch;
+use FalseExpression;
+use BinaryExpression;
+use FieldExpression;
+use VariableExpression;
+use ListExpression;
+use ScalarExpression;
+use DBObjectSet;
+use AttributeEnum;
+use AttributeImage;
+use AttributeFinalClass;
+use AttributeFriendlyName;
+use UserRights;
+use iPopupMenuExtension;
+use URLButtonItem;
+use JSButtonItem;
+use Combodo\iTop\Portal\Helper\ApplicationHelper;
+use Combodo\iTop\Portal\Helper\SecurityHelper;
+use Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
+use Combodo\iTop\Portal\Form\ObjectFormManager;
+use Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
+
+/**
+ * Class ObjectController
+ *
+ * Controller to handle basic view / edit / create of cmdbAbstractObjectClass ManageBrickController
+ *
+ * @package Combodo\iTop\Portal\Controller
+ * @author Guillaume Lajarige
+ * @since 2.3.0
+ */
+class ObjectController extends AbstractController
+{
+
+ const ENUM_MODE_VIEW = 'view';
+ const ENUM_MODE_EDIT = 'edit';
+ const ENUM_MODE_CREATE = 'create';
+
+ const DEFAULT_PAGE_NUMBER = 1;
+ const DEFAULT_LIST_LENGTH = 10;
+
+ /**
+ * Displays an cmdbAbstractObject if the connected user is allowed to.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sObjectClass (Class must be instance of cmdbAbstractObject)
+ * @param string $sObjectId
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ * @throws \DictExceptionMissingString
+ */
+ public function ViewAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
+ {
+ // Checking parameters
+ if ($sObjectClass === '' || $sObjectId === '')
+ {
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
+ $oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
+ }
+
+ // Checking security layers
+ if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sObjectClass, $sObjectId))
+ {
+ IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . '::' . $sObjectId . ' object.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Retrieving object
+ $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
+ if ($oObject === null)
+ {
+ // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
+
+ $aData = array('sMode' => 'view');
+ $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
+ $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:View:Title', MetaModel::GetName($sObjectClass), $oObject->GetName());
+
+ // Add an edit button if user is allowed
+ if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId))
+ {
+ $oModifyButton = new URLButtonItem(
+ 'modify_object',
+ Dict::S('UI:Menu:Modify'),
+ $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
+ );
+ // Putting this one first
+ $aData['form']['buttons']['links'][] = $oModifyButton->GetMenuItem();
+ }
+
+ // Preparing response
+ if ($oRequest->isXmlHttpRequest())
+ {
+ // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
+ if (empty($sOperation))
+ {
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
+ }
+ else
+ {
+ $oResponse = $oApp->json($aData);
+ }
+ }
+ else
+ {
+ // Adding brick if it was passed
+ $sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
+ if (!empty($sBrickId))
+ {
+ $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
+ if ($oBrick !== null)
+ {
+ $aData['oBrick'] = $oBrick;
+ }
+ }
+ $aData['sPageTitle'] = $aData['form']['title'];
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
+ }
+
+ return $oResponse;
+ }
+
+ /**
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param $sObjectClass
+ * @param $sObjectId
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ * @throws \DictExceptionMissingString
+ */
+ public function EditAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId)
+ {
+ // Checking parameters
+ if ($sObjectClass === '' || $sObjectId === '')
+ {
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass and sObjectId expected, "' . $sObjectClass . '" and "' . $sObjectId . '" given.');
+ $oApp->abort(500, Dict::Format('UI:Error:2ParametersMissing', 'class', 'id'));
+ }
+
+ // Checking security layers
+ // Warning : This is a dirty quick fix to allow editing its own contact information
+ $bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
+ if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite)
+ {
+ IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Retrieving object
+ $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
+ if ($oObject === null)
+ {
+ // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
+
+ $aData = array('sMode' => 'edit');
+ $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
+ $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Edit:Title', MetaModel::GetName($sObjectClass), $aData['form']['object_name']);
+
+ // Preparing response
+ if ($oRequest->isXmlHttpRequest())
+ {
+ // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
+ if (empty($sOperation))
+ {
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
+ }
+ else
+ {
+ $oResponse = $oApp->json($aData);
+ }
+ }
+ else
+ {
+ // Adding brick if it was passed
+ $sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
+ if (!empty($sBrickId))
+ {
+ $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
+ if ($oBrick !== null)
+ {
+ $aData['oBrick'] = $oBrick;
+ }
+ }
+ $aData['sPageTitle'] = $aData['form']['title'];
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
+ }
+
+ return $oResponse;
+ }
+
+ /**
+ * Creates an cmdbAbstractObject of the $sObjectClass
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sObjectClass
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \CoreException
+ * @throws \DictExceptionMissingString
+ */
+ public function CreateAction(Request $oRequest, Application $oApp, $sObjectClass)
+ {
+ // Checking security layers
+ if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_CREATE, $sObjectClass))
+ {
+ IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to create ' . $sObjectClass . ' object.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
+
+ $aData = array('sMode' => 'create');
+ $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass);
+ $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Create:Title', MetaModel::GetName($sObjectClass));
+
+ // Preparing response
+ if ($oRequest->isXmlHttpRequest())
+ {
+ // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
+ if (empty($sOperation))
+ {
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
+ }
+ else
+ {
+ $oResponse = $oApp->json($aData);
+ }
+ }
+ else
+ {
+ // Adding brick if it was passed
+ $sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
+ if (!empty($sBrickId))
+ {
+ $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
+ if ($oBrick !== null)
+ {
+ $aData['oBrick'] = $oBrick;
+ }
+ }
+ $aData['sPageTitle'] = $aData['form']['title'];
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
+ }
+
+ return $oResponse;
+ }
+
+ /**
+ * Creates an cmdbAbstractObject of a class determined by the method encoded in $sEncodedMethodName.
+ * This method use an origin DBObject in order to determine the created cmdbAbstractObject.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sObjectClass Class of the origin object
+ * @param string $sObjectId ID of the origin object
+ * @param string $sEncodedMethodName Base64 encoded factory method name
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ */
+ public function CreateFromFactoryAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sEncodedMethodName)
+ {
+ $sMethodName = base64_decode($sEncodedMethodName);
+
+ // Checking that the factory method is valid
+ if (!is_callable($sMethodName))
+ {
+ IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Invalid factory method "' . $sMethodName . '" used when creating an object.');
+ $oApp->abort(500, 'Invalid factory method "' . $sMethodName . '" used when creating an object');
+ }
+
+ // Retrieving origin object
+ // Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
+ $oOriginObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
+
+ // Retrieving target object (We check if the method is a simple function or if it's part of a class in which case only static function are supported)
+ if (!strpos($sMethodName, '::'))
+ {
+ $oTargetObject = $sMethodName($oOriginObject);
+ }
+ else
+ {
+ $aMethodNameParts = explode('::', $sMethodName);
+ $sMethodClass = $aMethodNameParts[0];
+ $sMethodName = $aMethodNameParts[1];
+ $oTargetObject = $sMethodClass::$sMethodName($oOriginObject);
+ }
+
+ // Preparing redirection
+ // - Route
+ $aRouteParams = array(
+ 'sObjectClass' => get_class($oTargetObject)
+ );
+ $sRedirectRoute = $oApp['url_generator']->generate('p_object_create', $aRouteParams);
+ // - Request
+ $oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
+
+ return $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
+ }
+
+ /**
+ * Applies a stimulus $sStimulus on an cmdbAbstractObject
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sObjectClass
+ * @param string $sObjectId
+ * @param string $sStimulusCode
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ */
+ public function ApplyStimulusAction(Request $oRequest, Application $oApp, $sObjectClass, $sObjectId, $sStimulusCode)
+ {
+ // Checking parameters
+ if ($sObjectClass === '' || $sObjectId === '' || $sStimulusCode === '')
+ {
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, sObjectId and $sStimulusCode expected, "' . $sObjectClass . '", "' . $sObjectId . '" and "' . $sStimulusCode . '" given.');
+ $oApp->abort(500, Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus'));
+ }
+
+ // Checking security layers
+ if(!SecurityHelper::IsStimulusAllowed($oApp, $sStimulusCode, $sObjectClass))
+ {
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Retrieving object
+ $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
+ if ($oObject === null)
+ {
+ // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Retrieving request parameters
+ $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
+
+ // Retrieving form properties
+ $aStimuliForms = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, 'apply_stimulus');
+ if(array_key_exists($sStimulusCode, $aStimuliForms))
+ {
+ $aFormProperties = $aStimuliForms[$sStimulusCode];
+ }
+ // Or preparing a default form for the stimulus application
+ else
+ {
+ // Preparing default form
+ $aFormProperties = array(
+ 'id' => 'apply-stimulus',
+ 'type' => 'custom_list',
+ 'fields' => array(),
+ 'layout' => null
+ );
+ }
+
+ // Adding stimulus code to form
+ $aFormProperties['stimulus_code'] = $sStimulusCode;
+
+ // Adding target_state to current_values
+ $oRequest->request->set('apply_stimulus', array('code' => $sStimulusCode));
+
+ $aData = array('sMode' => 'apply_stimulus');
+ $aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
+ $aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Stimulus:Title');
+ $aData['form']['validation']['redirection'] = array(
+ 'url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId))
+ );
+
+ // TODO : This is a ugly patch to avoid showing a modal with a readonly form to the user as it would prevent user from finishing the transition.
+ // Instead, we apply the stimulus directly here and then go to the edited object.
+ if (empty($sOperation))
+ {
+ if (isset($aData['form']['editable_fields_count']) && $aData['form']['editable_fields_count'] === 0)
+ {
+ $sOperation = 'redirect';
+
+ $oSubRequest = $oRequest;
+ $oSubRequest->request->set('operation', 'submit');
+ $oSubRequest->request->set('stimulus_code', '');
+
+ $aData = array('sMode' => 'apply_stimulus');
+ $aData['form'] = $this->HandleForm($oSubRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
+ // Redefining the array to be as simple as possible :
+ $aData = array('redirection' =>
+ array('url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId)))
+ );
+ }
+ }
+
+ // Preparing response
+ if ($oRequest->isXmlHttpRequest())
+ {
+ // We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
+ if (empty($sOperation))
+ {
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
+ }
+ elseif ($sOperation === 'redirect')
+ {
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/modal/mode_loader.html.twig', $aData);
+ }
+ else
+ {
+ $oResponse = $oApp->json($aData);
+ }
+ }
+ else
+ {
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
+ }
+
+ return $oResponse;
+ }
+
+ /**
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sMode
+ * @param string $sObjectClass
+ * @param string $sObjectId
+ * @param string $aFormProperties
+ *
+ * @return array
+ *
+ * @throws \Exception
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ * @throws \OQLException
+ * @throws \Twig_Error_Loader
+ * @throws \Twig_Error_Runtime
+ * @throws \Twig_Error_Syntax
+ */
+ public static function HandleForm(Request $oRequest, Application $oApp, $sMode, $sObjectClass, $sObjectId = null, $aFormProperties = null)
+ {
+ $aFormData = array();
+ $sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
+ $bModal = ($oRequest->isXmlHttpRequest() && empty($sOperation));
+
+ // - Retrieve form properties
+ if ($aFormProperties === null)
+ {
+ $aFormProperties = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, $sMode);
+ }
+
+ // - Create and
+ if (empty($sOperation))
+ {
+ // Retrieving action rules
+ //
+ // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
+ // But it would not be a security issue as it only presets values in the form.
+ $sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
+ $aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
+
+ // Preparing object
+ if ($sObjectId === null)
+ {
+ // Create new UserRequest
+ $oObject = MetaModel::NewObject($sObjectClass);
+
+ // Retrieve action rules information to auto-fill the form if available
+ // Preparing object
+ $oApp['context_manipulator']->PrepareObject($aActionRules, $oObject);
+ $aPrefillFormParam = array( 'user' => $_SESSION["auth_user"],
+ 'origin' => 'portal');
+ $oObject->PrefillForm('creation_from_0', $aPrefillFormParam);
+ }
+ else
+ {
+ $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, true, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass));
+ }
+
+ // Preparing buttons
+ $aFormData['buttons'] = array(
+ 'transitions' => array(),
+ 'actions' => array(),
+ 'links' => array(),
+ 'submit' => array(
+ 'label' => Dict::S('Portal:Button:Submit'),
+ ),
+ );
+ if ($sMode !== 'apply_stimulus')
+ {
+ // Add transition buttons
+ $oSetToCheckRights = DBObjectSet::FromObject($oObject);
+ $aStimuli = Metamodel::EnumStimuli($sObjectClass);
+ foreach ($oObject->EnumTransitions() as $sStimulusCode => $aTransitionDef)
+ {
+ if(SecurityHelper::IsStimulusAllowed($oApp, $sStimulusCode, $sObjectClass, $oSetToCheckRights))
+ {
+ $aFormData['buttons']['transitions'][$sStimulusCode] = $aStimuli[$sStimulusCode]->GetLabel();
+ }
+ }
+
+ // Add plugin buttons
+ foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
+ {
+ foreach($oExtensionInstance->EnumItems(iPopupMenuExtension::PORTAL_OBJDETAILS_ACTIONS, array('portal_id' => $oApp['combodo.portal.instance.id'], 'object' => $oObject)) as $oMenuItem)
+ {
+ if (is_object($oMenuItem))
+ {
+ if($oMenuItem instanceof JSButtonItem)
+ {
+ $aFormData['buttons']['actions'][] = $oMenuItem->GetMenuItem() + array('js_files' => $oMenuItem->GetLinkedScripts());
+ }
+ elseif($oMenuItem instanceof URLButtonItem)
+ {
+ $aFormData['buttons']['links'][] = $oMenuItem->GetMenuItem();
+ }
+ }
+ }
+ }
+
+ // Hiding submit button or changing its label if necessary
+ if(!empty($aFormData['buttons']['transitions']) && isset($aFormProperties['properties']) &&$aFormProperties['properties']['always_show_submit'] === false)
+ {
+ unset($aFormData['buttons']['submit']);
+ }
+ elseif($sMode === static::ENUM_MODE_EDIT)
+ {
+ $aFormData['buttons']['submit']['label'] = Dict::S('Portal:Button:Apply');
+ }
+ }
+ else
+ {
+ $aPrefillFormParam = array(
+ 'user' => $_SESSION["auth_user"],
+ 'origin' => 'portal',
+ 'stimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)['code'],
+ );
+ $oObject->PrefillForm('state_change', $aPrefillFormParam);
+ }
+
+ // Preparing callback urls
+ $aCallbackUrls = $oApp['context_manipulator']->GetCallbackUrls($oApp, $aActionRules, $oObject, $bModal);
+ $aFormData['submit_callback'] = $aCallbackUrls['submit'];
+ $aFormData['cancel_callback'] = $aCallbackUrls['cancel'];
+
+ // Preparing renderer
+ // Note : We might need to distinguish form & renderer endpoints
+ if (in_array($sMode, array('create', 'edit', 'view')))
+ {
+ $sFormEndpoint = $oApp['url_generator']->generate('p_object_' . $sMode, array('sObjectClass' => $sObjectClass, 'sObjectId' => $sObjectId));
+ }
+ else
+ {
+ $sFormEndpoint = $_SERVER['REQUEST_URI'];
+ }
+ $oFormRenderer = new BsFormRenderer();
+ $oFormRenderer->SetEndpoint($sFormEndpoint);
+
+ $oFormManager = new ObjectFormManager();
+ $oFormManager->SetApplication($oApp)
+ ->SetObject($oObject)
+ ->SetMode($sMode)
+ ->SetActionRulesToken($sActionRulesToken)
+ ->SetRenderer($oFormRenderer)
+ ->SetFormProperties($aFormProperties);
+
+ $oFormManager->Build();
+
+ // Check the number of editable fields
+ $aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
+ }
+ else
+ {
+ // Update / Submit / Cancel
+ $sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
+ $sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
+ if ( empty($sFormManagerClass) || empty($sFormManagerData) )
+ {
+ IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameters formmanager_class and formamanager_data must be defined.');
+ $oApp->abort(500, 'Parameters formmanager_class and formmanager_data must be defined.');
+ }
+
+ $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
+ $oFormManager->SetApplication($oApp);
+
+ // Applying action rules if present
+ if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
+ {
+ $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
+ $oObj = $oFormManager->GetObject();
+ $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
+ $oFormManager->SetObject($oObj);
+ }
+
+ switch ($sOperation)
+ {
+ case 'submit':
+ // Applying modification to object
+ $aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'attachmentIds' => $oApp['request_manipulator']->ReadParam('attachment_ids', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties, 'applyStimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)));
+ if ($aFormData['validation']['valid'] === true)
+ {
+ // Note : We don't use $sObjectId there as it can be null if we are creating a new one. Instead we use the id from the created object once it has been seralized
+ // Check if stimulus has to be applied
+ $sStimulusCode = $oApp['request_manipulator']->ReadParam('stimulus_code', '');
+ if (!empty($sStimulusCode))
+ {
+ $aFormData['validation']['redirection'] = array(
+ 'url' => $oApp['url_generator']->generate('p_object_apply_stimulus', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey(), 'sStimulusCode' => $sStimulusCode)),
+ 'ajax' => true
+ );
+ }
+ // Otherwise, we show the object if there is no default
+// else
+// {
+// $aFormData['validation']['redirection'] = array(
+// 'alternative_url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
+// );
+// }
+ }
+ break;
+
+ case 'update':
+ $oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties));
+ break;
+
+ case 'cancel':
+ $oFormManager->OnCancel();
+ break;
+ }
+ }
+
+ // Preparing field_set data
+ $aFieldSetData = array(
+ //'fields_list' => $oFormManager->GetRenderer()->Render(), // GLA : This should be done just after in the if statement.
+ 'fields_impacts' => $oFormManager->GetForm()->GetFieldsImpacts(),
+ 'form_path' => $oFormManager->GetForm()->GetId()
+ );
+
+ // Preparing fields list regarding the operation
+ if ($sOperation === 'update')
+ {
+ $aRequestedFields = $oApp['request_manipulator']->ReadParam('requested_fields', array(), FILTER_UNSAFE_RAW);
+ $sFormPath = $oApp['request_manipulator']->ReadParam('form_path', '');
+
+ // Checking if the update was on a subform, if so we need to make the rendering for that part only
+ if ( !empty($sFormPath) && $sFormPath !== $oFormManager->GetForm()->GetId() )
+ {
+ $oSubForm = $oFormManager->GetForm()->FindSubForm($sFormPath);
+ $oSubFormRenderer = new BsFormRenderer($oSubForm);
+ $oSubFormRenderer->SetEndpoint($oFormManager->GetRenderer()->GetEndpoint());
+ $aFormData['updated_fields'] = $oSubFormRenderer->Render($aRequestedFields);
+ }
+ else
+ {
+ $aFormData['updated_fields'] = $oFormManager->GetRenderer()->Render($aRequestedFields);
+ }
+ }
+ else
+ {
+ $aFieldSetData['fields_list'] = $oFormManager->GetRenderer()->Render();
+ }
+
+ // Preparing form data
+ $aFormData['id'] = $oFormManager->GetForm()->GetId();
+ $aFormData['transaction_id'] = $oFormManager->GetForm()->GetTransactionId();
+ $aFormData['formmanager_class'] = $oFormManager->GetClass();
+ $aFormData['formmanager_data'] = $oFormManager->ToJSON();
+ $aFormData['renderer'] = $oFormManager->GetRenderer();
+ $aFormData['object_name'] = $oFormManager->GetObject()->GetName();
+ $aFormData['object_state'] = $oFormManager->GetObject()->GetState();
+ $aFormData['fieldset'] = $aFieldSetData;
+ $aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE;
+
+ return $aFormData;
+ }
+
+ /**
+ * Handles the autocomplete search
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
+ * @param string $sHostObjectClass Class name of the host object
+ * @param string $sHostObjectId Id of the host object
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ * @throws \OQLException
+ */
+ public function SearchAutocompleteAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
+ {
+ $aData = array(
+ 'results' => array(
+ 'count' => 0,
+ 'items' => array()
+ )
+ );
+
+ // Parsing parameters from request payload
+ parse_str($oRequest->getContent(), $aRequestContent);
+
+ // Checking parameters
+ if (!isset($aRequestContent['sQuery']))
+ {
+ IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameter sQuery missing.');
+ $oApp->abort(500, Dict::Format('UI:Error:ParameterMissing', 'sQuery'));
+ }
+
+ // Retrieving parameters
+ $sQuery = $aRequestContent['sQuery'];
+ $sFieldId = $aRequestContent['sFieldId'];
+
+ // Checking security layers
+ if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
+ {
+ IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sHostObjectClass . '::' . $sHostObjectId . '.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Retrieving host object for future DBSearch parameters
+ if ($sHostObjectId !== null)
+ {
+ // Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
+ $oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
+ }
+ else
+ {
+ $oHostObject = MetaModel::NewObject($sHostObjectClass);
+ // Retrieving action rules
+ //
+ // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
+ // But it would not be a security issue as it only presets values in the form.
+ $sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
+ $aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
+ // Preparing object
+ $oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
+ }
+
+ // Updating host object with form data / values
+ $sFormManagerClass = $aRequestContent['formmanager_class'];
+ $sFormManagerData = $aRequestContent['formmanager_data'];
+ if (!empty($sFormManagerClass) && !empty($sFormManagerData))
+ {
+ $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
+ $oFormManager->SetApplication($oApp);
+ $oFormManager->SetObject($oHostObject);
+
+ // Applying action rules if present
+ if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
+ {
+ $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
+ $oObj = $oFormManager->GetObject();
+ $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
+ $oFormManager->SetObject($oObj);
+ }
+
+ // Updating host object
+ $oFormManager->OnUpdate(array('currentValues' => $aRequestContent['current_values']));
+ $oHostObject = $oFormManager->GetObject();
+ }
+
+ // Building search query
+ // - Retrieving target object class from attcode
+ $oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
+ if ($oTargetAttDef->GetEditClass() === 'CustomFields')
+ {
+ $oRequestTemplate = $oHostObject->Get($sTargetAttCode);
+ $oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
+ $sTargetObjectClass = $oTemplateFieldSearch->GetClass();
+ }
+ elseif ($oTargetAttDef->IsLinkSet())
+ {
+ throw new Exception('Search autocomplete cannot apply on AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' (' . $sHostObjectClass . '->' . $sTargetAttCode . ') given.');
+ }
+ else
+ {
+ $sTargetObjectClass = $oTargetAttDef->GetTargetClass();
+ }
+ // - Base query from meta model
+ if ($oTargetAttDef->GetEditClass() === 'CustomFields')
+ {
+ $oSearch = $oTemplateFieldSearch;
+ }
+ else
+ {
+ $oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
+ }
+ // - Adding query condition
+ $oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('friendlyname', $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('ac_query')));
+ // - Intersecting with scope constraints
+ // Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
+ // It is the responsability of the template designer to write the right query so the user see only what he should.
+ if ($oTargetAttDef->GetEditClass() !== 'CustomFields')
+ {
+ $oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
+ $oSearch = $oSearch->Intersect($oScopeSearch);
+ // - Allowing all data if necessary
+ if ($oScopeSearch->IsAllDataAllowed())
+ {
+ $oSearch->AllowAllData();
+ }
+ }
+
+ // Retrieving results
+ // - Preparing object set
+ $oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%' . $sQuery . '%'));
+ $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
+ // Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
+ if ($oTargetAttDef->GetEditClass() === 'CustomFields')
+ {
+ $oSet->SetLimit(static::DEFAULT_LIST_LENGTH);
+ }
+ else
+ {
+ $oSet->SetLimit($oTargetAttDef->GetMaximumComboLength()); // TODO : Is this the right limit value ? We might want to use another parameter
+ }
+ // - Retrieving objects
+ while ($oItem = $oSet->Fetch())
+ {
+ $aData['results']['items'][] = array('id' => $oItem->GetKey(), 'name' => html_entity_decode($oItem->GetName(), ENT_QUOTES, 'UTF-8'));
+ $aData['results']['count'] ++;
+ }
+
+ // Preparing response
+ if ($oRequest->isXmlHttpRequest())
+ {
+ $oResponse = $oApp->json($aData);
+ }
+ else
+ {
+ $oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ return $oResponse;
+ }
+
+ /**
+ * Handles the regular (table) search from an attribute
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
+ * @param string $sHostObjectClass Class name of the host object
+ * @param string $sHostObjectId Id of the host object
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ * @throws \DictExceptionMissingString
+ * @throws \OQLException
+ */
+ public function SearchFromAttributeAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
+ {
+ $aData = array(
+ 'sMode' => 'search_regular',
+ 'sTargetAttCode' => $sTargetAttCode,
+ 'sHostObjectClass' => $sHostObjectClass,
+ 'sHostObjectId' => $sHostObjectId,
+ 'sActionRulesToken' => $oApp['request_manipulator']->ReadParam('ar_token', ''),
+ );
+
+ // Checking security layers
+ if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostObjectClass, $sHostObjectId))
+ {
+ IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sHostObjectClass . '::' . $sHostObjectId . ' object.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Retrieving host object for future DBSearch parameters
+ if ($sHostObjectId !== null)
+ {
+ // Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
+ $oHostObject = MetaModel::GetObject($sHostObjectClass, $sHostObjectId, true, true);
+ }
+ else
+ {
+ $oHostObject = MetaModel::NewObject($sHostObjectClass);
+ // Retrieving action rules
+ //
+ // Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
+ // But it would not be a security issue as it only presets values in the form.
+ $aActionRules = !empty($aData['sActionRulesToken']) ? ContextManipulatorHelper::DecodeRulesToken($aData['sActionRulesToken']) : array();
+ // Preparing object
+ $oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
+ }
+
+ // Updating host object with form data / values
+ $sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
+ $sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
+ if ( !empty($sFormManagerClass) && !empty($sFormManagerData) )
+ {
+ $oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
+ $oFormManager->SetApplication($oApp);
+ $oFormManager->SetObject($oHostObject);
+
+ // Applying action rules if present
+ if (($oFormManager->GetActionRulesToken() !== null) && ($oFormManager->GetActionRulesToken() !== ''))
+ {
+ $aActionRules = ContextManipulatorHelper::DecodeRulesToken($oFormManager->GetActionRulesToken());
+ $oObj = $oFormManager->GetObject();
+ $oApp['context_manipulator']->PrepareObject($aActionRules, $oObj);
+ $oFormManager->SetObject($oObj);
+ }
+
+ // Updating host object
+ $oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW)));
+ $oHostObject = $oFormManager->GetObject();
+ }
+
+ // Retrieving request parameters
+ $iPageNumber = $oApp['request_manipulator']->ReadParam('iPageNumber', static::DEFAULT_PAGE_NUMBER, FILTER_SANITIZE_NUMBER_INT);
+ $iListLength = $oApp['request_manipulator']->ReadParam('iListLength', static::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT);
+ $bInitalPass = $oApp['request_manipulator']->HasParam('draw') ? false : true;
+ $sQuery = $oApp['request_manipulator']->ReadParam('sSearchValue', '');
+ $sFormPath = $oApp['request_manipulator']->ReadParam('sFormPath', '');
+ $sFieldId = $oApp['request_manipulator']->ReadParam('sFieldId', '');
+ $aObjectIdsToIgnore = $oApp['request_manipulator']->ReadParam('aObjectIdsToIgnore', null, FILTER_UNSAFE_RAW);
+
+ // Building search query
+ // - Retrieving target object class from attcode
+ $oTargetAttDef = MetaModel::GetAttributeDef($sHostObjectClass, $sTargetAttCode);
+ if ($oTargetAttDef->IsExternalKey())
+ {
+ $sTargetObjectClass = $oTargetAttDef->GetTargetClass();
+ }
+ elseif ($oTargetAttDef->IsLinkSet())
+ {
+ if (!$oTargetAttDef->IsIndirect())
+ {
+ $sTargetObjectClass = $oTargetAttDef->GetLinkedClass();
+ }
+ else
+ {
+ $oRemoteAttDef = MetaModel::GetAttributeDef($oTargetAttDef->GetLinkedClass(), $oTargetAttDef->GetExtKeyToRemote());
+ $sTargetObjectClass = $oRemoteAttDef->GetTargetClass();
+ }
+ }
+ elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
+ {
+ $oRequestTemplate = $oHostObject->Get($sTargetAttCode);
+ $oTemplateFieldSearch = $oRequestTemplate->GetForm()->GetField('user_data')->GetForm()->GetField($sFieldId)->GetSearch();
+ $sTargetObjectClass = $oTemplateFieldSearch->GetClass();
+ }
+ else
+ {
+ throw new Exception('Search from attribute can only apply on AttributeExternalKey or AttributeLinkedSet objects, ' . get_class($oTargetAttDef) . ' given.');
+ }
+
+ // - Retrieving class attribute list
+ $aAttCodes = ApplicationHelper::GetLoadedListFromClass($oApp, $sTargetObjectClass, 'list');
+ // - Adding friendlyname attribute to the list is not already in it
+ $sTitleAttCode = 'friendlyname';
+ if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodes))
+ {
+ $aAttCodes = array_merge(array($sTitleAttCode), $aAttCodes);
+ }
+
+ // - Retrieving scope search
+ // Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
+ // It is the responsability of the template designer to write the right query so the user see only what he should.
+ $oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sTargetObjectClass, UR_ACTION_READ);
+ $aInternalParams = array();
+ if (($oScopeSearch === null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
+ {
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' has no scope query for ' . $sTargetObjectClass . ' class.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // - Base query from meta model
+ if ($oTargetAttDef->IsExternalKey())
+ {
+ $oSearch = DBSearch::FromOQL($oTargetAttDef->GetValuesDef()->GetFilterExpression());
+ }
+ elseif ($oTargetAttDef->IsLinkSet())
+ {
+ $oSearch = $oScopeSearch;
+ }
+ elseif ($oTargetAttDef->GetEditClass() === 'CustomFields')
+ {
+ // Note : $oTemplateFieldSearch has been defined in the "Retrieving target object class from attcode" part, it is not available otherwise
+ $oSearch = $oTemplateFieldSearch;
+ }
+
+ // - Filtering objects to ignore
+ if (($aObjectIdsToIgnore !== null) && (is_array($aObjectIdsToIgnore)))
+ {
+ //$oSearch->AddConditionExpression('id', $aObjectIdsToIgnore, 'NOT IN');
+ $aExpressions = array();
+ foreach ($aObjectIdsToIgnore as $sObjectIdToIgnore)
+ {
+ $aExpressions[] = new ScalarExpression($sObjectIdToIgnore);
+ }
+ $oSearch->AddConditionExpression(new BinaryExpression(new FieldExpression('id', $oSearch->GetClassAlias()), 'NOT IN', new ListExpression($aExpressions)));
+ }
+
+ // - Adding query condition
+ $aInternalParams['this'] = $oHostObject;
+ if (!empty($sQuery))
+ {
+ $oFullExpr = null;
+ for ($i = 0; $i < count($aAttCodes); $i++)
+ {
+ // Checking if the current attcode is an external key in order to search on the friendlyname
+ $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $aAttCodes[$i]);
+ $sAttCode = (!$oAttDef->IsExternalKey()) ? $aAttCodes[$i] : $aAttCodes[$i] . '_friendlyname';
+ // Building expression for the current attcode
+ // - For attributes that need conversion from their display value to storage value
+ // Note : This is dirty hack that will need to be refactored in the OQL core in order to be nicer and to be extended to other types such as dates etc...
+ if (($oAttDef instanceof AttributeEnum) || ($oAttDef instanceof AttributeFinalClass))
+ {
+ // Looking up storage value
+ $aMatchedCodes = array();
+ foreach ($oAttDef->GetAllowedValues() as $sValueCode => $sValueLabel)
+ {
+ if (stripos($sValueLabel, $sQuery) !== false)
+ {
+ $aMatchedCodes[] = $sValueCode;
+ }
+ }
+ // Building expression
+ if (!empty($aMatchedCodes))
+ {
+ $oEnumeratedListExpr = ListExpression::FromScalars($aMatchedCodes);
+ $oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'IN', $oEnumeratedListExpr);
+ }
+ else
+ {
+ $oBinExpr = new FalseExpression();
+ }
+ }
+ // - For regular attributs
+ else
+ {
+ $oBinExpr = new BinaryExpression(new FieldExpression($sAttCode, $oSearch->GetClassAlias()), 'LIKE', new VariableExpression('re_query'));
+ }
+ // Adding expression to the full expression (all attcodes)
+ if ($i === 0)
+ {
+ $oFullExpr = $oBinExpr;
+ }
+ else
+ {
+ $oFullExpr = new BinaryExpression($oFullExpr, 'OR', $oBinExpr);
+ }
+ }
+ // Adding full expression to the search object
+ $oSearch->AddConditionExpression($oFullExpr);
+ $aInternalParams['re_query'] = '%' . $sQuery . '%';
+ }
+
+ // - Intersecting with scope constraints
+ // Note : This do NOT apply to custom fields as the portal administrator is not supposed to know which objects will be put in the templates.
+ // It is the responsability of the template designer to write the right query so the user see only what he should.
+ if (($oScopeSearch !== null) && ($oTargetAttDef->GetEditClass() !== 'CustomFields'))
+ {
+ $oSearch = $oSearch->Intersect($oScopeSearch);
+ // - Allowing all data if necessary
+ if ($oScopeSearch->IsAllDataAllowed())
+ {
+ $oSearch->AllowAllData();
+ }
+ }
+
+ // Retrieving results
+ // - Preparing object set
+ $oSet = new DBObjectSet($oSearch, array(), $aInternalParams);
+ $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => $aAttCodes));
+ $oSet->SetLimit($iListLength, $iListLength * ($iPageNumber - 1));
+ // - Retrieving columns properties
+ $aColumnProperties = array();
+ foreach ($aAttCodes as $sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef($sTargetObjectClass, $sAttCode);
+ $aColumnProperties[$sAttCode] = array(
+ 'title' => $oAttDef->GetLabel()
+ );
+ }
+ // - Retrieving objects
+ $aItems = array();
+ while ($oItem = $oSet->Fetch())
+ {
+ $aItems[] = $this->PrepareObjectInformations($oApp, $oItem, $aAttCodes);
+ }
+
+ // Preparing response
+ if ($bInitalPass)
+ {
+ $aData = $aData + array(
+ 'form' => array(
+ 'id' => 'object_search_form_' . time(),
+ 'title' => Dict::Format('Brick:Portal:Object:Search:Regular:Title', $oTargetAttDef->GetLabel(), MetaModel::GetName($sTargetObjectClass))
+ ),
+ 'aColumnProperties' => json_encode($aColumnProperties),
+ 'aResults' => array(
+ 'aItems' => json_encode($aItems),
+ 'iCount' => count($aItems)
+ ),
+ 'bMultipleSelect' => $oTargetAttDef->IsLinkSet(),
+ 'aSource' => array(
+ 'sFormPath' => $sFormPath,
+ 'sFieldId' => $sFieldId,
+ 'aObjectIdsToIgnore' => $aObjectIdsToIgnore,
+ 'sFormManagerClass' => $sFormManagerClass,
+ 'sFormManagerData' => $sFormManagerData
+ )
+ );
+
+ if ($oRequest->isXmlHttpRequest())
+ {
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
+ }
+ else
+ {
+ //$oResponse = $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/layout.html.twig', $aData);
+ }
+ }
+ else
+ {
+ $aData = $aData + array(
+ 'levelsProperties' => $aColumnProperties,
+ 'data' => $aItems,
+ 'recordsTotal' => $oSet->Count(),
+ 'recordsFiltered' => $oSet->Count()
+ );
+
+ $oResponse = $oApp->json($aData);
+ }
+
+ return $oResponse;
+ }
+
+ /**
+ * Handles the hierarchical search from an attribute
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sTargetAttCode Attribute code of the host object pointing to the Object class to search
+ * @param string $sHostObjectClass Class name of the host object
+ * @param string $sHostObjectId Id of the host object
+ *
+ * @return void
+ *
+ */
+ public function SearchHierarchyAction(Request $oRequest, Application $oApp, $sTargetAttCode, $sHostObjectClass, $sHostObjectId = null)
+ {
+ // TODO
+ }
+
+ /**
+ * Handles ormDocument display / download from an object
+ *
+ * Note: This is inspired from pages/ajax.document.php, but duplicated as there is no secret mecanism for ormDocument yet.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sOperation
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ */
+ public function DocumentAction(Request $oRequest, Application $oApp, $sOperation = null)
+ {
+ // Setting default operation
+ if($sOperation === null)
+ {
+ $sOperation = 'display';
+ }
+
+ // Retrieving ormDocument's host object
+ $sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
+ $sObjectId = $oApp['request_manipulator']->ReadParam('sObjectId', '');
+ $sObjectField = $oApp['request_manipulator']->ReadParam('sObjectField', '');
+
+ // When reaching to an Attachment, we have to check security on its host object instead of the Attachment itself
+ if($sObjectClass === 'Attachment')
+ {
+ $oAttachment = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
+ $sHostClass = $oAttachment->Get('item_class');
+ $sHostId = $oAttachment->Get('item_id');
+ }
+ else
+ {
+ $sHostClass = $sObjectClass;
+ $sHostId = $sObjectId;
+ }
+
+ // Checking security layers
+ if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sHostClass, $sHostId))
+ {
+ IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to retrieve document from attribute ' . $sObjectField . ' as it not allowed to read ' . $sHostClass . '::' . $sHostId . ' object.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Retrieving object
+ $oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* Must not be found */, $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sHostClass));
+ if ($oObject === null)
+ {
+ // We should never be there as the secuirty helper makes sure that the object exists, but just in case.
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : Could not load object ' . $sObjectClass . '::' . $sObjectId . '.');
+ $oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
+ }
+
+ // Setting cache timeout
+ // Note: Attachment download should be handle through AttachmentAction()
+ if($sObjectClass === 'Attachment')
+ {
+ // One year ahead: an attachement cannot change
+ $iCacheSec = 31556926;
+ }
+ else
+ {
+ $iCacheSec = $oApp['request_manipulator']->ReadParam('cache', 0, FILTER_SANITIZE_NUMBER_INT);
+ }
+
+ $aHeaders = array();
+ if($iCacheSec > 0)
+ {
+ $aHeaders['Expires'] = '';
+ $aHeaders['Cache-Control'] = 'no-transform, public,max-age='.$iCacheSec.',s-maxage='.$iCacheSec;
+ // Reset the value set previously
+ $aHeaders['Pragma'] = 'cache';
+ // An arbitrary date in the past is ok
+ $aHeaders['Last-Modified'] = 'Wed, 15 Jun 2015 13:21:15 GMT';
+ }
+
+ /** @var \ormDocument $oDocument */
+ $oDocument = $oObject->Get($sObjectField);
+ $aHeaders['Content-Type'] = $oDocument->GetMimeType();
+ $aHeaders['Content-Disposition'] = (($sOperation === 'display') ? 'inline' : 'attachment') . ';filename="'.$oDocument->GetFileName().'"';
+
+ return new Response($oDocument->GetData(), Response::HTTP_OK, $aHeaders);
+ }
+
+ /**
+ * Handles attachment add/remove on an object
+ *
+ * Note: This is inspired from itop-attachment/ajax.attachment.php
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ * @param string $sOperation
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \Exception
+ * @throws \CoreException
+ * @throws \CoreUnexpectedValue
+ */
+ public function AttachmentAction(Request $oRequest, Application $oApp, $sOperation = null)
+ {
+ $aData = array(
+ 'att_id' => 0,
+ 'preview' => false,
+ 'msg' => ''
+ );
+
+ // Retrieving sOperation from request only if it wasn't forced (determined by the route)
+ if ($sOperation === null)
+ {
+ $sOperation = $oApp['request_manipulator']->ReadParam('operation', null);
+ }
+ switch ($sOperation)
+ {
+ case 'add':
+ $sFieldName = $oApp['request_manipulator']->ReadParam('field_name', '');
+ $sObjectClass = $oApp['request_manipulator']->ReadParam('object_class', '');
+ $sTempId = $oApp['request_manipulator']->ReadParam('temp_id', '');
+
+ if (empty($sObjectClass) || empty($sTempId))
+ {
+ $aData['error'] = Dict::Format('UI:Error:2ParametersMissing', 'object_class', 'temp_id');
+ }
+ else
+ {
+ try
+ {
+ $oDocument = utils::ReadPostedDocument($sFieldName);
+ $oAttachment = MetaModel::NewObject('Attachment');
+ $oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime')); // one hour...
+ $oAttachment->Set('temp_id', $sTempId);
+ $oAttachment->Set('item_class', $sObjectClass);
+ $oAttachment->SetDefaultOrgId();
+ $oAttachment->Set('contents', $oDocument);
+ $iAttId = $oAttachment->DBInsert();
+
+ $aData['msg'] = htmlentities($oDocument->GetFileName(), ENT_QUOTES, 'UTF-8');
+ // TODO : Change icon location when itop-attachment is refactored
+ //$aData['icon'] = utils::GetAbsoluteUrlAppRoot() . AttachmentPlugIn::GetFileIcon($oDoc->GetFileName());
+ $aData['icon'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-attachments/icons/image.png';
+ $aData['att_id'] = $iAttId;
+ $aData['preview'] = $oDocument->IsPreviewAvailable() ? 'true' : 'false';
+ }
+ catch (FileUploadException $e)
+ {
+ $aData['error'] = $e->GetMessage();
+ }
+ }
+
+ // Note : The Content-Type header is set to 'text/plain' in order to be IE9 compatible. Otherwise ('application/json') IE9 will download the response as a JSON file to the user computer...
+ $oResponse = $oApp->json($aData, 200, array('Content-Type' => 'text/plain'));
+ break;
+
+ case 'download':
+ // Preparing redirection
+ // - Route
+ $aRouteParams = array(
+ 'sObjectClass' => 'Attachment',
+ 'sObjectId' => $oApp['request_manipulator']->ReadParam('sAttachmentId', null),
+ 'sObjectField' => 'contents',
+ );
+ $sRedirectRoute = $oApp['url_generator']->generate('p_object_document_download', $aRouteParams);
+ // - Request
+ $oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
+
+ $oResponse = $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
+ break;
+
+ default:
+ $oApp->abort(403);
+ break;
+ }
+
+ return $oResponse;
+ }
+
+ /**
+ * Returns a json response containing an array of objects informations.
+ *
+ * The service must be given 3 parameters :
+ * - sObjectClass : The class of objects to retrieve information from
+ * - aObjectIds : An array of object ids
+ * - aObjectAttCodes : An array of attribute codes to retrieve
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Silex\Application $oApp
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \OQLException
+ * @throws \CoreException
+ */
+ public function GetInformationsAsJsonAction(Request $oRequest, Application $oApp)
+ {
+ $aData = array();
+
+ // Retrieving parameters
+ $sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
+ $aObjectIds = $oApp['request_manipulator']->ReadParam('aObjectIds', array(), FILTER_UNSAFE_RAW);
+ $aObjectAttCodes = $oApp['request_manipulator']->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
+ if ( empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes) )
+ {
+ IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, aObjectIds and aObjectAttCodes expected, "' . $sObjectClass . '", "' . implode('/', $aObjectIds) . '" given.');
+ $oApp->abort(500, 'Invalid request data, some informations are missing');
+ }
+
+ // Checking that id is in the AttCodes
+ if (!in_array('id', $aObjectAttCodes))
+ {
+ $aObjectAttCodes = array_merge(array('id'), $aObjectAttCodes);
+ }
+
+ // Building the search
+ $bIgnoreSilos = $oApp['scope_validator']->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
+ $oSearch = DBObjectSearch::FromOQL("SELECT " . $sObjectClass . " WHERE id IN ('" . implode("','", $aObjectIds) . "')");
+ if ($bIgnoreSilos === true)
+ {
+ $oSearch->AllowAllData();
+ }
+ $oSet = new DBObjectSet($oSearch);
+ $oSet->OptimizeColumnLoad($aObjectAttCodes);
+
+ // Retrieving objects
+ while ($oObject = $oSet->Fetch())
+ {
+ $aData['items'][] = $this->PrepareObjectInformations($oApp, $oObject, $aObjectAttCodes);
+ }
+
+ return $oApp->json($aData);
+ }
+
+ /**
+ * Prepare a DBObject informations as an array for a client side usage (typically, add a row in a table)
+ *
+ * @param \Silex\Application $oApp
+ * @param \DBObject $oObject
+ * @param array $aAttCodes
+ *
+ * @return array
+ *
+ * @throws \Exception
+ * @throws \CoreException
+ */
+ protected function PrepareObjectInformations(Application $oApp, DBObject $oObject, $aAttCodes = array())
+ {
+ $sObjectClass = get_class($oObject);
+ $aObjectData = array(
+ 'id' => $oObject->GetKey(),
+ 'name' => $oObject->GetName(),
+ 'attributes' => array(),
+ );
+
+ // Retrieving attributes definitions
+ $aAttDefs = array();
+ foreach ($aAttCodes as $sAttCode)
+ {
+ if ($sAttCode === 'id')
+ continue;
+
+ $aAttDefs[$sAttCode] = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
+ }
+
+ // Preparing attribute data
+ foreach ($aAttDefs as $oAttDef)
+ {
+ $aAttData = array(
+ 'att_code' => $oAttDef->GetCode()
+ );
+
+ if ($oAttDef->IsExternalKey())
+ {
+ $aAttData['value'] = $oObject->GetAsHTML($oAttDef->GetCode() . '_friendlyname');
+
+ // Checking if user can access object's external key
+ if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass()))
+ {
+ $aAttData['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $oAttDef->GetTargetClass(), 'sObjectId' => $oObject->Get($oAttDef->GetCode())));
+ }
+ }
+ elseif ($oAttDef->IsLinkSet())
+ {
+ // We skip it
+ continue;
+ }
+ elseif ($oAttDef instanceof AttributeImage)
+ {
+ $oOrmDoc = $oObject->Get($oAttDef->GetCode());
+ if (is_object($oOrmDoc) && !$oOrmDoc->IsEmpty())
+ {
+ $sUrl = $oApp['url_generator']->generate('p_object_document_display', array('sObjectClass' => get_class($oObject), 'sObjectId' => $oObject->GetKey(), 'sObjectField' => $oAttDef->GetCode(), 'cache' => 86400));
+ }
+ else
+ {
+ $sUrl = $oAttDef->Get('default_image');
+ }
+ $aAttData['value'] = '
';
+ }
+ else
+ {
+ $aAttData['value'] = $oAttDef->GetAsHTML($oObject->Get($oAttDef->GetCode()));
+
+ if ($oAttDef instanceof AttributeFriendlyName)
+ {
+ // Checking if user can access object
+ if(SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $sObjectClass))
+ {
+ $aAttData['url'] = $oApp['url_generator']->generate('p_object_view', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oObject->GetKey()));
+ }
+ }
+ }
+
+ $aObjectData['attributes'][$oAttDef->GetCode()] = $aAttData;
+ }
+
+ return $aObjectData;
+ }
+
+}
diff --git a/js/utils.js b/js/utils.js
index 346ebf71a..fd5c971e9 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -1,718 +1,718 @@
-// Some general purpose JS functions for the iTop application
-
-//IE 8 compatibility, copied from: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/IndexOf
-if (!Array.prototype.indexOf) {
-
- if (false) // deactivated since it causes troubles: for(k in aData) => returns the indexOf function as first element on empty arrays !
- {
- Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
- "use strict";
- if (this == null) {
- throw new TypeError();
- }
- var t = Object(this);
- var len = t.length >>> 0;
- if (len === 0) {
- return -1;
- }
- var n = 0;
- if (arguments.length > 1) {
- n = Number(arguments[1]);
- if (n != n) { // shortcut for verifying if it's NaN
- n = 0;
- } else if (n != 0 && n != Infinity && n != -Infinity) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
- }
- }
- if (n >= len) {
- return -1;
- }
- var k = n >= 0 ? n : Math.max(len-Math.abs(n), 0);
- for (; k < len; k++) {
- if (k in t && t[k] === searchElement) {
- return k;
- }
- }
- return -1;
- }
- }
-}
-// Polyfill for Array.from for IE
-// Copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
-if (!Array.from) {
- Array.from = (function () {
- var toStr = Object.prototype.toString;
- var isCallable = function (fn) {
- return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
- };
- var toInteger = function (value) {
- var number = Number(value);
- if (isNaN(number)) { return 0; }
- if (number === 0 || !isFinite(number)) { return number; }
- return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
- };
- var maxSafeInteger = Math.pow(2, 53) - 1;
- var toLength = function (value) {
- var len = toInteger(value);
- return Math.min(Math.max(len, 0), maxSafeInteger);
- };
-
- // The length property of the from method is 1.
- return function from(arrayLike/*, mapFn, thisArg */) {
- // 1. Let C be the this value.
- var C = this;
-
- // 2. Let items be ToObject(arrayLike).
- var items = Object(arrayLike);
-
- // 3. ReturnIfAbrupt(items).
- if (arrayLike == null) {
- throw new TypeError('Array.from requires an array-like object - not null or undefined');
- }
-
- // 4. If mapfn is undefined, then let mapping be false.
- var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
- var T;
- if (typeof mapFn !== 'undefined') {
- // 5. else
- // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
- if (!isCallable(mapFn)) {
- throw new TypeError('Array.from: when provided, the second argument must be a function');
- }
-
- // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
- if (arguments.length > 2) {
- T = arguments[2];
- }
- }
-
- // 10. Let lenValue be Get(items, "length").
- // 11. Let len be ToLength(lenValue).
- var len = toLength(items.length);
-
- // 13. If IsConstructor(C) is true, then
- // 13. a. Let A be the result of calling the [[Construct]] internal method
- // of C with an argument list containing the single item len.
- // 14. a. Else, Let A be ArrayCreate(len).
- var A = isCallable(C) ? Object(new C(len)) : new Array(len);
-
- // 16. Let k be 0.
- var k = 0;
- // 17. Repeat, while k < len… (also steps a - h)
- var kValue;
- while (k < len) {
- kValue = items[k];
- if (mapFn) {
- A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
- } else {
- A[k] = kValue;
- }
- k += 1;
- }
- // 18. Let putStatus be Put(A, "length", len, true).
- A.length = len;
- // 20. Return A.
- return A;
- };
- }());
-}
-
-/**
- * Reload a truncated list
- */
-aTruncatedLists = {}; // To keep track of the list being loaded, each member is an ajaxRequest object
-
-function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) {
- $('#'+divId).block();
- //$('#'+divId).blockUI();
- if (aTruncatedLists[divId] != undefined) {
- try {
- aAjaxRequest = aTruncatedLists[divId];
- aAjaxRequest.abort();
- }
- catch (e) {
- // Do nothing special, just continue
- console.log('Uh,uh, exception !');
- }
- }
- aTruncatedLists[divId] = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style=list',
- {operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams},
- function (data) {
- aTruncatedLists[divId] = undefined;
- if (data.length > 0) {
- $('#'+divId).html(data);
- $('#'+divId+' .listResults').tableHover(); // hover tables
- $('#'+divId+' .listResults').each(function () {
- var table = $(this);
- var id = $(this).parent();
- aTruncatedLists[divId] = undefined;
- var checkbox = (table.find('th:first :checkbox').length > 0);
- if (checkbox) {
- // There is a checkbox in the first column, don't make it sortable
- table.tablesorter({headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']}).tablesorterPager({container: $("#pager")}); // sortable and zebra tables
- }
- else {
- // There is NO checkbox in the first column, all columns are considered sortable
- table.tablesorter({widgets: ['myZebra', 'truncatedList']}).tablesorterPager({container: $("#pager"), totalRows: 97, filter: sSerializedFilter, extra_params: sExtraParams}); // sortable and zebra tables
- }
- });
- $('#'+divId).unblock();
- }
- }
- );
-}
-
-/**
- * Truncate a previously expanded list !
- */
-function TruncateList(divId, iLimit, sNewLabel, sLinkLabel) {
- $('#'+divId).block();
- var iCount = 0;
- $('#'+divId+' table.listResults tr:gt('+iLimit+')').each(function () {
- $(this).remove();
- });
- $('#lbl_'+divId).html(sNewLabel);
- $('#'+divId+' table.listResults tr:last td').addClass('truncated');
- $('#'+divId+' table.listResults').addClass('truncated');
- $('#trc_'+divId).html(sLinkLabel);
- $('#'+divId+' .listResults').trigger("update"); // Reset the cache
- $('#'+divId).unblock();
-}
-
-/**
- * Reload any block -- used for periodic auto-reload
- */
-function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams) {
- // Check if the user is not editing the list properties right now
- var bDialogOpen = false;
- var oDataTable = $('#'+divId+' :itop-datatable');
- var bIsDataTable = false;
- if (oDataTable.length > 0) {
- bDialogOpen = oDataTable.datatable('IsDialogOpen');
- bIsDataTable = true;
- }
- if (!bDialogOpen) {
- if (bIsDataTable) {
- oDataTable.datatable('DoRefresh');
- }
- else {
- $('#'+divId).block();
-
- $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style='+sStyle,
- {operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams},
- function (data) {
- $('#'+divId).empty();
- $('#'+divId).append(data);
- $('#'+divId).removeClass('loading');
- }
- );
- }
- }
-}
-
-function SaveGroupBySortOrder(sTableId, aValues) {
- var sDashboardId = $('#'+sTableId).closest('.dashboard_contents').attr('id');
- var sPrefKey = 'GroupBy_'+sDashboardId+'_'+sTableId;
- if (aValues.length != 0) {
- $sValue = JSON.stringify(aValues);
- if (GetUserPreference(sPrefKey, null) != $sValue) {
- SetUserPreference(sPrefKey, $sValue, true);
- }
- }
-}
-
-function LoadGroupBySortOrder(sTableId) {
- var sDashboardId = $('#'+sTableId).closest('.dashboard_contents').attr('id');
- var sPrefKey = 'GroupBy_'+sDashboardId+'_'+sTableId;
- var sValues = GetUserPreference(sPrefKey, null);
- if (sValues != null) {
- aValues = JSON.parse(sValues);
- window.setTimeout(function () {
- $('#'+sTableId+' table.listResults').trigger('sorton', [aValues]);
- }, 50);
- }
-
-}
-
-/**
- * Update the display and value of a file input widget when the user picks a new file
- */
-function UpdateFileName(id, sNewFileName) {
- var aPath = sNewFileName.split('\\');
- var sNewFileName = aPath[aPath.length-1];
-
- $('#'+id).val(sNewFileName);
- $('#'+id).trigger('validate');
- $('#name_'+id).text(sNewFileName);
- return true;
-}
-
-/**
- * Reload a search form for the specified class
- */
-function ReloadSearchForm(divId, sClassName, sBaseClass, sContext, sTableId, sExtraParams) {
- var oDiv = $('#ds_'+divId);
- oDiv.block();
- // deprecated in jQuery 1.8
- //var oFormEvents = $('#ds_'+divId+' form').data('events');
- var oForm = $('#ds_'+divId+' form');
- var oFormEvents = $._data(oForm[0], "events");
-
- // Save the submit handlers
- aSubmit = new Array();
- if ((oFormEvents != null) && (oFormEvents.submit != undefined)) {
- for (var index = 0; index < oFormEvents.submit.length; index++) {
- aSubmit [index] = {data: oFormEvents.submit[index].data, namespace: oFormEvents.submit[index].namespace, handler: oFormEvents.submit[index].handler};
- }
- }
- sAction = $('#ds_'+divId+' form').attr('action');
-
- // Save the current values in the form
- var oMap = {};
- $('#ds_'+divId+" form :input[name!='']").each(function () {
- oMap[this.name] = this.value;
- });
- oMap.operation = 'search_form';
- oMap.className = sClassName;
- oMap.baseClass = sBaseClass;
- oMap.currentId = divId;
- oMap._table_id_ = sTableId;
- oMap.action = sAction;
- if(sExtraParams['selection_mode'])
- {
- oMap.selection_mode = sExtraParams['selection_mode'];
- }
- if(sExtraParams['result_list_outer_selector'])
- {
- oMap.result_list_outer_selector = sExtraParams['result_list_outer_selector'];
- }
- if(sExtraParams['cssCount'])
- {
- oMap.css_count = sExtraParams['cssCount'];
- $(sExtraParams['cssCount']).val(0).trigger('change');
- }
- if(sExtraParams['table_inner_id'])
- {
- oMap.table_inner_id = sExtraParams['table_inner_id'];
- }
- $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, oMap,
- function (data) {
- oDiv.empty();
- oDiv.append(data);
- oDiv.unblock();
- oDiv.parent().resize(); // Inform the parent that the form has just been (potentially) resized
- oDiv.find('form').triggerHandler('itop.search.form.reloaded');
- }
- );
-}
-
-/**
- * Stores - in a persistent way - user specific preferences
- * depends on a global variable oUserPreferences created/filled by the iTopWebPage
- * that acts as a local -write through- cache
- */
-function SetUserPreference(sPreferenceCode, sPrefValue, bPersistent) {
- sPreviousValue = undefined;
- try {
- sPreviousValue = oUserPreferences[sPreferenceCode];
- }
- catch (err) {
- sPreviousValue = undefined;
- }
- oUserPreferences[sPreferenceCode] = sPrefValue;
- if (bPersistent && (sPrefValue != sPreviousValue)) {
- ajax_request = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
- {operation: 'set_pref', code: sPreferenceCode, value: sPrefValue}); // Make it persistent
- }
-}
-
-/**
- * Get user specific preferences
- * depends on a global variable oUserPreferences created/filled by the iTopWebPage
- * that acts as a local -write through- cache
- */
-function GetUserPreference(sPreferenceCode, sDefaultValue) {
- var value = sDefaultValue;
- if (oUserPreferences[sPreferenceCode] != undefined) {
- value = oUserPreferences[sPreferenceCode];
- }
- return value;
-}
-
-/**
- * Check/uncheck a whole list of checkboxes
- */
-function CheckAll(sSelector, bValue) {
- var value = bValue;
- $(sSelector).each(function () {
- if (this.checked != value) {
- this.checked = value;
- $(this).trigger('change');
- }
- });
-}
-
-
-/**
- * Toggle (enabled/disabled) the specified field of a form
- */
-function ToggleField(value, field_id) {
- if (value) {
- $('#'+field_id).prop('disabled', false);
- // In case the field is rendered as a div containing several inputs (e.g. RedundancySettings)
- $('#'+field_id+' :input').prop('disabled', false);
- }
- else {
- $('#'+field_id).prop('disabled', true);
- // In case the field is rendered as a div containing several inputs (e.g. RedundancySettings)
- $('#'+field_id+' :input').prop('disabled', true);
- }
- $('#'+field_id).trigger('update');
- $('#'+field_id).trigger('validate');
-}
-
-/**
- * For the fields that cannot be visually disabled, they can be blocked
- * @return
- */
-function BlockField(field_id, bBlocked) {
- if (bBlocked) {
- $('#'+field_id).block({message: ' ** disabled ** '});
- }
- else {
- $('#'+field_id).unblock();
- }
-}
-
-/**
- * Updates (enables/disables) a "duration" field
- */
-function ToggleDurationField(field_id) {
- // Toggle all the subfields that compose the "duration" input
- aSubFields = new Array('d', 'h', 'm', 's');
-
- if ($('#'+field_id).prop('disabled')) {
- for (var i = 0; i < aSubFields.length; i++) {
- $('#'+field_id+'_'+aSubFields[i]).prop('disabled', true);
- }
- }
- else {
- for (var i = 0; i < aSubFields.length; i++) {
- $('#'+field_id+'_'+aSubFields[i]).prop('disabled', false);
- }
- }
-}
-
-/**
- * PropagateCheckBox
- */
-function PropagateCheckBox(bCurrValue, aFieldsList, bCheck) {
- if (bCurrValue == bCheck) {
- for (var i = 0; i < aFieldsList.length; i++) {
- var sFieldId = aFieldsList[i];
- $('#enable_'+sFieldId).prop('checked', bCheck);
- ToggleField(bCheck, sFieldId);
-
- // Cascade propagation
- $('#enable_'+sFieldId).trigger('change');
- }
- }
-}
-
-function FixTableSorter(table) {
- if (table[0].config == undefined) {
- // Table is not sort-able, let's fix it
- var checkbox = (table.find('th:first :checkbox').length > 0);
- if (checkbox) {
- // There is a checkbox in the first column, don't make it sort-able
- table.tablesorter({headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']}); // sort-able and zebra tables
- }
- else {
- // There is NO checkbox in the first column, all columns are considered sort-able
- table.tablesorter({widgets: ['myZebra', 'truncatedList']}); // sort-able and zebra tables
- }
- }
-}
-
-function DashletCreationDlg(sOQL, sContext) {
- $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, {operation: 'dashlet_creation_dlg', oql: sOQL}, function (data) {
- $('body').append(data);
- });
- return false;
-}
-
-function ShortcutListDlg(sOQL, sDataTableId, sContext) {
- var sDataTableName = 'datatable_'+sDataTableId;
- var oTableSettings = {
- oColumns: $('#'+sDataTableName).datatable('option', 'oColumns'),
- iPageSize: $('#'+sDataTableName).datatable('option', 'iPageSize')
- };
- var sTableSettings = JSON.stringify(oTableSettings);
-
- $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, {operation: 'shortcut_list_dlg', oql: sOQL, table_settings: sTableSettings}, function (data) {
- $('body').append(data);
- });
- return false;
-}
-
-function ExportListDlg(sOQL, sDataTableId, sFormat, sDlgTitle) {
- var aFields = [];
- if (sDataTableId != '') {
- var sDataTableName = 'datatable_'+sDataTableId;
- var oColumns = $('#'+sDataTableName).datatable('option', 'oColumns');
- for (var j in oColumns) {
- for (var k in oColumns[j]) {
- if (oColumns[j][k].checked) {
- var sCode = oColumns[j][k].code;
- if (sCode == '_key_') {
- sCode = 'id';
- }
- aFields.push(j+'.'+sCode);
- }
- }
- }
- }
-
- var oParams = {
- interactive: 1,
- mode: 'dialog',
- expression: sOQL,
- suggested_fields: aFields.join(','),
- dialog_title: sDlgTitle
- };
-
- if (sFormat !== null) {
- oParams.format = sFormat;
- }
-
- $.post(GetAbsoluteUrlAppRoot()+'webservices/export-v2.php', oParams, function (data) {
- $('body').append(data);
- });
- return false;
-}
-
-function ExportToggleFormat(sFormat) {
- $('.form_part').hide();
- for (k in window.aFormParts[sFormat]) {
- $('#form_part_'+window.aFormParts[sFormat][k]).show().trigger('form-part-activate');
- }
-}
-
-function ExportStartExport() {
- var oParams = {};
- $('.form_part:visible :input').each(function () {
- if (this.name != '') {
- if ((this.type == 'radio') || (this.type == 'checkbox')) {
- if (this.checked) {
- oParams[this.name] = $(this).val();
- }
- }
- else {
- oParams[this.name] = $(this).val();
- }
- }
- });
- $(':itop-tabularfieldsselector:visible').tabularfieldsselector('close_all_tooltips');
- $('#export-form').hide();
- $('#export-feedback').show();
- oParams.operation = 'export_build';
- oParams.format = $('#export-form :input[name=format]').val();
- var sQueryMode = $(':input[name=query_mode]:checked').val();
- if ($(':input[name=query_mode]:checked').length > 0) {
- if (sQueryMode == 'oql') {
- oParams.expression = $('#export-form :input[name=expression]').val();
- }
- else {
- oParams.query = $('#export-form :input[name=query]').val();
- }
- }
- else {
- oParams.expression = $('#export-form :input[name=expression]').val();
- oParams.query = $('#export-form :input[name=query]').val();
- }
- $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function (data) {
- if (data == null) {
- ExportError('Export failed (no data provided), please contact your administrator');
- }
- else {
- ExportRun(data);
- }
- }, 'json')
- .fail(function () {
- ExportError('Export failed, please contact your administrator');
- });
-}
-
-function ExportError(sMessage) {
- $('.export-message').html(sMessage);
- $('.export-progress-bar').hide();
- $('#export-btn').hide();
-}
-
-function ExportRun(data) {
- switch (data.code) {
- case 'run':
- // Continue
- $('.export-progress-bar').progressbar({value: data.percentage});
- $('.export-message').html(data.message);
- oParams = {};
- oParams.token = data.token;
- var sDataState = $('#export-form').attr('data-state');
- if (sDataState == 'cancelled') {
- oParams.operation = 'export_cancel';
- }
- else {
- oParams.operation = 'export_build';
- }
-
- $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function (data) {
- ExportRun(data);
- },
- 'json');
- break;
-
- case 'done':
- $('#export-btn').hide();
- sMessage = ''+data.message+'';
- $('.export-message').html(sMessage);
- $('.export-progress-bar').hide();
- $('#export-btn').hide();
- $('#export-form').attr('data-state', 'done');
- if (data.text_result != undefined) {
- if (data.mime_type == 'text/html') {
- $('#export_content').parent().html(data.text_result);
- $('#export_text_result').show();
- $('#export_text_result .listResults').tableHover();
- $('#export_text_result .listResults').tablesorter({widgets: ['myZebra']});
- }
- else {
- if ($('#export_text_result').closest('ui-dialog').length == 0) {
- // not inside a dialog box, adjust the height... approximately
- var jPane = $('#export_text_result').closest('.ui-layout-content');
- var iTotalHeight = jPane.height();
- jPane.children(':visible').each(function () {
- if ($(this).attr('id') != '') {
- iTotalHeight -= $(this).height();
- }
- });
- $('#export_content').height(iTotalHeight-80);
- }
- $('#export_content').val(data.text_result);
- $('#export_text_result').show();
- }
- }
- $('#export-dlg-submit').button('option', 'label', Dict.S('UI:Button:Done')).button('enable');
- break;
-
- case 'error':
- $('#export-form').attr('data-state', 'error');
- $('.export-progress-bar').progressbar({value: data.percentage});
- $('.export-message').html(data.message);
- $('#export-dlg-submit').button('option', 'label', Dict.S('UI:Button:Done')).button('enable');
- $('#export-btn').hide();
- default:
- }
-}
-
-function ExportInitButton(sSelector) {
- $(sSelector).on('click', function () {
- var sDataState = $('#export-form').attr('data-state');
- switch (sDataState) {
- case 'not-yet-started':
- $('.form_part:visible').each(function () {
- $('#export-form').data('validation_messages', []);
- var ret = $(this).trigger('validate');
- });
- var aMessages = $('#export-form').data('validation_messages');
-
- if (aMessages.length > 0) {
- alert(aMessages.join(''));
- return;
- }
- if ($(this).hasClass('ui-button')) {
- $(this).button('option', 'label', Dict.S('UI:Button:Cancel'));
- }
- else {
- $(this).html(Dict.S('UI:Button:Cancel'));
- }
- $('#export-form').attr('data-state', 'running');
- ExportStartExport();
- break;
-
- case 'running':
- if ($(this).hasClass('ui-button')) {
- $(this).button('disable');
- }
- else {
- $(this).prop('disabled', true);
- }
- $('#export-form').attr('data-state', 'cancelled');
- break;
-
- case 'done':
- case 'error':
- $('#interactive_export_dlg').dialog('close');
- break;
-
- default:
- // Do nothing
- }
- });
-}
-
-function DisplayHistory(sSelector, sFilter, iCount, iStart) {
- $(sSelector).block();
- var oParams = {operation: 'history_from_filter', filter: sFilter, start: iStart, count: iCount};
- $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function (data) {
- $(sSelector).html(data).unblock();
- }
- );
-}
-
-// Very simple equivalent to format: placeholders are %1$s %2$d ...
-function Format() {
- var args = [];
- var str = '';
- if (arguments[0] instanceof Array) {
- str = arguments[0][0].toString();
- args = arguments[0];
- }
- else {
- str = arguments[0].toString();
- if (arguments.length > 1) {
- var t = typeof arguments[1];
- args = ("string" === t || "number" === t) ? Array.prototype.slice.call(arguments) : arguments[1];
- }
- }
- var key;
- for (key in args) {
- str = str.replace(new RegExp("\\%"+key+"\\$.", "gi"), args[key]);
- }
-
- return str;
-}
-
-/**
- * Enable to access translation keys client side.
- * The called keys needs to be exported using \WebPage::add_dict_entry
- */
-var Dict = {};
-if (typeof aDictEntries == 'undefined') {
- Dict._entries = {}; // Entries have not been loaded (we are in the setup ?)
-}
-else {
- Dict._entries = aDictEntries; // Entries were loaded asynchronously via their own js files
-}
-Dict.S = function (sEntry) {
- if (sEntry in Dict._entries) {
- return Dict._entries[sEntry];
- }
- else {
- return sEntry;
- }
-};
-Dict.Format = function () {
- var args = Array.from(arguments);
- args[0] = Dict.S(arguments[0]);
- return Format(args);
+// Some general purpose JS functions for the iTop application
+
+//IE 8 compatibility, copied from: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/IndexOf
+if (!Array.prototype.indexOf) {
+
+ if (false) // deactivated since it causes troubles: for(k in aData) => returns the indexOf function as first element on empty arrays !
+ {
+ Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
+ "use strict";
+ if (this == null) {
+ throw new TypeError();
+ }
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = 0;
+ if (arguments.length > 1) {
+ n = Number(arguments[1]);
+ if (n != n) { // shortcut for verifying if it's NaN
+ n = 0;
+ } else if (n != 0 && n != Infinity && n != -Infinity) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len-Math.abs(n), 0);
+ for (; k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ return -1;
+ }
+ }
+}
+// Polyfill for Array.from for IE
+// Copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
+if (!Array.from) {
+ Array.from = (function () {
+ var toStr = Object.prototype.toString;
+ var isCallable = function (fn) {
+ return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
+ };
+ var toInteger = function (value) {
+ var number = Number(value);
+ if (isNaN(number)) { return 0; }
+ if (number === 0 || !isFinite(number)) { return number; }
+ return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
+ };
+ var maxSafeInteger = Math.pow(2, 53) - 1;
+ var toLength = function (value) {
+ var len = toInteger(value);
+ return Math.min(Math.max(len, 0), maxSafeInteger);
+ };
+
+ // The length property of the from method is 1.
+ return function from(arrayLike/*, mapFn, thisArg */) {
+ // 1. Let C be the this value.
+ var C = this;
+
+ // 2. Let items be ToObject(arrayLike).
+ var items = Object(arrayLike);
+
+ // 3. ReturnIfAbrupt(items).
+ if (arrayLike == null) {
+ throw new TypeError('Array.from requires an array-like object - not null or undefined');
+ }
+
+ // 4. If mapfn is undefined, then let mapping be false.
+ var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
+ var T;
+ if (typeof mapFn !== 'undefined') {
+ // 5. else
+ // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
+ if (!isCallable(mapFn)) {
+ throw new TypeError('Array.from: when provided, the second argument must be a function');
+ }
+
+ // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ if (arguments.length > 2) {
+ T = arguments[2];
+ }
+ }
+
+ // 10. Let lenValue be Get(items, "length").
+ // 11. Let len be ToLength(lenValue).
+ var len = toLength(items.length);
+
+ // 13. If IsConstructor(C) is true, then
+ // 13. a. Let A be the result of calling the [[Construct]] internal method
+ // of C with an argument list containing the single item len.
+ // 14. a. Else, Let A be ArrayCreate(len).
+ var A = isCallable(C) ? Object(new C(len)) : new Array(len);
+
+ // 16. Let k be 0.
+ var k = 0;
+ // 17. Repeat, while k < len… (also steps a - h)
+ var kValue;
+ while (k < len) {
+ kValue = items[k];
+ if (mapFn) {
+ A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
+ } else {
+ A[k] = kValue;
+ }
+ k += 1;
+ }
+ // 18. Let putStatus be Put(A, "length", len, true).
+ A.length = len;
+ // 20. Return A.
+ return A;
+ };
+ }());
+}
+
+/**
+ * Reload a truncated list
+ */
+aTruncatedLists = {}; // To keep track of the list being loaded, each member is an ajaxRequest object
+
+function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) {
+ $('#'+divId).block();
+ //$('#'+divId).blockUI();
+ if (aTruncatedLists[divId] != undefined) {
+ try {
+ aAjaxRequest = aTruncatedLists[divId];
+ aAjaxRequest.abort();
+ }
+ catch (e) {
+ // Do nothing special, just continue
+ console.log('Uh,uh, exception !');
+ }
+ }
+ aTruncatedLists[divId] = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style=list',
+ {operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams},
+ function (data) {
+ aTruncatedLists[divId] = undefined;
+ if (data.length > 0) {
+ $('#'+divId).html(data);
+ $('#'+divId+' .listResults').tableHover(); // hover tables
+ $('#'+divId+' .listResults').each(function () {
+ var table = $(this);
+ var id = $(this).parent();
+ aTruncatedLists[divId] = undefined;
+ var checkbox = (table.find('th:first :checkbox').length > 0);
+ if (checkbox) {
+ // There is a checkbox in the first column, don't make it sortable
+ table.tablesorter({headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']}).tablesorterPager({container: $("#pager")}); // sortable and zebra tables
+ }
+ else {
+ // There is NO checkbox in the first column, all columns are considered sortable
+ table.tablesorter({widgets: ['myZebra', 'truncatedList']}).tablesorterPager({container: $("#pager"), totalRows: 97, filter: sSerializedFilter, extra_params: sExtraParams}); // sortable and zebra tables
+ }
+ });
+ $('#'+divId).unblock();
+ }
+ }
+ );
+}
+
+/**
+ * Truncate a previously expanded list !
+ */
+function TruncateList(divId, iLimit, sNewLabel, sLinkLabel) {
+ $('#'+divId).block();
+ var iCount = 0;
+ $('#'+divId+' table.listResults tr:gt('+iLimit+')').each(function () {
+ $(this).remove();
+ });
+ $('#lbl_'+divId).html(sNewLabel);
+ $('#'+divId+' table.listResults tr:last td').addClass('truncated');
+ $('#'+divId+' table.listResults').addClass('truncated');
+ $('#trc_'+divId).html(sLinkLabel);
+ $('#'+divId+' .listResults').trigger("update"); // Reset the cache
+ $('#'+divId).unblock();
+}
+
+/**
+ * Reload any block -- used for periodic auto-reload
+ */
+function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams) {
+ // Check if the user is not editing the list properties right now
+ var bDialogOpen = false;
+ var oDataTable = $('#'+divId+' :itop-datatable');
+ var bIsDataTable = false;
+ if (oDataTable.length > 0) {
+ bDialogOpen = oDataTable.datatable('IsDialogOpen');
+ bIsDataTable = true;
+ }
+ if (!bDialogOpen) {
+ if (bIsDataTable) {
+ oDataTable.datatable('DoRefresh');
+ }
+ else {
+ $('#'+divId).block();
+
+ $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style='+sStyle,
+ {operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams},
+ function (data) {
+ $('#'+divId).empty();
+ $('#'+divId).append(data);
+ $('#'+divId).removeClass('loading');
+ }
+ );
+ }
+ }
+}
+
+function SaveGroupBySortOrder(sTableId, aValues) {
+ var sDashboardId = $('#'+sTableId).closest('.dashboard_contents').attr('id');
+ var sPrefKey = 'GroupBy_'+sDashboardId+'_'+sTableId;
+ if (aValues.length != 0) {
+ $sValue = JSON.stringify(aValues);
+ if (GetUserPreference(sPrefKey, null) != $sValue) {
+ SetUserPreference(sPrefKey, $sValue, true);
+ }
+ }
+}
+
+function LoadGroupBySortOrder(sTableId) {
+ var sDashboardId = $('#'+sTableId).closest('.dashboard_contents').attr('id');
+ var sPrefKey = 'GroupBy_'+sDashboardId+'_'+sTableId;
+ var sValues = GetUserPreference(sPrefKey, null);
+ if (sValues != null) {
+ aValues = JSON.parse(sValues);
+ window.setTimeout(function () {
+ $('#'+sTableId+' table.listResults').trigger('sorton', [aValues]);
+ }, 50);
+ }
+
+}
+
+/**
+ * Update the display and value of a file input widget when the user picks a new file
+ */
+function UpdateFileName(id, sNewFileName) {
+ var aPath = sNewFileName.split('\\');
+ var sNewFileName = aPath[aPath.length-1];
+
+ $('#'+id).val(sNewFileName);
+ $('#'+id).trigger('validate');
+ $('#name_'+id).text(sNewFileName);
+ return true;
+}
+
+/**
+ * Reload a search form for the specified class
+ */
+function ReloadSearchForm(divId, sClassName, sBaseClass, sContext, sTableId, sExtraParams) {
+ var oDiv = $('#ds_'+divId);
+ oDiv.block();
+ // deprecated in jQuery 1.8
+ //var oFormEvents = $('#ds_'+divId+' form').data('events');
+ var oForm = $('#ds_'+divId+' form');
+ var oFormEvents = $._data(oForm[0], "events");
+
+ // Save the submit handlers
+ aSubmit = new Array();
+ if ((oFormEvents != null) && (oFormEvents.submit != undefined)) {
+ for (var index = 0; index < oFormEvents.submit.length; index++) {
+ aSubmit [index] = {data: oFormEvents.submit[index].data, namespace: oFormEvents.submit[index].namespace, handler: oFormEvents.submit[index].handler};
+ }
+ }
+ sAction = $('#ds_'+divId+' form').attr('action');
+
+ // Save the current values in the form
+ var oMap = {};
+ $('#ds_'+divId+" form :input[name!='']").each(function () {
+ oMap[this.name] = this.value;
+ });
+ oMap.operation = 'search_form';
+ oMap.className = sClassName;
+ oMap.baseClass = sBaseClass;
+ oMap.currentId = divId;
+ oMap._table_id_ = sTableId;
+ oMap.action = sAction;
+ if(sExtraParams['selection_mode'])
+ {
+ oMap.selection_mode = sExtraParams['selection_mode'];
+ }
+ if(sExtraParams['result_list_outer_selector'])
+ {
+ oMap.result_list_outer_selector = sExtraParams['result_list_outer_selector'];
+ }
+ if(sExtraParams['cssCount'])
+ {
+ oMap.css_count = sExtraParams['cssCount'];
+ $(sExtraParams['cssCount']).val(0).trigger('change');
+ }
+ if(sExtraParams['table_inner_id'])
+ {
+ oMap.table_inner_id = sExtraParams['table_inner_id'];
+ }
+ $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, oMap,
+ function (data) {
+ oDiv.empty();
+ oDiv.append(data);
+ oDiv.unblock();
+ oDiv.parent().resize(); // Inform the parent that the form has just been (potentially) resized
+ oDiv.find('form').triggerHandler('itop.search.form.reloaded');
+ }
+ );
+}
+
+/**
+ * Stores - in a persistent way - user specific preferences
+ * depends on a global variable oUserPreferences created/filled by the iTopWebPage
+ * that acts as a local -write through- cache
+ */
+function SetUserPreference(sPreferenceCode, sPrefValue, bPersistent) {
+ sPreviousValue = undefined;
+ try {
+ sPreviousValue = oUserPreferences[sPreferenceCode];
+ }
+ catch (err) {
+ sPreviousValue = undefined;
+ }
+ oUserPreferences[sPreferenceCode] = sPrefValue;
+ if (bPersistent && (sPrefValue != sPreviousValue)) {
+ ajax_request = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
+ {operation: 'set_pref', code: sPreferenceCode, value: sPrefValue}); // Make it persistent
+ }
+}
+
+/**
+ * Get user specific preferences
+ * depends on a global variable oUserPreferences created/filled by the iTopWebPage
+ * that acts as a local -write through- cache
+ */
+function GetUserPreference(sPreferenceCode, sDefaultValue) {
+ var value = sDefaultValue;
+ if (oUserPreferences[sPreferenceCode] != undefined) {
+ value = oUserPreferences[sPreferenceCode];
+ }
+ return value;
+}
+
+/**
+ * Check/uncheck a whole list of checkboxes
+ */
+function CheckAll(sSelector, bValue) {
+ var value = bValue;
+ $(sSelector).each(function () {
+ if (this.checked != value) {
+ this.checked = value;
+ $(this).trigger('change');
+ }
+ });
+}
+
+
+/**
+ * Toggle (enabled/disabled) the specified field of a form
+ */
+function ToggleField(value, field_id) {
+ if (value) {
+ $('#'+field_id).prop('disabled', false);
+ // In case the field is rendered as a div containing several inputs (e.g. RedundancySettings)
+ $('#'+field_id+' :input').prop('disabled', false);
+ }
+ else {
+ $('#'+field_id).prop('disabled', true);
+ // In case the field is rendered as a div containing several inputs (e.g. RedundancySettings)
+ $('#'+field_id+' :input').prop('disabled', true);
+ }
+ $('#'+field_id).trigger('update');
+ $('#'+field_id).trigger('validate');
+}
+
+/**
+ * For the fields that cannot be visually disabled, they can be blocked
+ * @return
+ */
+function BlockField(field_id, bBlocked) {
+ if (bBlocked) {
+ $('#'+field_id).block({message: ' ** disabled ** '});
+ }
+ else {
+ $('#'+field_id).unblock();
+ }
+}
+
+/**
+ * Updates (enables/disables) a "duration" field
+ */
+function ToggleDurationField(field_id) {
+ // Toggle all the subfields that compose the "duration" input
+ aSubFields = new Array('d', 'h', 'm', 's');
+
+ if ($('#'+field_id).prop('disabled')) {
+ for (var i = 0; i < aSubFields.length; i++) {
+ $('#'+field_id+'_'+aSubFields[i]).prop('disabled', true);
+ }
+ }
+ else {
+ for (var i = 0; i < aSubFields.length; i++) {
+ $('#'+field_id+'_'+aSubFields[i]).prop('disabled', false);
+ }
+ }
+}
+
+/**
+ * PropagateCheckBox
+ */
+function PropagateCheckBox(bCurrValue, aFieldsList, bCheck) {
+ if (bCurrValue == bCheck) {
+ for (var i = 0; i < aFieldsList.length; i++) {
+ var sFieldId = aFieldsList[i];
+ $('#enable_'+sFieldId).prop('checked', bCheck);
+ ToggleField(bCheck, sFieldId);
+
+ // Cascade propagation
+ $('#enable_'+sFieldId).trigger('change');
+ }
+ }
+}
+
+function FixTableSorter(table) {
+ if (table[0].config == undefined) {
+ // Table is not sort-able, let's fix it
+ var checkbox = (table.find('th:first :checkbox').length > 0);
+ if (checkbox) {
+ // There is a checkbox in the first column, don't make it sort-able
+ table.tablesorter({headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']}); // sort-able and zebra tables
+ }
+ else {
+ // There is NO checkbox in the first column, all columns are considered sort-able
+ table.tablesorter({widgets: ['myZebra', 'truncatedList']}); // sort-able and zebra tables
+ }
+ }
+}
+
+function DashletCreationDlg(sOQL, sContext) {
+ $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, {operation: 'dashlet_creation_dlg', oql: sOQL}, function (data) {
+ $('body').append(data);
+ });
+ return false;
+}
+
+function ShortcutListDlg(sOQL, sDataTableId, sContext) {
+ var sDataTableName = 'datatable_'+sDataTableId;
+ var oTableSettings = {
+ oColumns: $('#'+sDataTableName).datatable('option', 'oColumns'),
+ iPageSize: $('#'+sDataTableName).datatable('option', 'iPageSize')
+ };
+ var sTableSettings = JSON.stringify(oTableSettings);
+
+ $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, {operation: 'shortcut_list_dlg', oql: sOQL, table_settings: sTableSettings}, function (data) {
+ $('body').append(data);
+ });
+ return false;
+}
+
+function ExportListDlg(sOQL, sDataTableId, sFormat, sDlgTitle) {
+ var aFields = [];
+ if (sDataTableId != '') {
+ var sDataTableName = 'datatable_'+sDataTableId;
+ var oColumns = $('#'+sDataTableName).datatable('option', 'oColumns');
+ for (var j in oColumns) {
+ for (var k in oColumns[j]) {
+ if (oColumns[j][k].checked) {
+ var sCode = oColumns[j][k].code;
+ if (sCode == '_key_') {
+ sCode = 'id';
+ }
+ aFields.push(j+'.'+sCode);
+ }
+ }
+ }
+ }
+
+ var oParams = {
+ interactive: 1,
+ mode: 'dialog',
+ expression: sOQL,
+ suggested_fields: aFields.join(','),
+ dialog_title: sDlgTitle
+ };
+
+ if (sFormat !== null) {
+ oParams.format = sFormat;
+ }
+
+ $.post(GetAbsoluteUrlAppRoot()+'webservices/export-v2.php', oParams, function (data) {
+ $('body').append(data);
+ });
+ return false;
+}
+
+function ExportToggleFormat(sFormat) {
+ $('.form_part').hide();
+ for (k in window.aFormParts[sFormat]) {
+ $('#form_part_'+window.aFormParts[sFormat][k]).show().trigger('form-part-activate');
+ }
+}
+
+function ExportStartExport() {
+ var oParams = {};
+ $('.form_part:visible :input').each(function () {
+ if (this.name != '') {
+ if ((this.type == 'radio') || (this.type == 'checkbox')) {
+ if (this.checked) {
+ oParams[this.name] = $(this).val();
+ }
+ }
+ else {
+ oParams[this.name] = $(this).val();
+ }
+ }
+ });
+ $(':itop-tabularfieldsselector:visible').tabularfieldsselector('close_all_tooltips');
+ $('#export-form').hide();
+ $('#export-feedback').show();
+ oParams.operation = 'export_build';
+ oParams.format = $('#export-form :input[name=format]').val();
+ var sQueryMode = $(':input[name=query_mode]:checked').val();
+ if ($(':input[name=query_mode]:checked').length > 0) {
+ if (sQueryMode == 'oql') {
+ oParams.expression = $('#export-form :input[name=expression]').val();
+ }
+ else {
+ oParams.query = $('#export-form :input[name=query]').val();
+ }
+ }
+ else {
+ oParams.expression = $('#export-form :input[name=expression]').val();
+ oParams.query = $('#export-form :input[name=query]').val();
+ }
+ $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function (data) {
+ if (data == null) {
+ ExportError('Export failed (no data provided), please contact your administrator');
+ }
+ else {
+ ExportRun(data);
+ }
+ }, 'json')
+ .fail(function () {
+ ExportError('Export failed, please contact your administrator');
+ });
+}
+
+function ExportError(sMessage) {
+ $('.export-message').html(sMessage);
+ $('.export-progress-bar').hide();
+ $('#export-btn').hide();
+}
+
+function ExportRun(data) {
+ switch (data.code) {
+ case 'run':
+ // Continue
+ $('.export-progress-bar').progressbar({value: data.percentage});
+ $('.export-message').html(data.message);
+ oParams = {};
+ oParams.token = data.token;
+ var sDataState = $('#export-form').attr('data-state');
+ if (sDataState == 'cancelled') {
+ oParams.operation = 'export_cancel';
+ }
+ else {
+ oParams.operation = 'export_build';
+ }
+
+ $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function (data) {
+ ExportRun(data);
+ },
+ 'json');
+ break;
+
+ case 'done':
+ $('#export-btn').hide();
+ sMessage = ''+data.message+'';
+ $('.export-message').html(sMessage);
+ $('.export-progress-bar').hide();
+ $('#export-btn').hide();
+ $('#export-form').attr('data-state', 'done');
+ if (data.text_result != undefined) {
+ if (data.mime_type == 'text/html') {
+ $('#export_content').parent().html(data.text_result);
+ $('#export_text_result').show();
+ $('#export_text_result .listResults').tableHover();
+ $('#export_text_result .listResults').tablesorter({widgets: ['myZebra']});
+ }
+ else {
+ if ($('#export_text_result').closest('ui-dialog').length == 0) {
+ // not inside a dialog box, adjust the height... approximately
+ var jPane = $('#export_text_result').closest('.ui-layout-content');
+ var iTotalHeight = jPane.height();
+ jPane.children(':visible').each(function () {
+ if ($(this).attr('id') != '') {
+ iTotalHeight -= $(this).height();
+ }
+ });
+ $('#export_content').height(iTotalHeight-80);
+ }
+ $('#export_content').val(data.text_result);
+ $('#export_text_result').show();
+ }
+ }
+ $('#export-dlg-submit').button('option', 'label', Dict.S('UI:Button:Done')).button('enable');
+ break;
+
+ case 'error':
+ $('#export-form').attr('data-state', 'error');
+ $('.export-progress-bar').progressbar({value: data.percentage});
+ $('.export-message').html(data.message);
+ $('#export-dlg-submit').button('option', 'label', Dict.S('UI:Button:Done')).button('enable');
+ $('#export-btn').hide();
+ default:
+ }
+}
+
+function ExportInitButton(sSelector) {
+ $(sSelector).on('click', function () {
+ var sDataState = $('#export-form').attr('data-state');
+ switch (sDataState) {
+ case 'not-yet-started':
+ $('.form_part:visible').each(function () {
+ $('#export-form').data('validation_messages', []);
+ var ret = $(this).trigger('validate');
+ });
+ var aMessages = $('#export-form').data('validation_messages');
+
+ if (aMessages.length > 0) {
+ alert(aMessages.join(''));
+ return;
+ }
+ if ($(this).hasClass('ui-button')) {
+ $(this).button('option', 'label', Dict.S('UI:Button:Cancel'));
+ }
+ else {
+ $(this).html(Dict.S('UI:Button:Cancel'));
+ }
+ $('#export-form').attr('data-state', 'running');
+ ExportStartExport();
+ break;
+
+ case 'running':
+ if ($(this).hasClass('ui-button')) {
+ $(this).button('disable');
+ }
+ else {
+ $(this).prop('disabled', true);
+ }
+ $('#export-form').attr('data-state', 'cancelled');
+ break;
+
+ case 'done':
+ case 'error':
+ $('#interactive_export_dlg').dialog('close');
+ break;
+
+ default:
+ // Do nothing
+ }
+ });
+}
+
+function DisplayHistory(sSelector, sFilter, iCount, iStart) {
+ $(sSelector).block();
+ var oParams = {operation: 'history_from_filter', filter: sFilter, start: iStart, count: iCount};
+ $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function (data) {
+ $(sSelector).html(data).unblock();
+ }
+ );
+}
+
+// Very simple equivalent to format: placeholders are %1$s %2$d ...
+function Format() {
+ var args = [];
+ var str = '';
+ if (arguments[0] instanceof Array) {
+ str = arguments[0][0].toString();
+ args = arguments[0];
+ }
+ else {
+ str = arguments[0].toString();
+ if (arguments.length > 1) {
+ var t = typeof arguments[1];
+ args = ("string" === t || "number" === t) ? Array.prototype.slice.call(arguments) : arguments[1];
+ }
+ }
+ var key;
+ for (key in args) {
+ str = str.replace(new RegExp("\\%"+key+"\\$.", "gi"), args[key]);
+ }
+
+ return str;
+}
+
+/**
+ * Enable to access translation keys client side.
+ * The called keys needs to be exported using \WebPage::add_dict_entry
+ */
+var Dict = {};
+if (typeof aDictEntries == 'undefined') {
+ Dict._entries = {}; // Entries have not been loaded (we are in the setup ?)
+}
+else {
+ Dict._entries = aDictEntries; // Entries were loaded asynchronously via their own js files
+}
+Dict.S = function (sEntry) {
+ if (sEntry in Dict._entries) {
+ return Dict._entries[sEntry];
+ }
+ else {
+ return sEntry;
+ }
+};
+Dict.Format = function () {
+ var args = Array.from(arguments);
+ args[0] = Dict.S(arguments[0]);
+ return Format(args);
}
\ No newline at end of file
diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php
index 4195c74bd..dd53a85f0 100644
--- a/setup/ajax.dataloader.php
+++ b/setup/ajax.dataloader.php
@@ -1,208 +1,208 @@
-
-
-
-/**
- * Does load data from XML files (currently used in the setup only)
- *
- * @copyright Copyright (C) 2010-2018 Combodo SARL
- * @license http://opensource.org/licenses/AGPL-3.0
- */
-
-/**
- * This page is called to perform "asynchronously" the setup actions
- * parameters
- * 'operation': one of 'compile_data_model', 'update_db_schema', 'after_db_creation', 'file'
- *
- * if 'operation' == 'update_db_schema':
- * 'mode': install | upgrade
- *
- * if 'operation' == 'after_db_creation':
- * 'mode': install | upgrade
- *
- * if 'operation' == 'file':
- * 'file': string Name of the file to load
- * 'session_status': string 'start', 'continue' or 'end'
- * 'percent': integer 0..100 the percentage of completion once the file has been loaded
- */
-define('SAFE_MINIMUM_MEMORY', 64*1024*1024);
-require_once('../approot.inc.php');
-require_once(APPROOT.'/application/utils.inc.php');
-require_once(APPROOT.'/setup/setuppage.class.inc.php');
-require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
-
-ini_set('max_execution_time', max(3600, ini_get('max_execution_time'))); // Under Windows SQL/backup operations are part of the timeout and require extra time
-date_default_timezone_set('Europe/Paris'); // Just to avoid a warning if the timezone is not set in php.ini
-
-$sMemoryLimit = trim(ini_get('memory_limit'));
-if (empty($sMemoryLimit))
-{
- // On some PHP installations, memory_limit does not exist as a PHP setting!
- // (encountered on a 5.2.0 under Windows)
- // In that case, ini_set will not work, let's keep track of this and proceed with the data load
- SetupPage::log_info("No memory limit has been defined in this instance of PHP");
-}
-else
-{
- // Check that the limit will allow us to load the data
- //
- $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit);
- if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY))
- {
- if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE)
- {
- SetupPage::log_error("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself.");
- }
- else
- {
- SetupPage::log_info("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY.".");
- }
- }
-}
-
-
-define('PHP_FATAL_ERROR_TAG', 'phpfatalerror');
-
-
-/**
- * Handler for register_shutdown_function, to catch PHP errors
- */
-function ShutdownCallback()
-{
- $error = error_get_last();
- $bIsErrorToReport = (($error !== null) && ($error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)));
- if (!$bIsErrorToReport)
- {
- return;
- }
-
- $errno = $error["type"];
- $errfile = $error["file"];
- $errline = $error["line"];
- $errstr = $error["message"];
- $sLogMessage = "PHP error occured : msg=$errstr, no=$errno, file=$errfile, line=$errline";
- SetupPage::log_error("Setup error: $sLogMessage");
- echo '<'.PHP_FATAL_ERROR_TAG.'>'.$sLogMessage.''.PHP_FATAL_ERROR_TAG.'>';
-}
-
-
-function FatalErrorCatcher($sOutput)
-{
- if (preg_match('|<'.PHP_FATAL_ERROR_TAG.'>.*'.PHP_FATAL_ERROR_TAG.'>|s', $sOutput, $aMatches))
- {
- header("HTTP/1.0 500 Internal server error.");
- $errors = '';
- foreach ($aMatches as $sMatch)
- {
- $errors .= strip_tags($sMatch)."\n";
- }
- $sOutput = "$errors\n";
- // Logging to a file does not work if the whole memory is exhausted...
- //SetupPage::log_error("Fatal error - in $__FILE__ , $errors");
- }
- return $sOutput;
-}
-
-//Define some bogus, invalid HTML tags that no sane
-//person would ever put in an actual document and tell
-//PHP to delimit fatal error warnings with them.
-ini_set('error_prepend_string', '<'.PHP_FATAL_ERROR_TAG.'>');
-ini_set('error_append_string', ''.PHP_FATAL_ERROR_TAG.'>');
-
-// callback on errors to log
-register_shutdown_function('ShutdownCallback');
-// Starts the capture of the ouput, and sets a filter to capture the fatal errors.
-ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it through the fatal error catcher
-
-require_once(APPROOT.'/core/config.class.inc.php');
-require_once(APPROOT.'/core/log.class.inc.php');
-require_once(APPROOT.'/core/kpi.class.inc.php');
-require_once(APPROOT.'/core/cmdbsource.class.inc.php');
-require_once('./xmldataloader.class.inc.php');
-require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
-
-
-// Never cache this page
-header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
-header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
-
-/**
- * Main program
- */
-$sOperation = Utils::ReadParam('operation', '');
-try
-{
- switch($sOperation)
- {
- case 'async_action':
- ini_set('max_execution_time', max(240, ini_get('max_execution_time')));
- // While running the setup it is desirable to see any error that may happen
- ini_set('display_errors', true);
- ini_set('display_startup_errors', true);
-
- require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
- require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
-
- $sClass = utils::ReadParam('step_class', '');
- $sState = utils::ReadParam('step_state', '');
- $sActionCode = utils::ReadParam('code', '');
- $aParams = utils::ReadParam('params', array(), false, 'raw_data');
- $oPage = new ajax_page('');
- $oDummyController = new WizardController('');
- if (is_subclass_of($sClass, 'WizardStep'))
- {
- /** @var WizardStep $oStep */
- $oStep = new $sClass($oDummyController, $sState);
- $sConfigFile = utils::GetConfigFilePath();
- if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig())
- {
- $oPage->error("Error: the configuration file '".$sConfigFile."' already exists and cannot be overwritten.");
- $oPage->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '".realpath($sConfigFile)."' can be modified by the web server.");
- $oPage->output();
- }
- else
- {
- $oStep->AsyncAction($oPage, $sActionCode, $aParams);
- }
- }
- $oPage->output();
- break;
-
- default:
- throw(new Exception("Error unsupported operation '$sOperation'"));
- }
-}
-catch(Exception $e)
-{
- header("HTTP/1.0 500 Internal server error.");
- echo "An error happened while processing the installation:
\n";
- echo ''.$e."
\n";
- SetupPage::log_error("An error happened while processing the installation: ".$e);
-}
-
-if (function_exists('memory_get_peak_usage'))
-{
- if ($sOperation == 'file')
- {
- SetupPage::log_info("loading file '$sFileName', peak memory usage. ".memory_get_peak_usage());
- }
- else
- {
- SetupPage::log_info("operation '$sOperation', peak memory usage. ".memory_get_peak_usage());
- }
+
+
+
+/**
+ * Does load data from XML files (currently used in the setup only)
+ *
+ * @copyright Copyright (C) 2010-2018 Combodo SARL
+ * @license http://opensource.org/licenses/AGPL-3.0
+ */
+
+/**
+ * This page is called to perform "asynchronously" the setup actions
+ * parameters
+ * 'operation': one of 'compile_data_model', 'update_db_schema', 'after_db_creation', 'file'
+ *
+ * if 'operation' == 'update_db_schema':
+ * 'mode': install | upgrade
+ *
+ * if 'operation' == 'after_db_creation':
+ * 'mode': install | upgrade
+ *
+ * if 'operation' == 'file':
+ * 'file': string Name of the file to load
+ * 'session_status': string 'start', 'continue' or 'end'
+ * 'percent': integer 0..100 the percentage of completion once the file has been loaded
+ */
+define('SAFE_MINIMUM_MEMORY', 64*1024*1024);
+require_once('../approot.inc.php');
+require_once(APPROOT.'/application/utils.inc.php');
+require_once(APPROOT.'/setup/setuppage.class.inc.php');
+require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
+
+ini_set('max_execution_time', max(3600, ini_get('max_execution_time'))); // Under Windows SQL/backup operations are part of the timeout and require extra time
+date_default_timezone_set('Europe/Paris'); // Just to avoid a warning if the timezone is not set in php.ini
+
+$sMemoryLimit = trim(ini_get('memory_limit'));
+if (empty($sMemoryLimit))
+{
+ // On some PHP installations, memory_limit does not exist as a PHP setting!
+ // (encountered on a 5.2.0 under Windows)
+ // In that case, ini_set will not work, let's keep track of this and proceed with the data load
+ SetupPage::log_info("No memory limit has been defined in this instance of PHP");
+}
+else
+{
+ // Check that the limit will allow us to load the data
+ //
+ $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit);
+ if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY))
+ {
+ if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE)
+ {
+ SetupPage::log_error("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself.");
+ }
+ else
+ {
+ SetupPage::log_info("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY.".");
+ }
+ }
+}
+
+
+define('PHP_FATAL_ERROR_TAG', 'phpfatalerror');
+
+
+/**
+ * Handler for register_shutdown_function, to catch PHP errors
+ */
+function ShutdownCallback()
+{
+ $error = error_get_last();
+ $bIsErrorToReport = (($error !== null) && ($error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)));
+ if (!$bIsErrorToReport)
+ {
+ return;
+ }
+
+ $errno = $error["type"];
+ $errfile = $error["file"];
+ $errline = $error["line"];
+ $errstr = $error["message"];
+ $sLogMessage = "PHP error occured : msg=$errstr, no=$errno, file=$errfile, line=$errline";
+ SetupPage::log_error("Setup error: $sLogMessage");
+ echo '<'.PHP_FATAL_ERROR_TAG.'>'.$sLogMessage.''.PHP_FATAL_ERROR_TAG.'>';
+}
+
+
+function FatalErrorCatcher($sOutput)
+{
+ if (preg_match('|<'.PHP_FATAL_ERROR_TAG.'>.*'.PHP_FATAL_ERROR_TAG.'>|s', $sOutput, $aMatches))
+ {
+ header("HTTP/1.0 500 Internal server error.");
+ $errors = '';
+ foreach ($aMatches as $sMatch)
+ {
+ $errors .= strip_tags($sMatch)."\n";
+ }
+ $sOutput = "$errors\n";
+ // Logging to a file does not work if the whole memory is exhausted...
+ //SetupPage::log_error("Fatal error - in $__FILE__ , $errors");
+ }
+ return $sOutput;
+}
+
+//Define some bogus, invalid HTML tags that no sane
+//person would ever put in an actual document and tell
+//PHP to delimit fatal error warnings with them.
+ini_set('error_prepend_string', '<'.PHP_FATAL_ERROR_TAG.'>');
+ini_set('error_append_string', ''.PHP_FATAL_ERROR_TAG.'>');
+
+// callback on errors to log
+register_shutdown_function('ShutdownCallback');
+// Starts the capture of the ouput, and sets a filter to capture the fatal errors.
+ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it through the fatal error catcher
+
+require_once(APPROOT.'/core/config.class.inc.php');
+require_once(APPROOT.'/core/log.class.inc.php');
+require_once(APPROOT.'/core/kpi.class.inc.php');
+require_once(APPROOT.'/core/cmdbsource.class.inc.php');
+require_once('./xmldataloader.class.inc.php');
+require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
+
+
+// Never cache this page
+header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
+header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
+
+/**
+ * Main program
+ */
+$sOperation = Utils::ReadParam('operation', '');
+try
+{
+ switch($sOperation)
+ {
+ case 'async_action':
+ ini_set('max_execution_time', max(240, ini_get('max_execution_time')));
+ // While running the setup it is desirable to see any error that may happen
+ ini_set('display_errors', true);
+ ini_set('display_startup_errors', true);
+
+ require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
+ require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
+
+ $sClass = utils::ReadParam('step_class', '');
+ $sState = utils::ReadParam('step_state', '');
+ $sActionCode = utils::ReadParam('code', '');
+ $aParams = utils::ReadParam('params', array(), false, 'raw_data');
+ $oPage = new ajax_page('');
+ $oDummyController = new WizardController('');
+ if (is_subclass_of($sClass, 'WizardStep'))
+ {
+ /** @var WizardStep $oStep */
+ $oStep = new $sClass($oDummyController, $sState);
+ $sConfigFile = utils::GetConfigFilePath();
+ if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig())
+ {
+ $oPage->error("Error: the configuration file '".$sConfigFile."' already exists and cannot be overwritten.");
+ $oPage->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '".realpath($sConfigFile)."' can be modified by the web server.");
+ $oPage->output();
+ }
+ else
+ {
+ $oStep->AsyncAction($oPage, $sActionCode, $aParams);
+ }
+ }
+ $oPage->output();
+ break;
+
+ default:
+ throw(new Exception("Error unsupported operation '$sOperation'"));
+ }
+}
+catch(Exception $e)
+{
+ header("HTTP/1.0 500 Internal server error.");
+ echo "An error happened while processing the installation:
\n";
+ echo ''.$e."
\n";
+ SetupPage::log_error("An error happened while processing the installation: ".$e);
+}
+
+if (function_exists('memory_get_peak_usage'))
+{
+ if ($sOperation == 'file')
+ {
+ SetupPage::log_info("loading file '$sFileName', peak memory usage. ".memory_get_peak_usage());
+ }
+ else
+ {
+ SetupPage::log_info("operation '$sOperation', peak memory usage. ".memory_get_peak_usage());
+ }
}
\ No newline at end of file
diff --git a/webservices/backoffice.dataloader.php b/webservices/backoffice.dataloader.php
index 691c347a9..2ba07f0bd 100644
--- a/webservices/backoffice.dataloader.php
+++ b/webservices/backoffice.dataloader.php
@@ -1,170 +1,170 @@
-
-
-
-/**
- * Does load data from XML files (currently used in the setup and the backoffice data loader utility)
- *
- * @copyright Copyright (C) 2010-2012 Combodo SARL
- * @license http://opensource.org/licenses/AGPL-3.0
- */
-
-/**
- * This page is called to load an XML file into the database
- * parameters
- * 'file' string Name of the file to load
- */
-define('SAFE_MINIMUM_MEMORY', 256*1024*1024);
-
-require_once('../approot.inc.php');
-require_once(APPROOT.'/application/application.inc.php');
-
-require_once(APPROOT.'/application/startup.inc.php');
-
-require_once(APPROOT.'/application/loginwebpage.class.inc.php');
-LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
-
-// required because the class xmldataloader is reporting errors in the setup.log file
-require_once(APPROOT.'/setup/setuppage.class.inc.php');
-require_once(APPROOT.'/setup/xmldataloader.class.inc.php');
-
-
-function SetMemoryLimit($oP)
-{
- $sMemoryLimit = trim(ini_get('memory_limit'));
- if (empty($sMemoryLimit))
- {
- // On some PHP installations, memory_limit does not exist as a PHP setting!
- // (encountered on a 5.2.0 under Windows)
- // In that case, ini_set will not work, let's keep track of this and proceed with the data load
- $oP->p("No memory limit has been defined in this instance of PHP");
- }
- else
- {
- // Check that the limit will allow us to load the data
- //
- $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit);
- if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY))
- {
- if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE)
- {
- $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself.");
- }
- else
- {
- $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY.".");
- }
- }
- }
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// Main
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// Never cache this page
-header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
-header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
-
-/**
- * Main program
- */
-$sFileName = Utils::ReadParam('file', '', false, 'raw_data');
-
-$oP = new WebPage("iTop - Backoffice data loader");
-
-
-try
-{
- // Note: the data model must be loaded first
- $oDataLoader = new XMLDataLoader();
-
- if (empty($sFileName))
- {
- throw(new Exception("Missing argument 'file'"));
- }
- if (!file_exists($sFileName))
- {
- throw(new Exception("File $sFileName does not exist"));
- }
-
- SetMemoryLimit($oP);
-
-
- // The XMLDataLoader constructor has initialized the DB, let's start a transaction
- CMDBSource::Query('START TRANSACTION');
-
- $oChange = MetaModel::NewObject("CMDBChange");
- $oChange->Set("date", time());
- $oChange->Set("userinfo", "Initialization");
- $iChangeId = $oChange->DBInsert();
- $oP->p("Starting data load.");
- $oDataLoader->StartSession($oChange);
-
- $oDataLoader->LoadFile($sFileName);
-
- $oP->p("Ending data load session");
- if ($oDataLoader->EndSession(true /* strict */))
- {
- $iCountCreated = $oDataLoader->GetCountCreated();
- CMDBSource::Query('COMMIT');
-
- $oP->p("Data successfully written into the DB: $iCountCreated objects created");
- }
- else
- {
- CMDBSource::Query('ROLLBACK');
- $oP->p("Some issues have been encountered, changes will not be recorded, please review the source data");
- $aErrors = $oDataLoader->GetErrors();
- if (count($aErrors) > 0)
- {
- $oP->p('Errors ('.count($aErrors).')');
- foreach ($aErrors as $sMsg)
- {
- $oP->p(' * '.$sMsg);
- }
- }
- $aWarnings = $oDataLoader->GetWarnings();
- if (count($aWarnings) > 0)
- {
- $oP->p('Warnings ('.count($aWarnings).')');
- foreach ($aWarnings as $sMsg)
- {
- $oP->p(' * '.$sMsg);
- }
- }
- }
-
-}
-catch(Exception $e)
-{
- $oP->p("An error happened while loading the data: ".$e->getMessage());
- $oP->p("Aborting (no data written)...");
- CMDBSource::Query('ROLLBACK');
-}
-
-if (function_exists('memory_get_peak_usage'))
-{
- $oP->p("Information: memory peak usage: ".memory_get_peak_usage());
-}
-
-$oP->Output();
-?>
+
+
+
+/**
+ * Does load data from XML files (currently used in the setup and the backoffice data loader utility)
+ *
+ * @copyright Copyright (C) 2010-2012 Combodo SARL
+ * @license http://opensource.org/licenses/AGPL-3.0
+ */
+
+/**
+ * This page is called to load an XML file into the database
+ * parameters
+ * 'file' string Name of the file to load
+ */
+define('SAFE_MINIMUM_MEMORY', 256*1024*1024);
+
+require_once('../approot.inc.php');
+require_once(APPROOT.'/application/application.inc.php');
+
+require_once(APPROOT.'/application/startup.inc.php');
+
+require_once(APPROOT.'/application/loginwebpage.class.inc.php');
+LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
+
+// required because the class xmldataloader is reporting errors in the setup.log file
+require_once(APPROOT.'/setup/setuppage.class.inc.php');
+require_once(APPROOT.'/setup/xmldataloader.class.inc.php');
+
+
+function SetMemoryLimit($oP)
+{
+ $sMemoryLimit = trim(ini_get('memory_limit'));
+ if (empty($sMemoryLimit))
+ {
+ // On some PHP installations, memory_limit does not exist as a PHP setting!
+ // (encountered on a 5.2.0 under Windows)
+ // In that case, ini_set will not work, let's keep track of this and proceed with the data load
+ $oP->p("No memory limit has been defined in this instance of PHP");
+ }
+ else
+ {
+ // Check that the limit will allow us to load the data
+ //
+ $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit);
+ if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY))
+ {
+ if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE)
+ {
+ $oP->p("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself.");
+ }
+ else
+ {
+ $oP->p("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY.".");
+ }
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Main
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Never cache this page
+header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
+header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
+
+/**
+ * Main program
+ */
+$sFileName = Utils::ReadParam('file', '', false, 'raw_data');
+
+$oP = new WebPage("iTop - Backoffice data loader");
+
+
+try
+{
+ // Note: the data model must be loaded first
+ $oDataLoader = new XMLDataLoader();
+
+ if (empty($sFileName))
+ {
+ throw(new Exception("Missing argument 'file'"));
+ }
+ if (!file_exists($sFileName))
+ {
+ throw(new Exception("File $sFileName does not exist"));
+ }
+
+ SetMemoryLimit($oP);
+
+
+ // The XMLDataLoader constructor has initialized the DB, let's start a transaction
+ CMDBSource::Query('START TRANSACTION');
+
+ $oChange = MetaModel::NewObject("CMDBChange");
+ $oChange->Set("date", time());
+ $oChange->Set("userinfo", "Initialization");
+ $iChangeId = $oChange->DBInsert();
+ $oP->p("Starting data load.");
+ $oDataLoader->StartSession($oChange);
+
+ $oDataLoader->LoadFile($sFileName);
+
+ $oP->p("Ending data load session");
+ if ($oDataLoader->EndSession(true /* strict */))
+ {
+ $iCountCreated = $oDataLoader->GetCountCreated();
+ CMDBSource::Query('COMMIT');
+
+ $oP->p("Data successfully written into the DB: $iCountCreated objects created");
+ }
+ else
+ {
+ CMDBSource::Query('ROLLBACK');
+ $oP->p("Some issues have been encountered, changes will not be recorded, please review the source data");
+ $aErrors = $oDataLoader->GetErrors();
+ if (count($aErrors) > 0)
+ {
+ $oP->p('Errors ('.count($aErrors).')');
+ foreach ($aErrors as $sMsg)
+ {
+ $oP->p(' * '.$sMsg);
+ }
+ }
+ $aWarnings = $oDataLoader->GetWarnings();
+ if (count($aWarnings) > 0)
+ {
+ $oP->p('Warnings ('.count($aWarnings).')');
+ foreach ($aWarnings as $sMsg)
+ {
+ $oP->p(' * '.$sMsg);
+ }
+ }
+ }
+
+}
+catch(Exception $e)
+{
+ $oP->p("An error happened while loading the data: ".$e->getMessage());
+ $oP->p("Aborting (no data written)...");
+ CMDBSource::Query('ROLLBACK');
+}
+
+if (function_exists('memory_get_peak_usage'))
+{
+ $oP->p("Information: memory peak usage: ".memory_get_peak_usage());
+}
+
+$oP->Output();
+?>