summoner.class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2020 Johannes Keßler
  7. *
  8. * Development starting from 2011: Johannes Keßler
  9. * https://www.bananas-playground.net/projekt/insipid/
  10. *
  11. * creator:
  12. * Luke Reeves <luke@neuro-tech.net>
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU General Public License as published by
  16. * the Free Software Foundation, either version 3 of the License, or
  17. * (at your option) any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU General Public License
  25. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  26. *
  27. */
  28. /**
  29. * a static helper class
  30. */
  31. class Summoner {
  32. private const BROWSER_AGENT_STRING = 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0';
  33. /**
  34. * validate the given string with the given type. Optional check the string
  35. * length
  36. *
  37. * @param string $input The string to check
  38. * @param string $mode How the string should be checked
  39. * @param mixed $limit If int given the string is checked for length
  40. *
  41. * @see http://de.php.net/manual/en/regexp.reference.unicode.php
  42. * http://www.sql-und-xml.de/unicode-database/#pc
  43. *
  44. * the pattern replaces all that is allowed. the correct result after
  45. * the replace should be empty, otherwise are there chars which are not
  46. * allowed
  47. *
  48. * @return bool
  49. */
  50. static function validate($input,$mode='text',$limit=false) {
  51. // check if we have input
  52. $input = trim($input);
  53. if($input == "") return false;
  54. $ret = false;
  55. switch ($mode) {
  56. case 'mail':
  57. if(filter_var($input,FILTER_VALIDATE_EMAIL) === $input) {
  58. return true;
  59. }
  60. else {
  61. return false;
  62. }
  63. break;
  64. case 'url':
  65. if(filter_var($input,FILTER_VALIDATE_URL) === $input) {
  66. return true;
  67. }
  68. else {
  69. return false;
  70. }
  71. break;
  72. case 'nospace':
  73. // text without any whitespace and special chars
  74. $pattern = '/[\p{L}\p{N}]/u';
  75. break;
  76. case 'nospaceP':
  77. // text without any whitespace and special chars
  78. // but with Punctuation other
  79. # http://www.sql-und-xml.de/unicode-database/po.html
  80. $pattern = '/[\p{L}\p{N}\p{Po}\-]/u';
  81. break;
  82. case 'digit':
  83. // only numbers and digit
  84. // warning with negative numbers...
  85. $pattern = '/[\p{N}\-]/';
  86. break;
  87. case 'pageTitle':
  88. // text with whitespace and without special chars
  89. // but with Punctuation
  90. $pattern = '/[\p{L}\p{N}\p{Po}\p{Z}\s-]/u';
  91. break;
  92. # strange. the \p{M} is needed.. don't know why..
  93. case 'filename':
  94. $pattern = '/[\p{L}\p{N}\p{M}\-_\.\p{Zs}]/u';
  95. break;
  96. case 'text':
  97. default:
  98. $pattern = '/[\p{L}\p{N}\p{P}\p{S}\p{Z}\p{M}\s]/u';
  99. }
  100. $value = preg_replace($pattern, '', $input);
  101. if($value === "") {
  102. $ret = true;
  103. }
  104. if(!empty($limit)) {
  105. # isset starts with 0
  106. if(isset($input[$limit])) {
  107. # too long
  108. $ret = false;
  109. }
  110. }
  111. return $ret;
  112. }
  113. /**
  114. * return if the given string is utf8
  115. * http://php.net/manual/en/function.mb-detect-encoding.php
  116. *
  117. * @param string $string
  118. * @return number
  119. */
  120. static function is_utf8($string) {
  121. // From http://w3.org/International/questions/qa-forms-utf-8.html
  122. return preg_match('%^(?:
  123. [\x09\x0A\x0D\x20-\x7E] # ASCII
  124. | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
  125. | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
  126. | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
  127. | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
  128. | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
  129. | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
  130. | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
  131. )*$%xs', $string);
  132. }
  133. /**
  134. * execute a curl call to the given $url
  135. * @param string $url The request url
  136. * @param bool $port
  137. * @return bool|mixed
  138. */
  139. static function curlCall($url,$port=false) {
  140. $ret = false;
  141. $ch = curl_init();
  142. curl_setopt($ch, CURLOPT_URL, $url);
  143. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  144. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  145. curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  146. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  147. curl_setopt($ch, CURLOPT_MAXREDIRS, 2);
  148. curl_setopt($ch, CURLOPT_USERAGENT,self::BROWSER_AGENT_STRING);
  149. // curl_setopt($ch, CURLOPT_VERBOSE, true);
  150. //curl_setopt($ch, CURLOPT_HEADER, true);
  151. if(!empty($port)) {
  152. curl_setopt($ch, CURLOPT_PORT, $port);
  153. }
  154. $do = curl_exec($ch);
  155. if(is_string($do) === true) {
  156. $ret = $do;
  157. }
  158. else {
  159. error_log('ERROR '.var_export(curl_error($ch),true));
  160. }
  161. curl_close($ch);
  162. return $ret;
  163. }
  164. /**
  165. * Download given url to given file
  166. * @param $url
  167. * @param $whereToStore
  168. * @param bool $port
  169. * @return bool
  170. */
  171. static function downloadFile($url, $whereToStore, $port=false) {
  172. $fh = fopen($whereToStore, 'w+');
  173. $ret = false;
  174. if($fh !== false) {
  175. $ch = curl_init($url);
  176. curl_setopt($ch, CURLOPT_FILE, $fh);
  177. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  178. curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  179. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  180. curl_setopt($ch, CURLOPT_MAXREDIRS, 2);
  181. curl_setopt($ch, CURLOPT_USERAGENT, self::BROWSER_AGENT_STRING);
  182. if(!empty($port)) {
  183. curl_setopt($ch, CURLOPT_PORT, $port);
  184. }
  185. curl_exec($ch);
  186. curl_close($ch);
  187. $ret = true;
  188. }
  189. fclose($fh);
  190. return $ret;
  191. }
  192. /**
  193. * check if a string starts with a given string
  194. *
  195. * @param string $haystack
  196. * @param string $needle
  197. * @return boolean
  198. */
  199. static function startsWith($haystack, $needle) {
  200. $length = strlen($needle);
  201. return (substr($haystack, 0, $length) === $needle);
  202. }
  203. /**
  204. * check if a string ends with a given string
  205. *
  206. * @param string $haystack
  207. * @param string $needle
  208. * @return boolean
  209. */
  210. static function endsWith($haystack, $needle) {
  211. $length = strlen($needle);
  212. if ($length == 0) {
  213. return true;
  214. }
  215. return (substr($haystack, -$length) === $needle);
  216. }
  217. /**
  218. * simulate the Null coalescing operator in php5
  219. * this only works with arrays and checking if the key is there and echo/return it.
  220. * http://php.net/manual/en/migration70.new-features.php#migration70.new-features.null-coalesce-op
  221. *
  222. * @param $array
  223. * @param $key
  224. * @return bool
  225. */
  226. static function ifset($array,$key) {
  227. return isset($array[$key]) ? $array[$key] : false;
  228. }
  229. /**
  230. * try to gather meta information from given URL
  231. * @param string $url
  232. * @return array|bool
  233. */
  234. static function gatherInfoFromURL($url) {
  235. $ret = false;
  236. if(self::validate($url,'url')) {
  237. $data = self::curlCall($url);
  238. if(!empty($data)) {
  239. $ret = self::socialMetaInfos($data);
  240. }
  241. }
  242. return $ret;
  243. }
  244. /**
  245. * get as much as possible social meta infos from given string
  246. * the string is usually a HTML source
  247. * @param string $string
  248. * @return array
  249. */
  250. static function socialMetaInfos($string) {
  251. #http://www.w3bees.com/2013/11/fetch-facebook-og-meta-tags-with-php.html
  252. #http://www.9lessons.info/2014/01/social-meta-tags-for-google-twitter-and.html
  253. #http://ogp.me/
  254. #https://moz.com/blog/meta-data-templates-123
  255. $dom = new DomDocument;
  256. # surpress invalid html warnings
  257. @$dom->loadHTML($string);
  258. $xpath = new DOMXPath($dom);
  259. $metas = $xpath->query('//*/meta');
  260. $mediaInfos = array();
  261. # meta tags
  262. foreach($metas as $meta) {
  263. if($meta->getAttribute('property')) {
  264. $prop = $meta->getAttribute('property');
  265. $prop = mb_strtolower($prop);
  266. # minimum required information
  267. # http://ogp.me/#metadata
  268. if($prop == "og:title") {
  269. $mediaInfos['title'] = $meta->getAttribute('content');
  270. }
  271. elseif($prop == "og:image") {
  272. $mediaInfos['image'] = $meta->getAttribute('content');
  273. }
  274. elseif($prop == "og:url") {
  275. $mediaInfos['link'] = $meta->getAttribute('content');
  276. }
  277. elseif($prop == "og:description") {
  278. $mediaInfos['description'] = $meta->getAttribute('content');
  279. }
  280. }
  281. elseif($meta->getAttribute('name')) {
  282. $name = $meta->getAttribute('name');
  283. $name = mb_strtolower($name);
  284. # twitter
  285. # https://dev.twitter.com/cards/overview
  286. if($name == "twitter:title") {
  287. $mediaInfos['title'] = $meta->getAttribute('content');
  288. }
  289. elseif($name == "twitter:description") {
  290. $mediaInfos['description'] = $meta->getAttribute('content');
  291. }
  292. elseif($name == "twitter:image") {
  293. $mediaInfos['image'] = $meta->getAttribute('content');
  294. }
  295. elseif($name == "description") {
  296. $mediaInfos['description'] = $meta->getAttribute('content');
  297. }
  298. }
  299. elseif($meta->getAttribute('itemprop')) {
  300. $itemprop = $meta->getAttribute('itemprop');
  301. $itemprop = mb_strtolower($itemprop);
  302. # google plus
  303. if($itemprop == "name") {
  304. $mediaInfos['title'] = $meta->getAttribute('content');
  305. }
  306. elseif($itemprop == "description") {
  307. $mediaInfos['description'] = $meta->getAttribute('content');
  308. }
  309. elseif($itemprop == "image") {
  310. $mediaInfos['image'] = $meta->getAttribute('content');
  311. }
  312. }
  313. }
  314. if(!isset($mediaInfos['title'])) {
  315. $titleDom = $xpath->query('//title');
  316. $mediaInfos['title'] = $titleDom->item(0)->nodeValue;
  317. }
  318. return $mediaInfos;
  319. }
  320. /**
  321. * at creation a category or tag can be a string with multiple values.
  322. * separated with space or ,
  323. * category and tag is a single string without any separators
  324. *
  325. * @param string $string
  326. * @return array
  327. */
  328. static function prepareTagOrCategoryStr($string) {
  329. $ret = array();
  330. $_ret = array();
  331. $string = trim($string, ", ");
  332. if(strstr($string, ",")) {
  333. $_t = explode(",", $string);
  334. foreach($_t as $n) {
  335. $_ret[$n] = $n;
  336. }
  337. unset($_t);
  338. unset($n);
  339. foreach($_ret as $e) {
  340. if(strstr($e, " ")) {
  341. unset($ret[$e]);
  342. $_t = explode(" ", $e);
  343. foreach($_t as $new) {
  344. $new = trim($new);
  345. $_c = self::validate($new,'nospace');
  346. if(!empty($new) && $_c === true) {
  347. $ret[$new] = $new;
  348. }
  349. }
  350. }
  351. else {
  352. $new = trim($e);
  353. $_c = self::validate($new,'nospace');
  354. if(!empty($new) && $_c === true) {
  355. $ret[$new] = $new;
  356. }
  357. }
  358. }
  359. }
  360. else {
  361. $_t = explode(" ", $string);
  362. foreach($_t as $new) {
  363. $new = trim($new);
  364. $_c = self::validate($new,'nospace');
  365. if(!empty($new) && $_c === true) {
  366. $ret[$new] = $new;
  367. }
  368. }
  369. }
  370. return $ret;
  371. }
  372. /**
  373. * a very simple HTTP_AUTH authentication.
  374. */
  375. static function simpleAuth() {
  376. if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])
  377. || $_SERVER['PHP_AUTH_USER'] !== FRONTEND_USERNAME || $_SERVER['PHP_AUTH_PW'] !== FRONTEND_PASSWORD
  378. ) {
  379. header('WWW-Authenticate: Basic realm="Insipid edit area"');
  380. header('HTTP/1.0 401 Unauthorized');
  381. echo 'No Access.';
  382. exit;
  383. }
  384. }
  385. /**
  386. * check if we have a valid auth. Nothing more.
  387. * @see Summoner::simpleAuth to trigger the auth
  388. * @return bool
  389. */
  390. static function simpleAuthCheck() {
  391. if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])
  392. && $_SERVER['PHP_AUTH_USER'] === FRONTEND_USERNAME && $_SERVER['PHP_AUTH_PW'] === FRONTEND_PASSWORD
  393. ) {
  394. return true;
  395. }
  396. return false;
  397. }
  398. /**
  399. * Checks if in the given urlstring a scheme is existent. If not add http:// to it
  400. * @param $urlString
  401. * @return string
  402. */
  403. static function addSchemeToURL($urlString) {
  404. $ret = $urlString;
  405. if(empty(parse_url($ret, PHP_URL_SCHEME))) {
  406. $ret = "http://".$ret;
  407. }
  408. return $ret;
  409. }
  410. /**
  411. * retrieve the folder size with its children of given folder path
  412. * @param $folder
  413. * @return false|int
  414. */
  415. static function folderSize($folder) {
  416. $ret = 0;
  417. if(file_exists($folder) && is_readable($folder)) {
  418. foreach (glob(rtrim($folder, '/') . '/*', GLOB_NOSORT) as $each) {
  419. $ret += is_file($each) ? filesize($each) : self::folderSize($each);
  420. }
  421. }
  422. return $ret;
  423. }
  424. /**
  425. * Calculate the given byte size in more human readable format.
  426. * @param $size
  427. * @param string $unit
  428. * @return string
  429. */
  430. static function humanFileSize($size,$unit="") {
  431. $ret = number_format($size)." bytes";
  432. if((!$unit && $size >= 1<<30) || $unit == "GB") {
  433. $ret = number_format($size / (1 << 30), 2)."GB";
  434. }
  435. elseif((!$unit && $size >= 1<<20) || $unit == "MB") {
  436. $ret = number_format($size / (1 << 20), 2) . "MB";
  437. }
  438. elseif( (!$unit && $size >= 1<<10) || $unit == "KB") {
  439. $ret = number_format($size / (1 << 10), 2) . "KB";
  440. }
  441. return $ret;
  442. }
  443. /**
  444. * delete and/or empty a directory
  445. *
  446. * $empty = true => empty the directory but do not delete it
  447. *
  448. * @param string $directory
  449. * @param boolean $empty
  450. * @param int $fTime If not false remove files older then this value in sec.
  451. * @return boolean
  452. */
  453. static function recursive_remove_directory($directory,$empty=false,$fTime=0) {
  454. if(substr($directory,-1) == '/') {
  455. $directory = substr($directory,0,-1);
  456. }
  457. if(!file_exists($directory) || !is_dir($directory)) {
  458. return false;
  459. }
  460. elseif(!is_readable($directory)) {
  461. return false;
  462. }
  463. else {
  464. $handle = opendir($directory);
  465. // and scan through the items inside
  466. while (false !== ($item = readdir($handle))) {
  467. if($item[0] != '.') {
  468. $path = $directory.'/'.$item;
  469. if(is_dir($path)) {
  470. recursive_remove_directory($path);
  471. }
  472. else {
  473. if($fTime !== false && is_int($fTime)) {
  474. $ft = filemtime($path);
  475. $offset = time()-$fTime;
  476. if($ft <= $offset) {
  477. unlink($path);
  478. }
  479. }
  480. else {
  481. unlink($path);
  482. }
  483. }
  484. }
  485. }
  486. closedir($handle);
  487. if($empty === false) {
  488. if(!rmdir($directory)) {
  489. return false;
  490. }
  491. }
  492. return true;
  493. }
  494. }
  495. /**
  496. * http_build_query with modify array
  497. * modify will add: key AND value not empty
  498. * modify will remove: only key with no value
  499. *
  500. * @param $array
  501. * @param bool $modify
  502. * @return string
  503. */
  504. static function createFromParameterLinkQuery($array,$modify=false) {
  505. $ret = '';
  506. if(!empty($modify)) {
  507. foreach($modify as $k=>$v) {
  508. if(empty($v)) {
  509. unset($array[$k]);
  510. }
  511. else {
  512. $array[$k] = $v;
  513. }
  514. }
  515. }
  516. if(!empty($array)) {
  517. $ret = http_build_query($array);
  518. }
  519. return $ret;
  520. }
  521. /**
  522. * Simple helper to detect the $_FILES upload status
  523. * Expects the error value from $_FILES['error']
  524. * @param $error
  525. * @return array
  526. */
  527. static function checkFileUploadStatus($error) {
  528. $message = "Unknown upload error";
  529. $status = false;
  530. switch ($error) {
  531. case UPLOAD_ERR_OK:
  532. $message = "There is no error, the file uploaded with success.";
  533. $status = true;
  534. break;
  535. case UPLOAD_ERR_INI_SIZE:
  536. $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini";
  537. break;
  538. case UPLOAD_ERR_FORM_SIZE:
  539. $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form";
  540. break;
  541. case UPLOAD_ERR_PARTIAL:
  542. $message = "The uploaded file was only partially uploaded";
  543. break;
  544. case UPLOAD_ERR_NO_FILE:
  545. $message = "No file was uploaded";
  546. break;
  547. case UPLOAD_ERR_NO_TMP_DIR:
  548. $message = "Missing a temporary folder";
  549. break;
  550. case UPLOAD_ERR_CANT_WRITE:
  551. $message = "Failed to write file to disk";
  552. break;
  553. case UPLOAD_ERR_EXTENSION:
  554. $message = "File upload stopped by extension";
  555. break;
  556. }
  557. return array(
  558. 'message' => $message,
  559. 'status' => $status
  560. );
  561. }
  562. /**
  563. * just a very basic system call execution
  564. * needs error handling and stuff
  565. */
  566. static function systemcall($command,$params) {
  567. //escapeshellarg
  568. $command = escapeshellcmd($command." ".$params);
  569. exec($command,$return);
  570. return $return;
  571. }
  572. }