helper.class.php 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644
  1. <?php
  2. /**
  3. * ZenTaoPHP的baseHelper类。
  4. * The baseHelper class file of ZenTaoPHP framework.
  5. *
  6. * @package framework
  7. *
  8. * The author disclaims copyright to this source code. In place of
  9. * a legal notice, here is a blessing:
  10. *
  11. * May you do good and not evil.
  12. * May you find forgiveness for yourself and forgive others.
  13. * May you share freely, never taking more than you give.
  14. */
  15. class baseHelper
  16. {
  17. /**
  18. * 已加载的目录.
  19. * The loaded directories.
  20. *
  21. * @static
  22. * @var array
  23. * @access public
  24. */
  25. static $loadedDirs = array();
  26. /**
  27. * 已经包含的文件.
  28. * The included files.
  29. *
  30. * @static
  31. * @var array
  32. * @access public
  33. */
  34. static $includedFiles = array();
  35. /**
  36. * 设置一个对象的成员变量。
  37. * Set the member's value of one object.
  38. * <code>
  39. * <?php
  40. * $lang->db->user = 'wwccss';
  41. * helper::setMember('lang', 'db.user', 'chunsheng.wang');
  42. * ?>
  43. * </code>
  44. * @param string $objName the var name of the object.
  45. * @param string $key the key of the member, can be parent.child.
  46. * @param mixed $value the value to be set.
  47. * @static
  48. * @access public
  49. * @return bool
  50. */
  51. static public function setMember($objName, $key, $value)
  52. {
  53. global ${$objName};
  54. if(!is_object(${$objName}) or empty($key)) return false;
  55. $key = str_replace('.', '->', $key);
  56. $value = serialize($value);
  57. $code = ("\${$objName}->{$key}=unserialize(<<<EOT\n$value\nEOT\n);");
  58. eval($code);
  59. return true;
  60. }
  61. /**
  62. * 生成一个模块方法的链接。control类的createLink实际上调用的是这个方法。
  63. * Create a link to a module's method, mapped in control class to call conveniently.
  64. *
  65. * <code>
  66. * <?php
  67. * helper::createLink('hello', 'index', 'var1=value1&var2=value2');
  68. * helper::createLink('hello', 'index', array('var1' => 'value1', 'var2' => 'value2');
  69. * ?>
  70. * </code>
  71. * @param string $moduleName module name, can pass appName like app.module.
  72. * @param string $methodName method name
  73. * @param string|array $vars the params passed to the method, can be array('key' => 'value') or key1=value1&key2=value2) or key1=value1&key2=value2
  74. * @param string $viewType the view type
  75. * @param bool $onlyBody pass onlyBody=yes to the link thus the app can control the header and footer hide or show..
  76. * @static
  77. * @access public
  78. * @return string the link string.
  79. */
  80. static public function createLink($moduleName, $methodName = 'index', $vars = '', $viewType = '', $onlyBody = false)
  81. {
  82. $link = null;
  83. /* 设置$appName和$moduleName。Set appName and moduleName. */
  84. global $app, $config;
  85. if(strpos($moduleName, '.') !== false)
  86. {
  87. [$appName, $moduleName] = explode('.', $moduleName);
  88. }
  89. else
  90. {
  91. $appName = $app->getAppName();
  92. }
  93. if(!empty($appName)) $appName .= '/';
  94. /* 处理$viewType和$vars。Set $viewType and $vars. */
  95. if(empty($viewType)) $viewType = $app->getViewType();
  96. if(!is_array($vars))
  97. {
  98. /* Prevent + from converting to spaces. */
  99. $vars = str_replace('+', '%2B', $vars);
  100. parse_str($vars, $vars);
  101. }
  102. /* 生成url链接的开始部分。Set the begin parts of the link. */
  103. if($config->requestType == 'PATH_INFO2')
  104. {
  105. $link = '/';
  106. }
  107. elseif($config->requestType == 'PATH_INFO')
  108. {
  109. $link = $config->webRoot . $appName;
  110. }
  111. elseif($app->apiVersion)
  112. {
  113. $link = $config->webRoot . 'index.php';
  114. }
  115. else
  116. {
  117. $link = $config->webRoot . $appName . basename((string) $_SERVER['SCRIPT_NAME']);
  118. }
  119. /**
  120. * #1: RequestType为GET。When the requestType is GET.
  121. * Input: moduleName=article&methodName=index&var1=value1. Output: ?m=article&f=index&var1=value1.
  122. *
  123. */
  124. if($config->requestType == 'GET')
  125. {
  126. $link .= "?{$config->moduleVar}=$moduleName&{$config->methodVar}=$methodName";
  127. if($viewType != 'html') $link .= "&{$config->viewVar}=" . $viewType;
  128. foreach($vars as $key => $value) $link .= "&$key=$value";
  129. return static::processOnlyBodyParam($link, $onlyBody);
  130. }
  131. /**
  132. * #2: 方法名不是默认值或者是默认值,但有传参。MethodName equals the default method or vars not empty.
  133. * Input: moduleName=article&methodName=view. Output: article-view.html
  134. * Input: moduleName=article&methodName=view. Output: article-index-abc.html
  135. *
  136. */
  137. if($methodName != $config->default->method or !empty($vars))
  138. {
  139. $link .= "$moduleName{$config->requestFix}$methodName";
  140. foreach($vars as $value) $link .= "{$config->requestFix}$value";
  141. $link .= '.' . $viewType;
  142. return static::processOnlyBodyParam($link, $onlyBody);
  143. }
  144. /**
  145. * #3: 方法名为默认值且没有传参且模块名为默认值。MethodName is the default and moduleName is default and vars empty.
  146. * Input: moduleName=index&methodName=index. Output: index.html
  147. *
  148. */
  149. if($moduleName == $config->default->module)
  150. {
  151. $link .= $config->default->method . '.' . $viewType;
  152. return static::processOnlyBodyParam($link, $onlyBody);
  153. }
  154. /**
  155. * #4: 方法名为默认值且没有传参且模块名不为默认值,viewType和app指定的相等。MethodName is default but moduleName not and viewType equal app's viewType..
  156. * Input: moduleName=article&methodName=index&viewType=html. Output: /article/
  157. *
  158. */
  159. if($viewType == $app->getViewType())
  160. {
  161. $link .= $moduleName . '.' . $viewType;
  162. return static::processOnlyBodyParam($link, $onlyBody);
  163. }
  164. /**
  165. * #5: 方法名为默认值且没有传参且模块名不为默认值,viewType有另外指定。MethodName is default but moduleName not and viewType no equls app's viewType.
  166. * Input: moduleName=article&methodName=index&viewType=json. Output: /article.json
  167. *
  168. */
  169. $link .= $moduleName . '.' . $viewType;
  170. return static::processOnlyBodyParam($link, $onlyBody);
  171. }
  172. /**
  173. * 处理onlyBody 参数。
  174. * Process the onlyBody param in url.
  175. *
  176. * 如果传参的时候设定了$onlyBody为真,或者当前页面请求中包含了onlybody=yes,在生成链接的时候继续追加。
  177. * If $onlyBody set to true or onlybody=yes in the url, append onlyBody param to the link.
  178. *
  179. * @param string $link
  180. * @param bool $onlyBody
  181. * @static
  182. * @access public
  183. * @return string
  184. */
  185. public static function processOnlyBodyParam($link, $onlyBody = false)
  186. {
  187. global $config;
  188. $sign = strpos($link, '?') === false ? "?" : "&";
  189. $appendString = '';
  190. if($onlyBody or (static::inOnlyBodyMode() && !static::isAjaxRequest('modal'))) $appendString = $sign . "onlybody=yes";
  191. if(static::isWithTID() and strpos($link, 'tid=') === false) $appendString .= empty($appendString) ? "{$sign}tid={$_GET['tid']}" : "&tid={$_GET['tid']}";
  192. return $link . $appendString;
  193. }
  194. /**
  195. * 检查是否是onlybody模式。
  196. * Check in only body mode or not.
  197. *
  198. * @access public
  199. * @return bool
  200. */
  201. public static function inOnlyBodyMode()
  202. {
  203. return (isset($_GET['onlybody']) and $_GET['onlybody'] == 'yes');
  204. }
  205. /**
  206. * Is with tid.
  207. *
  208. * @static
  209. * @access public
  210. * @return bool
  211. */
  212. public static function isWithTID()
  213. {
  214. global $config;
  215. return (!empty($config->tabSession) and isset($_GET['tid']));
  216. }
  217. /**
  218. * 使用helper::import()来引入文件,不要直接使用include或者require.
  219. * Using helper::import() to import a file, instead of include or require.
  220. *
  221. * @param string $file the file to be imported.
  222. * @static
  223. * @access public
  224. * @return bool
  225. */
  226. static public function import($file)
  227. {
  228. $file = realpath($file);
  229. if($file === false) return false;
  230. if(isset(static::$includedFiles[$file])) return true;
  231. if(!is_file($file)) return false;
  232. include $file;
  233. static::$includedFiles[$file] = true;
  234. return true;
  235. }
  236. /**
  237. * 使用helper::importControl()来引入Control文件,不要直接使用include或者require.
  238. * Using helper::importControl() to import a file, instead of include or require.
  239. *
  240. * @param string $moduleName.
  241. * @static
  242. * @access public
  243. * @return bool
  244. */
  245. static public function importControl($moduleName)
  246. {
  247. global $app;
  248. return helper::import($app->getModulePath('', $moduleName) . 'control.php');
  249. }
  250. /**
  251. * 转义 'order by' 之后下一个关键字之前的SQL语句。Wrap partial sql after 'order by' keyword and before the next keyword such as 'limit'.
  252. * 将部分MySQL保留字用反引号包裹起来。Wrap some reserved word of MySQL in backticks.
  253. * 例如/example: 'order_desc' -> '`order` desc', 'order_asc' -> '`order` asc'
  254. * @see https://dev.mysql.com/doc/refman/en/identifiers.html
  255. * @see https://dev.mysql.com/doc/refman/en/keywords.html
  256. * @param string $sql
  257. * @return string
  258. */
  259. static public function wrapSqlAfterOrderBy($sql)
  260. {
  261. $sql = trim($sql);
  262. $sql = preg_replace('/_(asc|desc)$/i', ' $1', $sql);
  263. if(empty($sql)) return '';
  264. if(strpos($sql, ' ') === false and strpos($sql, ',') === false) return '`' . $sql . '`';
  265. $reservedWords = ['asc', 'desc', 'order', 'limit'];
  266. $sqlParts = explode(',', $sql);
  267. $wrappedParts = [];
  268. foreach ($sqlParts as $part)
  269. {
  270. if(in_array($part, $reservedWords))
  271. {
  272. $wrappedParts[] = '`' . $part . '`';
  273. }
  274. else
  275. {
  276. if(strpos($part, ' ') !== false)
  277. {
  278. $part = preg_replace('/([0-9,a-z,A-Z$_]+) (asc|desc)/i', '`$1` $2', $part);
  279. $wrappedParts[] = $part;
  280. }
  281. else
  282. {
  283. $wrappedParts[] = '`' . $part . '`';
  284. }
  285. }
  286. }
  287. return implode(',', $wrappedParts);
  288. }
  289. /**
  290. * 将数组或者列表转化成 IN( 'a', 'b') 的形式。
  291. * Convert a list to IN('a', 'b') string.
  292. *
  293. * @param string|array $idList 列表,可以是数组或者用逗号隔开的列表。The id lists, can be a array or a string joined with comma.
  294. * @static
  295. * @access public
  296. * @return string the string like IN('a', 'b').
  297. */
  298. static public function dbIN($idList)
  299. {
  300. if(is_array($idList))
  301. {
  302. foreach($idList as $key=>$value) $idList[$key] = addslashes((string) $value);
  303. $idCount = count($idList);
  304. if($idCount == 0) return "= NULL";
  305. if($idCount == 1) return "= '" . current($idList) . "'";
  306. return "IN ('" . join("','", $idList) . "')";
  307. }
  308. if(is_null($idList)) return '=NULL';
  309. if(!is_string($idList)) $idList = json_encode($idList);
  310. $idList = str_replace(',', "','", str_replace(' ', '', addslashes($idList)));
  311. if(substr_count($idList, ',') != 0) return "IN ('" . $idList . "')";
  312. return $idList == '' ? '= NULL' : "= '$idList'";
  313. }
  314. /**
  315. * 安全的Base64编码,框架对'/'字符比较敏感,转换为'.'。
  316. * Create safe base64 encoded string for the framework.
  317. *
  318. * @param string $string the string to encode.
  319. * @static
  320. * @access public
  321. * @return string encoded string.
  322. */
  323. static public function safe64Encode($string)
  324. {
  325. return strtr(base64_encode($string), '/', '.');
  326. }
  327. /**
  328. * 解码base64,先将之前的'.' 转换回'/'
  329. * Decode the string encoded by safe64Encode.
  330. *
  331. * @param string $string the string to decode
  332. * @static
  333. * @access public
  334. * @return string decoded string.
  335. */
  336. static public function safe64Decode($string)
  337. {
  338. return base64_decode(strtr($string, '.', '/'));
  339. }
  340. /**
  341. * JSON编码,自动处理转义的问题。
  342. * JSON encode, process the slashes.
  343. *
  344. * @param mixed $data the object to encode
  345. * @static
  346. * @access public
  347. * @return string decoded string.
  348. */
  349. static public function jsonEncode($data)
  350. {
  351. return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION);
  352. }
  353. /**
  354. * Encrypt password.
  355. *
  356. * @param string $password
  357. * @static
  358. * @access public
  359. * @return string
  360. */
  361. public static function encryptPassword($password)
  362. {
  363. global $config;
  364. $encrypted = '';
  365. if(!empty($config->encryptSecret) and $password)
  366. {
  367. $secret = $config->encryptSecret;
  368. $iv = str_repeat("\0", 8);
  369. if(function_exists('mcrypt_encrypt'))
  370. {
  371. $encrypted = base64_encode((string) mcrypt_encrypt(MCRYPT_DES, substr((string) $secret, 0, 8), $password, MCRYPT_MODE_CBC, $iv));
  372. }
  373. elseif(function_exists('openssl_encrypt'))
  374. {
  375. /* Set password length to multiple of 8. For compatible mcrypt_encrypt function. */
  376. $oversize = strlen($password) % 8;
  377. if($oversize != 0) $password .= str_repeat("\0", 8 - $oversize);
  378. $encrypted = openssl_encrypt($password, 'DES-CBC', substr((string) $secret, 0, 8), OPENSSL_ZERO_PADDING, $iv);
  379. }
  380. }
  381. if(empty($encrypted)) $encrypted = $password;
  382. return $encrypted;
  383. }
  384. /**
  385. * Decrypt password.
  386. *
  387. * @param string $password
  388. * @static
  389. * @access public
  390. * @return string
  391. */
  392. public static function decryptPassword($password)
  393. {
  394. global $config;
  395. $decryptedPassword = '';
  396. if(!empty($config->encryptSecret) and $password)
  397. {
  398. $secret = $config->encryptSecret;
  399. $iv = str_repeat("\0", 8);
  400. if(function_exists('mcrypt_decrypt'))
  401. {
  402. $decryptedPassword = trim((string) mcrypt_decrypt(MCRYPT_DES, substr((string) $secret, 0, 8), base64_decode($password), MCRYPT_MODE_CBC, $iv));
  403. }
  404. elseif(function_exists('openssl_decrypt'))
  405. {
  406. $decryptedPassword = trim(openssl_decrypt($password, 'DES-CBC', substr((string) $secret, 0, 8), OPENSSL_ZERO_PADDING, $iv));
  407. }
  408. /* Check decrypted password. Judge whether there is garbled code. */
  409. $jsoned = json_encode($decryptedPassword);
  410. if($jsoned === 'null' or empty($jsoned)) $decryptedPassword = '';
  411. }
  412. if(empty($decryptedPassword)) $decryptedPassword = $password;
  413. $decryptedPassword = trim($decryptedPassword);
  414. return $decryptedPassword;
  415. }
  416. /**
  417. * 判断是否是utf8编码
  418. * Judge a string is utf-8 or not.
  419. *
  420. * @author hmdker@gmail.com
  421. * @param string $string
  422. * @see http://php.net/manual/en/function.mb-detect-encoding.php
  423. * @static
  424. * @access public
  425. * @return bool
  426. */
  427. static public function isUTF8($string)
  428. {
  429. $c = 0;
  430. $b = 0;
  431. $bits = 0;
  432. $len = strlen($string);
  433. for($i=0; $i<$len; $i++)
  434. {
  435. $c = ord($string[$i]);
  436. if($c > 128)
  437. {
  438. if(($c >= 254)) return false;
  439. elseif($c >= 252) $bits=6;
  440. elseif($c >= 248) $bits=5;
  441. elseif($c >= 240) $bits=4;
  442. elseif($c >= 224) $bits=3;
  443. elseif($c >= 192) $bits=2;
  444. else return false;
  445. if(($i+$bits) > $len) return false;
  446. while($bits > 1)
  447. {
  448. $i++;
  449. $b=ord($string[$i]);
  450. if($b < 128 || $b > 191) return false;
  451. $bits--;
  452. }
  453. }
  454. }
  455. return true;
  456. }
  457. /**
  458. * 去掉UTF-8 Bom头。
  459. * Remove UTF-8 Bom.
  460. *
  461. * @param string|false $string
  462. * @access public
  463. * @return string
  464. */
  465. public static function removeUTF8Bom($string)
  466. {
  467. /* The ob_get_clean() will return false if output buffering isn't active. */
  468. if(!is_string($string)) return '';
  469. if(substr($string, 0, 3) == pack('CCC', 239, 187, 191)) return substr($string, 3);
  470. return $string;
  471. }
  472. /**
  473. * 增强substr方法:支持多字节语言,比如中文。
  474. * Enhanced substr version: support multibyte languages like Chinese.
  475. *
  476. * @param string $string
  477. * @param int $length
  478. * @param string $append
  479. * @return string
  480. */
  481. public static function substr($string, $length, $append = '')
  482. {
  483. $rawString = $string;
  484. if(function_exists('mb_substr')) $string = mb_substr($string, 0, $length, 'utf-8');
  485. preg_match_all("/./su", $string, $data);
  486. $string = implode("", array_slice($data[0], 0, $length));
  487. return ($string != $rawString) ? $string . $append : $string;
  488. }
  489. /**
  490. * Get browser name and version.
  491. *
  492. * @access public
  493. * @return array
  494. */
  495. public static function getBrowser()
  496. {
  497. $browser = array('name'=>'unknown', 'version'=>'unknown');
  498. if(empty($_SERVER['HTTP_USER_AGENT'])) return $browser;
  499. $agent = $_SERVER["HTTP_USER_AGENT"];
  500. /* Chrome should check before safari.*/
  501. if(strpos((string) $agent, 'Firefox') !== false) $browser['name'] = "firefox";
  502. if(strpos((string) $agent, 'Opera') !== false) $browser['name'] = 'opera';
  503. if(strpos((string) $agent, 'Safari') !== false) $browser['name'] = 'safari';
  504. if(strpos((string) $agent, 'Chrome') !== false) $browser['name'] = "chrome";
  505. // Check the name of browser
  506. if(strpos((string) $agent, 'MSIE') !== false || strpos((string) $agent, 'rv:11.0')) $browser['name'] = 'ie';
  507. if(strpos((string) $agent, 'Edge') !== false) $browser['name'] = 'edge';
  508. // Check the version of browser
  509. if(preg_match('/MSIE\s(\d+)\..*/i', (string) $agent, $regs)) $browser['version'] = $regs[1];
  510. if(preg_match('/FireFox\/(\d+)\..*/i', (string) $agent, $regs)) $browser['version'] = $regs[1];
  511. if(preg_match('/Opera[\s|\/](\d+)\..*/i', (string) $agent, $regs)) $browser['version'] = $regs[1];
  512. if(preg_match('/Chrome\/(\d+)\..*/i', (string) $agent, $regs)) $browser['version'] = $regs[1];
  513. if((strpos((string) $agent, 'Chrome') === false) && preg_match('/Safari\/(\d+)\..*$/i', (string) $agent, $regs)) $browser['version'] = $regs[1];
  514. if(preg_match('/rv:(\d+)\..*/i', (string) $agent, $regs)) $browser['version'] = $regs[1];
  515. if(preg_match('/Edge\/(\d+)\..*/i', (string) $agent, $regs)) $browser['version'] = $regs[1];
  516. return $browser;
  517. }
  518. /**
  519. * Get client os from agent info.
  520. *
  521. * @static
  522. * @access public
  523. * @return string
  524. */
  525. public static function getOS()
  526. {
  527. if(empty($_SERVER['HTTP_USER_AGENT'])) return 'unknow';
  528. $osList = array();
  529. $osList['/windows nt 10/i'] = 'Windows 10';
  530. $osList['/windows nt 6.3/i'] = 'Windows 8.1';
  531. $osList['/windows nt 6.2/i'] = 'Windows 8';
  532. $osList['/windows nt 6.1/i'] = 'Windows 7';
  533. $osList['/windows nt 6.0/i'] = 'Windows Vista';
  534. $osList['/windows nt 5.2/i'] = 'Windows Server 2003/XP x64';
  535. $osList['/windows nt 5.1/i'] = 'Windows XP';
  536. $osList['/windows xp/i'] = 'Windows XP';
  537. $osList['/windows nt 5.0/i'] = 'Windows 2000';
  538. $osList['/windows me/i'] = 'Windows ME';
  539. $osList['/win98/i'] = 'Windows 98';
  540. $osList['/win95/i'] = 'Windows 95';
  541. $osList['/win16/i'] = 'Windows 3.11';
  542. $osList['/macintosh|mac os x/i'] = 'Mac OS X';
  543. $osList['/mac_powerpc/i'] = 'Mac OS 9';
  544. $osList['/linux/i'] = 'Linux';
  545. $osList['/ubuntu/i'] = 'Ubuntu';
  546. $osList['/iphone/i'] = 'iPhone';
  547. $osList['/ipod/i'] = 'iPod';
  548. $osList['/ipad/i'] = 'iPad';
  549. $osList['/android/i'] = 'Android';
  550. $osList['/blackberry/i'] = 'BlackBerry';
  551. $osList['/webos/i'] = 'Mobile';
  552. foreach ($osList as $regex => $value)
  553. {
  554. if(preg_match($regex, (string) $_SERVER['HTTP_USER_AGENT'])) return $value;
  555. }
  556. return 'unknown';
  557. }
  558. /**
  559. * 计算两个日期相差的天数,取整。
  560. * Compute the diff days of two date.
  561. *
  562. * @param string $date1 the first date.
  563. * @param string $date2 the second date.
  564. * @access public
  565. * @return int the diff of the two days.
  566. */
  567. static public function diffDate($date1, $date2)
  568. {
  569. if(empty($date1) or empty($date2)) return 0;
  570. /* Get the timestamp in the current operating system. */
  571. $date1 = new DateTime($date1);
  572. $date2 = new DateTime($date2);
  573. $date1 = date_format($date1, "U"); /* Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT). */
  574. $date2 = date_format($date2, "U");
  575. return (int)round(($date1 - $date2) / 86400, 0);
  576. }
  577. /**
  578. * 获取当前时间,使用common语言文件定义的DT_DATETIME1常量。
  579. * Get now time use the DT_DATETIME1 constant defined in the lang file.
  580. *
  581. * @access public
  582. * @return string now
  583. */
  584. static public function now()
  585. {
  586. return date(DT_DATETIME1);
  587. }
  588. /**
  589. * 获取当前日期,使用common语言文件定义的DT_DATE1常量。
  590. * Get today according to the DT_DATE1 constant defined in the lang file.
  591. *
  592. * @access public
  593. * @return string today
  594. */
  595. static public function today()
  596. {
  597. return date(DT_DATE1);
  598. }
  599. /**
  600. * 获取当前日期,使用common语言文件定义的DT_TIME1常量。
  601. * Get now time use the DT_TIME1 constant defined in the lang file.
  602. *
  603. * @access public
  604. * @return string today
  605. */
  606. static public function time()
  607. {
  608. return date(DT_TIME1);
  609. }
  610. /**
  611. * 判断日期是不是零。
  612. * Judge a date is zero or not.
  613. *
  614. * @access public
  615. * @return bool
  616. */
  617. static public function isZeroDate($date)
  618. {
  619. return (empty($date) or substr($date, 0, 4) <= '1970');
  620. }
  621. /**
  622. * 列出目录中符合该正则表达式的文件。
  623. * Get files match the pattern under a directory.
  624. *
  625. * @access public
  626. * @return array the files match the pattern
  627. */
  628. static public function ls($dir, $pattern = '')
  629. {
  630. if(empty($dir)) return array();
  631. if(isset(static::$loadedDirs[$dir][$pattern])) return static::$loadedDirs[$dir][$pattern];
  632. $files = array();
  633. $dir = realpath($dir);
  634. if($dir === false) return array();
  635. if(is_dir($dir)) $files = glob($dir . DIRECTORY_SEPARATOR . '*' . $pattern);
  636. static::$loadedDirs[$dir][$pattern] = $files ?: array();
  637. return static::$loadedDirs[$dir][$pattern];
  638. }
  639. /**
  640. * 切换目录。第一次调用的时候记录当前的路径,再次调用的时候切换回之前的路径。
  641. * Change directory: first call, save the $cwd, second call, change to $cwd.
  642. *
  643. * @param string $path
  644. * @static
  645. * @access public
  646. * @return void
  647. */
  648. static public function cd($path = '')
  649. {
  650. static $cwd = '';
  651. if($path) $cwd = getcwd();
  652. !empty($path) ? chdir($path) : chdir($cwd);
  653. }
  654. /**
  655. * 通过域名获取站点代号。
  656. * Get siteCode for a domain.
  657. *
  658. * www.xirang.com => xirang
  659. * xirang.com => xirang
  660. * xirang.com.cn => xirang
  661. * xirang.cn => xirang
  662. * xirang => xirang
  663. *
  664. * @param string $domain
  665. * @return string $siteCode
  666. **/
  667. public static function parseSiteCode($domain)
  668. {
  669. global $config;
  670. /* 去除域名中的端口部分。Remove the port part of the domain. */
  671. if(strpos($domain, ':') !== false) $domain = substr($domain, 0, strpos($domain, ':'));
  672. $domain = strtolower($domain);
  673. /* $config里面有定义或者是localhost,直接返回。 Return directly if defined in $config or is localhost. */
  674. if(isset($config->siteCodeList[$domain])) return $config->siteCodeList[$domain];
  675. if($domain == 'localhost') return $domain;
  676. /* 将域名中的-改为_。Replace '-' with '_' in the domain. */
  677. $domain = str_replace('-', '_', $domain);
  678. $items = explode('.', $domain);
  679. /* 类似a.com的形式。 Domain like a.com. */
  680. $postfix = str_replace($items[0] . '.', '', $domain);
  681. if(isset($config->domainPostfix) and strpos((string) $config->domainPostfix, "|$postfix|") !== false) return $items[0];
  682. /* 类似www.a.com的形式。 Domain like www.a.com. */
  683. $postfix = str_replace($items[0] . '.' . $items[1] . '.', '', $domain);
  684. if(isset($config->domainPostfix) and strpos((string) $config->domainPostfix, "|$postfix|") !== false) return $items[1];
  685. /* 类似xxx.sub.a.com的形式。 Domain like xxx.sub.a.com. */
  686. $postfix = str_replace($items[0] . '.' . $items[1] . '.' . $items[2] . '.', '', $domain);
  687. if(isset($config->domainPostfix) and strpos((string) $config->domainPostfix, "|$postfix|") !== false) return $items[0];
  688. return '';
  689. }
  690. /**
  691. * 检查是否是AJAX请求。
  692. * Check is ajax request.
  693. *
  694. * @param ?string $type zin|modal|fetch
  695. * @static
  696. * @access public
  697. * @return bool
  698. */
  699. public static function isAjaxRequest($type = null)
  700. {
  701. $isAjax = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') || (isset($_GET['HTTP_X_REQUESTED_WITH']) && $_GET['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
  702. if($isAjax === false) return false;
  703. if($type === 'zin') return array_key_exists('HTTP_X_ZIN_OPTIONS', $_SERVER);
  704. if($type === 'modal') return isset($_SERVER['HTTP_X_ZUI_MODAL']) && $_SERVER['HTTP_X_ZUI_MODAL'] == true;
  705. if($type === 'fetch') return !array_key_exists('HTTP_X_ZIN_OPTIONS', $_SERVER) && !(isset($_SERVER['HTTP_X_ZUI_MODAL']) && $_SERVER['HTTP_X_ZUI_MODAL'] == true);
  706. return $isAjax;
  707. }
  708. /**
  709. * 301跳转。
  710. * Header 301 Moved Permanently.
  711. *
  712. * @param string $locate
  713. * @access public
  714. * @return void
  715. */
  716. public static function header301($locate)
  717. {
  718. helper::setStatus(301);
  719. helper::header('location', $locate);
  720. helper::end();
  721. }
  722. /**
  723. * 获取远程IP。
  724. * Get remote ip.
  725. *
  726. * @param bool $proxy
  727. * @param bool $single
  728. * @access public
  729. * @return string
  730. */
  731. public static function getRemoteIp($proxy = true, $single = true)
  732. {
  733. $ip = '';
  734. if(!empty($_SERVER["REMOTE_ADDR"])) $ip = $_SERVER["REMOTE_ADDR"];
  735. if($proxy)
  736. {
  737. if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"])) $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
  738. if(!empty($_SERVER['HTTP_CLIENT_IP'])) $ip = $_SERVER['HTTP_CLIENT_IP'];
  739. /* The Proxy Server like CDN will change the HTTP_X_FORWARDED_FOR header with value 'realIP, proxyIP'. */
  740. if($single && strpos($ip, ',') !== false) $ip = trim(explode(',', $ip)[0]);
  741. }
  742. return $ip;
  743. }
  744. /**
  745. * Restart session.
  746. *
  747. * @param string $sessionID
  748. * @static
  749. * @access public
  750. * @return void
  751. */
  752. public static function restartSession($sessionID = '')
  753. {
  754. if(!preg_match('/^\w+$/', $sessionID)) $sessionID = '';
  755. if(empty($sessionID)) $sessionID = sha1((string)mt_rand(0, mt_getrandmax()));
  756. session_write_close();
  757. if(ini_get('session.save_handler') == 'user') // Once enable the custom session handler in baseRouter, session.save_handler becomes user.
  758. {
  759. $ztSessionHandler = new ztSessionHandler();
  760. session_set_save_handler($ztSessionHandler, true);
  761. }
  762. session_id($sessionID);
  763. session_start();
  764. global $app;
  765. if($app && isset($_SESSION['user'])) $app->user = $_SESSION['user'];
  766. if($app && !isset($_SESSION['user'])) unset($app->user);
  767. }
  768. /**
  769. * Check DB to repair table.
  770. *
  771. * @param object $exception
  772. * @static
  773. * @access public
  774. * @return string
  775. */
  776. public static function checkDB2Repair($exception)
  777. {
  778. global $config, $lang;
  779. $repairCode = '|1034|1035|1194|1195|1459|';
  780. $errorInfo = $exception->errorInfo;
  781. $errorCode = zget($errorInfo, 1, 0);
  782. $errorMsg = zget($errorInfo, 2, '');
  783. $message = $exception->getMessage();
  784. if(strpos($repairCode, "|$errorCode|") !== false or ($errorCode == '1016' and strpos((string) $errorMsg, 'errno: 145') !== false) or strpos((string) $message, 'repair') !== false)
  785. {
  786. if(isset($config->framework->autoRepairTable) and $config->framework->autoRepairTable)
  787. {
  788. helper::header('location', $config->webRoot . 'checktable.php');
  789. helper::end();
  790. }
  791. return $lang->repairTable;
  792. }
  793. return null;
  794. }
  795. /**
  796. * Send a cookie.
  797. *
  798. * @param string $name
  799. * @param string|int|bool $value
  800. * @param int|null $expire
  801. * @param string|null $path
  802. * @param string $domain
  803. * @param bool|null $secure
  804. * @param bool $httponly
  805. * @static
  806. * @access public
  807. * @return bool
  808. */
  809. public static function setcookie($name, $value = '', $expire = null, $path = null, $domain = '', $secure = null, $httponly = true)
  810. {
  811. if(defined('RUN_MODE') && RUN_MODE == 'test')
  812. {
  813. $_COOKIE[$name] = (string)$value;
  814. return;
  815. }
  816. global $config, $app;
  817. if($expire === null) $expire = $config->cookieLife;
  818. if($path === null) $path = $config->webRoot;
  819. if($secure === null) $secure = $config->cookieSecure;
  820. $value = (string)$value;
  821. $_COOKIE[$name] = $value;
  822. if(isset($app->worker))
  823. {
  824. $app->worker->response->setCookie($name, $value, $expire, $path, $domain, $secure, $httponly);
  825. }
  826. else
  827. {
  828. return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
  829. }
  830. }
  831. /**
  832. * 设置状态码。
  833. * Set status code.
  834. *
  835. * @param int $code
  836. * @static
  837. * @access public
  838. * @return void
  839. */
  840. static public function setStatus($code)
  841. {
  842. global $app;
  843. if(isset($app->worker))
  844. {
  845. $app->worker->response->setStatus($code);
  846. }
  847. else
  848. {
  849. $PHRASES = array(
  850. 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing',
  851. 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported',
  852. 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect',
  853. 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons',
  854. 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required',
  855. );
  856. header('HTTP/1.1 ' . (string)$code . ' ' . $PHRASES[$code], true, $code);
  857. }
  858. }
  859. /**
  860. * 发送HTTP头信息。
  861. * Send http header.
  862. *
  863. * @param string $key
  864. * @param string $value
  865. * @param bool $replace
  866. * @param int $response_code
  867. * @static
  868. * @access public
  869. * @return void
  870. */
  871. static public function header($key, $value, $replace = true, $response_code = 0)
  872. {
  873. global $app;
  874. if(isset($app->worker))
  875. {
  876. $key = trim(strtolower($key));
  877. $app->worker->response->setHeader($key, $value);
  878. if($key == 'location')
  879. {
  880. $app->worker->response->setStatus(302);
  881. helper::end();
  882. }
  883. }
  884. else
  885. {
  886. header($key . ': ' . $value, $replace, $response_code);
  887. }
  888. }
  889. /**
  890. * Generate rand string.
  891. *
  892. * @param int $length
  893. * @access public
  894. * @return string
  895. */
  896. static public function randStr($length = 4)
  897. {
  898. $seeds = str_shuffle('abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789');
  899. return substr($seeds, 0, $length);
  900. }
  901. /**
  902. * 获取二维数组中的某一列。
  903. * Get array column to array.
  904. *
  905. * @param array $input
  906. * @param int|string|null $columnKey
  907. * @param int|string|null $indexKey
  908. * @static
  909. * @access public
  910. * @return array
  911. */
  912. public static function arrayColumn($input, $columnKey, $indexKey = null)
  913. {
  914. /* If php version greater than 7, calling system functions returns. */
  915. if(defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000) return \array_column($input, $columnKey, $indexKey);
  916. $output = array();
  917. foreach($input as $row)
  918. {
  919. $key = $value = null;
  920. $keySet = $valueSet = false;
  921. if($indexKey !== null && array_key_exists($indexKey, (array) $row))
  922. {
  923. $keySet = true;
  924. $key = \is_object($row) ? (string) $row->$indexKey : (string) $row[$indexKey];
  925. }
  926. if(null === $columnKey)
  927. {
  928. $valueSet = true;
  929. $value = $row;
  930. }
  931. elseif(\is_array($row) && \array_key_exists($columnKey, $row))
  932. {
  933. $valueSet = true;
  934. $value = $row[$columnKey];
  935. }
  936. elseif(\is_object($row) && \property_exists($row, $columnKey))
  937. {
  938. $valueSet = true;
  939. $value = $row->$columnKey;
  940. }
  941. if($valueSet)
  942. {
  943. if($keySet)
  944. {
  945. $output[$key] = $value;
  946. }
  947. else
  948. {
  949. $output[] = $value;
  950. }
  951. }
  952. }
  953. return $output;
  954. }
  955. /**
  956. * 在本地语言没有中文的环境下解析pathinfo.
  957. * pathinfo of utf-8.
  958. *
  959. * @param string $filepath
  960. * @access public
  961. * @return array
  962. */
  963. public static function mbPathinfo($filepath)
  964. {
  965. $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
  966. preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im',$filepath,$m);
  967. $ret['dirname'] = !empty($m[1]) ? $m[1] : '';
  968. $ret['basename'] = !empty($m[2]) ? $m[2] : '';
  969. $ret['extension'] = !empty($m[5]) ? $m[5] : '';
  970. $ret['filename'] = !empty($m[3]) ? $m[3] : '';
  971. return $ret;
  972. }
  973. /**
  974. * 代替 die、exit 函数终止并输出。
  975. * Instead of die, exit function to terminate and output.
  976. *
  977. * @param string $content
  978. * @return never
  979. * @throws EndResponseException
  980. */
  981. public static function end($content = '')
  982. {
  983. throw EndResponseException::create($content);
  984. }
  985. /**
  986. * 连接 Redis 服务器。
  987. * Connect to Redis server.
  988. *
  989. * @param object $setting
  990. * @static
  991. * @access public
  992. * @return object
  993. */
  994. public static function connectRedis($setting)
  995. {
  996. if(!class_exists('Redis')) throw new Exception('The Redis extension is not installed.');
  997. try
  998. {
  999. $redis = new Redis();
  1000. $version = phpversion('redis');
  1001. $password = !empty($setting->password) ? htmlspecialchars_decode($setting->password) : null;
  1002. if(version_compare($version, '5.3.0', 'ge'))
  1003. {
  1004. $redis->connect($setting->host, (int)$setting->port, 1, null, 0, 0, ['auth' => [$setting->username ?: null, $password]]);
  1005. }
  1006. else
  1007. {
  1008. $redis->connect($setting->host, (int)$setting->port, 1, null, 0, 0);
  1009. $redis->auth($password);
  1010. }
  1011. if(!$redis->ping()) throw new Exception('Can not connect to Redis server.');
  1012. $databases = $redis->config('GET', 'databases');
  1013. if($setting->database >= $databases['databases']) throw new Exception("The database number is out of range. Your Redis server's max database number is " . ($databases['databases'] - 1) . '.');
  1014. return $redis;
  1015. }
  1016. catch(RedisException $e)
  1017. {
  1018. throw new Exception('Can not connect to Redis server. The error message is: ' . $e->getMessage());
  1019. }
  1020. }
  1021. /**
  1022. * 转换类型。
  1023. * Convert the type.
  1024. *
  1025. * @param mixed $value
  1026. * @param string $type
  1027. * @static
  1028. * @access public
  1029. * @return array|bool|float|int|object|string
  1030. */
  1031. public static function convertType($value, $type)
  1032. {
  1033. switch($type)
  1034. {
  1035. case 'int':
  1036. return (int)$value;
  1037. case 'float':
  1038. return (float)$value;
  1039. case 'bool':
  1040. return (bool)filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
  1041. case 'array':
  1042. return array_filter((array)$value, function($var){return ($var === '0' || !empty($var));});
  1043. case 'object':
  1044. return (object)$value;
  1045. case 'datetime':
  1046. case 'date':
  1047. return $value ? trim((string)$value) : null;
  1048. case 'string':
  1049. default:
  1050. return trim((string)$value);
  1051. }
  1052. }
  1053. /**
  1054. * 将科学计数法展示的工时转换为字符串。
  1055. * Convert scientific notation of hours to string.
  1056. *
  1057. * @param string|float $hours
  1058. * @access public
  1059. * @return string
  1060. */
  1061. public static function formatHours($hours = '', $decimals = 2, $characters = '.')
  1062. {
  1063. return rtrim(rtrim(number_format((float)$hours, $decimals, $characters, ''), '0'), $characters);
  1064. }
  1065. /**
  1066. * 判断当前页面是否需要进行htmlspecialchars_decode解码。
  1067. * Check if current page need to decode with htmlspecialchars_decode.
  1068. *
  1069. * @access public
  1070. * @return bool
  1071. */
  1072. public static function needDecodeHtmlSpecialChars()
  1073. {
  1074. /* API模式下,所有页面都需要解码。In API mode, all pages need to be decoded. */
  1075. if(defined('RUN_MODE') && RUN_MODE == 'api') return true;
  1076. global $app, $config;
  1077. /* baseRouter 构造函数中会调用此方法,此时 $app 还未初始化完成。The baseRouter constructor will call this method, at this time $app is not initialized yet. */
  1078. if(is_null($app)) return false;
  1079. /* 当前请求已经判断过,直接返回结果。 The current request has been judged, return the result directly. */
  1080. if(!is_null($app->needDecodeHtmlSpecialChars)) return $app->needDecodeHtmlSpecialChars;
  1081. /* 如果是旧页面,则不需要解码。 If it is an old page, no need to decode. */
  1082. $moduleName = $app->getModuleName();
  1083. $methodName = $app->getMethodName();
  1084. if(!isset($config->index->oldPages)) $app->loadConfig('index');
  1085. $app->needDecodeHtmlSpecialChars = !in_array("{$moduleName}-{$methodName}", $config->index->oldPages);
  1086. return $app->needDecodeHtmlSpecialChars;
  1087. }
  1088. /**
  1089. * 对数据对象或数组中的标题字段进行htmlspecialchars_decode解码。
  1090. * Decode the title field in data object or array with htmlspecialchars_decode.
  1091. *
  1092. * @param string $table
  1093. * @param object|array $data
  1094. * @access public
  1095. * @return bool|object|array
  1096. */
  1097. public static function decodeHtmlSpecialChars($table, $data)
  1098. {
  1099. if(empty($data)) return $data;
  1100. if(!is_object($data) && !is_array($data)) return $data;
  1101. if(!self::needDecodeHtmlSpecialChars()) return $data;
  1102. global $app, $config;
  1103. $objectType = str_replace(['`', $config->db->prefix], '', $table);
  1104. if(in_array($objectType, array('config', 'user'))) return $data;
  1105. if(!isset($config->action->objectNameFields)) $app->loadConfig('action');
  1106. $titleField = $config->action->objectNameFields[$objectType] ?? '';
  1107. if(!$titleField) return $data;
  1108. if(is_object($data))
  1109. {
  1110. if(isset($data->$titleField)) $data->$titleField = htmlspecialchars_decode((string)$data->$titleField, ENT_QUOTES);
  1111. return $data;
  1112. }
  1113. foreach($data as $key => $row)
  1114. {
  1115. if(is_object($row) && isset($row->$titleField)) $row->$titleField = htmlspecialchars_decode((string)$row->$titleField, ENT_QUOTES);
  1116. if(is_array($row) && isset($row[$titleField])) $row[$titleField] = htmlspecialchars_decode((string)$row[$titleField], ENT_QUOTES);
  1117. $data[$key] = $row;
  1118. }
  1119. return $data;
  1120. }
  1121. }
  1122. //------------------------------- 常用函数。Some tool functions.-------------------------------//
  1123. /**
  1124. * helper::createLink()的别名,方便创建本模块方法的链接。
  1125. * The short alias of helper::createLink() method to create link to control method of current module.
  1126. *
  1127. * @param string $methodName the method name
  1128. * @param string|array $vars the params passed to the method, can be array('key' => 'value') or key1=value1&key2=value2)
  1129. * @param string $viewType
  1130. * @return string the link string.
  1131. */
  1132. function inLink($methodName = 'index', $vars = '', $viewType = '', $onlybody = false)
  1133. {
  1134. global $app;
  1135. return helper::createLink($app->getModuleName(), $methodName, $vars, $viewType, $onlybody);
  1136. }
  1137. /**
  1138. * 通过一个静态游标,可以遍历数组。
  1139. * Static cycle a array.
  1140. *
  1141. * @param array $items the array to be cycled.
  1142. * @return mixed
  1143. */
  1144. function cycle($items)
  1145. {
  1146. static $i = 0;
  1147. if(!is_array($items)) $items = explode(',', $items);
  1148. if(!isset($items[$i])) $i = 0;
  1149. $result = $items[$i];
  1150. $i++;
  1151. return $result;
  1152. }
  1153. /**
  1154. * 获取当前时间的Unix时间戳,精确到微妙。
  1155. * Get current microtime.
  1156. *
  1157. * @access public
  1158. * @return float current time.
  1159. */
  1160. function getTime()
  1161. {
  1162. [$usec, $sec] = explode(" ", microtime());
  1163. return ((float)$usec + (float)$sec);
  1164. }
  1165. /**
  1166. * 打印变量的信息,支持传入多个变量信息。
  1167. * dump a var, support multi vars.
  1168. *
  1169. * @access public
  1170. * @param mixed ...$vars the vars to be dumped.
  1171. * @return int return the count of vars.
  1172. */
  1173. function a()
  1174. {
  1175. $vars = func_get_args();
  1176. foreach($vars as $var)
  1177. {
  1178. echo "<pre style='background:#eee;border:1px solid #aaa;padding:0.4em 0.8em;font-size:12px;overflow:auto;color:#000;margin:12px 0'><code>";
  1179. print_r($var);
  1180. echo "</code></pre>";
  1181. }
  1182. return count($vars);
  1183. }
  1184. /**
  1185. * 判断是否内外IP。
  1186. * Judge the server ip is local or not.
  1187. *
  1188. * @access public
  1189. * @return bool
  1190. */
  1191. function isLocalIP()
  1192. {
  1193. if(PHP_SAPI == 'cli' || php_sapi_name() == 'cli') return true;
  1194. global $config;
  1195. if(isset($config->isLocalIP)) return $config->isLocalIP;
  1196. $serverIP = $_SERVER['SERVER_ADDR'];
  1197. if($serverIP == '127.0.0.1' or $serverIP == '::1') return true;
  1198. if(strpos((string) $serverIP, '10.70') !== false) return false;
  1199. return !filter_var($serverIP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE);
  1200. }
  1201. /**
  1202. * 获取webRoot。
  1203. * Get web root.
  1204. *
  1205. * @access public
  1206. * @return string
  1207. */
  1208. function getWebRoot($full = false)
  1209. {
  1210. $envWebRoot = (string)getenv('ZT_WEB_ROOT');
  1211. if($envWebRoot) $envWebRoot = '/' . trim($envWebRoot, '/') . '/';
  1212. if(!$full && $envWebRoot) return $envWebRoot;
  1213. $path = $_SERVER['SCRIPT_NAME'];
  1214. if(PHP_SAPI == 'cli')
  1215. {
  1216. if(isset($_SERVER['argv'][1]))
  1217. {
  1218. $url = parse_url((string) $_SERVER['argv'][1]);
  1219. $path = empty($url['path']) ? '/' : rtrim($url['path'], '/');
  1220. }
  1221. $path = empty($path) ? '/' : preg_replace('/\/www$/', '/www/', (string) $path);
  1222. }
  1223. if($full)
  1224. {
  1225. $http = isHttps() ? 'https://' : 'http://';
  1226. return $http . $_SERVER['HTTP_HOST'] . substr((string) $path, 0, (strrpos((string) $path, '/') + 1)) . trim($envWebRoot, '/');
  1227. }
  1228. $pos = strrpos((string) $path, '/');
  1229. if($pos === false) return '';
  1230. $path = substr((string) $path, 0, ($pos + 1));
  1231. $path = str_replace('\\', '/', $path);
  1232. return $path;
  1233. }
  1234. /**
  1235. * 当数组/对象变量$var存在$key项时,返回存在的对应值或设定值,否则返回$key或不存在的设定值。
  1236. * When the $var has the $key, return it, else result one default value.
  1237. *
  1238. * @param array|object $var
  1239. * @param mixed $valueWhenNone value when the key not exits.
  1240. * @param mixed $valueWhenExists value when the key exits.
  1241. * @access public
  1242. * @return mixed
  1243. */
  1244. function zget($var, $key, $valueWhenNone = false, $valueWhenExists = false)
  1245. {
  1246. if(!is_array($var) and !is_object($var)) return false;
  1247. $type = is_array($var) ? 'array' : 'object';
  1248. $checkExists = $type == 'array' ? isset($var[$key]) : isset($var->$key);
  1249. if($checkExists)
  1250. {
  1251. if($valueWhenExists !== false) return $valueWhenExists;
  1252. return $type == 'array' ? $var[$key] : $var->$key;
  1253. }
  1254. if($valueWhenNone !== false) return $valueWhenNone;
  1255. return $key;
  1256. }
  1257. /**
  1258. * Is https.
  1259. *
  1260. * @access public
  1261. * @return bool
  1262. */
  1263. function isHttps()
  1264. {
  1265. if(!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off') return true;
  1266. if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') return true;
  1267. if(!empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower((string) $_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') return true;
  1268. return false;
  1269. }
  1270. /**
  1271. * Compatibility for htmlspecialchars.
  1272. *
  1273. * @param string $string
  1274. * @param int $flags
  1275. * @param string $encoding
  1276. * @access public
  1277. * @return string
  1278. */
  1279. function htmlSpecialString($string, $flags = '', $encoding = 'UTF-8')
  1280. {
  1281. if(!$flags) $flags = defined('ENT_SUBSTITUTE') ? ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 : ENT_QUOTES;
  1282. return htmlspecialchars((string)$string, $flags, $encoding);
  1283. }
  1284. /**
  1285. * 获取环境变量。
  1286. * Get environment variable.
  1287. *
  1288. * @param string $name
  1289. * @param string $default
  1290. * @param string $format
  1291. * @access public
  1292. * @return mixed
  1293. */
  1294. function getEnvData($name, $default = '', $format = 'string')
  1295. {
  1296. $value = getenv($name);
  1297. if($value === false) $value = $default;
  1298. return helper::convertType($value, $format);
  1299. }
  1300. if(!function_exists('array_column'))
  1301. {
  1302. function array_column($input, $columnKey, $indexKey = null)
  1303. {
  1304. $output = array();
  1305. foreach($input as $row)
  1306. {
  1307. $key = $value = null;
  1308. $keySet = $valueSet = false;
  1309. if(null !== $indexKey && array_key_exists($indexKey, $row))
  1310. {
  1311. $keySet = true;
  1312. $key = (string) $row[$indexKey];
  1313. }
  1314. if(null === $columnKey)
  1315. {
  1316. $valueSet = true;
  1317. $value = $row;
  1318. }
  1319. elseif(\is_array($row) && \array_key_exists($columnKey, $row))
  1320. {
  1321. $valueSet = true;
  1322. $value = $row[$columnKey];
  1323. }
  1324. elseif(\is_object($row) && \property_exists($row, $columnKey))
  1325. {
  1326. $valueSet = true;
  1327. $value = $row->$columnKey;
  1328. }
  1329. if($valueSet)
  1330. {
  1331. if($keySet)
  1332. {
  1333. $output[$key] = $value;
  1334. }
  1335. else
  1336. {
  1337. $output[] = $value;
  1338. }
  1339. }
  1340. }
  1341. return $output;
  1342. }
  1343. }
  1344. if(!function_exists('getallheaders'))
  1345. {
  1346. function getallheaders()
  1347. {
  1348. $headers = array();
  1349. foreach($_SERVER as $name => $value)
  1350. {
  1351. if(strncmp($name, 'HTTP_', strlen('HTTP_')) === 0)
  1352. {
  1353. $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
  1354. }
  1355. }
  1356. return $headers;
  1357. }
  1358. }
  1359. /*
  1360. * 兼容json扩展不存在的情况。
  1361. * Initialization without JSON extension.
  1362. */
  1363. if(!interface_exists('JsonSerializable'))
  1364. {
  1365. interface JsonSerializable
  1366. {
  1367. /**
  1368. * @return mixed
  1369. */
  1370. public function jsonSerialize();
  1371. }
  1372. function json_encode($data, $flags = 0, $depth = 512)
  1373. {
  1374. return $data;
  1375. }
  1376. function json_decode($data, $associative = null, $depth = 512, $flags = 0)
  1377. {
  1378. return $data;
  1379. }
  1380. }
  1381. /**
  1382. * 把一个或多个数组附加到第一个数组,用于替换数组 + 运算符。
  1383. * Append one or more arrays to the first array, used to replace the array + operator.
  1384. *
  1385. * reference: https://www.php.net/manual/en/language.operators.array.php.
  1386. *
  1387. * @param array ...$args
  1388. * @return array
  1389. */
  1390. function arrayUnion(...$args)
  1391. {
  1392. $args = array_filter($args, function($arg){return is_array($arg);});
  1393. $count = count($args);
  1394. if($count == 0) return [];
  1395. if($count == 1) return reset($args);
  1396. $result = array_shift($args);
  1397. foreach($args as $arg)
  1398. {
  1399. foreach($arg as $key => $value)
  1400. {
  1401. if(!isset($result[$key])) $result[$key] = $value;
  1402. }
  1403. }
  1404. return $result;
  1405. }
  1406. /**
  1407. * 压缩 ID 列表。
  1408. * Compress ID list.
  1409. *
  1410. * @param string|array $idList
  1411. * @access public
  1412. * @return string
  1413. */
  1414. function compress($idList)
  1415. {
  1416. if(is_string($idList)) $idList = array_filter(explode(',', $idList));
  1417. if(!is_array($idList)) return $idList;
  1418. $firstID = reset($idList);
  1419. if(!is_numeric($firstID) || (float)$firstID != $firstID) return $idList; // If the first ID is not numeric, return the original array.
  1420. $idList = array_values($idList);
  1421. asort($idList);
  1422. $count = count($idList);
  1423. $encoded = [$idList[0]];
  1424. for($i = 1; $i < $count; $i++) $encoded[] = $idList[$i] - $idList[$i-1];
  1425. return gzcompress(implode(',', $encoded));
  1426. }
  1427. /**
  1428. * 解压缩 ID 列表。
  1429. * Uncompress ID list.
  1430. *
  1431. * @param string $encoded
  1432. * @access public
  1433. * @return array
  1434. */
  1435. function uncompress($encoded)
  1436. {
  1437. $decoded = explode(',', gzuncompress($encoded));
  1438. for($i = 1; $i < count($decoded); $i++) $decoded[$i] += $decoded[$i-1];
  1439. return $decoded;
  1440. }
  1441. /**
  1442. * 将字符串及数组转为Int数组
  1443. * Convert string array to int array.
  1444. *
  1445. * @param array|string $strArray
  1446. * @access public
  1447. * @return array
  1448. */
  1449. function toIntArray($strArray)
  1450. {
  1451. if(is_string($strArray)) $strArray = explode(',', $strArray);
  1452. $intArray = [];
  1453. foreach($strArray as $val)
  1454. {
  1455. if($val === '') continue;
  1456. $intArray[] = (int)$val;
  1457. }
  1458. return $intArray;
  1459. }