summoner.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <?php
  2. /**
  3. * Bibliotheca
  4. *
  5. * Copyright 2018-2023 Johannes Keßler
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  19. */
  20. /**
  21. * Class Summoner
  22. * a static helper class
  23. */
  24. class Summoner {
  25. /**
  26. * Return path to given theme file with fallback to default theme
  27. *
  28. * @param string $file relative path from THEME/
  29. * @param string $theme Theme name
  30. * @param string $defaultTheme Default theme name can be overwritten
  31. * @return string False of nothing is found
  32. */
  33. static function themefile(string $file, string $theme, string $defaultTheme = 'default'): string {
  34. $ret = '';
  35. if(file_exists('view/'.$theme.'/'.$file)) {
  36. $ret = 'view/'.$theme.'/'.$file;
  37. }
  38. elseif (file_exists('view/'.$defaultTheme.'/'.$file)) {
  39. $ret = 'view/'.$defaultTheme.'/'.$file;
  40. }
  41. return $ret;
  42. }
  43. /**
  44. * Return the current config for a theme based on UI_THEME
  45. *
  46. * @param string $configProperty The property to fetch
  47. * @param String $theme Theme name
  48. * @param string $defaultTheme Default theme name can be overwritten
  49. * @return string
  50. */
  51. static function themeConfig(string $configProperty, string $theme, string $defaultTheme = 'default'): string {
  52. $ret = '';
  53. if(defined('UI_THEME_CONFIG')) {
  54. $ret = UI_THEME_CONFIG[$theme][$configProperty] ?? UI_THEME_CONFIG[$defaultTheme][$configProperty];
  55. }
  56. return $ret;
  57. }
  58. /**
  59. * validate the given string with the given type. Optional check the string
  60. * length
  61. *
  62. * @param string $input The string to check
  63. * @param string $mode How the string should be checked
  64. * @param integer $limit If int given the string is checked for length
  65. *
  66. * @return bool
  67. * @see http://de.php.net/manual/en/regexp.reference.unicode.php
  68. * http://www.sql-und-xml.de/unicode-database/#pc
  69. *
  70. * the pattern replaces all that is allowed. the correct result after
  71. * the replace should be empty, otherwise are there chars which are not
  72. * allowed
  73. *
  74. */
  75. static function validate(string $input, string $mode = 'text', int $limit = 0): bool {
  76. // check if we have input
  77. $input = trim($input);
  78. if($input == "") return false;
  79. $ret = false;
  80. switch ($mode) {
  81. case 'mail':
  82. if(filter_var($input,FILTER_VALIDATE_EMAIL) === $input) {
  83. return true;
  84. }
  85. else {
  86. return false;
  87. }
  88. break;
  89. case 'rights':
  90. return self::isRightsString($input);
  91. break;
  92. case 'url':
  93. if(filter_var($input,FILTER_VALIDATE_URL) === $input) {
  94. return true;
  95. }
  96. else {
  97. return false;
  98. }
  99. break;
  100. case 'nospace':
  101. // text without any whitespace and special chars
  102. $pattern = '/[\p{L}\p{N}]/u';
  103. break;
  104. case 'nospaceP':
  105. // text without any whitespace and special chars
  106. // but with Punctuation other
  107. # http://www.sql-und-xml.de/unicode-database/po.html
  108. $pattern = '/[\p{L}\p{N}\p{Po}\-_]/u';
  109. break;
  110. case 'digit':
  111. // only numbers and digit
  112. // warning with negative numbers...
  113. $pattern = '/[\p{N}\-]/u';
  114. break;
  115. case 'pageTitle':
  116. // text with whitespace and without special chars
  117. // but with Punctuation
  118. $pattern = '/[\p{L}\p{N}\p{Po}\p{Z}\s\-_]/u';
  119. break;
  120. # strange. the \p{M} is needed.. don't know why..
  121. case 'filename':
  122. $pattern = '/[\p{L}\p{N}\p{M}\-_\.\p{Zs}]/u';
  123. break;
  124. case 'text':
  125. default:
  126. $pattern = '/[\p{L}\p{N}\p{P}\p{S}\p{Z}\p{M}\s]/u';
  127. }
  128. $value = preg_replace($pattern, '', $input);
  129. if($value === "") {
  130. $ret = true;
  131. }
  132. if(!empty($limit)) {
  133. # isset starts with 0
  134. if(isset($input[$limit])) {
  135. # too long
  136. $ret = false;
  137. }
  138. }
  139. return $ret;
  140. }
  141. /**
  142. * check if the given string is a rights string.
  143. *
  144. * @param string $string
  145. * @return boolean
  146. */
  147. static function isRightsString(string $string): bool {
  148. $ret = false;
  149. $string = trim($string);
  150. if(empty($string)) return false;
  151. if(isset($string[9])) return false;
  152. $check = str_replace("r", "", $string);
  153. $check = str_replace("w", "", $check);
  154. $check = str_replace("x", "", $check);
  155. $check = str_replace("-", "", $check);
  156. if(empty($check)) {
  157. $ret = true;
  158. }
  159. return $ret;
  160. }
  161. /**
  162. * creates the rights string from the given rights array
  163. * check what options are set and set the missing ones to -
  164. *
  165. * then create the rights string
  166. * IMPORTANT: keep the order otherwise the rights will be messed up
  167. *
  168. * @param array $rightsArr
  169. * @return string
  170. */
  171. static function prepareRightsString(array $rightsArr): string {
  172. $rsArr = array();
  173. $ret = '';
  174. if(!empty($rightsArr)) {
  175. // we need a complete type list
  176. // since we can get an "incomplete" array
  177. // if the user hasnt the rights for a specific type
  178. if(!isset($rightsArr['user'])) {
  179. $rightsArr['user'] = "";
  180. }
  181. if(!isset($rightsArr['group'])) {
  182. $rightsArr['group'] = "";
  183. }
  184. if(!isset($rightsArr['other'])) {
  185. $rightsArr['other'] = "";
  186. }
  187. // create the rights information
  188. foreach ($rightsArr as $type=>$data) {
  189. if(!empty($data['read']) && $data['read'] == "1") {
  190. $rsArr[$type]['read'] = "r";
  191. }
  192. else {
  193. $rsArr[$type]['read'] = "-";
  194. }
  195. if(!empty($data['write']) && $data['write'] == "1") {
  196. $rsArr[$type]['write'] = "w";
  197. }
  198. else {
  199. $rsArr[$type]['write'] = "-";
  200. }
  201. if(!empty($data['delete']) && $data['delete'] == "1") {
  202. $rsArr[$type]['delete'] = "x";
  203. }
  204. else {
  205. $rsArr[$type]['delete'] = "-";
  206. }
  207. }
  208. $rString = $rsArr['user']['read'].$rsArr['user']['write'].$rsArr['user']['delete'];
  209. $rString .= $rsArr['group']['read'].$rsArr['group']['write'].$rsArr['group']['delete'];
  210. $rString .= $rsArr['other']['read'].$rsArr['other']['write'].$rsArr['other']['delete'];
  211. if(strlen($rString) != 9) {
  212. $ret = '';
  213. // invalid rights string !!
  214. }
  215. else {
  216. $ret = $rString;
  217. }
  218. }
  219. return $ret;
  220. }
  221. /**
  222. * Creates from given rights string the rights array
  223. *
  224. * @param string $rightsString
  225. * @return array
  226. */
  227. static function prepareRightsArray(string $rightsString): array {
  228. $ret = array();
  229. if(self::isRightsString($rightsString) === true) {
  230. $ret['user']['read'] = '-';
  231. $ret['user']['write'] = '-';
  232. $ret['user']['delete'] = '-';
  233. if($rightsString[0] === 'r') $ret['user']['read'] = 'r';
  234. if($rightsString[1] === 'w') $ret['user']['write'] = 'w';
  235. if($rightsString[2] === 'x') $ret['user']['delete'] = 'x';
  236. $ret['group']['read'] = '-';
  237. $ret['group']['write'] = '-';
  238. $ret['group']['delete'] = '-';
  239. if($rightsString[3] === 'r') $ret['group']['read'] = 'r';
  240. if($rightsString[4] === 'w') $ret['group']['write'] = 'w';
  241. if($rightsString[5] === 'x') $ret['group']['delete'] = 'x';
  242. $ret['other']['read'] = '-';
  243. $ret['other']['write'] = '-';
  244. $ret['other']['delete'] = '-';
  245. if($rightsString[6] === 'r') $ret['other']['read'] = 'r';
  246. if($rightsString[7] === 'w') $ret['other']['write'] = 'w';
  247. if($rightsString[8] === 'x') $ret['other']['delete'] = 'x';
  248. }
  249. return $ret;
  250. }
  251. /**
  252. * read a dir and return the entries as an array
  253. * with full path to the files
  254. *
  255. * @param string $directory The absolute path to the directory
  256. * @param array $ignore An Array with strings to ignored
  257. * @param bool $recursive If we run a recursive scan or not
  258. * @return array
  259. */
  260. static function readDir(string $directory, array $ignore = array(), bool $recursive = false): array {
  261. $files = array();
  262. $dh = opendir($directory);
  263. while(false !== ($file = readdir($dh))) {
  264. if($file[0] ==".") continue;
  265. if(!empty($ignore)) {
  266. foreach ($ignore as $ig) {
  267. if(strstr($file,$ig)) continue 2;
  268. }
  269. }
  270. if(is_file($directory."/".$file)) {
  271. array_push($files, $directory."/".$file);
  272. }
  273. elseif($recursive === true) {
  274. array_push($files, $directory."/".$file);
  275. $files = array_merge($files, self::readDir($directory."/".$file,$ignore, $recursive));
  276. }
  277. elseif(is_dir($directory."/".$file)) {
  278. array_push($files, $directory."/".$file);
  279. }
  280. }
  281. closedir($dh);
  282. return $files;
  283. }
  284. /**
  285. * delete and/or empty a directory
  286. *
  287. * $empty = true => empty the directory but do not delete it
  288. *
  289. * @param string $directory
  290. * @param bool $empty
  291. * @param int $fTime If not false remove files older then this value in sec.
  292. * @return bool
  293. */
  294. static function recursive_remove_directory(string $directory, bool $empty = false, int $fTime = 0): bool {
  295. // if the path has a slash at the end we remove it here
  296. if(substr($directory,-1) == '/') {
  297. $directory = substr($directory,0,-1);
  298. }
  299. // if the path is not valid or is not a directory ...
  300. if(!file_exists($directory) || !is_dir($directory)) {
  301. // ... we return false and exit the function
  302. return false;
  303. // ... if the path is not readable
  304. }elseif(!is_readable($directory)) {
  305. // ... we return false and exit the function
  306. return false;
  307. // ... else if the path is readable
  308. }
  309. else {
  310. // we open the directory
  311. $handle = opendir($directory);
  312. // and scan through the items inside
  313. while (false !== ($item = readdir($handle))) {
  314. // if the filepointer is not the current directory
  315. // or the parent directory
  316. //if($item != '.' && $item != '..' && $item != '.svn') {
  317. if($item[0] != '.') {
  318. // we build the new path to delete
  319. $path = $directory.'/'.$item;
  320. // if the new path is a directory
  321. if(is_dir($path)) {
  322. // we call this function with the new path
  323. self::recursive_remove_directory($path);
  324. // if the new path is a file
  325. }
  326. else {
  327. // we remove the file
  328. if($fTime !== false && is_int($fTime)) {
  329. // check filemtime
  330. $ft = filemtime($path);
  331. $offset = time()-$fTime;
  332. if($ft <= $offset) {
  333. unlink($path);
  334. }
  335. }
  336. else {
  337. unlink($path);
  338. }
  339. }
  340. }
  341. }
  342. // close the directory
  343. closedir($handle);
  344. // if the option to empty is not set to true
  345. if($empty == false) {
  346. // try to delete the now empty directory
  347. if(!rmdir($directory)) {
  348. // return false if not possible
  349. return false;
  350. }
  351. }
  352. // return success
  353. return true;
  354. }
  355. }
  356. /**
  357. * simulate the Null coalescing operator in php5
  358. *
  359. * this only works with arrays and checking if the key is there and echo/return it.
  360. *
  361. * http://php.net/manual/en/migration70.new-features.php#migration70.new-features.null-coalesce-op
  362. *
  363. * @param array $array
  364. * @param array|string $key
  365. * @return bool|mixed
  366. */
  367. static function ifset(array $array, array|string $key): mixed {
  368. if(is_array($key)) {
  369. $_t = $array;
  370. $_c = 0;
  371. foreach ($key as $k) {
  372. if(isset($_t[$k])) {
  373. $_t = $_t[$k];
  374. $_c++;
  375. }
  376. }
  377. return sizeof($key)==$_c ? $_t : false;
  378. } else {
  379. return isset($array[$key]) ? $array[$key] : false;
  380. }
  381. }
  382. /**
  383. * Replace in $haystack the $needle with $replace only once
  384. *
  385. * @param string $haystack
  386. * @param string $needle
  387. * @param string $replace
  388. * @return string
  389. */
  390. static function replaceOnce(string $haystack, string $needle, string $replace): string {
  391. $newstring = $haystack;
  392. $pos = strpos($haystack, $needle);
  393. if ($pos !== false) {
  394. $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
  395. }
  396. return $newstring;
  397. }
  398. /**
  399. * http_build_query with modify array
  400. * modify will add: key AND value not empty
  401. * modify will remove: only key with no value
  402. *
  403. * @param array $array
  404. * @param array $modify
  405. * @return string
  406. */
  407. static function createFromParameterLinkQuery(array $array, array $modify = array()): string {
  408. $ret = '';
  409. if(!empty($modify)) {
  410. foreach($modify as $k=>$v) {
  411. if(empty($v)) {
  412. unset($array[$k]);
  413. }
  414. else {
  415. $array[$k] = $v;
  416. }
  417. }
  418. }
  419. if(!empty($array)) {
  420. $ret = http_build_query($array);
  421. }
  422. return $ret;
  423. }
  424. /**
  425. * Return given string with given $endChar with the max $length
  426. *
  427. * @param string $string
  428. * @param int $length
  429. * @param string $endChar
  430. * @return string
  431. */
  432. static function limitWithDots(string $string, int $length, string $endChar): string {
  433. $ret = $string;
  434. if(strlen($string.$endChar) > $length) {
  435. $ret = substr($string,0, $length).$endChar;
  436. }
  437. return $ret;
  438. }
  439. /**
  440. * Size of the folder and the data within in bytes
  441. *
  442. * @param string $dir
  443. * @return int
  444. */
  445. static function folderSize(string $dir): int {
  446. $size = 0;
  447. foreach (glob(rtrim($dir, '/').'/*', GLOB_NOSORT) as $each) {
  448. $size += is_file($each) ? filesize($each) : self::folderSize($each);
  449. }
  450. return $size;
  451. }
  452. /**
  453. * Given bytes to human format with unit
  454. *
  455. * @param int $bytes
  456. * @return string
  457. */
  458. static function bytesToHuman(int $bytes): string {
  459. $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
  460. for ($i = 0; $bytes > 1024; $i++) {
  461. $bytes /= 1024;
  462. }
  463. return round($bytes, 2) . ' ' . $units[$i];
  464. }
  465. /**
  466. * Make the input more safe for logging
  467. *
  468. * @param mixed $input The string|array to be made more safe
  469. * @return string
  470. */
  471. static function cleanForLog(mixed $input): string {
  472. $input = var_export($input, true);
  473. $input = preg_replace( "/[\t\n\r]/", " ", $input);
  474. return addcslashes($input, "\000..\037\177..\377\\");
  475. }
  476. /**
  477. * error_log with a dedicated destination
  478. * Uses LOGFILE const
  479. *
  480. * @param string $msg The string to be written to the log
  481. */
  482. static function sysLog(string $msg): void {
  483. error_log(date("c")." ".$msg."\n", 3, LOGFILE);
  484. }
  485. /**
  486. * Create unique words from the given data
  487. *
  488. * @param $data string
  489. * @return array
  490. *
  491. */
  492. static function words(string $data): array {
  493. preg_match_all('/\w{3,}+/u',$data,$matches);
  494. return array_unique($matches[0]);
  495. }
  496. }