1<?php 2 3use Leafo\ScssPhp\Compiler; 4 5// Copyright (C) 2010-2017 Combodo SARL 6// 7// This file is part of iTop. 8// 9// iTop is free software; you can redistribute it and/or modify 10// it under the terms of the GNU Affero General Public License as published by 11// the Free Software Foundation, either version 3 of the License, or 12// (at your option) any later version. 13// 14// iTop is distributed in the hope that it will be useful, 15// but WITHOUT ANY WARRANTY; without even the implied warranty of 16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17// GNU Affero General Public License for more details. 18// 19// You should have received a copy of the GNU Affero General Public License 20// along with iTop. If not, see <http://www.gnu.org/licenses/> 21 22 23/** 24 * Static class utils 25 * 26 * @copyright Copyright (C) 2010-2017 Combodo SARL 27 * @license http://opensource.org/licenses/AGPL-3.0 28 */ 29 30require_once(APPROOT.'core/metamodel.class.php'); 31require_once(APPROOT.'core/config.class.inc.php'); 32require_once(APPROOT.'application/transaction.class.inc.php'); 33require_once(APPROOT.'application/Html2Text.php'); 34require_once(APPROOT.'application/Html2TextException.php'); 35 36define('ITOP_CONFIG_FILE', 'config-itop.php'); 37define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE); 38 39define('SERVER_NAME_PLACEHOLDER', '$SERVER_NAME$'); 40 41define('SERVER_MAX_URL_LENGTH', 2048); 42 43class FileUploadException extends Exception 44{ 45} 46 47 48/** 49 * Helper functions to interact with forms: read parameters, upload files... 50 * @package iTop 51 */ 52class utils 53{ 54 private static $oConfig = null; 55 private static $m_bCASClient = false; 56 57 // Parameters loaded from a file, parameters of the page/command line still have precedence 58 private static $m_aParamsFromFile = null; 59 private static $m_aParamSource = array(); 60 61 protected static function LoadParamFile($sParamFile) 62 { 63 if (!file_exists($sParamFile)) 64 { 65 throw new Exception("Could not find the parameter file: '".utils::HtmlEntities($sParamFile)."'"); 66 } 67 if (!is_readable($sParamFile)) 68 { 69 throw new Exception("Could not load parameter file: '".utils::HtmlEntities($sParamFile)."'"); 70 } 71 $sParams = file_get_contents($sParamFile); 72 73 if (is_null(self::$m_aParamsFromFile)) 74 { 75 self::$m_aParamsFromFile = array(); 76 } 77 78 $aParamLines = explode("\n", $sParams); 79 foreach ($aParamLines as $sLine) 80 { 81 $sLine = trim($sLine); 82 83 // Ignore the line after a '#' 84 if (($iCommentPos = strpos($sLine, '#')) !== false) 85 { 86 $sLine = substr($sLine, 0, $iCommentPos); 87 $sLine = trim($sLine); 88 } 89 90 // Note: the line is supposed to be already trimmed 91 if (preg_match('/^(\S*)\s*=(.*)$/', $sLine, $aMatches)) 92 { 93 $sParam = $aMatches[1]; 94 $value = trim($aMatches[2]); 95 self::$m_aParamsFromFile[$sParam] = $value; 96 self::$m_aParamSource[$sParam] = $sParamFile; 97 } 98 } 99 } 100 101 public static function UseParamFile($sParamFileArgName = 'param_file', $bAllowCLI = true) 102 { 103 $sFileSpec = self::ReadParam($sParamFileArgName, '', $bAllowCLI, 'raw_data'); 104 foreach(explode(',', $sFileSpec) as $sFile) 105 { 106 $sFile = trim($sFile); 107 if (!empty($sFile)) 108 { 109 self::LoadParamFile($sFile); 110 } 111 } 112 } 113 114 /** 115 * Return the source file from which the parameter has been found, 116 * usefull when it comes to pass user credential to a process executed 117 * in the background 118 * @param $sName Parameter name 119 * @return The file name if any, or null 120 */ 121 public static function GetParamSourceFile($sName) 122 { 123 if (array_key_exists($sName, self::$m_aParamSource)) 124 { 125 return self::$m_aParamSource[$sName]; 126 } 127 else 128 { 129 return null; 130 } 131 } 132 133 public static function IsModeCLI() 134 { 135 $sSAPIName = php_sapi_name(); 136 $sCleanName = strtolower(trim($sSAPIName)); 137 if ($sCleanName == 'cli') 138 { 139 return true; 140 } 141 else 142 { 143 return false; 144 } 145 } 146 147 protected static $bPageMode = null; 148 /** 149 * @var boolean[] 150 */ 151 protected static $aModes = array(); 152 153 public static function InitArchiveMode() 154 { 155 if (isset($_SESSION['archive_mode'])) 156 { 157 $iDefault = $_SESSION['archive_mode']; 158 } 159 else 160 { 161 $iDefault = 0; 162 } 163 // Read and record the value for switching the archive mode 164 $iCurrent = self::ReadParam('with-archive', $iDefault); 165 if (isset($_SESSION)) 166 { 167 $_SESSION['archive_mode'] = $iCurrent; 168 } 169 // Read and use the value for the current page (web services) 170 $iCurrent = self::ReadParam('with_archive', $iCurrent, true); 171 self::$bPageMode = ($iCurrent == 1); 172 } 173 174 /** 175 * @param boolean $bMode if true then activate archive mode (archived objects are visible), otherwise archived objects are 176 * hidden (archive = "soft deletion") 177 */ 178 public static function PushArchiveMode($bMode) 179 { 180 array_push(self::$aModes, $bMode); 181 } 182 183 public static function PopArchiveMode() 184 { 185 array_pop(self::$aModes); 186 } 187 188 /** 189 * @return boolean true if archive mode is enabled 190 */ 191 public static function IsArchiveMode() 192 { 193 if (count(self::$aModes) > 0) 194 { 195 $bRet = end(self::$aModes); 196 } 197 else 198 { 199 if (self::$bPageMode === null) 200 { 201 self::InitArchiveMode(); 202 } 203 $bRet = self::$bPageMode; 204 } 205 return $bRet; 206 } 207 208 /** 209 * Helper to be called by the GUI and define if the user will see obsolete data (otherwise, the user will have to dig further) 210 * @return bool 211 */ 212 public static function ShowObsoleteData() 213 { 214 $bDefault = MetaModel::GetConfig()->Get('obsolescence.show_obsolete_data'); // default is false 215 $bShow = appUserPreferences::GetPref('show_obsolete_data', $bDefault); 216 if (static::IsArchiveMode()) 217 { 218 $bShow = true; 219 } 220 return $bShow; 221 } 222 223 public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false, $sSanitizationFilter = 'parameter') 224 { 225 global $argv; 226 $retValue = $defaultValue; 227 228 if (!is_null(self::$m_aParamsFromFile)) 229 { 230 if (isset(self::$m_aParamsFromFile[$sName])) 231 { 232 $retValue = self::$m_aParamsFromFile[$sName]; 233 } 234 } 235 236 if (isset($_REQUEST[$sName])) 237 { 238 $retValue = $_REQUEST[$sName]; 239 } 240 elseif ($bAllowCLI && isset($argv)) 241 { 242 foreach($argv as $iArg => $sArg) 243 { 244 if (preg_match('/^--'.$sName.'=(.*)$/', $sArg, $aMatches)) 245 { 246 $retValue = $aMatches[1]; 247 } 248 } 249 } 250 return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter); 251 } 252 253 public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter') 254 { 255 $retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue; 256 return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter); 257 } 258 259 public static function Sanitize($value, $defaultValue, $sSanitizationFilter) 260 { 261 if ($value === $defaultValue) 262 { 263 // Preserve the real default value (can be used to detect missing mandatory parameters) 264 $retValue = $value; 265 } 266 else 267 { 268 $retValue = self::Sanitize_Internal($value, $sSanitizationFilter); 269 if ($retValue === false) 270 { 271 $retValue = $defaultValue; 272 } 273 } 274 return $retValue; 275 } 276 277 /** 278 * @param string|string[] $value 279 * @param string $sSanitizationFilter one of : integer, class, string, context_param, parameter, field_name, 280 * transaction_id, parameter, raw_data 281 * 282 * @return string|string[]|bool boolean for : 283 * * the 'class' filter (true if valid, false otherwise) 284 * * if the filter fails (@see \filter_var()) 285 * 286 * @since 2.5.2 2.6.0 new 'transaction_id' filter 287 */ 288 protected static function Sanitize_Internal($value, $sSanitizationFilter) 289 { 290 switch ($sSanitizationFilter) 291 { 292 case 'integer': 293 $retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT); 294 break; 295 296 case 'class': 297 $retValue = $value; 298 if (!MetaModel::IsValidClass($value)) 299 { 300 $retValue = false; 301 } 302 break; 303 304 case 'string': 305 $retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS); 306 break; 307 308 case 'context_param': 309 case 'parameter': 310 case 'field_name': 311 if (is_array($value)) 312 { 313 $retValue = array(); 314 foreach ($value as $key => $val) 315 { 316 $retValue[$key] = self::Sanitize_Internal($val, $sSanitizationFilter); // recursively check arrays 317 if ($retValue[$key] === false) 318 { 319 $retValue = false; 320 break; 321 } 322 } 323 } 324 else 325 { 326 switch ($sSanitizationFilter) 327 { 328 case 'transaction_id': 329 // same as parameter type but keep the dot character 330 // see N°1835 : when using file transaction_id on Windows you get *.tmp tokens 331 // it must be included at the regexp beginning otherwise you'll get an invalid character error 332 $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, 333 array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/'))); 334 break; 335 336 case 'parameter': 337 $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, 338 array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F' 339 // characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC) 340 break; 341 342 case 'field_name': 343 $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, 344 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 345 break; 346 347 case 'context_param': 348 $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, 349 array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/'))); 350 break; 351 352 } 353 } 354 break; 355 356 default: 357 case 'raw_data': 358 $retValue = $value; 359 // Do nothing 360 } 361 362 return $retValue; 363 } 364 365 /** 366 * Reads an uploaded file and turns it into an ormDocument object - Triggers an exception in case of error 367 * @param string $sName Name of the input used from uploading the file 368 * @param string $sIndex If Name is an array of posted files, then the index must be used to point out the file 369 * @return ormDocument The uploaded file (can be 'empty' if nothing was uploaded) 370 */ 371 public static function ReadPostedDocument($sName, $sIndex = null) 372 { 373 $oDocument = new ormDocument(); // an empty document 374 if(isset($_FILES[$sName])) 375 { 376 $aFileInfo = $_FILES[$sName]; 377 378 $sError = is_null($sIndex) ? $aFileInfo['error'] : $aFileInfo['error'][$sIndex]; 379 switch($sError) 380 { 381 case UPLOAD_ERR_OK: 382 $sTmpName = is_null($sIndex) ? $aFileInfo['tmp_name'] : $aFileInfo['tmp_name'][$sIndex]; 383 $sMimeType = is_null($sIndex) ? $aFileInfo['type'] : $aFileInfo['type'][$sIndex]; 384 $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex]; 385 386 $doc_content = file_get_contents($sTmpName); 387 if (function_exists('finfo_file')) 388 { 389 // as of PHP 5.3 the fileinfo extension is bundled within PHP 390 // in which case we don't trust the mime type provided by the browser 391 $rInfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension 392 if ($rInfo !== false) 393 { 394 $sType = @finfo_file($rInfo, $sTmpName); 395 if ( ($sType !== false) 396 && is_string($sType) 397 && (strlen($sType)>0)) 398 { 399 $sMimeType = $sType; 400 } 401 } 402 @finfo_close($rInfo); 403 } 404 $oDocument = new ormDocument($doc_content, $sMimeType, $sName); 405 break; 406 407 case UPLOAD_ERR_NO_FILE: 408 // no file to load, it's a normal case, just return an empty document 409 break; 410 411 case UPLOAD_ERR_FORM_SIZE: 412 case UPLOAD_ERR_INI_SIZE: 413 throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize'))); 414 break; 415 416 case UPLOAD_ERR_PARTIAL: 417 throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.')); 418 break; 419 420 case UPLOAD_ERR_NO_TMP_DIR: 421 throw new FileUploadException(Dict::S('UI:Error:NoTmpDir')); 422 break; 423 424 case UPLOAD_ERR_CANT_WRITE: 425 throw new FileUploadException(Dict::Format('UI:Error:CannotWriteToTmp_Dir', ini_get('upload_tmp_dir'))); 426 break; 427 428 case UPLOAD_ERR_EXTENSION: 429 $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex]; 430 throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName)); 431 break; 432 433 default: 434 throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError)); 435 break; 436 437 } 438 } 439 return $oDocument; 440 } 441 442 /** 443 * Interprets the results posted by a normal or paginated list (in multiple selection mode) 444 * 445 * @param $oFullSetFilter DBSearch The criteria defining the whole sets of objects being selected 446 * 447 * @return Array An array of object IDs corresponding to the objects selected in the set 448 */ 449 public static function ReadMultipleSelection($oFullSetFilter) 450 { 451 $aSelectedObj = utils::ReadParam('selectObject', array()); 452 $sSelectionMode = utils::ReadParam('selectionMode', ''); 453 if ($sSelectionMode != '') 454 { 455 // Paginated selection 456 $aExceptions = utils::ReadParam('storedSelection', array()); 457 if ($sSelectionMode == 'positive') 458 { 459 // Only the explicitely listed items are selected 460 $aSelectedObj = $aExceptions; 461 } 462 else 463 { 464 // All items of the set are selected, except the one explicitely listed 465 $aSelectedObj = array(); 466 $oFullSet = new DBObjectSet($oFullSetFilter); 467 $sClassAlias = $oFullSetFilter->GetClassAlias(); 468 $oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field 469 while($oObj = $oFullSet->Fetch()) 470 { 471 if (!in_array($oObj->GetKey(), $aExceptions)) 472 { 473 $aSelectedObj[] = $oObj->GetKey(); 474 } 475 } 476 } 477 } 478 return $aSelectedObj; 479 } 480 481 /** 482 * Interprets the results posted by a normal or paginated list (in multiple selection mode) 483 * 484 * @param DBSearch $oFullSetFilter The criteria defining the whole sets of objects being selected 485 * 486 * @return Array An array of object IDs:friendlyname corresponding to the objects selected in the set 487 * @throws \CoreException 488 */ 489 public static function ReadMultipleSelectionWithFriendlyname($oFullSetFilter) 490 { 491 $sSelectionMode = utils::ReadParam('selectionMode', ''); 492 493 if ($sSelectionMode != 'positive' && $sSelectionMode != 'negative') 494 { 495 throw new CoreException('selectionMode must be either positive or negative'); 496 } 497 498 // Paginated selection 499 $aSelectedIds = utils::ReadParam('storedSelection', array()); 500 if (count($aSelectedIds) > 0 ) 501 { 502 if ($sSelectionMode == 'positive') 503 { 504 // Only the explicitly listed items are selected 505 $oFullSetFilter->AddCondition('id', $aSelectedIds, 'IN'); 506 } 507 else 508 { 509 // All items of the set are selected, except the one explicitly listed 510 $oFullSetFilter->AddCondition('id', $aSelectedIds, 'NOTIN'); 511 } 512 } 513 514 $aSelectedObj = array(); 515 $oFullSet = new DBObjectSet($oFullSetFilter); 516 $sClassAlias = $oFullSetFilter->GetClassAlias(); 517 $oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field 518 while ($oObj = $oFullSet->Fetch()) 519 { 520 $aSelectedObj[$oObj->GetKey()] = $oObj->Get('friendlyname'); 521 } 522 523 return $aSelectedObj; 524 } 525 526 public static function GetNewTransactionId() 527 { 528 return privUITransaction::GetNewTransactionId(); 529 } 530 531 public static function IsTransactionValid($sId, $bRemoveTransaction = true) 532 { 533 return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction); 534 } 535 536 public static function RemoveTransaction($sId) 537 { 538 return privUITransaction::RemoveTransaction($sId); 539 } 540 541 /** 542 * Returns a unique tmp id for the current upload based on the transaction system (db). 543 * 544 * Build as static::GetNewTransactionId() 545 * 546 * @return string 547 */ 548 public static function GetUploadTempId($sTransactionId = null) 549 { 550 if ($sTransactionId === null) 551 { 552 $sTransactionId = static::GetNewTransactionId(); 553 } 554 return $sTransactionId; 555 } 556 557 public static function ReadFromFile($sFileName) 558 { 559 if (!file_exists($sFileName)) return false; 560 return file_get_contents($sFileName); 561 } 562 563 /** 564 * Helper function to convert a value expressed in a 'user friendly format' 565 * as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes 566 * @param mixed $value The value as read from php.ini 567 * @return number 568 */ 569 public static function ConvertToBytes( $value ) 570 { 571 $iReturn = $value; 572 if ( !is_numeric( $value ) ) 573 { 574 $iLength = strlen( $value ); 575 $iReturn = substr( $value, 0, $iLength - 1 ); 576 $sUnit = strtoupper( substr( $value, $iLength - 1 ) ); 577 switch ( $sUnit ) 578 { 579 case 'G': 580 $iReturn *= 1024; 581 case 'M': 582 $iReturn *= 1024; 583 case 'K': 584 $iReturn *= 1024; 585 } 586 } 587 return $iReturn; 588 } 589 590 /** 591 * Checks if the memory limit is at least what is required 592 * 593 * @param int $memoryLimit set limit in bytes 594 * @param int $requiredLimit required limit in bytes 595 * @return bool 596 */ 597 public static function IsMemoryLimitOk($memoryLimit, $requiredLimit) 598 { 599 return ($memoryLimit >= $requiredLimit) || ($memoryLimit == -1); 600 } 601 602 /** 603 * Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount. 604 * 605 * @param type $value 606 * @return string 607 */ 608 public static function BytesToFriendlyFormat($value) 609 { 610 $sReturn = ''; 611 // Kilobytes 612 if ($value >= 1024) 613 { 614 $sReturn = 'K'; 615 $value = $value / 1024; 616 } 617 // Megabytes 618 if ($value >= 1024) 619 { 620 $sReturn = 'M'; 621 $value = $value / 1024; 622 } 623 // Gigabytes 624 if ($value >= 1024) 625 { 626 $sReturn = 'G'; 627 $value = $value / 1024; 628 } 629 // Terabytes 630 if ($value >= 1024) 631 { 632 $sReturn = 'T'; 633 $value = $value / 1024; 634 } 635 636 $value = round($value, 1); 637 638 return $value . '' . $sReturn . 'B'; 639 } 640 641 /** 642 * 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) 643 * Example: StringToTime('01/05/11 12:03:45', '%d/%m/%y %H:%i:%s') 644 * @param string $sDate 645 * @param string $sFormat 646 * @return timestamp or false if the input format is not correct 647 */ 648 public static function StringToTime($sDate, $sFormat) 649 { 650 // Source: http://php.net/manual/fr/function.strftime.php 651 // (alternative: http://www.php.net/manual/fr/datetime.formats.date.php) 652 static $aDateTokens = null; 653 static $aDateRegexps = null; 654 if (is_null($aDateTokens)) 655 { 656 $aSpec = array( 657 '%d' =>'(?<day>[0-9]{2})', 658 '%m' => '(?<month>[0-9]{2})', 659 '%y' => '(?<year>[0-9]{2})', 660 '%Y' => '(?<year>[0-9]{4})', 661 '%H' => '(?<hour>[0-2][0-9])', 662 '%i' => '(?<minute>[0-5][0-9])', 663 '%s' => '(?<second>[0-5][0-9])', 664 ); 665 $aDateTokens = array_keys($aSpec); 666 $aDateRegexps = array_values($aSpec); 667 } 668 669 $sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat); 670 671 if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches)) 672 { 673 $sYear = isset($aMatches['year']) ? $aMatches['year'] : 0; 674 $sMonth = isset($aMatches['month']) ? $aMatches['month'] : 1; 675 $sDay = isset($aMatches['day']) ? $aMatches['day'] : 1; 676 $sHour = isset($aMatches['hour']) ? $aMatches['hour'] : 0; 677 $sMinute = isset($aMatches['minute']) ? $aMatches['minute'] : 0; 678 $sSecond = isset($aMatches['second']) ? $aMatches['second'] : 0; 679 return strtotime("$sYear-$sMonth-$sDay $sHour:$sMinute:$sSecond"); 680 } 681 else 682 { 683 return false; 684 } 685 // http://www.spaweditor.com/scripts/regex/index.php 686 } 687 688 /** 689 * Convert an old date/time format specifciation (using % placeholders) 690 * to a format compatible with DateTime::createFromFormat 691 * @param string $sOldDateTimeFormat 692 * @return string 693 */ 694 static public function DateTimeFormatToPHP($sOldDateTimeFormat) 695 { 696 $aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s'); 697 $aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's'); 698 return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat); 699 } 700 701 /** 702 * @return \Config from the current environement, or if not existing from the production env, else new Config made from scratch 703 * @uses \MetaModel::GetConfig() don't forget to add the needed <code>require_once(APPROOT.'core/metamodel.class.php');</code> 704 */ 705 static public function GetConfig() 706 { 707 if (self::$oConfig == null) 708 { 709 self::$oConfig = MetaModel::GetConfig(); 710 711 if (self::$oConfig == null) 712 { 713 $sConfigFile = self::GetConfigFilePath(); 714 if (!file_exists($sConfigFile)) 715 { 716 $sConfigFile = self::GetConfigFilePath('production'); 717 if (!file_exists($sConfigFile)) 718 { 719 $sConfigFile = null; 720 } 721 } 722 723 self::$oConfig = new Config($sConfigFile); 724 } 725 } 726 return self::$oConfig; 727 } 728 729 public static function InitTimeZone() { 730 $oConfig = self::GetConfig(); 731 $sItopTimeZone = $oConfig->Get('timezone'); 732 733 if (!empty($sItopTimeZone)) 734 { 735 date_default_timezone_set($sItopTimeZone); 736 } 737 else 738 { 739 // Leave as is... up to the admin to set a value somewhere... 740 // see http://php.net/manual/en/datetime.configuration.php#ini.date.timezone 741 } 742 } 743 744 /** 745 * Returns the absolute URL to the application root path 746 * 747 * @return string The absolute URL to the application root, without the first slash 748 * 749 * @throws \Exception 750 */ 751 static public function GetAbsoluteUrlAppRoot() 752 { 753 static $sUrl = null; 754 if ($sUrl === null) 755 { 756 $sUrl = self::GetConfig()->Get('app_root_url'); 757 if ($sUrl == '') 758 { 759 $sUrl = self::GetDefaultUrlAppRoot(); 760 } 761 elseif (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1) 762 { 763 if (isset($_SERVER['SERVER_NAME'])) 764 { 765 $sServerName = $_SERVER['SERVER_NAME']; 766 } 767 else 768 { 769 // CLI mode ? 770 $sServerName = php_uname('n'); 771 } 772 $sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl); 773 } 774 } 775 return $sUrl; 776 } 777 778 /** 779 * Builds an root url from the server's variables. 780 * 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. 781 * 782 * @return string 783 * 784 * @throws \Exception 785 */ 786 static public function GetDefaultUrlAppRoot() 787 { 788 // Build an absolute URL to this page on this server/port 789 $sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''; 790 $sProtocol = self::IsConnectionSecure() ? 'https' : 'http'; 791 $iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80; 792 if ($sProtocol == 'http') 793 { 794 $sPort = ($iPort == 80) ? '' : ':'.$iPort; 795 } 796 else 797 { 798 $sPort = ($iPort == 443) ? '' : ':'.$iPort; 799 } 800 // $_SERVER['REQUEST_URI'] is empty when running on IIS 801 // Let's use Ivan Tcholakov's fix (found on www.dokeos.com) 802 if (!empty($_SERVER['REQUEST_URI'])) 803 { 804 $sPath = $_SERVER['REQUEST_URI']; 805 } 806 else 807 { 808 $sPath = $_SERVER['SCRIPT_NAME']; 809 if (!empty($_SERVER['QUERY_STRING'])) 810 { 811 $sPath .= '?'.$_SERVER['QUERY_STRING']; 812 } 813 $_SERVER['REQUEST_URI'] = $sPath; 814 } 815 $sPath = $_SERVER['REQUEST_URI']; 816 817 // remove all the parameters from the query string 818 $iQuestionMarkPos = strpos($sPath, '?'); 819 if ($iQuestionMarkPos !== false) 820 { 821 $sPath = substr($sPath, 0, $iQuestionMarkPos); 822 } 823 $sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}"; 824 825 $sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']); 826 $sCurrentScript = str_replace('\\', '/', $sCurrentScript); // canonical path 827 $sAppRoot = str_replace('\\', '/', APPROOT); // canonical path 828 $sCurrentRelativePath = str_replace($sAppRoot, '', $sCurrentScript); 829 830 $sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath); 831 if ($sAppRootPos !== false) 832 { 833 $sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path 834 } 835 else 836 { 837 // Second attempt without index.php at the end... 838 $sCurrentRelativePath = str_replace('index.php', '', $sCurrentRelativePath); 839 $sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath); 840 if ($sAppRootPos !== false) 841 { 842 $sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path 843 } 844 else 845 { 846 // No luck... 847 throw new Exception("Failed to determine application root path $sAbsoluteUrl ($sCurrentRelativePath) APPROOT:'$sAppRoot'"); 848 } 849 } 850 return $sAppRootUrl; 851 } 852 853 /** 854 * Helper to handle the variety of HTTP servers 855 * See N°286 (fixed in [896]), and N°634 (this fix) 856 * 857 * Though the official specs says 'a non empty string', some servers like IIS do set it to 'off' ! 858 * nginx set it to an empty string 859 * Others might leave it unset (no array entry) 860 */ 861 static public function IsConnectionSecure() 862 { 863 $bSecured = false; 864 865 if (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off')) 866 { 867 $bSecured = true; 868 } 869 return $bSecured; 870 } 871 872 /** 873 * Tells whether or not log off operation is supported. 874 * Actually in only one case: 875 * 1) iTop is using an internal authentication 876 * 2) the user did not log-in using the "basic" mode (i.e basic authentication) or by passing credentials in the URL 877 * @return boolean True if logoff is supported, false otherwise 878 */ 879 static function CanLogOff() 880 { 881 $bResult = false; 882 if(isset($_SESSION['login_mode'])) 883 { 884 $sLoginMode = $_SESSION['login_mode']; 885 switch($sLoginMode) 886 { 887 case 'external': 888 $bResult = false; 889 break; 890 891 case 'form': 892 case 'basic': 893 case 'url': 894 case 'cas': 895 default: 896 $bResult = true; 897 898 } 899 } 900 return $bResult; 901 } 902 903 /** 904 * Initializes the CAS client 905 */ 906 static function InitCASClient() 907 { 908 $sCASIncludePath = self::GetConfig()->Get('cas_include_path'); 909 include_once($sCASIncludePath.'/CAS.php'); 910 911 $bCASDebug = self::GetConfig()->Get('cas_debug'); 912 if ($bCASDebug) 913 { 914 phpCAS::setDebug(APPROOT.'log/error.log'); 915 } 916 917 if (!self::$m_bCASClient) 918 { 919 // Initialize phpCAS 920 $sCASVersion = self::GetConfig()->Get('cas_version'); 921 $sCASHost = self::GetConfig()->Get('cas_host'); 922 $iCASPort = self::GetConfig()->Get('cas_port'); 923 $sCASContext = self::GetConfig()->Get('cas_context'); 924 phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */); 925 self::$m_bCASClient = true; 926 $sCASCACertPath = self::GetConfig()->Get('cas_server_ca_cert_path'); 927 if (empty($sCASCACertPath)) 928 { 929 // If no certificate authority is provided, do not attempt to validate 930 // the server's certificate 931 // THIS SETTING IS NOT RECOMMENDED FOR PRODUCTION. 932 // VALIDATING THE CAS SERVER IS CRUCIAL TO THE SECURITY OF THE CAS PROTOCOL! 933 phpCAS::setNoCasServerValidation(); 934 } 935 else 936 { 937 phpCAS::setCasServerCACert($sCASCACertPath); 938 } 939 } 940 } 941 942 static function DebugBacktrace($iLimit = 5) 943 { 944 $aFullTrace = debug_backtrace(); 945 $aLightTrace = array(); 946 for($i=1; ($i<=$iLimit && $i < count($aFullTrace)); $i++) // Skip the last function call... which is the call to this function ! 947 { 948 $aLightTrace[$i] = $aFullTrace[$i]['function'].'(), called from line '.$aFullTrace[$i]['line'].' in '.$aFullTrace[$i]['file']; 949 } 950 echo "<p><pre>".print_r($aLightTrace, true)."</pre></p>\n"; 951 } 952 953 /** 954 * Execute the given iTop PHP script, passing it the current credentials 955 * Only CLI mode is supported, because of the need to hand the credentials over to the next process 956 * Throws an exception if the execution fails or could not be attempted (config issue) 957 * @param string $sScript Name and relative path to the file (relative to the iTop root dir) 958 * @param hash $aArguments Associative array of 'arg' => 'value' 959 * @return array(iCode, array(output lines)) 960 */ 961 /** 962 */ 963 static function ExecITopScript($sScriptName, $aArguments) 964 { 965 $aDisabled = explode(', ', ini_get('disable_functions')); 966 if (in_array('exec', $aDisabled)) 967 { 968 throw new Exception("The PHP exec() function has been disabled on this server"); 969 } 970 971 $sPHPExec = trim(self::GetConfig()->Get('php_path')); 972 if (strlen($sPHPExec) == 0) 973 { 974 throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file."); 975 } 976 977 $sAuthUser = self::ReadParam('auth_user', '', 'raw_data'); 978 $sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data'); 979 $sParamFile = self::GetParamSourceFile('auth_user'); 980 if (is_null($sParamFile)) 981 { 982 $aArguments['auth_user'] = $sAuthUser; 983 $aArguments['auth_pwd'] = $sAuthPwd; 984 } 985 else 986 { 987 $aArguments['param_file'] = $sParamFile; 988 } 989 990 $aArgs = array(); 991 foreach($aArguments as $sName => $value) 992 { 993 // Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation 994 // It suggests to rely on pctnl_* function instead of using escapeshellargs 995 $aArgs[] = "--$sName=".escapeshellarg($value); 996 } 997 $sArgs = implode(' ', $aArgs); 998 999 $sScript = realpath(APPROOT.$sScriptName); 1000 if (!file_exists($sScript)) 1001 { 1002 throw new Exception("Could not find the script file '$sScriptName' from the directory '".APPROOT."'"); 1003 } 1004 1005 $sCommand = '"'.$sPHPExec.'" '.escapeshellarg($sScript).' -- '.$sArgs; 1006 1007 if (version_compare(phpversion(), '5.3.0', '<')) 1008 { 1009 if (substr(PHP_OS,0,3) == 'WIN') 1010 { 1011 // Under Windows, and for PHP 5.2.x, the whole command has to be quoted 1012 // Cf PHP doc: http://php.net/manual/fr/function.exec.php, comment from the 27-Dec-2010 1013 $sCommand = '"'.$sCommand.'"'; 1014 } 1015 } 1016 1017 $sLastLine = exec($sCommand, $aOutput, $iRes); 1018 if ($iRes == 1) 1019 { 1020 throw new Exception(Dict::S('Core:ExecProcess:Code1')." - ".$sCommand); 1021 } 1022 elseif ($iRes == 255) 1023 { 1024 $sErrors = implode("\n", $aOutput); 1025 throw new Exception(Dict::S('Core:ExecProcess:Code255')." - ".$sCommand.":\n".$sErrors); 1026 } 1027 1028 //$aOutput[] = $sCommand; 1029 return array($iRes, $aOutput); 1030 } 1031 1032 /** 1033 * Get the current environment 1034 */ 1035 public static function GetCurrentEnvironment() 1036 { 1037 if (isset($_SESSION['itop_env'])) 1038 { 1039 return $_SESSION['itop_env']; 1040 } 1041 else 1042 { 1043 return ITOP_DEFAULT_ENV; 1044 } 1045 } 1046 1047 /** 1048 * Returns a path to a folder into which any module can store cache data 1049 * The corresponding folder is created or cleaned upon code compilation 1050 * @return string 1051 */ 1052 public static function GetCachePath() 1053 { 1054 return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/'; 1055 } 1056 /** 1057 * Merge standard menu items with plugin provided menus items 1058 */ 1059 public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions, $sTableId = null, $sDataTableId = null) 1060 { 1061 // 1st - add standard built-in menu items 1062 // 1063 switch($iMenuId) 1064 { 1065 case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT: 1066 // $param is a DBObjectSet 1067 $oAppContext = new ApplicationContext(); 1068 $sContext = $oAppContext->GetForLink(); 1069 $sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId; 1070 $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass()); 1071 $sOQL = addslashes($param->GetFilter()->ToOQL(true)); 1072 $sFilter = urlencode($param->GetFilter()->serialize()); 1073 $sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}"; 1074 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); 1075 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); 1076 $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); 1077 1078 $aResult = array(); 1079 if (strlen($sUrl) < SERVER_MAX_URL_LENGTH) 1080 { 1081 $aResult[] = new SeparatorPopupMenuItem(); 1082 // Static menus: Email this page, CSV Export & Add to Dashboard 1083 $aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), 1084 "mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook 1085 ); 1086 } 1087 1088 if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO) 1089 { 1090 // Bulk export actions 1091 $aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"); 1092 $aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"); 1093 if (extension_loaded('gd')) 1094 { 1095 // PDF export requires GD 1096 $aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")"); 1097 } 1098 } 1099 $aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')"); 1100 $aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')"); 1101 1102 break; 1103 1104 case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS: 1105 // $param is a DBObject 1106 $oObj = $param; 1107 $sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey(); 1108 $sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey()); 1109 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); 1110 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); 1111 $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); 1112 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); 1113 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); 1114 $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); 1115 1116 $aResult = array( 1117 new SeparatorPopupMenuItem(), 1118 // Static menus: Email this page & CSV Export 1119 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 1120 new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"), 1121 new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"), 1122 new SeparatorPopupMenuItem(), 1123 new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'), 1124 ); 1125 break; 1126 1127 case iPopupMenuExtension::MENU_DASHBOARD_ACTIONS: 1128 // $param is a Dashboard 1129 /** @var \RuntimeDashboard $oDashboard */ 1130 $oDashboard = $param; 1131 $sDashboardId = $oDashboard->GetId(); 1132 $sDashboardFile = $oDashboard->GetDefinitionFile(); 1133 $sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle')); 1134 $sDlgText = addslashes(Dict::S('UI:ImportDashboardText')); 1135 $sCloseBtn = addslashes(Dict::S('UI:Button:Cancel')); 1136 $sDashboardFileJS = addslashes($sDashboardFile); 1137 $sDashboardFileURL = urlencode($sDashboardFile); 1138 $sUploadDashboardTransactId = utils::GetNewTransactionId(); 1139 $aResult = array( 1140 new SeparatorPopupMenuItem(), 1141 new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sDashboardId.'&file='.$sDashboardFileURL), 1142 new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sDashboardId', file: '$sDashboardFileJS', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn', transaction: '$sUploadDashboardTransactId' })"), 1143 ); 1144 if ($oDashboard->GetReloadURL()) 1145 { 1146 $aResult[] = new SeparatorPopupMenuItem(); 1147 $aResult[] = new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $oDashboard->GetReloadURL().'&printable=1', '_blank'); 1148 } 1149 1150 break; 1151 1152 default: 1153 // Unknown type of menu, do nothing 1154 $aResult = array(); 1155 } 1156 foreach($aResult as $oMenuItem) 1157 { 1158 $aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem(); 1159 } 1160 1161 // Invoke the plugins 1162 // 1163 foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) 1164 { 1165 if (is_object($param) && !($param instanceof DBObject)) 1166 { 1167 $tmpParam = clone $param; // In case the parameter is an DBObjectSet, clone it to prevent alterations 1168 } 1169 else 1170 { 1171 $tmpParam = $param; 1172 } 1173 foreach($oExtensionInstance->EnumItems($iMenuId, $tmpParam) as $oMenuItem) 1174 { 1175 if (is_object($oMenuItem)) 1176 { 1177 $aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem(); 1178 1179 foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript) 1180 { 1181 $oPage->add_linked_script($sLinkedScript); 1182 } 1183 } 1184 } 1185 } 1186 } 1187 /** 1188 * @return string target configuration file name (including full path) 1189 */ 1190 public static function GetConfigFilePath($sEnvironment = null) 1191 { 1192 if (is_null($sEnvironment)) 1193 { 1194 $sEnvironment = self::GetCurrentEnvironment(); 1195 } 1196 return APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE; 1197 } 1198 /** 1199 * @return string target configuration file name (including relative path) 1200 */ 1201 public static function GetConfigFilePathRelative($sEnvironment = null) 1202 { 1203 if (is_null($sEnvironment)) 1204 { 1205 $sEnvironment = self::GetCurrentEnvironment(); 1206 } 1207 return "conf/".$sEnvironment.'/'.ITOP_CONFIG_FILE; 1208 } 1209 1210 /** 1211 * @return string the absolute URL to the modules root path 1212 */ 1213 static public function GetAbsoluteUrlModulesRoot() 1214 { 1215 $sUrl = self::GetAbsoluteUrlAppRoot().'env-'.self::GetCurrentEnvironment().'/'; 1216 return $sUrl; 1217 } 1218 1219 /** 1220 * To be compatible with this mechanism, the called page must include approot with an absolute path OR not include 1221 * it at all (losing the direct access to the page) : 1222 * 1223 * ```php 1224 * if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__)); 1225 * require_once(__DIR__.'/../../approot.inc.php'); 1226 * ``` 1227 * 1228 * @param string $sModule 1229 * @param string $sPage 1230 * @param string[] $aArguments 1231 * @param string $sEnvironment 1232 * 1233 * @return string the URL to a page that will execute the requested module page, with query string values url encoded 1234 * 1235 * @see GetExecPageArguments can be used to submit using the GET method (see bug in N.1108) 1236 * @see GetAbsoluteUrlExecPage 1237 */ 1238 static public function GetAbsoluteUrlModulePage($sModule, $sPage, $aArguments = array(), $sEnvironment = null) 1239 { 1240 $aArgs = self::GetExecPageArguments($sModule, $sPage, $aArguments, $sEnvironment); 1241 $sArgs = http_build_query($aArgs); 1242 1243 return self::GetAbsoluteUrlExecPage()."?".$sArgs; 1244 } 1245 1246 /** 1247 * @param string $sModule 1248 * @param string $sPage 1249 * @param string[] $aArguments 1250 * @param string $sEnvironment 1251 * 1252 * @return string[] key/value pair for the exec page query string. <b>Warning</b> : values are not url encoded ! 1253 * @throws \Exception if one of the argument has a reserved name 1254 */ 1255 static public function GetExecPageArguments($sModule, $sPage, $aArguments = array(), $sEnvironment = null) 1256 { 1257 $sEnvironment = is_null($sEnvironment) ? self::GetCurrentEnvironment() : $sEnvironment; 1258 $aArgs = array(); 1259 $aArgs['exec_module'] = $sModule; 1260 $aArgs['exec_page'] = $sPage; 1261 $aArgs['exec_env'] = $sEnvironment; 1262 foreach($aArguments as $sName => $sValue) 1263 { 1264 if (($sName == 'exec_module') || ($sName == 'exec_page') || ($sName == 'exec_env')) 1265 { 1266 throw new Exception("Module page: $sName is a reserved page argument name"); 1267 } 1268 $aArgs[$sName] = $sValue; 1269 } 1270 1271 return $aArgs; 1272 } 1273 1274 /** 1275 * @return string 1276 */ 1277 static public function GetAbsoluteUrlExecPage() 1278 { 1279 return self::GetAbsoluteUrlAppRoot().'pages/exec.php'; 1280 } 1281 1282 /** 1283 * Returns a name unique amongst the given list 1284 * @param string $sProposed The default value 1285 * @param array $aExisting An array of existing values (strings) 1286 */ 1287 static public function MakeUniqueName($sProposed, $aExisting) 1288 { 1289 if (in_array($sProposed, $aExisting)) 1290 { 1291 $i = 1; 1292 while (in_array($sProposed.$i, $aExisting) && ($i < 50)) 1293 { 1294 $i++; 1295 } 1296 return $sProposed.$i; 1297 } 1298 else 1299 { 1300 return $sProposed; 1301 } 1302 } 1303 1304 /** 1305 * Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore) 1306 * @param string $sId The ID to sanitize 1307 * @return string The sanitized ID 1308 */ 1309 static public function GetSafeId($sId) 1310 { 1311 return str_replace(array(':', '[', ']', '+', '-'), '_', $sId); 1312 } 1313 1314 /** 1315 * Helper to execute an HTTP POST request 1316 * Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl 1317 * originaly named after do_post_request 1318 * Does not require cUrl but requires openssl for performing https POSTs. 1319 * 1320 * @param string $sUrl The URL to POST the data to 1321 * @param hash $aData The data to POST as an array('param_name' => value) 1322 * @param string $sOptionnalHeaders Additional HTTP headers as a string with newlines between headers 1323 * @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 1324 * @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 1325 * @return string The result of the POST request 1326 * @throws Exception 1327 */ 1328 static public function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null, &$aResponseHeaders = null, $aCurlOptions = array()) 1329 { 1330 // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. 1331 1332 if (function_exists('curl_init')) 1333 { 1334 // If cURL is available, let's use it, since it provides a greater control over the various HTTP/SSL options 1335 // For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112 1336 // by setting the SSLVERSION to 3 as done below. 1337 $aHeaders = explode("\n", $sOptionnalHeaders); 1338 $aHTTPHeaders = array(); 1339 foreach($aHeaders as $sHeaderString) 1340 { 1341 if(preg_match('/^([^:]): (.+)$/', $sHeaderString, $aMatches)) 1342 { 1343 $aHTTPHeaders[$aMatches[1]] = $aMatches[2]; 1344 } 1345 } 1346 // Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions 1347 $aOptions = array( 1348 CURLOPT_RETURNTRANSFER => true, // return the content of the request 1349 CURLOPT_HEADER => false, // don't return the headers in the output 1350 CURLOPT_FOLLOWLOCATION => true, // follow redirects 1351 CURLOPT_ENCODING => "", // handle all encodings 1352 CURLOPT_USERAGENT => "spider", // who am i 1353 CURLOPT_AUTOREFERER => true, // set referer on redirect 1354 CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect 1355 CURLOPT_TIMEOUT => 120, // timeout on response 1356 CURLOPT_MAXREDIRS => 10, // stop after 10 redirects 1357 CURLOPT_SSL_VERIFYPEER => false, // Disabled SSL Cert checks 1358 // SSLV3 (CURL_SSLVERSION_SSLv3 = 3) is now considered as obsolete/dangerous: http://disablessl3.com/#why 1359 // 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 1360 // CURLOPT_SSLVERSION => 3, 1361 CURLOPT_POST => count($aData), 1362 CURLOPT_POSTFIELDS => http_build_query($aData), 1363 CURLOPT_HTTPHEADER => $aHTTPHeaders, 1364 ); 1365 1366 $aAllOptions = $aCurlOptions + $aOptions; 1367 $ch = curl_init($sUrl); 1368 curl_setopt_array($ch, $aAllOptions); 1369 $response = curl_exec($ch); 1370 $iErr = curl_errno($ch); 1371 $sErrMsg = curl_error( $ch ); 1372 $aHeaders = curl_getinfo( $ch ); 1373 if ($iErr !== 0) 1374 { 1375 throw new Exception("Problem opening URL: $sUrl, $sErrMsg"); 1376 } 1377 if (is_array($aResponseHeaders)) 1378 { 1379 $aHeaders = curl_getinfo($ch); 1380 foreach($aHeaders as $sCode => $sValue) 1381 { 1382 $sName = str_replace(' ' , '-', ucwords(str_replace('_', ' ', $sCode))); // Transform "content_type" into "Content-Type" 1383 $aResponseHeaders[$sName] = $sValue; 1384 } 1385 } 1386 curl_close( $ch ); 1387 } 1388 else 1389 { 1390 // cURL is not available let's try with streams and fopen... 1391 1392 $sData = http_build_query($aData); 1393 $aParams = array('http' => array( 1394 'method' => 'POST', 1395 'content' => $sData, 1396 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", 1397 )); 1398 if ($sOptionnalHeaders !== null) 1399 { 1400 $aParams['http']['header'] .= $sOptionnalHeaders; 1401 } 1402 $ctx = stream_context_create($aParams); 1403 1404 $fp = @fopen($sUrl, 'rb', false, $ctx); 1405 if (!$fp) 1406 { 1407 global $php_errormsg; 1408 if (isset($php_errormsg)) 1409 { 1410 throw new Exception("Wrong URL: $sUrl, $php_errormsg"); 1411 } 1412 elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl')) 1413 { 1414 throw new Exception("Cannot connect to $sUrl: missing module 'openssl'"); 1415 } 1416 else 1417 { 1418 throw new Exception("Wrong URL: $sUrl"); 1419 } 1420 } 1421 $response = @stream_get_contents($fp); 1422 if ($response === false) 1423 { 1424 throw new Exception("Problem reading data from $sUrl, $php_errormsg"); 1425 } 1426 if (is_array($aResponseHeaders)) 1427 { 1428 $aMeta = stream_get_meta_data($fp); 1429 $aHeaders = $aMeta['wrapper_data']; 1430 foreach($aHeaders as $sHeaderString) 1431 { 1432 if(preg_match('/^([^:]+): (.+)$/', $sHeaderString, $aMatches)) 1433 { 1434 $aResponseHeaders[$aMatches[1]] = trim($aMatches[2]); 1435 } 1436 } 1437 } 1438 } 1439 return $response; 1440 } 1441 1442 /** 1443 * Get a standard list of character sets 1444 * 1445 * @param array $aAdditionalEncodings Additional values 1446 * @return array of iconv code => english label, sorted by label 1447 */ 1448 public static function GetPossibleEncodings($aAdditionalEncodings = array()) 1449 { 1450 // Encodings supported: 1451 // ICONV_CODE => Display Name 1452 // Each iconv installation supports different encodings 1453 // Some reasonably common and useful encodings are listed here 1454 $aPossibleEncodings = array( 1455 'UTF-8' => 'Unicode (UTF-8)', 1456 'ISO-8859-1' => 'Western (ISO-8859-1)', 1457 'WINDOWS-1251' => 'Cyrilic (Windows 1251)', 1458 'WINDOWS-1252' => 'Western (Windows 1252)', 1459 'ISO-8859-15' => 'Western (ISO-8859-15)', 1460 ); 1461 $aPossibleEncodings = array_merge($aPossibleEncodings, $aAdditionalEncodings); 1462 asort($aPossibleEncodings); 1463 return $aPossibleEncodings; 1464 } 1465 1466 /** 1467 * Helper to encapsulation iTop's htmlentities 1468 * @param string $sValue 1469 * @return string 1470 */ 1471 static public function HtmlEntities($sValue) 1472 { 1473 return htmlentities($sValue, ENT_QUOTES, 'UTF-8'); 1474 } 1475 1476 /** 1477 * Convert a string containing some (valid) HTML markup to plain text 1478 * @param string $sHtml 1479 * @return string 1480 */ 1481 public static function HtmlToText($sHtml) 1482 { 1483 try 1484 { 1485 //return '<?xml encoding="UTF-8">'.$sHtml; 1486 return \Html2Text\Html2Text::convert('<?xml encoding="UTF-8">'.$sHtml); 1487 } 1488 catch(Exception $e) 1489 { 1490 return $e->getMessage(); 1491 } 1492 } 1493 1494 /** 1495 * Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags 1496 * and escaping HTML entities 1497 * @param string $sText 1498 * @return string 1499 */ 1500 public static function TextToHtml($sText) 1501 { 1502 $sText = str_replace("\r\n", "\n", $sText); 1503 $sText = str_replace("\r", "\n", $sText); 1504 return str_replace("\n", '<br/>', htmlentities($sText, ENT_QUOTES, 'UTF-8')); 1505 } 1506 1507 /** 1508 * Eventually compiles the SASS (.scss) file into the CSS (.css) file 1509 * 1510 * @param string $sSassRelPath Relative path to the SCSS file (must have the extension .scss) 1511 * @param array $aImportPaths Array of absolute paths to load imports from 1512 * @return string Relative path to the CSS file (<name>.css) 1513 */ 1514 static public function GetCSSFromSASS($sSassRelPath, $aImportPaths = null) 1515 { 1516 // Avoiding compilation if file is already a css file. 1517 if (preg_match('/\.css(\?.*)?$/', $sSassRelPath)) 1518 { 1519 return $sSassRelPath; 1520 } 1521 1522 // Setting import paths 1523 if ($aImportPaths === null) 1524 { 1525 $aImportPaths = array(); 1526 } 1527 $aImportPaths[] = APPROOT . '/css'; 1528 1529 $sSassPath = APPROOT.$sSassRelPath; 1530 $sCssRelPath = preg_replace('/\.scss$/', '.css', $sSassRelPath); 1531 $sCssPath = APPROOT.$sCssRelPath; 1532 clearstatcache(); 1533 if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSassPath)))) 1534 { 1535 require_once(APPROOT.'lib/scssphp/scss.inc.php'); 1536 $oScss = new Compiler(); 1537 $oScss->setImportPaths($aImportPaths); 1538 $oScss->setFormatter('Leafo\\ScssPhp\\Formatter\\Expanded'); 1539 // Temporary disabling max exec time while compiling 1540 $iCurrentMaxExecTime = (int) ini_get('max_execution_time'); 1541 set_time_limit(0); 1542 $sCss = $oScss->compile(file_get_contents($sSassPath)); 1543 set_time_limit($iCurrentMaxExecTime); 1544 file_put_contents($sCssPath, $sCss); 1545 } 1546 return $sCssRelPath; 1547 } 1548 1549 static public function GetImageSize($sImageData) 1550 { 1551 if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher 1552 { 1553 $aRet = @getimagesizefromstring($sImageData); 1554 } 1555 else if(ini_get('allow_url_fopen')) 1556 { 1557 // work around to avoid creating a tmp file 1558 $sUri = 'data://application/octet-stream;base64,'.base64_encode($sImageData); 1559 $aRet = @getimagesize($sUri); 1560 } 1561 else 1562 { 1563 // Damned, need to create a tmp file 1564 $sTempFile = tempnam(SetupUtils::GetTmpDir(), 'img-'); 1565 @file_put_contents($sTempFile, $sImageData); 1566 $aRet = @getimagesize($sTempFile); 1567 @unlink($sTempFile); 1568 } 1569 return $aRet; 1570 } 1571 1572 /** 1573 * Resize an image attachment so that it fits in the given dimensions 1574 * @param ormDocument $oImage The original image stored as an ormDocument 1575 * @param int $iWidth Image's original width 1576 * @param int $iHeight Image's original height 1577 * @param int $iMaxImageWidth Maximum width for the resized image 1578 * @param int $iMaxImageHeight Maximum height for the resized image 1579 * @return ormDocument The resampled image 1580 */ 1581 public static function ResizeImageToFit(ormDocument $oImage, $iWidth, $iHeight, $iMaxImageWidth, $iMaxImageHeight) 1582 { 1583 // If image size smaller than maximums, we do nothing 1584 if (($iWidth <= $iMaxImageWidth) && ($iHeight <= $iMaxImageHeight)) 1585 { 1586 return $oImage; 1587 } 1588 1589 1590 // If gd extension is not loaded, we put a warning in the log and return the image as is 1591 if (extension_loaded('gd') === false) 1592 { 1593 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); 1594 return $oImage; 1595 } 1596 1597 1598 switch($oImage->GetMimeType()) 1599 { 1600 case 'image/gif': 1601 case 'image/jpeg': 1602 case 'image/png': 1603 $img = @imagecreatefromstring($oImage->GetData()); 1604 break; 1605 1606 default: 1607 // Unsupported image type, return the image as-is 1608 //throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used."); 1609 return $oImage; 1610 } 1611 if ($img === false) 1612 { 1613 //throw new Exception("Warning: corrupted image: '".$oImage->GetFileName()." / ".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used."); 1614 return $oImage; 1615 } 1616 else 1617 { 1618 // Let's scale the image, preserving the transparency for GIFs and PNGs 1619 1620 $fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight); 1621 1622 $iNewWidth = $iWidth * $fScale; 1623 $iNewHeight = $iHeight * $fScale; 1624 1625 $new = imagecreatetruecolor($iNewWidth, $iNewHeight); 1626 1627 // Preserve transparency 1628 if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png")) 1629 { 1630 imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127)); 1631 imagealphablending($new, false); 1632 imagesavealpha($new, true); 1633 } 1634 1635 imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight); 1636 1637 ob_start(); 1638 switch ($oImage->GetMimeType()) 1639 { 1640 case 'image/gif': 1641 imagegif($new); // send image to output buffer 1642 break; 1643 1644 case 'image/jpeg': 1645 imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality 1646 break; 1647 1648 case 'image/png': 1649 imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression 1650 break; 1651 } 1652 $oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName()); 1653 @ob_end_clean(); 1654 1655 imagedestroy($img); 1656 imagedestroy($new); 1657 1658 return $oResampledImage; 1659 } 1660 1661 } 1662 1663 /** 1664 * Create a 128 bit UUID in the format: {########-####-####-####-############} 1665 * 1666 * Note: this method can be run from the command line as well as from the web server. 1667 * Note2: this method is not cryptographically secure! If you need a cryptographically secure value 1668 * consider using open_ssl or PHP 7 methods. 1669 * @param string $sPrefix 1670 * @return string 1671 */ 1672 static public function CreateUUID($sPrefix = '') 1673 { 1674 $uid = uniqid("", true); 1675 $data = $sPrefix; 1676 $data .= __FILE__; 1677 $data .= mt_rand(); 1678 $hash = strtoupper(hash('ripemd128', $uid . md5($data))); 1679 $sUUID = '{' . 1680 substr($hash, 0, 8) . 1681 '-' . 1682 substr($hash, 8, 4) . 1683 '-' . 1684 substr($hash, 12, 4) . 1685 '-' . 1686 substr($hash, 16, 4) . 1687 '-' . 1688 substr($hash, 20, 12) . 1689 '}'; 1690 return $sUUID; 1691 } 1692 1693 /** 1694 * Returns the name of the module containing the file where the call to this function is made 1695 * or an empty string if no such module is found (or not called within a module file) 1696 * @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module 1697 * @return string 1698 */ 1699 static public function GetCurrentModuleName($iCallDepth = 0) 1700 { 1701 $sCurrentModuleName = ''; 1702 $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 1703 $sCallerFile = realpath($aCallStack[$iCallDepth]['file']); 1704 1705 foreach(GetModulesInfo() as $sModuleName => $aInfo) 1706 { 1707 if ($aInfo['root_dir'] !== '') 1708 { 1709 $sRootDir = realpath(APPROOT.$aInfo['root_dir']); 1710 1711 if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir) 1712 { 1713 $sCurrentModuleName = $sModuleName; 1714 break; 1715 } 1716 } 1717 } 1718 return $sCurrentModuleName; 1719 } 1720 1721 /** 1722 * Returns the relative (to APPROOT) path of the root directory of the module containing the file where the call to this function is made 1723 * or an empty string if no such module is found (or not called within a module file) 1724 * @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module 1725 * @return string 1726 */ 1727 static public function GetCurrentModuleDir($iCallDepth) 1728 { 1729 $sCurrentModuleDir = ''; 1730 $aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 1731 $sCallerFile = realpath($aCallStack[$iCallDepth]['file']); 1732 1733 foreach(GetModulesInfo() as $sModuleName => $aInfo) 1734 { 1735 if ($aInfo['root_dir'] !== '') 1736 { 1737 $sRootDir = realpath(APPROOT.$aInfo['root_dir']); 1738 1739 if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir) 1740 { 1741 $sCurrentModuleDir = basename($sRootDir); 1742 break; 1743 } 1744 } 1745 } 1746 return $sCurrentModuleDir; 1747 } 1748 1749 /** 1750 * Returns the base URL for all files in the current module from which this method is called 1751 * or an empty string if no such module is found (or not called within a module file) 1752 * @return string 1753 */ 1754 static public function GetCurrentModuleUrl() 1755 { 1756 $sDir = static::GetCurrentModuleDir(1); 1757 if ( $sDir !== '') 1758 { 1759 return static::GetAbsoluteUrlModulesRoot().'/'.$sDir; 1760 } 1761 return ''; 1762 } 1763 1764 /** 1765 * Get the value of a given setting for the current module 1766 * @param string $sProperty The name of the property to retrieve 1767 * @param mixed $defaultvalue 1768 * @return mixed 1769 */ 1770 static public function GetCurrentModuleSetting($sProperty, $defaultvalue = null) 1771 { 1772 $sModuleName = static::GetCurrentModuleName(1); 1773 return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue); 1774 } 1775 1776 /** 1777 * Get the compiled version of a given module, as it was seen by the compiler 1778 * @param string $sModuleName 1779 * @return string|NULL 1780 */ 1781 static public function GetCompiledModuleVersion($sModuleName) 1782 { 1783 $aModulesInfo = GetModulesInfo(); 1784 if (array_key_exists($sModuleName, $aModulesInfo)) 1785 { 1786 return $aModulesInfo[$sModuleName]['version']; 1787 } 1788 return null; 1789 } 1790 1791 /** 1792 * Check if the given path/url is an http(s) URL 1793 * @param string $sPath 1794 * @return boolean 1795 */ 1796 public static function IsURL($sPath) 1797 { 1798 $bRet = false; 1799 if ((substr($sPath, 0, 7) == 'http://') || (substr($sPath, 0, 8) == 'https://') || (substr($sPath, 0, 8) == 'ftp://')) 1800 { 1801 $bRet = true; 1802 } 1803 return $bRet; 1804 } 1805 1806 /** 1807 * Check if the given URL is a link to download a document/image on the CURRENT iTop 1808 * 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 1809 * @param string $sPath 1810 * @return false|ormDocument 1811 * @throws Exception 1812 */ 1813 public static function IsSelfURL($sPath) 1814 { 1815 $result = false; 1816 $sPageUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php'; 1817 if (substr($sPath, 0, strlen($sPageUrl)) == $sPageUrl) 1818 { 1819 // If the URL is an URL pointing to this instance of iTop, then 1820 // extract the "query" part of the URL and analyze it 1821 $sQuery = parse_url($sPath, PHP_URL_QUERY); 1822 if ($sQuery !== null) 1823 { 1824 $aParams = array(); 1825 foreach(explode('&', $sQuery) as $sChunk) 1826 { 1827 $aParts = explode('=', $sChunk); 1828 if (count($aParts) != 2) continue; 1829 $aParams[$aParts[0]] = urldecode($aParts[1]); 1830 } 1831 $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'); 1832 if ($result) 1833 { 1834 // This is a 'download_document' operation, let's retrieve the document directly from the database 1835 $sClass = $aParams['class']; 1836 $iKey = $aParams['id']; 1837 $sAttCode = $aParams['field']; 1838 1839 $oObj = MetaModel::GetObject($sClass, $iKey, false /* must exist */); // Users rights apply here !! 1840 if ($oObj) 1841 { 1842 /** 1843 * @var ormDocument $result 1844 */ 1845 $result = clone $oObj->Get($sAttCode); 1846 return $result; 1847 } 1848 } 1849 } 1850 throw new Exception('Invalid URL. This iTop URL is not pointing to a valid Document/Image.'); 1851 } 1852 return $result; 1853 } 1854 1855 /** 1856 * Read the content of a file (and retrieve its MIME type) from either: 1857 * - an URL pointing to a blob (image/document) on the current iTop server 1858 * - an http(s) URL 1859 * - the local file system (but only if you are an administrator) 1860 * @param string $sPath 1861 * @return ormDocument|null 1862 * @throws Exception 1863 */ 1864 public static function FileGetContentsAndMIMEType($sPath) 1865 { 1866 $oUploadedDoc = null; 1867 $aKnownExtensions = array( 1868 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 1869 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 1870 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 1871 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 1872 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 1873 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', 1874 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 1875 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 1876 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', 1877 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 1878 'jpg' => 'image/jpeg', 1879 'jpeg' => 'image/jpeg', 1880 'gif' => 'image/gif', 1881 'png' => 'image/png', 1882 'pdf' => 'application/pdf', 1883 'doc' => 'application/msword', 1884 'dot' => 'application/msword', 1885 'xls' => 'application/vnd.ms-excel', 1886 'ppt' => 'application/vnd.ms-powerpoint', 1887 'vsd' => 'application/x-visio', 1888 'vdx' => 'application/visio.drawing', 1889 'odt' => 'application/vnd.oasis.opendocument.text', 1890 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 1891 'odp' => 'application/vnd.oasis.opendocument.presentation', 1892 'zip' => 'application/zip', 1893 'txt' => 'text/plain', 1894 'htm' => 'text/html', 1895 'html' => 'text/html', 1896 'exe' => 'application/octet-stream' 1897 ); 1898 1899 $sData = null; 1900 $sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters... 1901 $sFileName = 'uploaded-file'; // Default name for downloaded-files 1902 $sExtension = '.txt'; // Default file extension in case we don't know the MIME Type 1903 1904 if(empty($sPath)) 1905 { 1906 // Empty path (NULL or '') means that there is no input, making an empty document. 1907 $oUploadedDoc = new ormDocument('', '', ''); 1908 } 1909 elseif (static::IsURL($sPath)) 1910 { 1911 if ($oUploadedDoc = static::IsSelfURL($sPath)) 1912 { 1913 // Nothing more to do, we've got it !! 1914 } 1915 else 1916 { 1917 // Remote file, let's use the HTTP headers to find the MIME Type 1918 $sData = @file_get_contents($sPath); 1919 if ($sData === false) 1920 { 1921 throw new Exception("Failed to load the file from the URL '$sPath'."); 1922 } 1923 else 1924 { 1925 if (isset($http_response_header)) 1926 { 1927 $aHeaders = static::ParseHeaders($http_response_header); 1928 $sMimeType = array_key_exists('Content-Type', $aHeaders) ? strtolower($aHeaders['Content-Type']) : 'application/x-octet-stream'; 1929 // Compute the file extension from the MIME Type 1930 foreach($aKnownExtensions as $sExtValue => $sMime) 1931 { 1932 if ($sMime === $sMimeType) 1933 { 1934 $sExtension = '.'.$sExtValue; 1935 break; 1936 } 1937 } 1938 } 1939 $sFileName .= $sExtension; 1940 } 1941 $oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName); 1942 } 1943 } 1944 else if (UserRights::IsAdministrator()) 1945 { 1946 // Only administrators are allowed to read local files 1947 $sData = @file_get_contents($sPath); 1948 if ($sData === false) 1949 { 1950 throw new Exception("Failed to load the file '$sPath'. The file does not exist or the current process is not allowed to access it."); 1951 } 1952 $sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION)); 1953 $sFileName = basename($sPath); 1954 1955 if (array_key_exists($sExtension, $aKnownExtensions)) 1956 { 1957 $sMimeType = $aKnownExtensions[$sExtension]; 1958 } 1959 else if (extension_loaded('fileinfo')) 1960 { 1961 $finfo = new finfo(FILEINFO_MIME); 1962 $sMimeType = $finfo->file($sPath); 1963 } 1964 $oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName); 1965 } 1966 return $oUploadedDoc; 1967 } 1968 1969 protected static function ParseHeaders($aHeaders) 1970 { 1971 $aCleanHeaders = array(); 1972 foreach( $aHeaders as $sKey => $sValue ) 1973 { 1974 $aTokens = explode(':', $sValue, 2); 1975 if(isset($aTokens[1])) 1976 { 1977 $aCleanHeaders[trim($aTokens[0])] = trim($aTokens[1]); 1978 } 1979 else 1980 { 1981 // The header is not in the form Header-Code: Value 1982 $aCleanHeaders[] = $sValue; // Store the value as-is 1983 $aMatches = array(); 1984 // Check if it's not the HTTP response code 1985 if( preg_match("|HTTP/[0-9\.]+\s+([0-9]+)|", $sValue, $aMatches) ) 1986 { 1987 $aCleanHeaders['reponse_code'] = intval($aMatches[1]); 1988 } 1989 } 1990 } 1991 return $aCleanHeaders; 1992 } 1993 1994 /** 1995 * Return a string based on compilation time or (if not available because the datamodel has not been loaded) 1996 * the version of iTop. This string is useful to prevent browser side caching of content that may vary at each 1997 * (re)installation of iTop (especially during development). 1998 * @return string 1999 */ 2000 public static function GetCacheBusterTimestamp() 2001 { 2002 if(!defined('COMPILATION_TIMESTAMP')) 2003 { 2004 return ITOP_VERSION; 2005 } 2006 return COMPILATION_TIMESTAMP; 2007 } 2008 2009 /** 2010 * Check if the given class if configured as a high cardinality class. 2011 * 2012 * @param $sClass 2013 * 2014 * @return bool 2015 */ 2016 public static function IsHighCardinality($sClass) 2017 { 2018 if (utils::GetConfig()->Get('search_manual_submit')) 2019 { 2020 return true; 2021 } 2022 $aHugeClasses = MetaModel::GetConfig()->Get('high_cardinality_classes'); 2023 return in_array($sClass, $aHugeClasses); 2024 } 2025 2026 /** 2027 * Check if iTop is in a development environment (VCS vs build number) 2028 * 2029 * @return bool 2030 */ 2031 public static function IsDevelopmentEnvironment() 2032 { 2033 return ITOP_REVISION === 'svn'; 2034 } 2035} 2036