PageRenderTime 405ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 1ms

/public/external/pydio/core/classes/class.AJXP_Utils.php

https://github.com/costinu/cms
PHP | 1855 lines | 1309 code | 122 blank | 424 comment | 381 complexity | 09649a84752d23a87ece065452403021 MD5 | raw file
Possible License(s): BSD-2-Clause, Apache-2.0, LGPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause, LGPL-2.0, AGPL-3.0
  1. <?php
  2. /*
  3. * Copyright 2007-2013 Charles du Jeu - Abstrium SAS <team (at) pyd.io>
  4. * This file is part of Pydio.
  5. *
  6. * Pydio is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Pydio is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with Pydio. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. * The latest code can be found at <http://pyd.io/>.
  20. */
  21. defined('AJXP_EXEC') or die('Access not allowed');
  22. define('AJXP_SANITIZE_HTML', 1);
  23. define('AJXP_SANITIZE_HTML_STRICT', 2);
  24. define('AJXP_SANITIZE_ALPHANUM', 3);
  25. define('AJXP_SANITIZE_EMAILCHARS', 4);
  26. define('AJXP_SANITIZE_FILENAME', 5);
  27. // THESE ARE DEFINED IN bootstrap_context.php
  28. // REPEAT HERE FOR BACKWARD COMPATIBILITY.
  29. if (!defined('PBKDF2_HASH_ALGORITHM')) {
  30. define("PBKDF2_HASH_ALGORITHM", "sha256");
  31. define("PBKDF2_ITERATIONS", 1000);
  32. define("PBKDF2_SALT_BYTE_SIZE", 24);
  33. define("PBKDF2_HASH_BYTE_SIZE", 24);
  34. define("HASH_SECTIONS", 4);
  35. define("HASH_ALGORITHM_INDEX", 0);
  36. define("HASH_ITERATION_INDEX", 1);
  37. define("HASH_SALT_INDEX", 2);
  38. define("HASH_PBKDF2_INDEX", 3);
  39. define("USE_OPENSSL_RANDOM", false);
  40. }
  41. /**
  42. * Various functions used everywhere, static library
  43. * @package Pydio
  44. * @subpackage Core
  45. */
  46. class AJXP_Utils
  47. {
  48. /**
  49. * Performs a natural sort on the array keys.
  50. * Behaves the same as ksort() with natural sorting added.
  51. *
  52. * @param Array $array The array to sort
  53. * @return boolean
  54. */
  55. public static function natksort(&$array)
  56. {
  57. uksort($array, 'strnatcasecmp');
  58. return true;
  59. }
  60. /**
  61. * Performs a reverse natural sort on the array keys
  62. * Behaves the same as krsort() with natural sorting added.
  63. *
  64. * @param Array $array The array to sort
  65. * @return boolean
  66. */
  67. public static function natkrsort(&$array)
  68. {
  69. natksort($array);
  70. $array = array_reverse($array, TRUE);
  71. return true;
  72. }
  73. /**
  74. * Remove all "../../" tentatives, replace double slashes
  75. * @static
  76. * @param string $path
  77. * @return string
  78. */
  79. public static function securePath($path)
  80. {
  81. if ($path == null) $path = "";
  82. //
  83. // REMOVE ALL "../" TENTATIVES
  84. //
  85. $path = str_replace(chr(0), "", $path);
  86. $dirs = explode('/', $path);
  87. for ($i = 0; $i < count($dirs); $i++) {
  88. if ($dirs[$i] == '.' or $dirs[$i] == '..') {
  89. $dirs[$i] = '';
  90. }
  91. }
  92. // rebuild safe directory string
  93. $path = implode('/', $dirs);
  94. //
  95. // REPLACE DOUBLE SLASHES
  96. //
  97. while (preg_match('/\/\//', $path)) {
  98. $path = str_replace('//', '/', $path);
  99. }
  100. return $path;
  101. }
  102. public static function safeDirname($path)
  103. {
  104. return (DIRECTORY_SEPARATOR === "\\" ? str_replace("\\", "/", dirname($path)): dirname($path));
  105. }
  106. public static function safeBasename($path)
  107. {
  108. return (DIRECTORY_SEPARATOR === "\\" ? str_replace("\\", "/", basename($path)): basename($path));
  109. }
  110. public static function clearHexaCallback($array){
  111. return chr(hexdec($array[1]));
  112. }
  113. /**
  114. * Given a string, this function will determine if it potentially an
  115. * XSS attack and return boolean.
  116. *
  117. * @param string $string
  118. * The string to run XSS detection logic on
  119. * @return boolean
  120. * True if the given `$string` contains XSS, false otherwise.
  121. */
  122. public static function detectXSS($string) {
  123. $contains_xss = FALSE;
  124. // Skip any null or non string values
  125. if(is_null($string) || !is_string($string)) {
  126. return $contains_xss;
  127. }
  128. // Keep a copy of the original string before cleaning up
  129. $orig = $string;
  130. // Set the patterns we'll test against
  131. $patterns = array(
  132. // Match any attribute starting with "on" or xmlns
  133. '#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>?#iUu',
  134. // Match javascript:, livescript:, vbscript: and mocha: protocols
  135. '!((java|live|vb)script|mocha|feed|data):(\w)*!iUu',
  136. '#-moz-binding[\x00-\x20]*:#u',
  137. // Match style attributes
  138. '#(<[^>]+[\x00-\x20\"\'\/])style=[^>]*>?#iUu',
  139. // Match unneeded tags
  140. '#</*(applet|meta|xml|blink|link|style|script|embed|object|iframe|frame|frameset|ilayer|layer|bgsound|title|base)[^>]*>?#i'
  141. );
  142. foreach($patterns as $pattern) {
  143. // Test both the original string and clean string
  144. if(preg_match($pattern, $string) || preg_match($pattern, $orig)){
  145. $contains_xss = TRUE;
  146. }
  147. if ($contains_xss === TRUE) return TRUE;
  148. }
  149. return FALSE;
  150. }
  151. /**
  152. * Function to clean a string from specific characters
  153. *
  154. * @static
  155. * @param string $s
  156. * @param int $level Can be AJXP_SANITIZE_ALPHANUM, AJXP_SANITIZE_EMAILCHARS, AJXP_SANITIZE_HTML, AJXP_SANITIZE_HTML_STRICT
  157. * @param string $expand
  158. * @return mixed|string
  159. */
  160. public static function sanitize($s, $level = AJXP_SANITIZE_HTML, $expand = 'script|style|noframes|select|option')
  161. {
  162. if ($level == AJXP_SANITIZE_ALPHANUM) {
  163. return preg_replace("/[^a-zA-Z0-9_\-\.]/", "", $s);
  164. } else if ($level == AJXP_SANITIZE_EMAILCHARS) {
  165. return preg_replace("/[^a-zA-Z0-9_\-\.@!%\+=|~\?]/", "", $s);
  166. } else if ($level == AJXP_SANITIZE_FILENAME) {
  167. // Convert Hexadecimals
  168. $s = preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!', array('AJXP_Utils', 'clearHexaCallback'), $s);
  169. // Clean up entities
  170. $s = preg_replace('!(&#0+[0-9]+)!','$1;',$s);
  171. // Decode entities
  172. $s = html_entity_decode($s, ENT_NOQUOTES, 'UTF-8');
  173. // Strip whitespace characters
  174. $s = trim($s);
  175. $s = str_replace(chr(0), "", $s);
  176. $s = preg_replace("/[\"\/\|\?\\\]/", "", $s);
  177. if(self::detectXSS($s)){
  178. $s = "XSS Detected - Rename Me";
  179. }
  180. return $s;
  181. }
  182. /**/ //prep the string
  183. $s = ' ' . $s;
  184. //begin removal
  185. /**/ //remove comment blocks
  186. while (stripos($s, '<!--') > 0) {
  187. $pos[1] = stripos($s, '<!--');
  188. $pos[2] = stripos($s, '-->', $pos[1]);
  189. $len[1] = $pos[2] - $pos[1] + 3;
  190. $x = substr($s, $pos[1], $len[1]);
  191. $s = str_replace($x, '', $s);
  192. }
  193. /**/ //remove tags with content between them
  194. if (strlen($expand) > 0) {
  195. $e = explode('|', $expand);
  196. for ($i = 0; $i < count($e); $i++) {
  197. while (stripos($s, '<' . $e[$i]) > 0) {
  198. $len[1] = strlen('<' . $e[$i]);
  199. $pos[1] = stripos($s, '<' . $e[$i]);
  200. $pos[2] = stripos($s, $e[$i] . '>', $pos[1] + $len[1]);
  201. $len[2] = $pos[2] - $pos[1] + $len[1];
  202. $x = substr($s, $pos[1], $len[2]);
  203. $s = str_replace($x, '', $s);
  204. }
  205. }
  206. }
  207. $s = strip_tags($s);
  208. if ($level == AJXP_SANITIZE_HTML_STRICT) {
  209. $s = preg_replace("/[\",;\/`<>:\*\|\?!\^\\\]/", "", $s);
  210. } else {
  211. $s = str_replace(array("<", ">"), array("&lt;", "&gt;"), $s);
  212. }
  213. return trim($s);
  214. }
  215. /**
  216. * Perform standard urldecode, sanitization and securepath
  217. * @static
  218. * @param $data
  219. * @param int $sanitizeLevel
  220. * @return string
  221. */
  222. public static function decodeSecureMagic($data, $sanitizeLevel = AJXP_SANITIZE_HTML)
  223. {
  224. return SystemTextEncoding::fromUTF8(AJXP_Utils::sanitize(AJXP_Utils::securePath($data), $sanitizeLevel));
  225. }
  226. /**
  227. * Try to load the tmp dir from the CoreConf AJXP_TMP_DIR, or the constant AJXP_TMP_DIR,
  228. * or the sys_get_temp_dir
  229. * @static
  230. * @return mixed|null|string
  231. */
  232. public static function getAjxpTmpDir()
  233. {
  234. $conf = ConfService::getCoreConf("AJXP_TMP_DIR");
  235. if (!empty($conf)) {
  236. return $conf;
  237. }
  238. if (defined("AJXP_TMP_DIR") && AJXP_TMP_DIR != "") {
  239. return AJXP_TMP_DIR;
  240. }
  241. return realpath(sys_get_temp_dir());
  242. }
  243. public static function detectApplicationFirstRun()
  244. {
  245. return !file_exists(AJXP_CACHE_DIR."/first_run_passed");
  246. }
  247. public static function setApplicationFirstRunPassed()
  248. {
  249. @file_put_contents(AJXP_CACHE_DIR."/first_run_passed", "true");
  250. }
  251. public static function forwardSlashDirname($path)
  252. {
  253. return (DIRECTORY_SEPARATOR === "\\" ? str_replace("\\", "/", dirname($path)): dirname($path));
  254. }
  255. public static function forwardSlashBasename($path)
  256. {
  257. return (DIRECTORY_SEPARATOR === "\\" ? str_replace("\\", "/", basename($path)): basename($path));
  258. }
  259. /**
  260. * Parse a Comma-Separated-Line value
  261. * @static
  262. * @param $string
  263. * @param bool $hash
  264. * @return array
  265. */
  266. public static function parseCSL($string, $hash = false)
  267. {
  268. $exp = array_map("trim", explode(",", $string));
  269. if (!$hash) return $exp;
  270. $assoc = array();
  271. foreach ($exp as $explVal) {
  272. $reExp = explode("|", $explVal);
  273. if (count($reExp) == 1) $assoc[$reExp[0]] = $reExp[0];
  274. else $assoc[$reExp[0]] = $reExp[1];
  275. }
  276. return $assoc;
  277. }
  278. /**
  279. * Parse the $fileVars[] PHP errors
  280. * @static
  281. * @param $boxData
  282. * @return array|null
  283. */
  284. public static function parseFileDataErrors($boxData)
  285. {
  286. $mess = ConfService::getMessages();
  287. $userfile_error = $boxData["error"];
  288. $userfile_tmp_name = $boxData["tmp_name"];
  289. $userfile_size = $boxData["size"];
  290. if ($userfile_error != UPLOAD_ERR_OK) {
  291. $errorsArray = array();
  292. $errorsArray[UPLOAD_ERR_FORM_SIZE] = $errorsArray[UPLOAD_ERR_INI_SIZE] = array(409, "File is too big! Max is" . ini_get("upload_max_filesize"));
  293. $errorsArray[UPLOAD_ERR_NO_FILE] = array(410, "No file found on server!");
  294. $errorsArray[UPLOAD_ERR_PARTIAL] = array(410, "File is partial");
  295. $errorsArray[UPLOAD_ERR_INI_SIZE] = array(410, "No file found on server!");
  296. $errorsArray[UPLOAD_ERR_NO_TMP_DIR] = array(410, "Cannot find the temporary directory!");
  297. $errorsArray[UPLOAD_ERR_CANT_WRITE] = array(411, "Cannot write into the temporary directory!");
  298. $errorsArray[UPLOAD_ERR_EXTENSION] = array(410, "A PHP extension stopped the upload process");
  299. if ($userfile_error == UPLOAD_ERR_NO_FILE) {
  300. // OPERA HACK, do not display "no file found error"
  301. if (!ereg('Opera', $_SERVER['HTTP_USER_AGENT'])) {
  302. return $errorsArray[$userfile_error];
  303. }
  304. } else {
  305. return $errorsArray[$userfile_error];
  306. }
  307. }
  308. if ($userfile_tmp_name == "none" || $userfile_size == 0) {
  309. return array(410, $mess[31]);
  310. }
  311. return null;
  312. }
  313. /**
  314. * Utilitary to pass some parameters directly at startup :
  315. * + repository_id / folder
  316. * + compile & skipDebug
  317. * + update_i18n, extract, create
  318. * + external_selector_type
  319. * + skipIOS
  320. * + gui
  321. * @static
  322. * @param $parameters
  323. * @param $output
  324. * @param $session
  325. * @return void
  326. */
  327. public static function parseApplicationGetParameters($parameters, &$output, &$session)
  328. {
  329. $output["EXT_REP"] = "/";
  330. if (isSet($parameters["repository_id"]) && isSet($parameters["folder"]) || isSet($parameters["goto"])) {
  331. if (isSet($parameters["goto"])) {
  332. $repoId = array_shift(explode("/", ltrim($parameters["goto"], "/")));
  333. $parameters["folder"] = str_replace($repoId, "", ltrim($parameters["goto"], "/"));
  334. } else {
  335. $repoId = $parameters["repository_id"];
  336. }
  337. $repository = ConfService::getRepositoryById($repoId);
  338. if ($repository == null) {
  339. $repository = ConfService::getRepositoryByAlias($repoId);
  340. if ($repository != null) {
  341. $parameters["repository_id"] = $repository->getId();
  342. }
  343. } else {
  344. $parameters["repository_id"] = $repository->getId();
  345. }
  346. require_once(AJXP_BIN_FOLDER . "/class.SystemTextEncoding.php");
  347. if (AuthService::usersEnabled()) {
  348. $loggedUser = AuthService::getLoggedUser();
  349. if ($loggedUser != null && $loggedUser->canSwitchTo($parameters["repository_id"])) {
  350. $output["FORCE_REGISTRY_RELOAD"] = true;
  351. $output["EXT_REP"] = SystemTextEncoding::toUTF8(urldecode($parameters["folder"]));
  352. $loggedUser->setArrayPref("history", "last_repository", $parameters["repository_id"]);
  353. $loggedUser->setPref("pending_folder", SystemTextEncoding::toUTF8(AJXP_Utils::decodeSecureMagic($parameters["folder"])));
  354. $loggedUser->save("user");
  355. AuthService::updateUser($loggedUser);
  356. } else {
  357. $session["PENDING_REPOSITORY_ID"] = $parameters["repository_id"];
  358. $session["PENDING_FOLDER"] = SystemTextEncoding::toUTF8(AJXP_Utils::decodeSecureMagic($parameters["folder"]));
  359. }
  360. } else {
  361. ConfService::switchRootDir($parameters["repository_id"]);
  362. $output["EXT_REP"] = SystemTextEncoding::toUTF8(urldecode($parameters["folder"]));
  363. }
  364. }
  365. if (isSet($parameters["skipDebug"])) {
  366. ConfService::setConf("JS_DEBUG", false);
  367. }
  368. if (ConfService::getConf("JS_DEBUG") && isSet($parameters["compile"])) {
  369. require_once(AJXP_BIN_FOLDER . "/class.AJXP_JSPacker.php");
  370. AJXP_JSPacker::pack();
  371. }
  372. if (ConfService::getConf("JS_DEBUG") && isSet($parameters["update_i18n"])) {
  373. if (isSet($parameters["extract"])) {
  374. self::extractConfStringsFromManifests();
  375. }
  376. self::updateAllI18nLibraries((isSet($parameters["create"]) ? $parameters["create"] : ""));
  377. }
  378. if (ConfService::getConf("JS_DEBUG") && isSet($parameters["clear_plugins_cache"])) {
  379. @unlink(AJXP_PLUGINS_CACHE_FILE);
  380. @unlink(AJXP_PLUGINS_REQUIRES_FILE);
  381. }
  382. if (AJXP_SERVER_DEBUG && isSet($parameters["extract_application_hooks"])) {
  383. self::extractHooksToDoc();
  384. }
  385. if (isSet($parameters["external_selector_type"])) {
  386. $output["SELECTOR_DATA"] = array("type" => $parameters["external_selector_type"], "data" => $parameters);
  387. }
  388. if (isSet($parameters["skipIOS"])) {
  389. setcookie("SKIP_IOS", "true");
  390. }
  391. if (isSet($parameters["skipANDROID"])) {
  392. setcookie("SKIP_ANDROID", "true");
  393. }
  394. if (isSet($parameters["gui"])) {
  395. setcookie("AJXP_GUI", $parameters["gui"]);
  396. if ($parameters["gui"] == "light") $session["USE_EXISTING_TOKEN_IF_EXISTS"] = true;
  397. } else {
  398. if (isSet($session["USE_EXISTING_TOKEN_IF_EXISTS"])) {
  399. unset($session["USE_EXISTING_TOKEN_IF_EXISTS"]);
  400. }
  401. setcookie("AJXP_GUI", null);
  402. }
  403. if (isSet($session["OVERRIDE_GUI_START_PARAMETERS"])) {
  404. $output = array_merge($output, $session["OVERRIDE_GUI_START_PARAMETERS"]);
  405. }
  406. }
  407. /**
  408. * Remove windows carriage return
  409. * @static
  410. * @param $fileContent
  411. * @return mixed
  412. */
  413. public static function removeWinReturn($fileContent)
  414. {
  415. $fileContent = str_replace(chr(10), "", $fileContent);
  416. $fileContent = str_replace(chr(13), "", $fileContent);
  417. return $fileContent;
  418. }
  419. /**
  420. * Get the filename extensions using ConfService::getRegisteredExtensions()
  421. * @static
  422. * @param string $fileName
  423. * @param string $mode "image" or "text"
  424. * @param bool $isDir
  425. * @return string Returns the icon name ("image") or the mime label ("text")
  426. */
  427. public static function mimetype($fileName, $mode, $isDir)
  428. {
  429. $mess = ConfService::getMessages();
  430. $fileName = strtolower($fileName);
  431. $EXTENSIONS = ConfService::getRegisteredExtensions();
  432. if ($isDir) {
  433. $mime = $EXTENSIONS["ajxp_folder"];
  434. } else {
  435. foreach ($EXTENSIONS as $ext) {
  436. if (preg_match("/\.$ext[0]$/", $fileName)) {
  437. $mime = $ext;
  438. break;
  439. }
  440. }
  441. }
  442. if (!isSet($mime)) {
  443. $mime = $EXTENSIONS["ajxp_empty"];
  444. }
  445. if (is_numeric($mime[2]) || array_key_exists($mime[2], $mess)) {
  446. $mime[2] = $mess[$mime[2]];
  447. }
  448. return (($mode == "image" ? $mime[1] : $mime[2]));
  449. }
  450. public static $registeredExtensions;
  451. public static function mimeData($fileName, $isDir)
  452. {
  453. $fileName = strtolower($fileName);
  454. if (self::$registeredExtensions == null) {
  455. self::$registeredExtensions = ConfService::getRegisteredExtensions();
  456. }
  457. if ($isDir) {
  458. $mime = self::$registeredExtensions["ajxp_folder"];
  459. } else {
  460. $pos = strrpos($fileName, ".");
  461. if ($pos !== false) {
  462. $fileExt = substr($fileName, $pos + 1);
  463. if (!empty($fileExt) && array_key_exists($fileExt, self::$registeredExtensions) && $fileExt != "ajxp_folder" && $fileExt != "ajxp_empty") {
  464. $mime = self::$registeredExtensions[$fileExt];
  465. }
  466. }
  467. }
  468. if (!isSet($mime)) {
  469. $mime = self::$registeredExtensions["ajxp_empty"];
  470. }
  471. return array($mime[2], $mime[1]);
  472. }
  473. /**
  474. * Gather a list of mime that must be treated specially. Used for dynamic replacement in XML mainly.
  475. * @static
  476. * @param string $keyword "editable", "image", "audio", "zip"
  477. * @return string
  478. */
  479. public static function getAjxpMimes($keyword)
  480. {
  481. if ($keyword == "editable") {
  482. // Gather editors!
  483. $pServ = AJXP_PluginsService::getInstance();
  484. $plugs = $pServ->getPluginsByType("editor");
  485. //$plugin = new AJXP_Plugin();
  486. $mimes = array();
  487. foreach ($plugs as $plugin) {
  488. $node = $plugin->getManifestRawContent("/editor/@mimes", "node");
  489. $openable = $plugin->getManifestRawContent("/editor/@openable", "node");
  490. if ($openable->item(0) && $openable->item(0)->value == "true" && $node->item(0)) {
  491. $mimestring = $node->item(0)->value;
  492. $mimesplit = explode(",", $mimestring);
  493. foreach ($mimesplit as $value) {
  494. $mimes[$value] = $value;
  495. }
  496. }
  497. }
  498. return implode(",", array_values($mimes));
  499. } else if ($keyword == "image") {
  500. return "png,bmp,jpg,jpeg,gif";
  501. } else if ($keyword == "audio") {
  502. return "mp3";
  503. } else if ($keyword == "zip") {
  504. if (ConfService::zipEnabled()) {
  505. return "zip,ajxp_browsable_archive";
  506. } else {
  507. return "none_allowed";
  508. }
  509. }
  510. return "";
  511. }
  512. /**
  513. * Whether a file is to be considered as an image or not
  514. * @static
  515. * @param $fileName
  516. * @return bool
  517. */
  518. public static function is_image($fileName)
  519. {
  520. if (preg_match("/\.png$|\.bmp$|\.jpg$|\.jpeg$|\.gif$/i", $fileName)) {
  521. return 1;
  522. }
  523. return 0;
  524. }
  525. /**
  526. * Whether a file is to be considered as an mp3... Should be DEPRECATED
  527. * @static
  528. * @param string $fileName
  529. * @return bool
  530. * @deprecated
  531. */
  532. public static function is_mp3($fileName)
  533. {
  534. if (preg_match("/\.mp3$/i", $fileName)) return 1;
  535. return 0;
  536. }
  537. /**
  538. * Static image mime type headers
  539. * @static
  540. * @param $fileName
  541. * @return string
  542. */
  543. public static function getImageMimeType($fileName)
  544. {
  545. if (preg_match("/\.jpg$|\.jpeg$/i", $fileName)) {
  546. return "image/jpeg";
  547. } else if (preg_match("/\.png$/i", $fileName)) {
  548. return "image/png";
  549. } else if (preg_match("/\.bmp$/i", $fileName)) {
  550. return "image/bmp";
  551. } else if (preg_match("/\.gif$/i", $fileName)) {
  552. return "image/gif";
  553. }
  554. }
  555. /**
  556. * Headers to send when streaming
  557. * @static
  558. * @param $fileName
  559. * @return bool|string
  560. */
  561. public static function getStreamingMimeType($fileName)
  562. {
  563. if (preg_match("/\.mp3$/i", $fileName)) {
  564. return "audio/mp3";
  565. } else if (preg_match("/\.wav$/i", $fileName)) {
  566. return "audio/wav";
  567. } else if (preg_match("/\.aac$/i", $fileName)) {
  568. return "audio/aac";
  569. } else if (preg_match("/\.m4a$/i", $fileName)) {
  570. return "audio/m4a";
  571. } else if (preg_match("/\.aiff$/i", $fileName)) {
  572. return "audio/aiff";
  573. } else if (preg_match("/\.mp4$/i", $fileName)) {
  574. return "video/mp4";
  575. } else if (preg_match("/\.mov$/i", $fileName)) {
  576. return "video/quicktime";
  577. } else if (preg_match("/\.m4v$/i", $fileName)) {
  578. return "video/x-m4v";
  579. } else if (preg_match("/\.3gp$/i", $fileName)) {
  580. return "video/3gpp";
  581. } else if (preg_match("/\.3g2$/i", $fileName)) {
  582. return "video/3gpp2";
  583. } else return false;
  584. }
  585. public static $sizeUnit;
  586. /**
  587. * Display a human readable string for a bytesize (1MB, 2,3Go, etc)
  588. * @static
  589. * @param $filesize
  590. * @param bool $phpConfig
  591. * @return string
  592. */
  593. public static function roundSize($filesize, $phpConfig = false)
  594. {
  595. if (self::$sizeUnit == null) {
  596. $mess = ConfService::getMessages();
  597. self::$sizeUnit = $mess["byte_unit_symbol"];
  598. }
  599. if ($filesize < 0) {
  600. $filesize = sprintf("%u", $filesize);
  601. }
  602. if ($filesize >= 1073741824) {
  603. $filesize = round($filesize / 1073741824 * 100) / 100 . ($phpConfig ? "G" : " G" . self::$sizeUnit);
  604. } elseif ($filesize >= 1048576) {
  605. $filesize = round($filesize / 1048576 * 100) / 100 . ($phpConfig ? "M" : " M" . self::$sizeUnit);
  606. } elseif ($filesize >= 1024) {
  607. $filesize = round($filesize / 1024 * 100) / 100 . ($phpConfig ? "K" : " K" . self::$sizeUnit);
  608. } else {
  609. $filesize = $filesize . " " . self::$sizeUnit;
  610. }
  611. if ($filesize == 0) {
  612. $filesize = "-";
  613. }
  614. return $filesize;
  615. }
  616. /**
  617. * Hidden files start with dot
  618. * @static
  619. * @param string $fileName
  620. * @return bool
  621. */
  622. public static function isHidden($fileName)
  623. {
  624. return (substr($fileName, 0, 1) == ".");
  625. }
  626. /**
  627. * Whether a file is a browsable archive
  628. * @static
  629. * @param string $fileName
  630. * @return int
  631. */
  632. public static function isBrowsableArchive($fileName)
  633. {
  634. return preg_match("/\.zip$/i", $fileName);
  635. }
  636. /**
  637. * Convert a shorthand byte value from a PHP configuration directive to an integer value
  638. * @param string $value
  639. * @return int
  640. */
  641. public static function convertBytes($value)
  642. {
  643. if (is_numeric($value)) {
  644. return intval($value);
  645. } else {
  646. $value_length = strlen($value);
  647. $qty = substr($value, 0, $value_length - 1);
  648. $unit = strtolower(substr($value, $value_length - 1));
  649. switch ($unit) {
  650. case 'k':
  651. $qty *= 1024;
  652. break;
  653. case 'm':
  654. $qty *= 1048576;
  655. break;
  656. case 'g':
  657. $qty *= 1073741824;
  658. break;
  659. }
  660. return $qty;
  661. }
  662. }
  663. //Relative Date Function
  664. public static function relativeDate($time, $messages)
  665. {
  666. $today = strtotime(date('M j, Y'));
  667. $reldays = ($time - $today)/86400;
  668. $relTime = date($messages['date_relative_time_format'], $time);
  669. if ($reldays >= 0 && $reldays < 1) {
  670. return str_replace("TIME", $relTime, $messages['date_relative_today']);
  671. } else if ($reldays >= 1 && $reldays < 2) {
  672. return str_replace("TIME", $relTime, $messages['date_relative_tomorrow']);
  673. } else if ($reldays >= -1 && $reldays < 0) {
  674. return str_replace("TIME", $relTime, $messages['date_relative_yesterday']);
  675. }
  676. if (abs($reldays) < 7) {
  677. if ($reldays > 0) {
  678. $reldays = floor($reldays);
  679. return str_replace("%s", $reldays, $messages['date_relative_days_ahead']);
  680. //return 'In ' . $reldays . ' day' . ($reldays != 1 ? 's' : '');
  681. } else {
  682. $reldays = abs(floor($reldays));
  683. return str_replace("%s", $reldays, $messages['date_relative_days_ago']);
  684. //return $reldays . ' day' . ($reldays != 1 ? 's' : '') . ' ago';
  685. }
  686. }
  687. return str_replace("DATE", date($messages["date_relative_date_format"], $time ? $time : time()), $messages["date_relative_date"]);
  688. }
  689. /**
  690. * Replace specific chars by their XML Entities, for use inside attributes value
  691. * @static
  692. * @param $string
  693. * @param bool $toUtf8
  694. * @return mixed|string
  695. */
  696. public static function xmlEntities($string, $toUtf8 = false)
  697. {
  698. $xmlSafe = str_replace(array("&", "<", ">", "\"", "\n", "\r"), array("&amp;", "&lt;", "&gt;", "&quot;", "&#13;", "&#10;"), $string);
  699. if ($toUtf8 && SystemTextEncoding::getEncoding() != "UTF-8") {
  700. return SystemTextEncoding::toUTF8($xmlSafe);
  701. } else {
  702. return $xmlSafe;
  703. }
  704. }
  705. /**
  706. * Replace specific chars by their XML Entities, for use inside attributes value
  707. * @static
  708. * @param $string
  709. * @param bool $toUtf8
  710. * @return mixed|string
  711. */
  712. public static function xmlContentEntities($string, $toUtf8 = false)
  713. {
  714. $xmlSafe = str_replace(array("&", "<", ">", "\""), array("&amp;", "&lt;", "&gt;", "&quot;"), $string);
  715. if ($toUtf8) {
  716. return SystemTextEncoding::toUTF8($xmlSafe);
  717. } else {
  718. return $xmlSafe;
  719. }
  720. }
  721. /**
  722. * Search include path for a given file
  723. * @static
  724. * @param string $file
  725. * @return bool
  726. */
  727. public static function searchIncludePath($file)
  728. {
  729. $ps = explode(PATH_SEPARATOR, ini_get('include_path'));
  730. foreach ($ps as $path) {
  731. if (@file_exists($path . DIRECTORY_SEPARATOR . $file)) return true;
  732. }
  733. if (@file_exists($file)) return true;
  734. return false;
  735. }
  736. /**
  737. * @static
  738. * @param $from
  739. * @param $to
  740. * @return string
  741. */
  742. public static function getTravelPath($from, $to)
  743. {
  744. $from = explode('/', $from);
  745. $to = explode('/', $to);
  746. $relPath = $to;
  747. foreach ($from as $depth => $dir) {
  748. // find first non-matching dir
  749. if ($dir === $to[$depth]) {
  750. // ignore this directory
  751. array_shift($relPath);
  752. } else {
  753. // get number of remaining dirs to $from
  754. $remaining = count($from) - $depth;
  755. if ($remaining > 1) {
  756. // add traversals up to first matching dir
  757. $padLength = (count($relPath) + $remaining - 1) * -1;
  758. $relPath = array_pad($relPath, $padLength, '..');
  759. break;
  760. } else {
  761. $relPath[0] = './' . $relPath[0];
  762. }
  763. }
  764. }
  765. return implode('/', $relPath);
  766. }
  767. /**
  768. * Build the current server URL
  769. * @param bool $withURI
  770. * @internal param bool $witchURI
  771. * @static
  772. * @return string
  773. */
  774. public static function detectServerURL($withURI = false)
  775. {
  776. $setUrl = ConfService::getCoreConf("SERVER_URL");
  777. if (!empty($setUrl)) {
  778. return $setUrl;
  779. }
  780. if (php_sapi_name() == "cli") {
  781. AJXP_Logger::debug("WARNING, THE SERVER_URL IS NOT SET, WE CANNOT BUILD THE MAIL ADRESS WHEN WORKING IN CLI");
  782. }
  783. $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
  784. $port = (($protocol === 'http' && $_SERVER['SERVER_PORT'] == 80 || $protocol === 'https' && $_SERVER['SERVER_PORT'] == 443)
  785. ? "" : ":" . $_SERVER['SERVER_PORT']);
  786. $name = $_SERVER["SERVER_NAME"];
  787. if (!$withURI) {
  788. return "$protocol://$name$port";
  789. } else {
  790. return "$protocol://$name$port".dirname($_SERVER["REQUEST_URI"]);
  791. }
  792. }
  793. /**
  794. * Modifies a string to remove all non ASCII characters and spaces.
  795. * @param string $text
  796. * @return string
  797. */
  798. public static function slugify($text)
  799. {
  800. if (empty($text)) return "";
  801. // replace non letter or digits by -
  802. $text = preg_replace('~[^\\pL\d]+~u', '-', $text);
  803. // trim
  804. $text = trim($text, '-');
  805. // transliterate
  806. if (function_exists('iconv')) {
  807. $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
  808. }
  809. // lowercase
  810. $text = strtolower($text);
  811. // remove unwanted characters
  812. $text = preg_replace('~[^-\w]+~', '', $text);
  813. if (empty($text)) {
  814. return 'n-a';
  815. }
  816. return $text;
  817. }
  818. public static function getHooksFile()
  819. {
  820. return AJXP_INSTALL_PATH."/".AJXP_DOCS_FOLDER."/hooks.json";
  821. }
  822. public static function extractHooksToDoc()
  823. {
  824. $docFile = self::getHooksFile();
  825. if (is_file($docFile)) {
  826. copy($docFile, $docFile.".bak");
  827. $existingHooks = json_decode(file_get_contents($docFile), true);
  828. } else {
  829. $existingHooks = array();
  830. }
  831. $allPhpFiles = self::glob_recursive(AJXP_INSTALL_PATH."/*.php");
  832. $hooks = array();
  833. foreach ($allPhpFiles as $phpFile) {
  834. $fileContent = file($phpFile);
  835. foreach ($fileContent as $lineNumber => $line) {
  836. if (preg_match_all('/AJXP_Controller::applyHook\("([^"]+)", (.*)\)/', $line, $matches)) {
  837. $names = $matches[1];
  838. $params = $matches[2];
  839. foreach ($names as $index => $hookName) {
  840. if(!isSet($hooks[$hookName])) $hooks[$hookName] = array("TRIGGERS" => array(), "LISTENERS" => array());
  841. $hooks[$hookName]["TRIGGERS"][] = array("FILE" => substr($phpFile, strlen(AJXP_INSTALL_PATH)), "LINE" => $lineNumber);
  842. $hooks[$hookName]["PARAMETER_SAMPLE"] = $params[$index];
  843. }
  844. }
  845. }
  846. }
  847. $registryHooks = AJXP_PluginsService::getInstance()->searchAllManifests("//hooks/serverCallback", "xml", false, false, true);
  848. $regHooks = array();
  849. foreach ($registryHooks as $xmlHook) {
  850. $name = $xmlHook->getAttribute("hookName");
  851. $method = $xmlHook->getAttribute("methodName");
  852. $pluginId = $xmlHook->getAttribute("pluginId");
  853. if($pluginId == "") $pluginId = $xmlHook->parentNode->parentNode->parentNode->getAttribute("id");
  854. if(!isSet($regHooks[$name])) $regHooks[$name] = array();
  855. $regHooks[$name][] = array("PLUGIN_ID" => $pluginId, "METHOD" => $method);
  856. }
  857. foreach ($hooks as $h => $data) {
  858. if (isSet($regHooks[$h])) {
  859. $data["LISTENERS"] = $regHooks[$h];
  860. }
  861. if (isSet($existingHooks[$h])) {
  862. $existingHooks[$h]["TRIGGERS"] = $data["TRIGGERS"];
  863. $existingHooks[$h]["LISTENERS"] = $data["LISTENERS"];
  864. $existingHooks[$h]["PARAMETER_SAMPLE"] = $data["PARAMETER_SAMPLE"];
  865. } else {
  866. $existingHooks[$h] = $data;
  867. }
  868. }
  869. file_put_contents($docFile, self::prettyPrintJSON(json_encode($existingHooks)));
  870. }
  871. /**
  872. * Indents a flat JSON string to make it more human-readable.
  873. *
  874. * @param string $json The original JSON string to process.
  875. *
  876. * @return string Indented version of the original JSON string.
  877. */
  878. public function prettyPrintJSON($json)
  879. {
  880. $result = '';
  881. $pos = 0;
  882. $strLen = strlen($json);
  883. $indentStr = ' ';
  884. $newLine = "\n";
  885. $prevChar = '';
  886. $outOfQuotes = true;
  887. for ($i=0; $i<=$strLen; $i++) {
  888. // Grab the next character in the string.
  889. $char = substr($json, $i, 1);
  890. // Are we inside a quoted string?
  891. if ($char == '"' && $prevChar != '\\') {
  892. $outOfQuotes = !$outOfQuotes;
  893. // If this character is the end of an element,
  894. // output a new line and indent the next line.
  895. } else if (($char == '}' || $char == ']') && $outOfQuotes) {
  896. $result .= $newLine;
  897. $pos --;
  898. for ($j=0; $j<$pos; $j++) {
  899. $result .= $indentStr;
  900. }
  901. }
  902. // Add the character to the result string.
  903. $result .= $char;
  904. // If the last character was the beginning of an element,
  905. // output a new line and indent the next line.
  906. if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
  907. $result .= $newLine;
  908. if ($char == '{' || $char == '[') {
  909. $pos ++;
  910. }
  911. for ($j = 0; $j < $pos; $j++) {
  912. $result .= $indentStr;
  913. }
  914. }
  915. $prevChar = $char;
  916. }
  917. return $result;
  918. }
  919. /**
  920. * i18n utilitary for extracting the CONF_MESSAGE[] strings out of the XML files
  921. * @static
  922. * @return void
  923. */
  924. public static function extractConfStringsFromManifests()
  925. {
  926. $plugins = AJXP_PluginsService::getInstance()->getDetectedPlugins();
  927. $plug = new AJXP_Plugin("", "");
  928. foreach ($plugins as $pType => $plugs) {
  929. foreach ($plugs as $plug) {
  930. $lib = $plug->getManifestRawContent("//i18n", "nodes");
  931. if (!$lib->length) continue;
  932. $library = $lib->item(0);
  933. $namespace = $library->getAttribute("namespace");
  934. $path = $library->getAttribute("path");
  935. $xml = $plug->getManifestRawContent();
  936. // for core, also load mixins
  937. $refFile = AJXP_INSTALL_PATH . "/" . $path . "/conf/en.php";
  938. $reference = array();
  939. if (preg_match_all("/CONF_MESSAGE(\[.*?\])/", $xml, $matches, PREG_SET_ORDER)) {
  940. foreach ($matches as $match) {
  941. $match[1] = str_replace(array("[", "]"), "", $match[1]);
  942. $reference[$match[1]] = $match[1];
  943. }
  944. }
  945. if ($namespace == "") {
  946. $mixXml = file_get_contents(AJXP_INSTALL_PATH . "/" . AJXP_PLUGINS_FOLDER . "/core.ajaxplorer/ajxp_mixins.xml");
  947. if (preg_match_all("/MIXIN_MESSAGE(\[.*?\])/", $mixXml, $matches, PREG_SET_ORDER)) {
  948. foreach ($matches as $match) {
  949. $match[1] = str_replace(array("[", "]"), "", $match[1]);
  950. $reference[$match[1]] = $match[1];
  951. }
  952. }
  953. }
  954. if (count($reference)) {
  955. self::updateI18nFromRef($refFile, $reference);
  956. }
  957. }
  958. }
  959. }
  960. /**
  961. * Browse the i18n libraries and update the languages with the strings missing
  962. * @static
  963. * @param string $createLanguage
  964. * @return void
  965. */
  966. public static function updateAllI18nLibraries($createLanguage = "")
  967. {
  968. // UPDATE EN => OTHER LANGUAGES
  969. $nodes = AJXP_PluginsService::getInstance()->searchAllManifests("//i18n", "nodes");
  970. foreach ($nodes as $node) {
  971. $nameSpace = $node->getAttribute("namespace");
  972. $path = AJXP_INSTALL_PATH . "/" . $node->getAttribute("path");
  973. if ($nameSpace == "") {
  974. self::updateI18nFiles($path, false, $createLanguage);
  975. self::updateI18nFiles($path . "/conf", true, $createLanguage);
  976. } else {
  977. self::updateI18nFiles($path, true, $createLanguage);
  978. self::updateI18nFiles($path . "/conf", true, $createLanguage);
  979. }
  980. }
  981. }
  982. /**
  983. * Patch the languages files of an i18n library with the references strings from the "en" file.
  984. * @static
  985. * @param $baseDir
  986. * @param bool $detectLanguages
  987. * @param string $createLanguage
  988. * @return
  989. */
  990. public static function updateI18nFiles($baseDir, $detectLanguages = true, $createLanguage = "")
  991. {
  992. if (!is_dir($baseDir) || !is_file($baseDir . "/en.php")) return;
  993. if ($createLanguage != "" && !is_file($baseDir . "/$createLanguage.php")) {
  994. @copy(AJXP_INSTALL_PATH . "/plugins/core.ajaxplorer/i18n-template.php", $baseDir . "/$createLanguage.php");
  995. }
  996. if (!$detectLanguages) {
  997. $languages = ConfService::listAvailableLanguages();
  998. $filenames = array();
  999. foreach ($languages as $key => $value) {
  1000. $filenames[] = $baseDir . "/" . $key . ".php";
  1001. }
  1002. } else {
  1003. $filenames = glob($baseDir . "/*.php");
  1004. }
  1005. include($baseDir . "/en.php");
  1006. $reference = $mess;
  1007. foreach ($filenames as $filename) {
  1008. self::updateI18nFromRef($filename, $reference);
  1009. }
  1010. }
  1011. /**
  1012. * i18n Utilitary
  1013. * @static
  1014. * @param $filename
  1015. * @param $reference
  1016. * @return
  1017. */
  1018. public static function updateI18nFromRef($filename, $reference)
  1019. {
  1020. if (!is_file($filename)) return;
  1021. include($filename);
  1022. $missing = array();
  1023. foreach ($reference as $messKey => $message) {
  1024. if (!array_key_exists($messKey, $mess)) {
  1025. $missing[] = "\"$messKey\" => \"$message\",";
  1026. }
  1027. }
  1028. //print_r($missing);
  1029. if (count($missing)) {
  1030. $header = array();
  1031. $currentMessages = array();
  1032. $footer = array();
  1033. $fileLines = file($filename);
  1034. $insideArray = false;
  1035. foreach ($fileLines as $line) {
  1036. if (strstr($line, "\"") !== false) {
  1037. $currentMessages[] = trim($line);
  1038. $insideArray = true;
  1039. } else {
  1040. if (!$insideArray && strstr($line, ");") !== false) $insideArray = true;
  1041. if (!$insideArray) {
  1042. $header[] = trim($line);
  1043. } else {
  1044. $footer[] = trim($line);
  1045. }
  1046. }
  1047. }
  1048. $currentMessages = array_merge($header, $currentMessages, $missing, $footer);
  1049. file_put_contents($filename, join("\n", $currentMessages));
  1050. }
  1051. }
  1052. /**
  1053. * Generate an HTML table for the tests results. We should use a template somewhere...
  1054. * @static
  1055. * @param $outputArray
  1056. * @param $testedParams
  1057. * @param bool $showSkipLink
  1058. * @return void
  1059. */
  1060. public static function testResultsToTable($outputArray, $testedParams, $showSkipLink = true)
  1061. {
  1062. $dumpRows = "";
  1063. $passedRows = array();
  1064. $warnRows = "";
  1065. $errRows = "";
  1066. $errs = $warns = 0;
  1067. $ALL_ROWS = array(
  1068. "error" => array(),
  1069. "warning" => array(),
  1070. "dump" => array(),
  1071. "passed" => array(),
  1072. );
  1073. $TITLES = array(
  1074. "error" => "Failed Tests",
  1075. "warning" => "Warnings",
  1076. "dump" => "Server Information",
  1077. "passed" => "Other tests passed",
  1078. );
  1079. foreach ($outputArray as $item) {
  1080. // A test is output only if it hasn't succeeded (doText returned FALSE)
  1081. $result = $item["result"] ? "passed" : ($item["level"] == "info" ? "dump" : ($item["level"] == "warning"
  1082. ? "warning" : "error"));
  1083. $success = $result == "passed";
  1084. if($result == "dump") $result = "passed";
  1085. $ALL_ROWS[$result][$item["name"]] = $item["info"];
  1086. }
  1087. include(AJXP_INSTALL_PATH."/core/tests/startup.phtml");
  1088. }
  1089. /**
  1090. * @static
  1091. * @param $outputArray
  1092. * @param $testedParams
  1093. * @return bool
  1094. */
  1095. public static function runTests(&$outputArray, &$testedParams)
  1096. {
  1097. // At first, list folder in the tests subfolder
  1098. chdir(AJXP_TESTS_FOLDER);
  1099. $files = glob('*.php');
  1100. $outputArray = array();
  1101. $testedParams = array();
  1102. $passed = true;
  1103. foreach ($files as $file) {
  1104. require_once($file);
  1105. // Then create the test class
  1106. $testName = str_replace(".php", "", substr($file, 5));
  1107. if(!class_exists($testName)) continue;
  1108. $class = new $testName();
  1109. $result = $class->doTest();
  1110. if (!$result && $class->failedLevel != "info") $passed = false;
  1111. $outputArray[] = array(
  1112. "name" => $class->name,
  1113. "result" => $result,
  1114. "level" => $class->failedLevel,
  1115. "info" => $class->failedInfo);
  1116. if (count($class->testedParams)) {
  1117. $testedParams = array_merge($testedParams, $class->testedParams);
  1118. }
  1119. }
  1120. // PREPARE REPOSITORY LISTS
  1121. $repoList = array();
  1122. require_once("../classes/class.ConfService.php");
  1123. require_once("../classes/class.Repository.php");
  1124. include(AJXP_CONF_PATH . "/bootstrap_repositories.php");
  1125. foreach ($REPOSITORIES as $index => $repo) {
  1126. $repoList[] = ConfService::createRepositoryFromArray($index, $repo);
  1127. }
  1128. // Try with the serialized repositories
  1129. if (is_file(AJXP_DATA_PATH . "/plugins/conf.serial/repo.ser")) {
  1130. $fileLines = file(AJXP_DATA_PATH . "/plugins/conf.serial/repo.ser");
  1131. $repos = unserialize($fileLines[0]);
  1132. $repoList = array_merge($repoList, $repos);
  1133. }
  1134. // NOW TRY THE PLUGIN TESTS
  1135. chdir(AJXP_INSTALL_PATH . "/" . AJXP_PLUGINS_FOLDER);
  1136. $files = glob('access.*/test.*.php');
  1137. foreach ($files as $file) {
  1138. require_once($file);
  1139. // Then create the test class
  1140. list($accessFolder, $testFileName) = explode("/", $file);
  1141. $testName = str_replace(".php", "", substr($testFileName, 5) . "Test");
  1142. $class = new $testName();
  1143. foreach ($repoList as $repository) {
  1144. if($repository->isTemplate || $repository->getParentId() != null) continue;
  1145. $result = $class->doRepositoryTest($repository);
  1146. if ($result === false || $result === true) {
  1147. if (!$result && $class->failedLevel != "info") {
  1148. $passed = false;
  1149. }
  1150. $outputArray[] = array(
  1151. "name" => $class->name . "\n Testing repository : " . $repository->getDisplay(),
  1152. "result" => $result,
  1153. "level" => $class->failedLevel,
  1154. "info" => $class->failedInfo);
  1155. if (count($class->testedParams)) {
  1156. $testedParams = array_merge($testedParams, $class->testedParams);
  1157. }
  1158. }
  1159. }
  1160. }
  1161. return $passed;
  1162. }
  1163. /**
  1164. * @static
  1165. * @param $outputArray
  1166. * @param $testedParams
  1167. * @return void
  1168. */
  1169. public static function testResultsToFile($outputArray, $testedParams)
  1170. {
  1171. ob_start();
  1172. echo '$diagResults = ';
  1173. var_export($testedParams);
  1174. echo ';';
  1175. echo '$outputArray = ';
  1176. var_export($outputArray);
  1177. echo ';';
  1178. $content = '<?php ' . ob_get_contents() . ' ?>';
  1179. ob_end_clean();
  1180. //print_r($content);
  1181. file_put_contents(TESTS_RESULT_FILE, $content);
  1182. }
  1183. public static function isStream($path)
  1184. {
  1185. $wrappers = stream_get_wrappers();
  1186. $wrappers_re = '(' . join('|', $wrappers) . ')';
  1187. return preg_match( "!^$wrappers_re://!", $path ) === 1;
  1188. }
  1189. /**
  1190. * Load an array stored serialized inside a file.
  1191. *
  1192. * @param String $filePath Full path to the file
  1193. * @param Boolean $skipCheck do not test for file existence before opening
  1194. * @return Array
  1195. */
  1196. public static function loadSerialFile($filePath, $skipCheck = false, $format="ser")
  1197. {
  1198. $filePath = AJXP_VarsFilter::filter($filePath);
  1199. $result = array();
  1200. if ($skipCheck) {
  1201. $fileLines = @file($filePath);
  1202. if ($fileLines !== false) {
  1203. if($format == "ser") $result = unserialize(implode("", $fileLines));
  1204. else if($format == "json") $result = json_decode(implode("", $fileLines), true);
  1205. }
  1206. return $result;
  1207. }
  1208. if (is_file($filePath)) {
  1209. $fileLines = file($filePath);
  1210. if($format == "ser") $result = unserialize(implode("", $fileLines));
  1211. else if($format == "json") $result = json_decode(implode("", $fileLines), true);
  1212. }
  1213. return $result;
  1214. }
  1215. /**
  1216. * Stores an Array as a serialized string inside a file.
  1217. *
  1218. * @param String $filePath Full path to the file
  1219. * @param Array|Object $value The value to store
  1220. * @param Boolean $createDir Whether to create the parent folder or not, if it does not exist.
  1221. * @param bool $silent Silently write the file, are throw an exception on problem.
  1222. * @param string $format
  1223. * @throws Exception
  1224. */
  1225. public static function saveSerialFile($filePath, $value, $createDir = true, $silent = false, $format="ser", $jsonPrettyPrint = false)
  1226. {
  1227. $filePath = AJXP_VarsFilter::filter($filePath);
  1228. if ($createDir && !is_dir(dirname($filePath))) {
  1229. @mkdir(dirname($filePath), 0755, true);
  1230. if (!is_dir(dirname($filePath))) {
  1231. // Creation failed
  1232. if($silent) return;
  1233. else throw new Exception("[AJXP_Utils::saveSerialFile] Cannot write into " . dirname(dirname($filePath)));
  1234. }
  1235. }
  1236. try {
  1237. $fp = fopen($filePath, "w");
  1238. if($format == "ser") $content = serialize($value);
  1239. else if ($format == "json") {
  1240. $content = json_encode($value);
  1241. if($jsonPrettyPrint) $content = self::prettyPrintJSON($content);
  1242. }
  1243. fwrite($fp, $content);
  1244. fclose($fp);
  1245. } catch (Exception $e) {
  1246. if ($silent) return;
  1247. else throw $e;
  1248. }
  1249. }
  1250. /**
  1251. * Detect mobile browsers
  1252. * @static
  1253. * @return bool
  1254. */
  1255. public static function userAgentIsMobile()
  1256. {
  1257. $isMobile = false;
  1258. $op = strtolower($_SERVER['HTTP_X_OPERAMINI_PHONE'] OR "");
  1259. $ua = strtolower($_SERVER['HTTP_USER_AGENT']);
  1260. $ac = strtolower($_SERVER['HTTP_ACCEPT']);
  1261. $isMobile = strpos($ac, 'application/vnd.wap.xhtml+xml') !== false
  1262. || $op != ''
  1263. || strpos($ua, 'sony') !== false
  1264. || strpos($ua, 'symbian') !== false
  1265. || strpos($ua, 'nokia') !== false
  1266. || strpos($ua, 'samsung') !== false
  1267. || strpos($ua, 'mobile') !== false
  1268. || strpos($ua, 'android') !== false
  1269. || strpos($ua, 'windows ce') !== false
  1270. || strpos($ua, 'epoc') !== false
  1271. || strpos($ua, 'opera mini') !== false
  1272. || strpos($ua, 'nitro') !== false
  1273. || strpos($ua, 'j2me') !== false
  1274. || strpos($ua, 'midp-') !== false
  1275. || strpos($ua, 'cldc-') !== false
  1276. || strpos($ua, 'netfront') !== false
  1277. || strpos($ua, 'mot') !== false
  1278. || strpos($ua, 'up.browser') !== false
  1279. || strpos($ua, 'up.link') !== false
  1280. || strpos($ua, 'audiovox') !== false
  1281. || strpos($ua, 'blackberry') !== false
  1282. || strpos($ua, 'ericsson,') !== false
  1283. || strpos($ua, 'panasonic') !== false
  1284. || strpos($ua, 'philips') !== false
  1285. || strpos($ua, 'sanyo') !== false
  1286. || strpos($ua, 'sharp') !== false
  1287. || strpos($ua, 'sie-') !== false
  1288. || strpos($ua, 'portalmmm') !== false
  1289. || strpos($ua, 'blazer') !== false
  1290. || strpos($ua, 'avantgo') !== false
  1291. || strpos($ua, 'danger') !== false
  1292. || strpos($ua, 'palm') !== false
  1293. || strpos($ua, 'series60') !== false
  1294. || strpos($ua, 'palmsource') !== false
  1295. || strpos($ua, 'pocketpc') !== false
  1296. || strpos($ua, 'smartphone') !== false
  1297. || strpos($ua, 'rover') !== false
  1298. || strpos($ua, 'ipaq') !== false
  1299. || strpos($ua, 'au-mic,') !== false
  1300. || strpos($ua, 'alcatel') !== false
  1301. || strpos($ua, 'ericy') !== false
  1302. || strpos($ua, 'up.link') !== false
  1303. || strpos($ua, 'vodafone/') !== false
  1304. || strpos($ua, 'wap1.') !== false
  1305. || strpos($ua, 'wap2.') !== false;
  1306. /*
  1307. $isBot = false;
  1308. $ip = $_SERVER['REMOTE_ADDR'];
  1309. $isBot = $ip == '66.249.65.39'
  1310. || strpos($ua, 'googlebot') !== false
  1311. || strpos($ua, 'mediapartners') !== false
  1312. || strpos($ua, 'yahooysmcm') !== false
  1313. || strpos($ua, 'baiduspider') !== false
  1314. || strpos($ua, 'msnbot') !== false
  1315. || strpos($ua, 'slurp') !== false
  1316. || strpos($ua, 'ask') !== false
  1317. || strpos($ua, 'teoma') !== false
  1318. || strpos($ua, 'spider') !== false
  1319. || strpos($ua, 'heritrix') !== false
  1320. || strpos($ua, 'attentio') !== false
  1321. || strpos($ua, 'twiceler') !== false
  1322. || strpos($ua, 'irlbot') !== false
  1323. || strpos($ua, 'fast crawler') !== false
  1324. || strpos($ua, 'fastmobilecrawl') !== false
  1325. || strpos($ua, 'jumpbot') !== false
  1326. || strpos($ua, 'googlebot-mobile') !== false
  1327. || strpos($ua, 'yahooseeker') !== false
  1328. || strpos($ua, 'motionbot') !== false
  1329. || strpos($ua, 'mediobot') !== false
  1330. || strpos($ua, 'chtml generic') !== false
  1331. || strpos($ua, 'nokia6230i/. fast crawler') !== false;
  1332. */
  1333. return $isMobile;
  1334. }
  1335. /**
  1336. * Detect iOS browser
  1337. * @static
  1338. * @return bool
  1339. */
  1340. public static function userAgentIsIOS()
  1341. {
  1342. if (stripos($_SERVER["HTTP_USER_AGENT"], "iphone") !== false) return true;
  1343. if (stripos($_SERVER["HTTP_USER_AGENT"], "ipad") !== false) return true;
  1344. if (stripos($_SERVER["HTTP_USER_AGENT"], "ipod") !== false) return true;
  1345. return false;
  1346. }
  1347. /**
  1348. * Detect Android UA
  1349. * @static
  1350. * @return bool
  1351. */
  1352. public static function userAgentIsAndroid()
  1353. {
  1354. return (stripos($_SERVER["HTTP_USER_AGENT"], "android") !== false);
  1355. }
  1356. /**
  1357. * Try to remove a file without errors
  1358. * @static
  1359. * @param $file
  1360. * @return void
  1361. */
  1362. public static function silentUnlink($file)
  1363. {
  1364. @unlink($file);
  1365. }
  1366. /**
  1367. * Try to set an ini config, without errors
  1368. * @static
  1369. * @param string $paramName
  1370. * @param string $paramValue
  1371. * @return void
  1372. */
  1373. public static function safeIniSet($paramName, $paramValue)
  1374. {
  1375. $current = ini_get($paramName);
  1376. if ($current == $paramValue) return;
  1377. @ini_set($paramName, $paramValue);
  1378. }
  1379. /**
  1380. * Parse URL ignoring # and ?
  1381. * @param $path
  1382. * @return array
  1383. */
  1384. public static function safeParseUrl($path)
  1385. {
  1386. $parts = parse_url(str_replace(array("#", "?"), array("__AJXP_FRAGMENT__", "__AJXP_MARK__"), $path));
  1387. $parts["path"]= str_replace(array("__AJXP_FRAGMENT__", "__AJXP_MARK__"), array("#", "?"), $parts["path"]);
  1388. return $parts;
  1389. }
  1390. /**
  1391. * @static
  1392. * @param string $url
  1393. * @return bool|mixed|string
  1394. */
  1395. public static function getRemoteContent($url)
  1396. {
  1397. if (ini_get("allow_url_fopen")) {
  1398. return file_get_contents($url);
  1399. } else if (function_exists("curl_init")) {
  1400. $ch = curl_init();
  1401. $timeout = 30; // set to zero for no timeout
  1402. curl_setopt ($ch, CURLOPT_URL, $url);
  1403. curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
  1404. curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
  1405. $return = curl_exec($ch);
  1406. curl_close($ch);
  1407. return $return;
  1408. } else {
  1409. $i = parse_url($url);
  1410. $httpClient = new HttpClient($i["host"]);
  1411. $httpClient->timeout = 30;
  1412. return $httpClient->quickGet($url);
  1413. }
  1414. }
  1415. public static function parseStandardFormParameters(&$repDef, &$options, $userId = null, $prefix = "DRIVER_OPTION_", $binariesContext = null)
  1416. {
  1417. if ($binariesContext === null) {
  1418. $binariesContext = array("USER" => (AuthService::getLoggedUser()!= null)?AuthService::getLoggedUser()->getId():"shared");
  1419. }
  1420. $replicationGroups = array();
  1421. $switchesGroups = array();
  1422. foreach ($repDef as $key => $value) {
  1423. if( ( ( !empty($prefix) && strpos($key, $prefix)!== false && strpos($key, $prefix)==0 ) || empty($prefix) )
  1424. && strpos($key, "ajxptype") === false
  1425. && strpos($key, "_original_binary") === false
  1426. && strpos($key, "_replication") === false
  1427. && strpos($key, "_checkbox") === false){
  1428. if (isSet($repDef[$key."_ajxptype"])) {
  1429. $type = $repDef[$key."_ajxptype"];
  1430. if ($type == "boolean") {
  1431. $value = ($value == "true"?true:false);
  1432. } else if ($type == "integer") {
  1433. $value = intval($value);
  1434. } else if ($type == "array") {
  1435. $value = explode(",", $value);
  1436. } else if ($type == "password" && $userId!=null) {
  1437. if (trim($value) != "" && function_exists('mcrypt_encrypt')) {
  1438. // We encode as base64 so if we need to store the result in a database, it can be stored in text column
  1439. $value = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($userId."\1CDAFx¨op#"), $value, MCRYPT_MODE_ECB));
  1440. }
  1441. } else if ($type == "binary" && $binariesContext !== null) {
  1442. if (!empty($value)) {
  1443. if ($value == "ajxp-remove-original") {
  1444. if (!empty($repDef[$key."_original_binary"])) {
  1445. ConfService::getConfStorageImpl()->deleteBinary($binariesContext, $repDef[$key."_original_binary"]);
  1446. }
  1447. $value = "";
  1448. } else {
  1449. $file = AJXP_Utils::getAjxpTmpDir()."/".$value;
  1450. if (file_exists($file)) {
  1451. $id= !empty($repDef[$key."_original_binary"]) ? $repDef[$key."_original_binary"] : null;
  1452. $id=ConfService::getConfStorageImpl()->saveBinary($binariesContext, $file, $id);
  1453. $value = $id;
  1454. }
  1455. }
  1456. } else if (!empty($repDef[$key."_original_binary"])) {
  1457. $value = $repDef[$key."_original_binary"];
  1458. }
  1459. } else if (strpos($type,"group_switch:") === 0) {
  1460. $tmp = explode(":", $type);
  1461. $gSwitchName = $tmp[1];
  1462. $switchesGroups[substr($key, strlen($prefix))] = $gSwitchName;
  1463. } else if ($type == "text/json") {
  1464. $value = json_decode($value, true);
  1465. }
  1466. if (!in_array($type, array("textarea", "boolean", "text/json"))) {
  1467. $value = AJXP_Utils::sanitize($value, AJXP_SANITIZE_HTML);
  1468. }
  1469. unset($repDef[$key."_ajxptype"]);
  1470. }
  1471. if (isSet($repDef[$key."_checkbox"])) {
  1472. $checked = $repDef[$key."_checkbox"] == "checked";
  1473. unset($repDef[$key."_checkbox"]);
  1474. if(!$checked) continue;
  1475. }
  1476. if (isSet($repDef[$key."_replication"])) {
  1477. $repKey = $repDef[$key."_replication"];
  1478. if(!is_array($replicationGroups[$repKey])) $replicationGroups[$repKey] = array();
  1479. $replicationGroups[$repKey][] = $key;
  1480. }
  1481. $options[substr($key, strlen($prefix))] = $value;
  1482. unset($repDef[$key]);
  1483. } else {
  1484. if ($key == "DISPLAY") {
  1485. $value = SystemTextEncoding::fromUTF8(AJXP_Utils::securePath($value));
  1486. }
  1487. $repDef[$key] = $value;
  1488. }
  1489. }
  1490. // DO SOMETHING WITH REPLICATED PARAMETERS?
  1491. if (count($switchesGroups)) {
  1492. foreach ($switchesGroups as $fieldName => $groupName) {
  1493. if (isSet($options[$fieldName])) {
  1494. $gValues = array();
  1495. $radic = $groupName."_".$options[$fieldName]."_";
  1496. foreach ($options as $optN => $optV) {
  1497. if (strpos($optN, $radic) === 0) {
  1498. $newName = substr($optN, strlen($radic));
  1499. $gValues[$newName] = $optV;
  1500. }
  1501. }
  1502. }
  1503. $options[$fieldName."_group_switch"] = $options[$fieldName];
  1504. $options[$fieldName] = $gValues;
  1505. }
  1506. }
  1507. }
  1508. public static function cleanDibiDriverParameters($params)
  1509. {
  1510. if(!is_array($params)) return $params;
  1511. $value = $params["group_switch_value"];
  1512. if (isSet($value)) {
  1513. if ($value == "core") {
  1514. $bootStorage = ConfService::getBootConfStorageImpl();
  1515. $configs = $bootStorage->loadPluginConfig("core", "conf");
  1516. $params = $configs["DIBI_PRECONFIGURATION"];
  1517. if (!is_array($params)) {
  1518. throw new Exception("Empty SQL default connexion, there is something wrong with your setup! You may have switch to an SQL-based plugin without defining a connexion.");
  1519. }
  1520. } else {
  1521. unset($params["group_switch_value"]);
  1522. }
  1523. foreach ($params as $k => $v) {
  1524. $params[array_pop(explode("_", $k, 2))] = AJXP_VarsFilter::filter($v);
  1525. unset($params[$k]);
  1526. }
  1527. }
  1528. switch ($params["driver"]) {
  1529. case "sqlite":
  1530. case "sqlite3":
  1531. $params["formatDateTime"] = "'Y-m-d H:i:s'";
  1532. $params["formatDate"] = "'Y-m-d'";
  1533. break;
  1534. }
  1535. return $params;
  1536. }
  1537. public static function runCreateTablesQuery($p, $file)
  1538. {
  1539. require_once(AJXP_BIN_FOLDER."/dibi.compact.php");
  1540. switch ($p["driver"]) {
  1541. case "sqlite":
  1542. case "sqlite3":
  1543. if (!file_exists(dirname($p["database"]))) {
  1544. @mkdir(dirname($p["database"]), 0755, true);
  1545. }
  1546. $ext = ".sqlite";
  1547. break;
  1548. case "mysql":
  1549. $ext = ".mysql";
  1550. break;
  1551. case "postgre":
  1552. $ext = ".pgsql";
  1553. break;
  1554. default:
  1555. return "ERROR!, DB driver "+ $p["driver"] +" not supported yet in __FUNCTION__";
  1556. }
  1557. $result = array();
  1558. $file = dirname($file) ."/". str_replace(".sql", $ext, basename($file) );
  1559. $sql = file_get_contents($file);
  1560. $parts = explode(";", $sql);
  1561. $remove = array();
  1562. for ($i = 0 ; $i < count($parts); $i++) {
  1563. $part = $parts[$i];
  1564. if (strpos($part, "BEGIN") && isSet($parts[$i+1])) {
  1565. $parts[$i] .= ';'.$parts[$i+1];
  1566. $remove[] = $i+1;
  1567. }
  1568. }
  1569. foreach($remove as $rk) unset($parts[$rk]);
  1570. dibi::connect($p);
  1571. dibi::begin();
  1572. foreach ($parts as $createPart) {
  1573. $sqlPart = trim($createPart);
  1574. if (empty($sqlPart)) continue;
  1575. try {
  1576. dibi::nativeQuery($sqlPart);
  1577. $resKey = str_replace("\n", "", substr($sqlPart, 0, 50))."...";
  1578. $result[] = "OK: $resKey executed successfully";
  1579. } catch (DibiException $e) {
  1580. $result[] = "ERROR! $sqlPart failed";
  1581. }
  1582. }
  1583. dibi::commit();
  1584. dibi::disconnect();
  1585. $message = implode("\n", $result);
  1586. if (strpos($message, "ERROR!")) return $message;
  1587. else return "SUCCESS:".$message;
  1588. }
  1589. /*
  1590. * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
  1591. * $algorithm - The hash algorithm to use. Recommended: SHA256
  1592. * $password - The password.
  1593. * $salt - A salt that is unique to the password.
  1594. * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
  1595. * $key_length - The length of the derived key in bytes.
  1596. * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
  1597. * Returns: A $key_length-byte key derived from the password and salt.
  1598. *
  1599. * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
  1600. *
  1601. * This implementation of PBKDF2 was originally created by https://defuse.ca
  1602. * With improvements by http://www.variations-of-shadow.com
  1603. */
  1604. public static function pbkdf2_apply($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
  1605. {
  1606. $algorithm = strtolower($algorithm);
  1607. if(!in_array($algorithm, hash_algos(), true))
  1608. die('PBKDF2 ERROR: Invalid hash algorithm.');
  1609. if($count <= 0 || $key_length <= 0)
  1610. die('PBKDF2 ERROR: Invalid parameters.');
  1611. $hash_length = strlen(hash($algorithm, "", true));
  1612. $block_count = ceil($key_length / $hash_length);
  1613. $output = "";
  1614. for ($i = 1; $i <= $block_count; $i++) {
  1615. // $i encoded as 4 bytes, big endian.
  1616. $last = $salt . pack("N", $i);
  1617. // first iteration
  1618. $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
  1619. // perform the other $count - 1 iterations
  1620. for ($j = 1; $j < $count; $j++) {
  1621. $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
  1622. }
  1623. $output .= $xorsum;
  1624. }
  1625. if($raw_output)
  1626. return substr($output, 0, $key_length);
  1627. else
  1628. return bin2hex(substr($output, 0, $key_length));
  1629. }
  1630. // Compares two strings $a and $b in length-constant time.
  1631. public static function pbkdf2_slow_equals($a, $b)
  1632. {
  1633. $diff = strlen($a) ^ strlen($b);
  1634. for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
  1635. $diff |= ord($a[$i]) ^ ord($b[$i]);
  1636. }
  1637. return $diff === 0;
  1638. }
  1639. public static function pbkdf2_validate_password($password, $correct_hash)
  1640. {
  1641. $params = explode(":", $correct_hash);
  1642. if (count($params) < HASH_SECTIONS) {
  1643. if (strlen($correct_hash) == 32 && count($params) == 1) {
  1644. return md5($password) == $correct_hash;
  1645. }
  1646. return false;
  1647. }
  1648. $pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
  1649. return self::pbkdf2_slow_equals(
  1650. $pbkdf2,
  1651. self::pbkdf2_apply(
  1652. $params[HASH_ALGORITHM_INDEX],
  1653. $password,
  1654. $params[HASH_SALT_INDEX],
  1655. (int) $params[HASH_ITERATION_INDEX],
  1656. strlen($pbkdf2),
  1657. true
  1658. )
  1659. );
  1660. }
  1661. public static function pbkdf2_create_hash($password)
  1662. {
  1663. // format: algorithm:iterations:salt:hash
  1664. $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM));
  1665. return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" .
  1666. base64_encode(self::pbkdf2_apply(
  1667. PBKDF2_HASH_ALGORITHM,
  1668. $password,
  1669. $salt,
  1670. PBKDF2_ITERATIONS,
  1671. PBKDF2_HASH_BYTE_SIZE,
  1672. true
  1673. ));
  1674. }
  1675. /**
  1676. * generates a random password, uses base64: 0-9a-zA-Z
  1677. * @param int [optional] $length length of password, default 24 (144 Bit)
  1678. * @return string password
  1679. */
  1680. public static function generateRandomString($length = 24)
  1681. {
  1682. if (function_exists('openssl_random_pseudo_bytes') && USE_OPENSSL_RANDOM) {
  1683. $password = base64_encode(openssl_random_pseudo_bytes($length, $strong));
  1684. if($strong == TRUE)
  1685. return substr(str_replace(array("/","+"), "", $password), 0, $length); //base64 is about 33% longer, so we need to truncate the result
  1686. }
  1687. //fallback to mt_rand if php < 5.3 or no openssl available
  1688. $characters = '0123456789';
  1689. $characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  1690. $charactersLength = strlen($characters)-1;
  1691. $password = '';
  1692. //select some random characters
  1693. for ($i = 0; $i < $length; $i++) {
  1694. $password .= $characters[mt_rand(0, $charactersLength)];
  1695. }
  1696. return $password;
  1697. }
  1698. // Does not support flag GLOB_BRACE
  1699. public static function glob_recursive($pattern, $flags = 0)
  1700. {
  1701. $files = glob($pattern, $flags);
  1702. foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
  1703. $files = array_merge($files, self::glob_recursive($dir.'/'.basename($pattern), $flags));
  1704. }
  1705. return $files;
  1706. }
  1707. }