PageRenderTime 62ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/com/misc.php

http://github.com/unirgy/buckyball
PHP | 3899 lines | 3041 code | 219 blank | 639 comment | 297 complexity | c856600b7d0b0471aede00c3723d951b MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Copyright 2011 Unirgy LLC
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. * @package BuckyBall
  18. * @link http://github.com/unirgy/buckyball
  19. * @author Boris Gurvich <boris@unirgy.com>
  20. * @copyright (c) 2010-2012 Boris Gurvich
  21. * @license http://www.apache.org/licenses/LICENSE-2.0.html
  22. */
  23. /**
  24. * Utility class to parse and construct strings and data structures
  25. */
  26. class BUtil extends BClass
  27. {
  28. /**
  29. * IV for mcrypt operations
  30. *
  31. * @var string
  32. */
  33. protected static $_mcryptIV;
  34. /**
  35. * Encryption key from configuration (encrypt/key)
  36. *
  37. * @var string
  38. */
  39. protected static $_mcryptKey;
  40. /**
  41. * Default hash algorithm
  42. *
  43. * @var string default sha512 for strength and slowness
  44. */
  45. protected static $_hashAlgo = 'bcrypt';
  46. /**
  47. * Default number of hash iterations
  48. *
  49. * @var int
  50. */
  51. protected static $_hashIter = 3;
  52. /**
  53. * Default full hash string separator
  54. *
  55. * @var string
  56. */
  57. protected static $_hashSep = '$';
  58. /**
  59. * Default character pool for random and sequence strings
  60. *
  61. * Chars "c", "C" are ommited to avoid accidental obscene language
  62. * Chars "0", "1", "I" are removed to avoid leading 0 and ambiguity in print
  63. *
  64. * @var string
  65. */
  66. protected static $_defaultCharPool = '23456789abdefghijklmnopqrstuvwxyzABDEFGHJKLMNOPQRSTUVWXYZ';
  67. /**
  68. * Shortcut to help with IDE autocompletion
  69. *
  70. * @return BUtil
  71. */
  72. public static function i($new=false, array $args=array())
  73. {
  74. return BClassRegistry::i()->instance(__CLASS__, $args, !$new);
  75. }
  76. /**
  77. * Convert any data to JSON string
  78. *
  79. * $data can be BData instance, or array of BModel objects, will be automatically converted to array
  80. *
  81. * @param mixed $data
  82. * @return string
  83. */
  84. public static function toJson($data)
  85. {
  86. if (is_array($data) && is_object(current($data)) && current($data) instanceof BModel) {
  87. $data = BDb::many_as_array($data);
  88. } elseif (is_object($data) && $data instanceof BData) {
  89. $data = $data->as_array(true);
  90. }
  91. return json_encode($data);
  92. }
  93. /**
  94. * Parse JSON into PHP data
  95. *
  96. * @param string $json
  97. * @param bool $asObject if false will attempt to convert to array,
  98. * otherwise standard combination of objects and arrays
  99. */
  100. public static function fromJson($json, $asObject=false)
  101. {
  102. $obj = json_decode($json);
  103. return $asObject ? $obj : static::objectToArray($obj);
  104. }
  105. /**
  106. * Indents a flat JSON string to make it more human-readable.
  107. *
  108. * @param string $json The original JSON string to process.
  109. *
  110. * @return string Indented version of the original JSON string.
  111. */
  112. public static function jsonIndent($json)
  113. {
  114. $result = '';
  115. $pos = 0;
  116. $strLen = strlen($json);
  117. $indentStr = ' ';
  118. $newLine = "\n";
  119. $prevChar = '';
  120. $outOfQuotes = true;
  121. for ($i=0; $i<=$strLen; $i++) {
  122. // Grab the next character in the string.
  123. $char = substr($json, $i, 1);
  124. // Are we inside a quoted string?
  125. if ($char == '"' && $prevChar != '\\') {
  126. $outOfQuotes = !$outOfQuotes;
  127. // If this character is the end of an element,
  128. // output a new line and indent the next line.
  129. } else if(($char == '}' || $char == ']') && $outOfQuotes) {
  130. $result .= $newLine;
  131. $pos --;
  132. for ($j=0; $j<$pos; $j++) {
  133. $result .= $indentStr;
  134. }
  135. }
  136. // Add the character to the result string.
  137. $result .= $char;
  138. // If the last character was the beginning of an element,
  139. // output a new line and indent the next line.
  140. if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
  141. $result .= $newLine;
  142. if ($char == '{' || $char == '[') {
  143. $pos ++;
  144. }
  145. for ($j = 0; $j < $pos; $j++) {
  146. $result .= $indentStr;
  147. }
  148. }
  149. $prevChar = $char;
  150. }
  151. return $result;
  152. }
  153. /**
  154. * Convert data to JavaScript string
  155. *
  156. * Notable difference from toJson: allows raw function callbacks
  157. *
  158. * @param mixed $val
  159. * @return string
  160. */
  161. public static function toJavaScript($val)
  162. {
  163. if (is_null($val)) {
  164. return 'null';
  165. } elseif (is_bool($val)) {
  166. return $val ? 'true' : 'false';
  167. } elseif (is_string($val)) {
  168. if (preg_match('#^\s*function\s*\(#', $val)) {
  169. return $val;
  170. } else {
  171. return "'".addslashes($val)."'";
  172. }
  173. } elseif (is_int($val) || is_float($val)) {
  174. return $val;
  175. } elseif ($val instanceof BValue) {
  176. return $val->toPlain();
  177. } elseif (($isObj = is_object($val)) || is_array($val)) {
  178. $out = array();
  179. if (!empty($val) && ($isObj || array_keys($val) !== range(0, count($val)-1))) { // assoc?
  180. foreach ($val as $k=>$v) {
  181. $out[] = "'".addslashes($k)."':".static::toJavaScript($v);
  182. }
  183. return '{'.join(',', $out).'}';
  184. } else {
  185. foreach ($val as $k=>$v) {
  186. $out[] = static::toJavaScript($v);
  187. }
  188. return '['.join(',', $out).']';
  189. }
  190. }
  191. return '"UNSUPPORTED TYPE"';
  192. }
  193. public static function toRss($data)
  194. {
  195. $lang = !empty($data['language']) ? $data['language'] : 'en-us';
  196. $ttl = !empty($data['ttl']) ? (int)$data['ttl'] : 40;
  197. $descr = !empty($data['description']) ? $data['description'] : $data['title'];
  198. $xml = '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel>'
  199. .'<title><![CDATA['.$data['title'].']]></title><link><![CDATA['.$data['link'].']]></link>'
  200. .'<description><![CDATA['.$descr.']]></description><language><![CDATA['.$lang.']]></language><ttl>'.$ttl.'</ttl>';
  201. foreach ($data['items'] as $item) {
  202. if (!is_numeric($item['pubDate'])) {
  203. $item['pubDate'] = strtotime($item['pubDate']);
  204. }
  205. if (empty($item['guid'])) {
  206. $item['guid'] = $item['link'];
  207. }
  208. $xml .= '<item><title><![CDATA['.$item['title'].']]></title>'
  209. .'<description><![CDATA['.$item['description'].']]></description>'
  210. .'<pubDate>'.date('r', $item['pubDate']).'</pubDate>'
  211. .'<guid><![CDATA['.$item['guid'].']]></guid><link><![CDATA['.$item['link'].']]></link></item>';
  212. }
  213. $xml .= '</channel></rss>';
  214. return $xml;
  215. }
  216. /**
  217. * Convert object to array recursively
  218. *
  219. * @param object $d
  220. * @return array
  221. */
  222. public static function objectToArray($d)
  223. {
  224. if (is_object($d)) {
  225. $d = get_object_vars($d);
  226. }
  227. if (is_array($d)) {
  228. return array_map('BUtil::objectToArray', $d);
  229. }
  230. return $d;
  231. }
  232. /**
  233. * Convert array to object
  234. *
  235. * @param mixed $d
  236. * @return object
  237. */
  238. public static function arrayToObject($d)
  239. {
  240. if (is_array($d)) {
  241. return (object) array_map('BUtil::objectToArray', $d);
  242. }
  243. return $d;
  244. }
  245. /**
  246. * version of sprintf for cases where named arguments are desired (php syntax)
  247. *
  248. * with sprintf: sprintf('second: %2$s ; first: %1$s', '1st', '2nd');
  249. *
  250. * with sprintfn: sprintfn('second: %second$s ; first: %first$s', array(
  251. * 'first' => '1st',
  252. * 'second'=> '2nd'
  253. * ));
  254. *
  255. * @see http://www.php.net/manual/en/function.sprintf.php#94608
  256. * @param string $format sprintf format string, with any number of named arguments
  257. * @param array $args array of [ 'arg_name' => 'arg value', ... ] replacements to be made
  258. * @return string|false result of sprintf call, or bool false on error
  259. */
  260. public static function sprintfn($format, $args = array())
  261. {
  262. $args = (array)$args;
  263. // map of argument names to their corresponding sprintf numeric argument value
  264. $arg_nums = array_slice(array_flip(array_keys(array(0 => 0) + $args)), 1);
  265. // find the next named argument. each search starts at the end of the previous replacement.
  266. for ($pos = 0; preg_match('/(?<=%)([a-zA-Z_]\w*)(?=\$)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
  267. $arg_pos = $match[0][1];
  268. $arg_len = strlen($match[0][0]);
  269. $arg_key = $match[1][0];
  270. // programmer did not supply a value for the named argument found in the format string
  271. if (! array_key_exists($arg_key, $arg_nums)) {
  272. user_error("sprintfn(): Missing argument '${arg_key}'", E_USER_WARNING);
  273. return false;
  274. }
  275. // replace the named argument with the corresponding numeric one
  276. $format = substr_replace($format, $replace = $arg_nums[$arg_key], $arg_pos, $arg_len);
  277. $pos = $arg_pos + strlen($replace); // skip to end of replacement for next iteration
  278. }
  279. if (!$args) {
  280. $args = array('');
  281. }
  282. return vsprintf($format, array_values($args));
  283. }
  284. /**
  285. * Inject vars into string template
  286. *
  287. * Ex: echo BUtil::injectVars('One :two :three', array('two'=>2, 'three'=>3))
  288. * Result: "One 2 3"
  289. *
  290. * @param string $str
  291. * @param array $vars
  292. * @return string
  293. */
  294. public static function injectVars($str, $vars)
  295. {
  296. $from = array(); $to = array();
  297. foreach ($vars as $k=>$v) {
  298. $from[] = ':'.$k;
  299. $to[] = $v;
  300. }
  301. return str_replace($from, $to, $str);
  302. }
  303. /**
  304. * Merges any number of arrays / parameters recursively, replacing
  305. * entries with string keys with values from latter arrays.
  306. * If the entry or the next value to be assigned is an array, then it
  307. * automagically treats both arguments as an array.
  308. * Numeric entries are appended, not replaced, but only if they are
  309. * unique
  310. *
  311. * calling: result = BUtil::arrayMerge(a1, a2, ... aN)
  312. *
  313. * @param array $array1
  314. * @param array $array2...
  315. * @return array
  316. **/
  317. public static function arrayMerge() {
  318. $arrays = func_get_args();
  319. $base = array_shift($arrays);
  320. if (!is_array($base)) {
  321. $base = empty($base) ? array() : array($base);
  322. }
  323. foreach ($arrays as $append) {
  324. if (!is_array($append)) {
  325. $append = array($append);
  326. }
  327. foreach ($append as $key => $value) {
  328. if (is_numeric($key)) {
  329. if (!in_array($value, $base)) {
  330. $base[] = $value;
  331. }
  332. } elseif (!array_key_exists($key, $base)) {
  333. $base[$key] = $value;
  334. } elseif (is_array($value) && is_array($base[$key])) {
  335. $base[$key] = static::arrayMerge($base[$key], $append[$key]);
  336. } else {
  337. $base[$key] = $value;
  338. }
  339. }
  340. }
  341. return $base;
  342. }
  343. /**
  344. * Compare 2 arrays recursively
  345. *
  346. * @param array $array1
  347. * @param array $array2
  348. */
  349. public static function arrayCompare(array $array1, array $array2)
  350. {
  351. $diff = false;
  352. // Left-to-right
  353. foreach ($array1 as $key => $value) {
  354. if (!array_key_exists($key,$array2)) {
  355. $diff[0][$key] = $value;
  356. } elseif (is_array($value)) {
  357. if (!is_array($array2[$key])) {
  358. $diff[0][$key] = $value;
  359. $diff[1][$key] = $array2[$key];
  360. } else {
  361. $new = static::arrayCompare($value, $array2[$key]);
  362. if ($new !== false) {
  363. if (isset($new[0])) $diff[0][$key] = $new[0];
  364. if (isset($new[1])) $diff[1][$key] = $new[1];
  365. }
  366. }
  367. } elseif ($array2[$key] !== $value) {
  368. $diff[0][$key] = $value;
  369. $diff[1][$key] = $array2[$key];
  370. }
  371. }
  372. // Right-to-left
  373. foreach ($array2 as $key => $value) {
  374. if (!array_key_exists($key,$array1)) {
  375. $diff[1][$key] = $value;
  376. }
  377. // No direct comparsion because matching keys were compared in the
  378. // left-to-right loop earlier, recursively.
  379. }
  380. return $diff;
  381. }
  382. /**
  383. * Walk over array of objects and perform method or callback on each row
  384. *
  385. * @param array $arr
  386. * @param callback $cb
  387. * @param array $args
  388. * @param boolean $ignoreExceptions
  389. * @return array
  390. */
  391. static public function arrayWalk($arr, $cb, $args=array(), $ignoreExceptions=false)
  392. {
  393. $result = array();
  394. foreach ($arr as $i=>$r) {
  395. $callback = is_string($cb) && $cb[0]==='.' ? array($r, substr($cb, 1)) : $cb;
  396. if ($ignoreExceptions) {
  397. try {
  398. $result[] = call_user_func_array($callback, $args);
  399. } catch (Exception $e) {
  400. BDebug::warning('EXCEPTION class('.get_class($r).') arrayWalk('.$i.'): '.$e->getMessage());
  401. }
  402. } else {
  403. $result[] = call_user_func_array($callback, $args);
  404. }
  405. }
  406. return $result;
  407. }
  408. /**
  409. * Clean array of ints from empty and non-numeric values
  410. *
  411. * If parameter is a string, splits by comma
  412. *
  413. * @param array|string $arr
  414. * @return array
  415. */
  416. static public function arrayCleanInt($arr)
  417. {
  418. $res = array();
  419. if (is_string($arr)) {
  420. $arr = explode(',', $arr);
  421. }
  422. if (is_array($arr)) {
  423. foreach ($arr as $k=>$v) {
  424. if (is_numeric($v)) {
  425. $res[$k] = intval($v);
  426. }
  427. }
  428. }
  429. return $res;
  430. }
  431. /**
  432. * Insert 1 or more items into array at specific position
  433. *
  434. * Note: code repetition is for better iteration performance
  435. *
  436. * @param array $array The original container array
  437. * @param array $items Items to be inserted
  438. * @param string $where
  439. * - start
  440. * - end
  441. * - offset==$key
  442. * - key.(before|after)==$key
  443. * - obj.(before|after).$object_property==$key
  444. * - arr.(before|after).$item_array_key==$key
  445. * @return array resulting array
  446. */
  447. static public function arrayInsert($array, $items, $where)
  448. {
  449. $result = array();
  450. $w1 = explode('==', $where, 2);
  451. $w2 = explode('.', $w1[0], 3);
  452. switch ($w2[0]) {
  453. case 'start':
  454. $result = array_merge($items, $array);
  455. break;
  456. case 'end':
  457. $result = array_merge($array, $items);
  458. break;
  459. case 'offset': // for associative only
  460. $key = $w1[1];
  461. $i = 0;
  462. foreach ($array as $k=>$v) {
  463. if ($key===$i++) {
  464. foreach ($items as $k1=>$v1) {
  465. $result[$k1] = $v1;
  466. }
  467. }
  468. $result[$k] = $v;
  469. }
  470. break;
  471. case 'key': // for associative only
  472. $rel = $w2[1];
  473. $key = $w1[1];
  474. foreach ($array as $k=>$v) {
  475. if ($key===$k) {
  476. if ($rel==='after') {
  477. $result[$k] = $v;
  478. }
  479. foreach ($items as $k1=>$v1) {
  480. $result[$k1] = $v1;
  481. }
  482. if ($rel==='before') {
  483. $result[$k] = $v;
  484. }
  485. } else {
  486. $result[$k] = $v;
  487. }
  488. }
  489. break;
  490. case 'obj':
  491. $rel = $w2[1];
  492. $f = $w2[2];
  493. $key = $w1[1];
  494. foreach ($array as $k=>$v) {
  495. if ($key===$v->$f) {
  496. if ($rel==='after') {
  497. $result[$k] = $v;
  498. }
  499. foreach ($items as $k1=>$v1) {
  500. $result[$k1] = $v1;
  501. }
  502. if ($rel==='before') {
  503. $result[$k] = $v;
  504. }
  505. } else {
  506. $result[$k] = $v;
  507. }
  508. }
  509. break;
  510. case 'arr':
  511. $rel = $w2[1];
  512. $f = $w2[2];
  513. $key = $w1[1];
  514. foreach ($array as $k=>$v) {
  515. if ($key===$v[$f]) {
  516. if ($rel==='after') {
  517. $result[$k] = $v;
  518. }
  519. foreach ($items as $k1=>$v1) {
  520. $result[$k1] = $v1;
  521. }
  522. if ($rel==='before') {
  523. $result[$k] = $v;
  524. }
  525. } else {
  526. $result[$k] = $v;
  527. }
  528. }
  529. break;
  530. default: BDebug::error('Invalid where condition: '.$where);
  531. }
  532. return $result;
  533. }
  534. /**
  535. * Return only specific fields from source array
  536. *
  537. * @param array $source
  538. * @param array|string $fields
  539. * @param boolean $inverse if true, will return anything NOT in $fields
  540. * @param boolean $setNulls fill missing fields with nulls
  541. * @result array
  542. */
  543. static public function arrayMask(array $source, $fields, $inverse=false, $setNulls=true)
  544. {
  545. if (is_string($fields)) {
  546. $fields = explode(',', $fields);
  547. array_walk($fields, 'trim');
  548. }
  549. $result = array();
  550. if (!$inverse) {
  551. foreach ($fields as $k) {
  552. if (isset($source[$k])) {
  553. $result[$k] = $source[$k];
  554. } elseif ($setNulls) {
  555. $result[$k] = null;
  556. }
  557. }
  558. } else {
  559. foreach ($source as $k=>$v) {
  560. if (!in_array($k, $fields)) $result[$k] = $v;
  561. }
  562. }
  563. return $result;
  564. }
  565. static public function arrayToOptions($source, $labelField, $keyField=null, $emptyLabel=null)
  566. {
  567. $options = array();
  568. if (!is_null($emptyLabel)) {
  569. $options = array("" => $emptyLabel);
  570. }
  571. if (empty($source)) {
  572. return array();
  573. }
  574. $isObject = is_object(current($source));
  575. foreach ($source as $k=>$item) {
  576. if ($isObject) {
  577. $key = is_null($keyField) ? $k : $item->$keyField;
  578. $label = $labelField[0]==='.' ? $item->{substr($labelField, 1)}() : $item->labelField;
  579. $options[$key] = $label;
  580. } else {
  581. $key = is_null($keyField) ? $k : $item[$keyField];
  582. $options[$key] = $item[$labelField];
  583. }
  584. }
  585. return $options;
  586. }
  587. static public function arrayMakeAssoc($source, $keyField)
  588. {
  589. $isObject = is_object(current($source));
  590. $assocArray = array();
  591. foreach ($source as $k => $item) {
  592. if ($isObject) {
  593. $assocArray[$item->$keyField] = $item;
  594. } else {
  595. $assocArray[$item[$keyField]] = $item;
  596. }
  597. }
  598. return $assocArray;
  599. }
  600. /**
  601. * Create IV for mcrypt operations
  602. *
  603. * @return string
  604. */
  605. static public function mcryptIV()
  606. {
  607. if (!static::$_mcryptIV) {
  608. static::$_mcryptIV = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_DEV_URANDOM);
  609. }
  610. return static::$_mcryptIV;
  611. }
  612. /**
  613. * Fetch default encryption key from config
  614. *
  615. * @return string
  616. */
  617. static public function mcryptKey($key=null, $configPath=null)
  618. {
  619. if (!is_null($key)) {
  620. static::$_mcryptKey = $key;
  621. } elseif (is_null(static::$_mcryptKey) && $configPath) {
  622. static::$_mcryptKey = BConfig::i()->get($configPath);
  623. }
  624. return static::$_mcryptKey;
  625. }
  626. /**
  627. * Encrypt using AES256
  628. *
  629. * Requires PHP extension mcrypt
  630. *
  631. * @param string $value
  632. * @param string $key
  633. * @param boolean $base64
  634. * @return string
  635. */
  636. static public function encrypt($value, $key=null, $base64=true)
  637. {
  638. if (is_null($key)) $key = static::mcryptKey();
  639. $enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $value, MCRYPT_MODE_ECB, static::mcryptIV());
  640. return $base64 ? trim(base64_encode($enc)) : $enc;
  641. }
  642. /**
  643. * Decrypt using AES256
  644. *
  645. * Requires PHP extension mcrypt
  646. *
  647. * @param string $value
  648. * @param string $key
  649. * @param boolean $base64
  650. * @return string
  651. */
  652. static public function decrypt($value, $key=null, $base64=true)
  653. {
  654. if (is_null($key)) $key = static::mcryptKey();
  655. $enc = $base64 ? base64_decode($value) : $value;
  656. return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $enc, MCRYPT_MODE_ECB, static::mcryptIV()));
  657. }
  658. /**
  659. * Generate random string
  660. *
  661. * @param int $strLen length of resulting string
  662. * @param string $chars allowed characters to be used
  663. */
  664. public static function randomString($strLen=8, $chars=null)
  665. {
  666. if (is_null($chars)) {
  667. $chars = static::$_defaultCharPool;
  668. }
  669. $charsLen = strlen($chars)-1;
  670. $str = '';
  671. for ($i=0; $i<$strLen; $i++) {
  672. $str .= $chars[mt_rand(0, $charsLen)];
  673. }
  674. return $str;
  675. }
  676. /**
  677. * Generate random string based on pattern
  678. *
  679. * Syntax: {ULD10}-{U5}
  680. * - U: upper case letters
  681. * - L: lower case letters
  682. * - D: digits
  683. *
  684. * @param string $pattern
  685. * @return string
  686. */
  687. public static function randomPattern($pattern)
  688. {
  689. static $chars = array('L'=>'bcdfghjkmnpqrstvwxyz', 'U'=>'BCDFGHJKLMNPQRSTVWXYZ', 'D'=>'123456789');
  690. while (preg_match('#\{([ULD]+)([0-9]+)\}#i', $pattern, $m)) {
  691. for ($i=0, $c=''; $i<strlen($m[1]); $i++) $c .= $chars[$m[1][$i]];
  692. $pattern = preg_replace('#'.preg_quote($m[0]).'#', BUtil::randomString($m[2], $c), $pattern, 1);
  693. }
  694. return $pattern;
  695. }
  696. public static function nextStringValue($string='', $chars=null)
  697. {
  698. if (is_null($chars)) {
  699. $chars = static::$_defaultCharPool; // avoid leading 0
  700. }
  701. $pos = strlen($string);
  702. $lastChar = substr($chars, -1);
  703. while (--$pos>=-1) {
  704. if ($pos==-1) {
  705. $string = $chars[0].$string;
  706. return $string;
  707. } elseif ($string[$pos]===$lastChar) {
  708. $string[$pos] = $chars[0];
  709. continue;
  710. } else {
  711. $string[$pos] = $chars[strpos($chars, $string[$pos])+1];
  712. return $string;
  713. }
  714. }
  715. // should never get here
  716. return $string;
  717. }
  718. /**
  719. * Set or retrieve current hash algorithm
  720. *
  721. * @param string $algo
  722. */
  723. public static function hashAlgo($algo=null)
  724. {
  725. if (is_null($algo)) {
  726. return static::$_hashAlgo;
  727. }
  728. static::$_hashAlgo = $algo;
  729. }
  730. public static function hashIter($iter=null)
  731. {
  732. if (is_null($iter)) {
  733. return static::$_hashIter;
  734. }
  735. static::$iter = $iter;
  736. }
  737. /**
  738. * Generate salted hash
  739. *
  740. * @deprecated by Bcrypt
  741. * @param string $string original text
  742. * @param mixed $salt
  743. * @param mixed $algo
  744. * @return string
  745. */
  746. public static function saltedHash($string, $salt, $algo=null)
  747. {
  748. $algo = !is_null($algo) ? $algo : static::$_hashAlgo;
  749. return hash($algo, $salt.$string);
  750. }
  751. /**
  752. * Generate fully composed salted hash
  753. *
  754. * Ex: $sha512$2$<salt1>$<salt2>$<double-hashed-string-here>
  755. *
  756. * @deprecated by Bcrypt
  757. * @param string $string
  758. * @param string $salt
  759. * @param string $algo
  760. * @param integer $iter
  761. */
  762. public static function fullSaltedHash($string, $salt=null, $algo=null, $iter=null)
  763. {
  764. $algo = !is_null($algo) ? $algo : static::$_hashAlgo;
  765. if ('bcrypt'===$algo) {
  766. return Bcrypt::i()->hash($string);
  767. }
  768. $iter = !is_null($iter) ? $iter : static::$_hashIter;
  769. $s = static::$_hashSep;
  770. $hash = $s.$algo.$s.$iter;
  771. for ($i=0; $i<$iter; $i++) {
  772. $salt1 = !is_null($salt) ? $salt : static::randomString();
  773. $hash .= $s.$salt1;
  774. $string = static::saltedHash($string, $salt1, $algo);
  775. }
  776. return $hash.$s.$string;
  777. }
  778. /**
  779. * Validate salted hash against original text
  780. *
  781. * @deprecated by BUtil::bcrypt()
  782. * @param string $string original text
  783. * @param string $storedHash fully composed salted hash
  784. * @return bool
  785. */
  786. public static function validateSaltedHash($string, $storedHash)
  787. {
  788. if (strpos($storedHash, '$2a$')===0 || strpos($storedHash, '$2y$')===0) {
  789. return Bcrypt::i()->verify($string, $storedHash);
  790. }
  791. if (!$storedHash) {
  792. return false;
  793. }
  794. $sep = $storedHash[0];
  795. $arr = explode($sep, $storedHash);
  796. array_shift($arr);
  797. $algo = array_shift($arr);
  798. $iter = array_shift($arr);
  799. $verifyHash = $string;
  800. for ($i=0; $i<$iter; $i++) {
  801. $salt = array_shift($arr);
  802. $verifyHash = static::saltedHash($verifyHash, $salt, $algo);
  803. }
  804. $knownHash = array_shift($arr);
  805. return $verifyHash===$knownHash;
  806. }
  807. public static function sha512base64($str)
  808. {
  809. return base64_encode(pack('H*', hash('sha512', $str)));
  810. }
  811. static protected $_lastRemoteHttpInfo;
  812. /**
  813. * Send simple POST request to external server and retrieve response
  814. *
  815. * @param string $method
  816. * @param string $url
  817. * @param array $data
  818. * @return string
  819. */
  820. public static function remoteHttp($method, $url, $data = array())
  821. {
  822. $timeout = 5;
  823. $userAgent = 'Mozilla/5.0';
  824. if ($method==='GET' && $data) {
  825. if(is_array($data)){
  826. $request = http_build_query($data, '', '&');
  827. } else {
  828. $request = $data;
  829. }
  830. $url .= (strpos($url, '?')===false ? '?' : '&') . $request;
  831. }
  832. // curl disabled because file upload doesn't work for some reason. TODO: figure out why
  833. if (false && function_exists('curl_init')) {
  834. $curlOpt = array(
  835. CURLOPT_USERAGENT => $userAgent,
  836. CURLOPT_URL => $url,
  837. CURLOPT_ENCODING => '',
  838. CURLOPT_FOLLOWLOCATION => true,
  839. CURLOPT_RETURNTRANSFER => true,
  840. CURLOPT_AUTOREFERER => true,
  841. CURLOPT_SSL_VERIFYPEER => true,
  842. CURLOPT_CAINFO => dirname(__DIR__).'/ssl/ca-bundle.crt',
  843. CURLOPT_SSL_VERIFYHOST => 2,
  844. CURLOPT_CONNECTTIMEOUT => $timeout,
  845. CURLOPT_TIMEOUT => $timeout,
  846. CURLOPT_MAXREDIRS => 10,
  847. CURLOPT_HTTPHEADER, array('Expect:'), //Fixes the HTTP/1.1 417 Expectation Failed
  848. CURLOPT_HEADER => true,
  849. );
  850. if (false) { // TODO: figure out cookies handling
  851. $cookieDir = BConfig::i()->get('fs/storage_dir').'/cache';
  852. BUtil::ensureDir($cookieDir);
  853. $cookie = tempnam($cookieDir, 'CURLCOOKIE');
  854. $curlOpt += array(
  855. CURLOPT_COOKIEJAR => $cookie,
  856. );
  857. }
  858. if ($method==='POST') {
  859. $curlOpt += array(
  860. CURLOPT_POSTFIELDS => $data,
  861. CURLOPT_POST => 1,
  862. );
  863. } elseif ($method==='PUT') {
  864. $curlOpt += array(
  865. CURLOPT_POSTFIELDS => $data,
  866. CURLOPT_PUT => 1,
  867. );
  868. }
  869. $ch = curl_init();
  870. curl_setopt_array($ch, $curlOpt);
  871. $rawResponse = curl_exec($ch);
  872. list($response, $headers) = explode("\r\n\r\n", $rawResponse, 2);
  873. static::$_lastRemoteHttpInfo = curl_getinfo($ch);
  874. $respHeaders = explode("\r\n", $headers);
  875. if(curl_errno($ch) != 0){
  876. static::$_lastRemoteHttpInfo['errno'] = curl_errno($ch);
  877. static::$_lastRemoteHttpInfo['error'] = curl_error($ch);
  878. }
  879. curl_close($ch);
  880. } else {
  881. $opts = array('http' => array(
  882. 'method' => $method,
  883. 'timeout' => $timeout,
  884. 'header' => "User-Agent: {$userAgent}\r\n",
  885. ));
  886. if ($method==='POST' || $method==='PUT') {
  887. $multipart = false;
  888. foreach ($data as $k=>$v) {
  889. if (is_string($v) && $v[0]==='@') {
  890. $multipart = true;
  891. break;
  892. }
  893. }
  894. if (!$multipart) {
  895. $contentType = 'application/x-www-form-urlencoded';
  896. $opts['http']['content'] = http_build_query($data);
  897. } else {
  898. $boundary = '--------------------------'.microtime(true);
  899. $contentType = 'multipart/form-data; boundary='.$boundary;
  900. $opts['http']['content'] = '';
  901. //TODO: implement recursive forms
  902. foreach ($data as $k =>$v) {
  903. if (is_string($v) && $v[0]==='@') {
  904. $filename = substr($v, 1);
  905. $fileContents = file_get_contents($filename);
  906. $opts['http']['content'] .= "--{$boundary}\r\n".
  907. "Content-Disposition: form-data; name=\"{$k}\"; filename=\"".basename($filename)."\"\r\n".
  908. "Content-Type: application/zip\r\n".
  909. "\r\n".
  910. "{$fileContents}\r\n";
  911. } else {
  912. $opts['http']['content'] .= "--{$boundary}\r\n".
  913. "Content-Disposition: form-data; name=\"{$k}\"\r\n".
  914. "\r\n".
  915. "{$v}\r\n";
  916. }
  917. }
  918. $opts['http']['content'] .= "--{$boundary}--\r\n";
  919. }
  920. $opts['http']['header'] .= "Content-Type: {$contentType}\r\n";
  921. //."Content-Length: ".strlen($request)."\r\n";
  922. if (preg_match('#^(ssl|ftps|https):#', $url)) {
  923. $opts['ssl'] = array(
  924. 'verify_peer' => true,
  925. 'cafile' => dirname(__DIR__).'/ssl/ca-bundle.crt',
  926. 'verify_depth' => 5,
  927. );
  928. }
  929. }
  930. $response = file_get_contents($url, false, stream_context_create($opts));
  931. static::$_lastRemoteHttpInfo = array(); //TODO: emulate curl data?
  932. $respHeaders = $http_response_header;
  933. }
  934. foreach ($respHeaders as $i => $line) {
  935. if ($i) {
  936. $arr = explode(':', $line, 2);
  937. } else {
  938. $arr = array(0, $line);
  939. }
  940. static::$_lastRemoteHttpInfo['headers'][strtolower($arr[0])] = trim($arr[1]);
  941. }
  942. return $response;
  943. }
  944. public static function lastRemoteHttpInfo()
  945. {
  946. return static::$_lastRemoteHttpInfo;
  947. }
  948. public static function normalizePath($path)
  949. {
  950. $path = str_replace('\\', '/', $path);
  951. if (strpos($path, '/..')!==false) {
  952. $a = explode('/', $path);
  953. $b = array();
  954. foreach ($a as $p) {
  955. if ($p==='..') array_pop($b); else $b[] = $p;
  956. }
  957. $path = join('/', $b);
  958. }
  959. return $path;
  960. }
  961. public static function globRecursive($pattern, $flags=0)
  962. {
  963. $files = glob($pattern, $flags);
  964. if (!$files) $files = array();
  965. $dirs = glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT);
  966. if ($dirs) {
  967. foreach ($dirs as $dir) {
  968. $files = array_merge($files, self::globRecursive($dir.'/'.basename($pattern), $flags));
  969. }
  970. }
  971. return $files;
  972. }
  973. public static function isPathAbsolute($path)
  974. {
  975. return !empty($path) && ($path[0]==='/' || $path[0]==='\\') // starting with / or \
  976. || !empty($path[1]) && $path[1]===':'; // windows drive letter C:
  977. }
  978. public static function isUrlFull($url)
  979. {
  980. return preg_match('#^(https?:)?//#', $url);
  981. }
  982. public static function ensureDir($dir)
  983. {
  984. if (is_file($dir)) {
  985. BDebug::warning($dir.' is a file, directory required');
  986. return;
  987. }
  988. if (!is_dir($dir)) {
  989. @$res = mkdir($dir, 0777, true);
  990. if (!$res) {
  991. BDebug::warning("Can't create directory: ".$dir);
  992. }
  993. }
  994. }
  995. /**
  996. * Put together URL components generated by parse_url() function
  997. *
  998. * @see http://us2.php.net/manual/en/function.parse-url.php#106731
  999. * @param array $p result of parse_url()
  1000. * @return string
  1001. */
  1002. public static function unparseUrl($p)
  1003. {
  1004. $scheme = isset($p['scheme']) ? $p['scheme'] . '://' : '';
  1005. $user = isset($p['user']) ? $p['user'] : '';
  1006. $pass = isset($p['pass']) ? ':' . $p['pass'] : '';
  1007. $pass = ($user || $pass) ? $pass . '@' : '';
  1008. $host = isset($p['host']) ? $p['host'] : '';
  1009. $port = isset($p['port']) ? ':' . $p['port'] : '';
  1010. $path = isset($p['path']) ? $p['path'] : '';
  1011. $query = isset($p['query']) ? '?' . $p['query'] : '';
  1012. $fragment = isset($p['fragment']) ? '#' . $p['fragment'] : '';
  1013. return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
  1014. }
  1015. /**
  1016. * Add or set URL query parameters
  1017. *
  1018. * @param string $url
  1019. * @param array $params
  1020. * @return string
  1021. */
  1022. public static function setUrlQuery($url, $params)
  1023. {
  1024. if (true === $url) {
  1025. $url = BRequest::currentUrl();
  1026. }
  1027. $parsed = parse_url($url);
  1028. $query = array();
  1029. if (!empty($parsed['query'])) {
  1030. foreach (explode('&', $parsed['query']) as $q) {
  1031. $a = explode('=', $q);
  1032. if ($a[0]==='') {
  1033. continue;
  1034. }
  1035. $a[0] = urldecode($a[0]);
  1036. $query[$a[0]] = urldecode($a[1]);
  1037. }
  1038. }
  1039. foreach($params as $k => $v){
  1040. if($v === ""){
  1041. if(isset($query[$k])){
  1042. unset($query[$k]);
  1043. }
  1044. unset($params[$k]);
  1045. }
  1046. }
  1047. $query = array_merge($query, $params);
  1048. $parsed['query'] = http_build_query($query);
  1049. return static::unparseUrl($parsed);
  1050. }
  1051. public static function paginateSortUrl($url, $state, $field)
  1052. {
  1053. return static::setUrlQuery($url, array(
  1054. 's'=>$field,
  1055. 'sd'=>$state['s']!=$field || $state['sd']=='desc' ? 'asc' : 'desc',
  1056. ));
  1057. }
  1058. public static function paginateSortAttr($url, $state, $field, $class='')
  1059. {
  1060. return 'href="'.static::paginateSortUrl($url, $state, $field)
  1061. .'" class="'.$class.' '.($state['s']==$field ? $state['sd'] : '').'"';
  1062. }
  1063. /**
  1064. * @param string $tag
  1065. * @param array $attrs
  1066. * @param null $content
  1067. * @return string
  1068. */
  1069. public static function tagHtml($tag, $attrs = array(), $content = null)
  1070. {
  1071. $attrsHtmlArr = array();
  1072. foreach ($attrs as $k => $v) {
  1073. if ('' === $v || is_null($v) || false === $v) {
  1074. continue;
  1075. }
  1076. if (true === $v) {
  1077. $v = $k;
  1078. } elseif (is_array($v)) {
  1079. switch ($k) {
  1080. case 'class':
  1081. $v = join(' ', $v);
  1082. break;
  1083. case 'style':
  1084. $attrHtmlArr = array();
  1085. foreach ($v as $k1 => $v1) {
  1086. $attrHtmlArr[] = $k1.':'.$v1;
  1087. }
  1088. $v = join('; ', $attrHtmlArr);
  1089. break;
  1090. default:
  1091. $v = join('', $v);
  1092. }
  1093. }
  1094. $attrsHtmlArr[] = $k.'="'.htmlspecialchars($v, ENT_QUOTES, 'UTF-8').'"';
  1095. }
  1096. return '<'.$tag.' '.join(' ', $attrsHtmlArr).'>'.$content.'</'.$tag.'>';
  1097. }
  1098. /**
  1099. * @param array $options
  1100. * @param string $default
  1101. * @return string
  1102. */
  1103. public static function optionsHtml($options, $default = '')
  1104. {
  1105. if(!is_array($default)){
  1106. $default = (string)$default;
  1107. }
  1108. $htmlArr = array();
  1109. foreach ($options as $k => $v) {
  1110. $k = (string)$k;
  1111. if (is_array($v) && $k!=='' && $k[0] === '@') { // group
  1112. $label = trim(substr($k, 1));
  1113. $htmlArr[] = BUtil::tagHtml('optgroup', array('label' => $label), static::optionsHtml($v, $default));
  1114. continue;
  1115. }
  1116. if (is_array($v)) {
  1117. $attr = $v;
  1118. $v = !empty($attr['text']) ? $attr['text'] : '';
  1119. unset($attr['text']);
  1120. } else {
  1121. $attr = array();
  1122. }
  1123. $attr['value'] = $k;
  1124. $attr['selected'] = is_array($default) && in_array($k, $default) || $default === $k;
  1125. $htmlArr[] = BUtil::tagHtml('option', $attr, $v);
  1126. }
  1127. return join("\n", $htmlArr);
  1128. }
  1129. /**
  1130. * Strip html tags and shorten to specified length, to the whole word
  1131. *
  1132. * @param string $text
  1133. * @param integer $limit
  1134. */
  1135. public static function previewText($text, $limit)
  1136. {
  1137. $text = strip_tags($text);
  1138. if (strlen($text) < $limit) {
  1139. return $text;
  1140. }
  1141. preg_match('/^(.{1,'.$limit.'})\b/', $text, $matches);
  1142. return $matches[1];
  1143. }
  1144. public static function isEmptyDate($date)
  1145. {
  1146. return preg_replace('#[0 :-]#', '', (string)$date)==='';
  1147. }
  1148. /**
  1149. * Get gravatar image src by email
  1150. *
  1151. * @param string $email
  1152. * @param array $params
  1153. * - size (default 80)
  1154. * - rating (G, PG, R, X)
  1155. * - default
  1156. * - border
  1157. */
  1158. public static function gravatar($email, $params=array())
  1159. {
  1160. if (empty($params['default'])) {
  1161. $params['default'] = 'identicon';
  1162. }
  1163. return BRequest::i()->scheme().'://www.gravatar.com/avatar/'.md5(strtolower($email))
  1164. .($params ? '?'.http_build_query($params) : '');
  1165. }
  1166. public static function extCallback($callback)
  1167. {
  1168. if (is_string($callback)) {
  1169. if (strpos($callback, '.')!==false) {
  1170. list($class, $method) = explode('.', $callback);
  1171. } elseif (strpos($callback, '->')) {
  1172. list($class, $method) = explode('->', $callback);
  1173. }
  1174. if (!empty($class)) {
  1175. $callback = array($class::i(), $method);
  1176. }
  1177. }
  1178. return $callback;
  1179. }
  1180. public static function call($callback, $args=array(), $array=false)
  1181. {
  1182. $callback = static::extCallback($callback);
  1183. if ($array) {
  1184. return call_user_func_array($callback, $args);
  1185. } else {
  1186. return call_user_func($callback, $args);
  1187. }
  1188. }
  1189. public static function formatDateRecursive($source, $format='m/d/Y')
  1190. {
  1191. foreach ($source as $i=>$val) {
  1192. if (is_string($val)) {
  1193. // checking only beginning of string for speed, assuming it is a date
  1194. if (preg_match('#^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]( |$)#', $val)) {
  1195. $source[$i] = date($format, strtotime($val));
  1196. }
  1197. } elseif (is_array($val)) {
  1198. $source[$i] = static::formatDateRecursive($val, $format);
  1199. }
  1200. }
  1201. return $source;
  1202. }
  1203. public static function timeAgo($ptime, $now=null, $long=false)
  1204. {
  1205. if (!is_numeric($ptime)) {
  1206. $ptime = strtotime($ptime);
  1207. }
  1208. if (!$now) {
  1209. $now = time();
  1210. } elseif (!is_numeric($now)) {
  1211. $now = strtotime($now);
  1212. }
  1213. $etime = $now - $ptime;
  1214. if ($etime < 1) {
  1215. return $long ? 'less than 1 second' : '0s';
  1216. }
  1217. $a = array(
  1218. 12 * 30 * 24 * 60 * 60 => array('year', 'y'),
  1219. 30 * 24 * 60 * 60 => array('month', 'mon'),
  1220. 24 * 60 * 60 => array('day', 'd'),
  1221. 60 * 60 => array('hour', 'h'),
  1222. 60 => array('minute', 'm'),
  1223. 1 => array('second', 's'),
  1224. );
  1225. foreach ($a as $secs => $sa) {
  1226. $d = $etime / $secs;
  1227. if ($d >= 1) {
  1228. $r = round($d);
  1229. return $r . ($long ? ' ' . $sa[0] . ($r > 1 ? 's' : '') : $sa[1]);
  1230. }
  1231. }
  1232. }
  1233. /**
  1234. * Simplify string to allowed characters only
  1235. *
  1236. * @param string $str input string
  1237. * @param string $pattern RegEx pattern to specify not allowed characters
  1238. * @param string $filler character to replace not allowed characters with
  1239. * @return string
  1240. */
  1241. static public function simplifyString($str, $pattern='#[^a-z0-9-]+#', $filler='-')
  1242. {
  1243. return trim(preg_replace($pattern, $filler, strtolower($str)), $filler);
  1244. }
  1245. /**
  1246. * Remove directory recursively
  1247. *
  1248. * DANGEROUS, I'm afraid to enable it
  1249. *
  1250. * @param string $dir
  1251. */
  1252. /*
  1253. static public function rmdirRecursive_YesIHaveCheckedThreeTimes($dir, $first=true)
  1254. {
  1255. if ($first) {
  1256. $dir = realpath($dir);
  1257. }
  1258. if (!file_exists($dir)) {
  1259. return true;
  1260. }
  1261. if (!is_dir($dir) || is_link($dir)) {
  1262. return unlink($dir);
  1263. }
  1264. foreach (scandir($dir) as $item) {
  1265. if ($item == '.' || $item == '..') {
  1266. continue;
  1267. }
  1268. if (!static::rmdirRecursive($dir . "/" . $item, false)) {
  1269. chmod($dir . "/" . $item, 0777);
  1270. if (!static::rmdirRecursive($dir . "/" . $item, false)) return false;
  1271. }
  1272. }
  1273. return rmdir($dir);
  1274. }
  1275. */
  1276. static public function topoSort(array $array, array $args=array())
  1277. {
  1278. if (empty($array)) {
  1279. return array();
  1280. }
  1281. // nodes listed in 'after' are parents
  1282. // nodes listed in 'before' are children
  1283. // prepare initial $nodes array
  1284. $beforeVar = !empty($args['before']) ? $args['before'] : 'before';
  1285. $afterVar = !empty($args['before']) ? $args['after'] : 'after';
  1286. $isObject = is_object(current($array));
  1287. $nodes = array();
  1288. foreach ($array as $k=>$v) {
  1289. $before = $isObject ? $v->$beforeVar : $v[$beforeVar];
  1290. if (is_string($before)) {
  1291. $before = array_walk(explode(',', $before), 'trim');
  1292. }
  1293. $after = $isObject ? $v->$afterVar : $v[$afterVar];
  1294. if (is_string($after)) {
  1295. $after = array_walk(explode(',', $after), 'trim');
  1296. }
  1297. $nodes[$k] = array('key' => $k, 'item' => $v, 'parents' => (array)$after, 'children' => (array)$before);
  1298. }
  1299. // get nodes without parents
  1300. $rootNodes = array();
  1301. foreach ($nodes as $k=>$node) {
  1302. if (empty($node['parents'])) {
  1303. $rootNodes[] = $node;
  1304. }
  1305. }
  1306. // begin algorithm
  1307. $sorted = array();
  1308. while ($nodes) {
  1309. // check for circular reference
  1310. if (!$rootNodes) return false;
  1311. // remove this node from root nodes and add it to the output
  1312. $n = array_pop($rootNodes);
  1313. $sorted[$n['key']] = $n['item'];
  1314. // for each of its children: queue the new node, finally remove the original
  1315. for ($i = count($n['children'])-1; $i>=0; $i--) {
  1316. // get child node
  1317. $childNode = $nodes[$n['children'][$i]];
  1318. // remove child nodes from parent
  1319. unset($n['children'][$i]);
  1320. // remove parent from child node
  1321. unset($childNode['parents'][array_search($n['name'], $childNode['parents'])]);
  1322. // check if this child has other parents. if not, add it to the root nodes list
  1323. if (!$childNode['parents']) {
  1324. array_push($rootNodes, $childNode);
  1325. }
  1326. }
  1327. // remove processed node from list
  1328. unset($nodes[$n['key']]);
  1329. }
  1330. return $sorted;
  1331. }
  1332. /**
  1333. * Wrapper for ZipArchive::open+extractTo
  1334. *
  1335. * @param string $filename
  1336. * @param string $targetDir
  1337. * @return boolean Result
  1338. */
  1339. static public function zipExtract($filename, $targetDir)
  1340. {
  1341. if (!class_exists('ZipArchive')) {
  1342. throw new BException("Class ZipArchive doesn't exist");
  1343. }
  1344. $zip = new ZipArchive;
  1345. $res = $zip->open($filename);
  1346. if (!$res) {
  1347. throw new BException("Can't open zip archive for reading: " . $filename);
  1348. }
  1349. BUtil::ensureDir($targetDir);
  1350. $res = $zip->extractTo($targetDir);
  1351. $zip->close();
  1352. if (!$res) {
  1353. throw new BException("Can't extract zip archive: " . $filename . " to " . $targetDir);
  1354. }
  1355. return true;
  1356. }
  1357. static public function zipCreateFromDir($filename, $sourceDir)
  1358. {
  1359. if (!class_exists('ZipArchive')) {
  1360. throw new BException("Class ZipArchive doesn't exist");
  1361. }
  1362. $files = BUtil::globRecursive($sourceDir.'/*');
  1363. if (!$files) {
  1364. throw new BException('Invalid or empty source dir');
  1365. }
  1366. $zip = new ZipArchive;
  1367. $res = $zip->open($filename, ZipArchive::CREATE);
  1368. if (!$res) {
  1369. throw new BException("Can't open zip archive for writing: " . $filename);
  1370. }
  1371. foreach ($files as $file) {
  1372. $packedFile = str_replace($sourceDir.'/', '', $file);
  1373. if (is_dir($file)) {
  1374. $zip->addEmptyDir($packedFile);
  1375. } else {
  1376. $zip->addFile($file, $packedFile);
  1377. }
  1378. }
  1379. $zip->close();
  1380. return true;
  1381. }
  1382. }
  1383. class BHTML extends BClass
  1384. {
  1385. }
  1386. /**
  1387. * @todo Verify license compatibility and integrate with https://github.com/PHPMailer/PHPMailer
  1388. */
  1389. class BEmail extends BClass
  1390. {
  1391. static protected $_handlers = array();
  1392. static protected $_defaultHandler = 'default';
  1393. public function __construct()
  1394. {
  1395. $this->addHandler('default', array($this, 'defaultHandler'));
  1396. }
  1397. public function addHandler($name, $params)
  1398. {
  1399. if (is_callable($params)) {
  1400. $params = array(
  1401. 'description' => $name,
  1402. 'callback' => $params,
  1403. );
  1404. }
  1405. static::$_handlers[$name] = $params;
  1406. }
  1407. public function getHandlers()
  1408. {
  1409. return static::$_handlers;
  1410. }
  1411. public function setDefaultHandler($name)
  1412. {
  1413. static::$_defaultHandler = $name;
  1414. }
  1415. public function send($data)
  1416. {
  1417. static $allowedHeadersRegex = '/^(to|from|cc|bcc|reply-to|return-path|content-type|list-unsubscribe|x-.*)$/';
  1418. $data = array_change_key_case($data, CASE_LOWER);
  1419. $body = trim($data['body']);
  1420. unset($data['body']);
  1421. $to = '';
  1422. $subject = '';
  1423. $headers = array();
  1424. $params = array();
  1425. $files = array();
  1426. foreach ($data as $k => $v) {
  1427. if ($k == 'subject') {
  1428. $subject = $v;
  1429. } elseif ($k == 'to') {
  1430. $to = $v;
  1431. } elseif ($k == 'attach') {
  1432. foreach ((array)$v as $file) {
  1433. $files[] = $file;
  1434. }
  1435. } elseif ($k[0] === '-') {
  1436. $params[$k] = $k . ' ' . $v;
  1437. } elseif (preg_match($allowedHeadersRegex, $k)) {
  1438. if (!empty($v) && $v!=='"" <>') {
  1439. $headers[$k] = $k . ': ' . $v;
  1440. }
  1441. }
  1442. }
  1443. $origBody = $body;
  1444. if ($files) {
  1445. // $body and $headers will be updated
  1446. $th

Large files files are truncated, but you can click here to view the full file