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; } /** * @param string|string[] $value * @param string $sSanitizationFilter one of : integer, class, string, context_param, parameter, field_name, * element_identifier, transaction_id, parameter, raw_data * * @return string|string[]|bool boolean for : * * the 'class' filter (true if valid, false otherwise) * * if the filter fails (@see \filter_var()) * * @since 2.5.2 2.6.0 new 'transaction_id' filter * @since 2.7.0 new 'element_identifier' filter * @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter */ 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': case 'transaction_id': 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 'transaction_id': // same as parameter type but keep the dot character // see N°1835 : when using file transaction_id on Windows you get *.tmp tokens // it must be included at the regexp beginning otherwise you'll get an invalid character error $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/'))); break; case 'parameter': $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // 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; // For XML / HTML node identifiers case 'element_identifier': $retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value); break; // For URL case 'url': // N°6350 - returns only valid URLs $retValue = filter_var($value, FILTER_VALIDATE_URL); 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) * @throws \FileUploadException */ 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); $sMimeType = self::GetFileMimeType($sTmpName); $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 DBSearch $oFullSetFilter 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 * @throws \CoreException * @throws \CoreUnexpectedValue * @throws \MySQLException */ 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 != 'positive' && $sSelectionMode != 'negative') { throw new CoreException('selectionMode must be either positive or negative'); } // Paginated selection $aSelectedIds = utils::ReadParam('storedSelection', array()); $aSelectedObjIds = utils::ReadParam('selectObject', array()); //it means that the user has selected all the results of the search query if (count($aSelectedObjIds) > 0 ) { $sFilter=utils::ReadParam("sFilter",'',false,'raw_data'); if ($sFilter!='') { $oFullSetFilter=DBSearch::unserialize($sFilter); } } 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); } /** * Build as static::GetNewTransactionId() * * @param string $sTransactionId * * @return string unique tmp id for the current upload based on the transaction system (db). Build as static::GetNewTransactionId() */ public static function GetUploadTempId($sTransactionId = null) { if ($sTransactionId === null) { $sTransactionId = static::GetNewTransactionId(); } return $sTransactionId; } public static function ReadFromFile($sFileName) { if (!file_exists($sFileName)) { return false; } return file_get_contents($sFileName); } /** * @param mixed $value The value as read from php.ini (eg 256k, 2M, 1G etc.) * * @return int conversion to number of bytes * * @since 2.7.5 3.0.0 convert to int numeric values * * @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes Shorthand bytes value reference in PHP.net FAQ */ public static function ConvertToBytes($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; } } else { $iReturn = (int)$value; } return $iReturn; } /** * Checks if the memory limit is at least what is required * * @param int $iMemoryLimit set limit in bytes, use {@link utils::ConvertToBytes()} to convert current php.ini value * @param int $iRequiredLimit required limit in bytes * * @return bool */ public static function IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit) { if ($iMemoryLimit === -1) { // -1 means : no limit (see https://www.php.net/manual/fr/ini.core.php#ini.memory-limit) return true; } return ($iMemoryLimit >= $iRequiredLimit); } /** * Set memory_limit to required value * * @param string $sRequiredLimit required limit, for example '512M' * * @return bool|null null if nothing was done, true if modifying memory_limit was successful, false otherwise * * @uses utils::ConvertToBytes() * @uses \ini_get('memory_limit') * @uses \ini_set() * @uses utils::ConvertToBytes() * * @since 2.7.5 N°3806 */ public static function SetMinMemoryLimit($sRequiredLimit) { $iRequiredLimit = static::ConvertToBytes($sRequiredLimit); $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 return false; } $iMemoryLimit = static::ConvertToBytes($sMemoryLimit); if (static::IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit)) { return null; } return ini_set('memory_limit', $iRequiredLimit); } /** * Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount. * * @param float $value * @param int $iPrecision * * @return string */ public static function BytesToFriendlyFormat($value, $iPrecision = 0) { $sReturn = ''; // Kilobytes if ($value >= 1024) { $sReturn = 'K'; $value = $value / 1024; if ($iPrecision === 0) { $iPrecision = 1; } } // 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, $iPrecision); 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 string|false false if the input format is not correct, timestamp otherwise */ 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 specification (using % placeholders) * to a format compatible with DateTime::createFromFormat * @param string $sOldDateTimeFormat * @return string */ public static 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); } /** * Allow to set cached config. Useful when running with {@link Parameters} for example. * @param \Config $oConfig */ public static function SetConfig(Config $oConfig) { self::$oConfig = $oConfig; } /** * @return \Config Get object in the following order : *
    *
  1. from {@link MetaModel::GetConfig} if loaded *
  2. {@link oConfig} attribute if set *
  3. from disk (current env, using {@link GetConfigFilePath}) => if loaded this will be stored in {@link oConfig} attribute *
  4. from disk, env production => if loaded this will be stored in {@link oConfig} attribute *
  5. default Config object *
* @throws \ConfigException * @throws \CoreException * * @since 2.7.0 N°2478 this method will now always call {@link MetaModel::GetConfig} first, and cache in this class is only set when loading from disk */ public static function GetConfig() { $oMetaModelConfig = MetaModel::GetConfig(); if ($oMetaModelConfig !== null) { return $oMetaModelConfig; } if (self::$oConfig !== null) { return self::$oConfig; } $sCurrentEnvConfigPath = self::GetConfigFilePath(); if (file_exists($sCurrentEnvConfigPath)) { $oCurrentEnvDiskConfig = new Config($sCurrentEnvConfigPath); self::SetConfig($oCurrentEnvDiskConfig); return self::$oConfig; } $sProductionEnvConfigPath = self::GetConfigFilePath('production'); if (file_exists($sProductionEnvConfigPath)) { $oProductionEnvDiskConfig = new Config($sProductionEnvConfigPath); self::SetConfig($oProductionEnvDiskConfig); return self::$oConfig; } return new Config(); } 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 } } /** * @return bool The boolean value of the conf. "behind_reverse_proxy" (except if there is no REMOTE_ADDR int his case, it return false) * * @since 2.7.4 */ public static function IsProxyTrusted() { if (empty($_SERVER['REMOTE_ADDR'])) { return false; } $bTrustProxies = (bool) self::GetConfig()->Get('behind_reverse_proxy'); return $bTrustProxies; } /** * Returns the absolute URL to the application root path * * @param bool $bForceTrustProxy * * @return string The absolute URL to the application root, without the first slash * * @throws \Exception * * @since 2.7.4 $bForceTrustProxy param added */ public static function GetAbsoluteUrlAppRoot($bForceTrustProxy = false) { static $sUrl = null; if ($sUrl === null || $bForceTrustProxy) { $sUrl = self::GetConfig()->Get('app_root_url'); if ($sUrl == '') { $sUrl = self::GetDefaultUrlAppRoot($bForceTrustProxy); } 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. * * @param bool $bForceTrustProxy * * @return string * * @throws \Exception * * @since 2.7.4 $bForceTrustProxy param added */ public static function GetDefaultUrlAppRoot($bForceTrustProxy = false) { $sAbsoluteUrl = self::GetCurrentAbsoluteUrl($bForceTrustProxy, true); $sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']); $sAppRoot = realpath(APPROOT); return self::GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl); } /** * Build the current absolute URL from the server's variables. * * For almost every usage, you should use the more secure utils::GetAbsoluteUrlAppRoot() : instead of reading the current uri, it provide you the configured application's root URL (this is done during the setup and chn be changed in the configuration file) * * @see utils::GetAbsoluteUrlAppRoot * * @param bool $bForceTrustProxy * @param bool $bTrimQueryString * * @return string * * @since 2.7.4 */ public static function GetCurrentAbsoluteUrl($bForceTrustProxy = false, $bTrimQueryString = false) { // Build an absolute URL to this page on this server/port $sServerName = self::GetServerName($bForceTrustProxy); $bIsSecure = self::IsConnectionSecure($bForceTrustProxy); $sProtocol = $bIsSecure ? 'https' : 'http'; $iPort = self::GetServerPort($bForceTrustProxy); if ($bIsSecure) { $sPort = ($iPort == 443) ? '' : ':'.$iPort; } else { $sPort = ($iPort == 80) ? '' : ':'.$iPort; } $sPath = self::GetRequestUri($bForceTrustProxy); if ($bTrimQueryString) { // remove all the parameters from the query string $iQuestionMarkPos = strpos($sPath, '?'); if ($iQuestionMarkPos !== false) { $sPath = substr($sPath, 0, $iQuestionMarkPos); } } $sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}"; return $sAbsoluteUrl; } /** * @param bool $bForceTrustProxy * * @return string * * @since 2.7.4 */ public static function GetServerName($bForceTrustProxy = false) { $bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted(); $sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''; if ($bTrustProxy) { $sServerName = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $sServerName; } return $sServerName; } /** * @param bool $bForceTrustProxy * * @return int|mixed * @since 2.7.4 */ public static function GetServerPort($bForceTrustProxy = false) { $bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted(); $sServerPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80; if ($bTrustProxy) { $sServerPort = isset($_SERVER['HTTP_X_FORWARDED_PORT']) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $sServerPort; } return $sServerPort; } /** * @return string * * @since 2.7.4 */ public static function GetRequestUri() { // $_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['SCRIPT_NAME']; if (!empty($_SERVER['QUERY_STRING'])) { $sPath .= '?'.$_SERVER['QUERY_STRING']; } $_SERVER['REQUEST_URI'] = $sPath; } $sPath = $_SERVER['REQUEST_URI']; return $sPath; } /** * @param $sCurrentScript * @param $sAppRoot * @param $sAbsoluteUrl * * @return false|string * @throws \Exception */ public static function GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl) { $sCurrentScript = str_replace('\\', '/', $sCurrentScript); // canonical path $sAppRoot = str_replace('\\', '/', $sAppRoot).'/'; // canonical path with the trailing '/' appended $sCurrentRelativePath = str_ireplace($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_ireplace('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 N°286 (fixed in [896]), and N°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) * * @param bool $bForceTrustProxy * * @return bool * * @since 2.7.4 reverse proxies handling */ public static function IsConnectionSecure($bForceTrustProxy = false) { $bSecured = false; $bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted(); if ($bTrustProxy && !empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { $bSecured = ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'); } elseif ($bTrustProxy && !empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) { $bSecured = ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https'); } elseif ((!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() { return (isset($_SESSION['can_logoff']) ? $_SESSION['can_logoff'] : false); } /** * Get the _SESSION variable for logging purpose * @return false|string */ public static function GetSessionLog() { ob_start(); print_r($_SESSION); $sSessionLog = ob_get_contents(); ob_end_clean(); return $sSessionLog; } 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)) */ /** * @param string $sScriptName * @param array $aArguments * * @return array * @throws \ConfigException * @throws \CoreException */ public 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; } } /** * @return string A path to a folder into which any module can store cache data * The corresponding folder is created or cleaned upon code compilation */ public static function GetCachePath() { return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/'; } /** * @return string A path to a folder into which any module can store log * @since 2.7.0 */ public static function GetLogPath() { return APPROOT.'log/'; } /** * Merge standard menu items with plugin provided menus items * * @param \WebPage $oPage * @param int $iMenuId * @param \DBObjectSet $param * @param array $aActions * @param string $sTableId * @param string $sDataTableId * * @throws \Exception */ 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(); $sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey()); $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 /** @var \RuntimeDashboard $oDashboard */ $oDashboard = $param; $sDashboardId = $oDashboard->GetId(); $sDashboardFile = $oDashboard->GetDefinitionFile(); $sDashboardFileRelative = utils::LocalPath($sDashboardFile); $sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle')); $sDlgText = addslashes(Dict::S('UI:ImportDashboardText')); $sCloseBtn = addslashes(Dict::S('UI:Button:Cancel')); $sDashboardFileJS = addslashes($sDashboardFileRelative); $sDashboardFileURL = urlencode($sDashboardFileRelative); $sUploadDashboardTransactId = utils::GetNewTransactionId(); $aResult = array( new SeparatorPopupMenuItem(), new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sDashboardId.'&file='.$sDashboardFileURL), new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sDashboardId', file: '$sDashboardFileJS', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn', transaction: '$sUploadDashboardTransactId' })"), ); if ($oDashboard->GetReloadURL()) { $aResult[] = new SeparatorPopupMenuItem(); $aResult[] = new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $oDashboard->GetReloadURL().'&printable=1', '_blank'); } break; default: // Unknown type of menu, do nothing $aResult = array(); } foreach ($aResult as $oMenuItem) { $aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem(); } // Invoke the plugins // /** @var \iPopupMenuExtension $oExtensionInstance */ 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); } } } } } /** * @param string $sEnvironment * * @return string 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; } /** * @param string $sEnvironment * * @return string target configuration file name (including relative path) */ public static function GetConfigFilePathRelative($sEnvironment = null) { if (is_null($sEnvironment)) { $sEnvironment = self::GetCurrentEnvironment(); } return "conf/".$sEnvironment.'/'.ITOP_CONFIG_FILE; } /** * @return string the absolute URL to the modules root path * @throws \Exception */ public static 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'); * ``` * * @see GetExecPageArguments can be used to submit using the GET method (see bug in N.1108) * @see GetAbsoluteUrlExecPage * * @param string[] $aArguments * @param string $sEnvironment * * @param string $sModule * @param string $sPage * * @return string the URL to a page that will execute the requested module page, with query string values url encoded * * @throws \Exception */ public static 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 */ public static 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 * @throws \Exception */ public static function GetAbsoluteUrlExecPage() { return self::GetAbsoluteUrlAppRoot().'pages/exec.php'; } /** * @param string $sProposed The default value * @param array $aExisting An array of existing values (strings) * * @return string a unique name amongst the given list */ public static 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 */ public static 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 array $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 array $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 array $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 with a specific error message depending on the cause */ public static 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; } /** * Helper to encapsulation iTop's htmlentities * @param string $sValue * @return string */ public static function HtmlEntities($sValue) { return htmlentities($sValue, ENT_QUOTES, 'UTF-8'); } /** * Helper to encapsulation iTop's html_entity_decode * @param string $sValue * @return string * @since 2.7.0 */ public static function HtmlEntityDecode($sValue) { return html_entity_decode($sValue, ENT_QUOTES, 'UTF-8'); } /** * 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) */ public static 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)))) { $sCss = static::CompileCSSFromSASS(file_get_contents($sSassPath), $aImportPaths); file_put_contents($sCssPath, $sCss); } return $sCssRelPath; } /** * Return a string of CSS compiled from the $sSassContent * * @param string $sSassContent * @param array $aImportPaths * @param array $aVariables * * @return string * * @since 2.7.0 */ public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array()) { $oSass = new Compiler(); $oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Expanded'); // Setting our variables $oSass->setVariables($aVariables); // Setting our imports paths $oSass->setImportPaths($aImportPaths); // Temporary disabling max exec time while compiling $iCurrentMaxExecTime = (int) ini_get('max_execution_time'); set_time_limit(0); // Compiling SASS $sCss = $oSass->compile($sSassContent); set_time_limit($iCurrentMaxExecTime); return $sCss; } public static 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 */ public static 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 int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module * @return string */ public static function GetCurrentModuleName($iCallDepth = 0) { return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1); } /** * **Warning** : returned result can be invalid as we're using backtrace to find the module dir name * * @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module * * @return string the relative (to MODULESROOT) 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) * * @uses \debug_backtrace() */ public static function GetCurrentModuleDir($iCallDepth) { return ModuleService::GetInstance()->GetCurrentModuleDir($iCallDepth); } /** * **Warning** : as this method uses {@see GetCurrentModuleDir} it produces hazardous results. * You should better uses directly {@see GetAbsoluteUrlModulesRoot} and add the module dir name yourself ! See N°4573 * * @return string 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) * @throws \Exception * * @uses GetCurrentModuleDir */ public static function GetCurrentModuleUrl() { return ModuleService::GetInstance()->GetCurrentModuleUrl(1); } /** * @param string $sProperty The name of the property to retrieve * @param mixed $defaultvalue * @return mixed the value of a given setting for the current module */ public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null) { return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue); } /** * @param string $sModuleName * @return string|NULL compiled version of a given module, as it was seen by the compiler */ public static function GetCompiledModuleVersion($sModuleName) { return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName); } /** * 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 string 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). */ public static function GetCacheBusterTimestamp() { if(!defined('COMPILATION_TIMESTAMP')) { return ITOP_VERSION; } return COMPILATION_TIMESTAMP; } /** * @return string eg : '2_7_0' if iTop core version is '2.7.5-2' * @throws \ApplicationException if constant value is invalid * @uses ITOP_CORE_VERSION */ public static function GetItopVersionWikiSyntax($sItopVersion = ITOP_CORE_VERSION) { $aExplodedVersion = explode('.', $sItopVersion); if ((false === isset($aExplodedVersion[0])) || (false === isset($aExplodedVersion[1]))) { throw new ApplicationException('iTop version is wrongfully configured!'); } return "{$aExplodedVersion[0]}_{$aExplodedVersion[1]}_0"; } /** * Check if the given class if configured as a high cardinality class. * * @param $sClass * * @return bool * @throws \ConfigException * @throws \CoreException */ 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'; } /** * @see https://php.net/manual/en/function.finfo-file.php * * @param string $sFilePath file full path * @param string $sDefaultMimeType * * @return string mime type, defaults to application/octet-stream * @uses finfo_file in FileInfo extension (bundled in PHP since version 5.3) * @since 2.7.0 N°2366 */ public static function GetFileMimeType($sFilePath, $sDefaultMimeType = 'application/octet-stream') { if (!function_exists('finfo_file')) { return $sDefaultMimeType; } $sMimeType = $sDefaultMimeType; $rInfo = @finfo_open(FILEINFO_MIME_TYPE); if ($rInfo !== false) { $sType = @finfo_file($rInfo, $sFilePath); if (($sType !== false) && is_string($sType) && ($sType !== '')) { $sMimeType = $sType; } } @finfo_close($rInfo); return $sMimeType; } /** * helper to test if a string starts with another * @param $haystack * @param $needle * * @return bool */ final public static function StartsWith($haystack, $needle) { if (strlen($needle) > strlen($haystack)) { return false; } return substr_compare($haystack, $needle, 0, strlen($needle)) === 0; } /** * helper to test if a string ends with another * @param $haystack * @param $needle * * @return bool */ final public static function EndsWith($haystack, $needle) { if (strlen($needle) > strlen($haystack)) { return false; } return substr_compare($haystack, $needle, -strlen($needle)) === 0; } /** * @param string $sPath for example `/var/www/html/itop/data/backups/manual/itop_27-2019-10-03_15_35.tar.gz` * **Warning**, if path is a symlink, it will be resolved ! * So `C:\Dev\wamp64\www\itop-dev/env-production/itop-hub-connector/land.php` * Will become `C:\Dev\wamp64\www\itop-dev\datamodels\2.x\itop-hub-connector\land.php` * @param string $sBasePath for example `/var/www/html/itop/data/` * * @return bool|string false if path : * * invalid * * not allowed * * not contained in base path * Otherwise return the real path (see realpath()) * * @uses \realpath() * @uses static::StartsWith * @since 2.6.5 2.7.0 N°2538 * @since 2.7.5 details in PHPDoc about symlink resolution */ final public static function RealPath($sPath, $sBasePath) { $sFileRealPath = realpath($sPath); if ($sFileRealPath === false) { return false; } $sRealBasePath = realpath($sBasePath); // avoid problems when having '/' on Windows for example if (!self::StartsWith($sFileRealPath, $sRealBasePath)) { return false; } return $sFileRealPath; } /** * Returns the local path relative to the iTop installation of an existing file * Dir separator is changed to '/' for consistency among the different OS * * @param string $sAbsolutePath absolute path * * @return false|string */ final public static function LocalPath($sAbsolutePath) { $sRootPath = realpath(APPROOT); $sFullPath = realpath($sAbsolutePath); if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath)) { return false; } $sLocalPath = substr($sFullPath, strlen($sRootPath.DIRECTORY_SEPARATOR)); $sLocalPath = str_replace(DIRECTORY_SEPARATOR, '/', $sLocalPath); return $sLocalPath; } /** * return absolute path of an existing file located in iTop * * @param string $sPath relative iTop path * * @return string|false absolute path */ public static function AbsolutePath($sPath) { $sRootPath = realpath(APPROOT); $sFullPath = realpath($sRootPath.DIRECTORY_SEPARATOR.$sPath); if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath)) { return false; } return $sFullPath; } public static function GetAbsoluteModulePath($sModule) { return APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$sModule.'/'; } public static function GetCurrentUserName() { if (function_exists('posix_getpwuid')) { return posix_getpwuid(posix_geteuid())['name']; } return getenv('username'); } /** * Transform a snake_case $sInput into a CamelCase string * * @since 2.7.0 * @param string $sInput * * @return string */ public static function ToCamelCase($sInput) { return str_replace(' ', '', ucwords(strtr($sInput, '_-', ' '))); } /** * @param \cmdbAbstractObject $oCmdbAbstract * @param \Exception $oException * * @throws \Exception * @since 2.7.2/ 2.8.0 */ public static function EnrichRaisedException($oCmdbAbstract, $oException) { if (is_null($oCmdbAbstract) || ! is_a($oCmdbAbstract, \cmdbAbstractObject::class)) { throw $oException; } $sCmdbAbstractInfo = str_replace("\n", '', "" . $oCmdbAbstract); $sMessage = $oException->getMessage() . " (" . $sCmdbAbstractInfo . ")"; $e = new CoreException($sMessage, null, '', $oException); throw $e; } /** * @return bool : indicate whether we run under a windows environnement or not * @since 2.7.4 : N°3412 */ public static function IsWindowsEnvironment(){ return (substr(PHP_OS,0,3) === 'WIN'); } }