summoner.class.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2019 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. /**
  33. * validate the given string with the given type. Optional check the string
  34. * length
  35. *
  36. * @param string $input The string to check
  37. * @param string $mode How the string should be checked
  38. * @param mixed $limit If int given the string is checked for length
  39. *
  40. * @see http://de.php.net/manual/en/regexp.reference.unicode.php
  41. * http://www.sql-und-xml.de/unicode-database/#pc
  42. *
  43. * the pattern replaces all that is allowed. the correct result after
  44. * the replace should be empty, otherwise are there chars which are not
  45. * allowed
  46. *
  47. * @return bool
  48. */
  49. static function validate($input,$mode='text',$limit=false) {
  50. // check if we have input
  51. $input = trim($input);
  52. if($input == "") return false;
  53. $ret = false;
  54. switch ($mode) {
  55. case 'mail':
  56. if(filter_var($input,FILTER_VALIDATE_EMAIL) === $input) {
  57. return true;
  58. }
  59. else {
  60. return false;
  61. }
  62. break;
  63. case 'url':
  64. if(filter_var($input,FILTER_VALIDATE_URL) === $input) {
  65. return true;
  66. }
  67. else {
  68. return false;
  69. }
  70. break;
  71. case 'nospace':
  72. // text without any whitespace and special chars
  73. $pattern = '/[\p{L}\p{N}]/u';
  74. break;
  75. case 'nospaceP':
  76. // text without any whitespace and special chars
  77. // but with Punctuation other
  78. # http://www.sql-und-xml.de/unicode-database/po.html
  79. $pattern = '/[\p{L}\p{N}\p{Po}\-]/u';
  80. break;
  81. case 'digit':
  82. // only numbers and digit
  83. // warning with negative numbers...
  84. $pattern = '/[\p{N}\-]/';
  85. break;
  86. case 'pageTitle':
  87. // text with whitespace and without special chars
  88. // but with Punctuation
  89. $pattern = '/[\p{L}\p{N}\p{Po}\p{Z}\s-]/u';
  90. break;
  91. # strange. the \p{M} is needed.. don't know why..
  92. case 'filename':
  93. $pattern = '/[\p{L}\p{N}\p{M}\-_\.\p{Zs}]/u';
  94. break;
  95. case 'text':
  96. default:
  97. $pattern = '/[\p{L}\p{N}\p{P}\p{S}\p{Z}\p{M}\s]/u';
  98. }
  99. $value = preg_replace($pattern, '', $input);
  100. #if($input === $value) {
  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,'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0');
  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,'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0');
  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. $string = trim($string, ", ");
  331. if(strstr($string, ",")) {
  332. $_t = explode(",", $string);
  333. foreach($_t as $new) {
  334. $ret[$new] = $new;
  335. }
  336. unset($_t);
  337. unset($new);
  338. foreach($ret as $e) {
  339. if(strstr($e, " ")) {
  340. unset($ret[$e]);
  341. $_t = explode(" ", $e);
  342. foreach($_t as $new) {
  343. $new = trim($new);
  344. if(!empty($new)) {
  345. $ret[$new] = $new;
  346. }
  347. }
  348. }
  349. }
  350. }
  351. else {
  352. $_t = explode(" ", $string);
  353. foreach($_t as $new) {
  354. $new = trim($new);
  355. if(!empty($new)) {
  356. $ret[$new] = $new;
  357. }
  358. }
  359. }
  360. return $ret;
  361. }
  362. /**
  363. * a very simple HTTP_AUTH authentication.
  364. */
  365. static function simpleAuth() {
  366. if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])
  367. || $_SERVER['PHP_AUTH_USER'] !== FRONTEND_USERNAME || $_SERVER['PHP_AUTH_PW'] !== FRONTEND_PASSWORD
  368. ) {
  369. header('WWW-Authenticate: Basic realm="Insipid edit area"');
  370. header('HTTP/1.0 401 Unauthorized');
  371. echo 'No Access.';
  372. exit;
  373. }
  374. }
  375. /**
  376. * check if we have a valid auth. Nothing more.
  377. * @see Summoner::simpleAuth to trigger the auth
  378. * @return bool
  379. */
  380. static function simpleAuthCheck() {
  381. if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])
  382. && $_SERVER['PHP_AUTH_USER'] === FRONTEND_USERNAME && $_SERVER['PHP_AUTH_PW'] === FRONTEND_PASSWORD
  383. ) {
  384. return true;
  385. }
  386. return false;
  387. }
  388. /**
  389. * Checks if in the given urlstring a scheme is existent. If not add http:// to it
  390. * @param $urlString
  391. * @return string
  392. */
  393. static function addSchemeToURL($urlString) {
  394. $ret = $urlString;
  395. if(empty(parse_url($ret, PHP_URL_SCHEME))) {
  396. $ret = "http://".$ret;
  397. }
  398. return $ret;
  399. }
  400. /**
  401. * retrieve the folder size with its children of given folder path
  402. * @param $folder
  403. * @return false|int
  404. */
  405. static function folderSize($folder) {
  406. $ret = 0;
  407. if(file_exists($folder) && is_readable($folder)) {
  408. foreach (glob(rtrim($folder, '/') . '/*', GLOB_NOSORT) as $each) {
  409. $ret += is_file($each) ? filesize($each) : self::folderSize($each);
  410. }
  411. }
  412. return $ret;
  413. }
  414. /**
  415. * Calculate the given byte size in more human readable format.
  416. * @param $size
  417. * @param string $unit
  418. * @return string
  419. */
  420. static function humanFileSize($size,$unit="") {
  421. $ret = number_format($size)." bytes";
  422. if((!$unit && $size >= 1<<30) || $unit == "GB") {
  423. $ret = number_format($size / (1 << 30), 2)."GB";
  424. }
  425. elseif((!$unit && $size >= 1<<20) || $unit == "MB") {
  426. $ret = number_format($size / (1 << 20), 2) . "MB";
  427. }
  428. elseif( (!$unit && $size >= 1<<10) || $unit == "KB") {
  429. $ret = number_format($size / (1 << 10), 2) . "KB";
  430. }
  431. return $ret;
  432. }
  433. /**
  434. * delete and/or empty a directory
  435. *
  436. * $empty = true => empty the directory but do not delete it
  437. *
  438. * @param string $directory
  439. * @param boolean $empty
  440. * @param int $fTime If not false remove files older then this value in sec.
  441. * @return boolean
  442. */
  443. static function recursive_remove_directory($directory,$empty=false,$fTime=0) {
  444. if(substr($directory,-1) == '/') {
  445. $directory = substr($directory,0,-1);
  446. }
  447. if(!file_exists($directory) || !is_dir($directory)) {
  448. return false;
  449. }
  450. elseif(!is_readable($directory)) {
  451. return false;
  452. }
  453. else {
  454. $handle = opendir($directory);
  455. // and scan through the items inside
  456. while (false !== ($item = readdir($handle))) {
  457. if($item[0] != '.') {
  458. $path = $directory.'/'.$item;
  459. if(is_dir($path)) {
  460. recursive_remove_directory($path);
  461. }
  462. else {
  463. if($fTime !== false && is_int($fTime)) {
  464. $ft = filemtime($path);
  465. $offset = time()-$fTime;
  466. if($ft <= $offset) {
  467. unlink($path);
  468. }
  469. }
  470. else {
  471. unlink($path);
  472. }
  473. }
  474. }
  475. }
  476. closedir($handle);
  477. if($empty === false) {
  478. if(!rmdir($directory)) {
  479. return false;
  480. }
  481. }
  482. return true;
  483. }
  484. }
  485. }