summoner.class.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2021 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): bool {
  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. * execute a curl call to the given $url
  115. *
  116. * @param string $url The request url
  117. * @param bool $port
  118. * @return string
  119. */
  120. static function curlCall(string $url, $port=false): string {
  121. $ret = '';
  122. $ch = curl_init();
  123. curl_setopt($ch, CURLOPT_URL, $url);
  124. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  125. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  126. curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  127. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  128. curl_setopt($ch, CURLOPT_MAXREDIRS, 2);
  129. curl_setopt($ch, CURLOPT_USERAGENT,self::BROWSER_AGENT_STRING);
  130. // curl_setopt($ch, CURLOPT_VERBOSE, true);
  131. //curl_setopt($ch, CURLOPT_HEADER, true);
  132. if(!empty($port)) {
  133. curl_setopt($ch, CURLOPT_PORT, $port);
  134. }
  135. $do = curl_exec($ch);
  136. if(is_string($do) === true) {
  137. $ret = $do;
  138. }
  139. else {
  140. error_log('ERROR '.var_export(curl_error($ch),true));
  141. }
  142. curl_close($ch);
  143. return $ret;
  144. }
  145. /**
  146. * Download given url to given file
  147. *
  148. * @param string $url
  149. * @param string $whereToStore
  150. * @param bool $port
  151. * @return bool
  152. */
  153. static function downloadFile(string $url, string $whereToStore, $port=false): bool {
  154. $fh = fopen($whereToStore, 'w+');
  155. $ret = false;
  156. if($fh !== false) {
  157. $ch = curl_init($url);
  158. curl_setopt($ch, CURLOPT_FILE, $fh);
  159. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  160. curl_setopt($ch, CURLOPT_TIMEOUT, 30);
  161. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  162. curl_setopt($ch, CURLOPT_MAXREDIRS, 2);
  163. curl_setopt($ch, CURLOPT_USERAGENT, self::BROWSER_AGENT_STRING);
  164. if(!empty($port)) {
  165. curl_setopt($ch, CURLOPT_PORT, $port);
  166. }
  167. curl_exec($ch);
  168. curl_close($ch);
  169. $ret = true;
  170. }
  171. fclose($fh);
  172. return $ret;
  173. }
  174. /**
  175. * simulate the Null coalescing operator in php5
  176. * this only works with arrays and checking if the key is there and echo/return it.
  177. * http://php.net/manual/en/migration70.new-features.php#migration70.new-features.null-coalesce-op
  178. *
  179. * @param array $array
  180. * @param string $key
  181. * @return mixed
  182. */
  183. static function ifset(array $array, string $key) {
  184. return $array[$key] ?? false;
  185. }
  186. /**
  187. * try to gather meta information from given URL
  188. *
  189. * @param string $url
  190. * @return array
  191. */
  192. static function gatherInfoFromURL(string $url): array {
  193. $ret = array();
  194. if(self::validate($url,'url')) {
  195. $data = self::curlCall($url);
  196. if(!empty($data)) {
  197. $ret = self::socialMetaInfos($data);
  198. }
  199. }
  200. return $ret;
  201. }
  202. /**
  203. * get as much as possible social meta infos from given string
  204. * the string is usually a HTML source
  205. *
  206. * @param string $string
  207. * @return array
  208. */
  209. static function socialMetaInfos(string $string): array {
  210. #http://www.w3bees.com/2013/11/fetch-facebook-og-meta-tags-with-php.html
  211. #http://www.9lessons.info/2014/01/social-meta-tags-for-google-twitter-and.html
  212. #http://ogp.me/
  213. #https://moz.com/blog/meta-data-templates-123
  214. $dom = new DomDocument;
  215. # surpress invalid html warnings
  216. @$dom->loadHTML($string);
  217. $xpath = new DOMXPath($dom);
  218. $metas = $xpath->query('//*/meta');
  219. $mediaInfos = array();
  220. # meta tags
  221. foreach($metas as $meta) {
  222. if($meta->getAttribute('property')) {
  223. $prop = $meta->getAttribute('property');
  224. $prop = mb_strtolower($prop);
  225. # minimum required information
  226. # http://ogp.me/#metadata
  227. if($prop == "og:title") {
  228. $mediaInfos['title'] = $meta->getAttribute('content');
  229. }
  230. elseif($prop == "og:image") {
  231. $mediaInfos['image'] = $meta->getAttribute('content');
  232. }
  233. elseif($prop == "og:url") {
  234. $mediaInfos['link'] = $meta->getAttribute('content');
  235. }
  236. elseif($prop == "og:description") {
  237. $mediaInfos['description'] = $meta->getAttribute('content');
  238. }
  239. }
  240. elseif($meta->getAttribute('name')) {
  241. $name = $meta->getAttribute('name');
  242. $name = mb_strtolower($name);
  243. # twitter
  244. # https://dev.twitter.com/cards/overview
  245. if($name == "twitter:title") {
  246. $mediaInfos['title'] = $meta->getAttribute('content');
  247. }
  248. elseif($name == "twitter:description") {
  249. $mediaInfos['description'] = $meta->getAttribute('content');
  250. }
  251. elseif($name == "twitter:image") {
  252. $mediaInfos['image'] = $meta->getAttribute('content');
  253. }
  254. elseif($name == "description") {
  255. $mediaInfos['description'] = $meta->getAttribute('content');
  256. }
  257. }
  258. elseif($meta->getAttribute('itemprop')) {
  259. $itemprop = $meta->getAttribute('itemprop');
  260. $itemprop = mb_strtolower($itemprop);
  261. # google plus
  262. if($itemprop == "name") {
  263. $mediaInfos['title'] = $meta->getAttribute('content');
  264. }
  265. elseif($itemprop == "description") {
  266. $mediaInfos['description'] = $meta->getAttribute('content');
  267. }
  268. elseif($itemprop == "image") {
  269. $mediaInfos['image'] = $meta->getAttribute('content');
  270. }
  271. }
  272. }
  273. if(!isset($mediaInfos['title'])) {
  274. $titleDom = $xpath->query('//title');
  275. $mediaInfos['title'] = $titleDom->item(0)->nodeValue;
  276. }
  277. return $mediaInfos;
  278. }
  279. /**
  280. * at creation a category or tag can be a string with multiple values.
  281. * separated with space or ,
  282. * category and tag is a single string without any separators
  283. *
  284. * @param string $string
  285. * @return array
  286. */
  287. static function prepareTagOrCategoryStr(string $string): array {
  288. $ret = array();
  289. $_ret = array();
  290. $string = trim($string, ", ");
  291. if(strstr($string, ",")) {
  292. $_t = explode(",", $string);
  293. foreach($_t as $n) {
  294. $_ret[$n] = $n;
  295. }
  296. unset($_t);
  297. unset($n);
  298. foreach($_ret as $e) {
  299. if(strstr($e, " ")) {
  300. unset($ret[$e]);
  301. $_t = explode(" ", $e);
  302. foreach($_t as $new) {
  303. $new = trim($new);
  304. $_c = self::validate($new,'nospace');
  305. if(!empty($new) && $_c === true) {
  306. $ret[$new] = $new;
  307. }
  308. }
  309. }
  310. else {
  311. $new = trim($e);
  312. $_c = self::validate($new,'nospace');
  313. if(!empty($new) && $_c === true) {
  314. $ret[$new] = $new;
  315. }
  316. }
  317. }
  318. }
  319. else {
  320. $_t = explode(" ", $string);
  321. foreach($_t as $new) {
  322. $new = trim($new);
  323. $_c = self::validate($new,'nospace');
  324. if(!empty($new) && $_c === true) {
  325. $ret[$new] = $new;
  326. }
  327. }
  328. }
  329. return $ret;
  330. }
  331. /**
  332. * a very simple HTTP_AUTH authentication.
  333. */
  334. static function simpleAuth() {
  335. if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])
  336. || $_SERVER['PHP_AUTH_USER'] !== FRONTEND_USERNAME || $_SERVER['PHP_AUTH_PW'] !== FRONTEND_PASSWORD
  337. ) {
  338. header('WWW-Authenticate: Basic realm="Insipid edit area"');
  339. header('HTTP/1.0 401 Unauthorized');
  340. echo 'No Access.';
  341. exit;
  342. }
  343. }
  344. /**
  345. * check if we have a valid auth. Nothing more.
  346. *
  347. * @see Summoner::simpleAuth to trigger the auth
  348. * @return bool
  349. */
  350. static function simpleAuthCheck(): bool {
  351. if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])
  352. && $_SERVER['PHP_AUTH_USER'] === FRONTEND_USERNAME && $_SERVER['PHP_AUTH_PW'] === FRONTEND_PASSWORD
  353. ) {
  354. return true;
  355. }
  356. return false;
  357. }
  358. /**
  359. * Checks if in the given urlstring a scheme is existent. If not add http:// to it
  360. *
  361. * @param string $urlString
  362. * @return string
  363. */
  364. static function addSchemeToURL($urlString): string {
  365. $ret = $urlString;
  366. if(empty(parse_url($ret, PHP_URL_SCHEME))) {
  367. $ret = "http://".$ret;
  368. }
  369. return $ret;
  370. }
  371. /**
  372. * retrieve the folder size with its children of given folder path
  373. *
  374. * @param string $folder
  375. * @return int
  376. */
  377. static function folderSize(string $folder): int {
  378. $ret = 0;
  379. if(file_exists($folder) && is_readable($folder)) {
  380. foreach (glob(rtrim($folder, '/') . '/*', GLOB_NOSORT) as $each) {
  381. $ret += is_file($each) ? filesize($each) : self::folderSize($each);
  382. }
  383. }
  384. return $ret;
  385. }
  386. /**
  387. * Calculate the given byte size in more human readable format.
  388. *
  389. * @param integer $size
  390. * @param string $unit
  391. * @return string
  392. */
  393. static function humanFileSize(int $size, $unit=""): string {
  394. $ret = number_format($size)." bytes";
  395. if((!$unit && $size >= 1<<30) || $unit == "GB") {
  396. $ret = number_format($size / (1 << 30), 2)."GB";
  397. }
  398. elseif((!$unit && $size >= 1<<20) || $unit == "MB") {
  399. $ret = number_format($size / (1 << 20), 2) . "MB";
  400. }
  401. elseif( (!$unit && $size >= 1<<10) || $unit == "KB") {
  402. $ret = number_format($size / (1 << 10), 2) . "KB";
  403. }
  404. return $ret;
  405. }
  406. /**
  407. * delete and/or empty a directory
  408. *
  409. * $empty = true => empty the directory but do not delete it
  410. *
  411. * @param string $directory
  412. * @param boolean $empty
  413. * @param int $fTime If not false remove files older then this value in sec.
  414. * @return boolean
  415. */
  416. static function recursive_remove_directory(string $directory, $empty=false, $fTime=0): bool {
  417. if(substr($directory,-1) == '/') {
  418. $directory = substr($directory,0,-1);
  419. }
  420. if(!file_exists($directory) || !is_dir($directory)) {
  421. return false;
  422. }
  423. elseif(!is_readable($directory)) {
  424. return false;
  425. }
  426. else {
  427. $handle = opendir($directory);
  428. // and scan through the items inside
  429. while (false !== ($item = readdir($handle))) {
  430. if($item[0] != '.') {
  431. $path = $directory.'/'.$item;
  432. if(is_dir($path)) {
  433. recursive_remove_directory($path);
  434. }
  435. else {
  436. if($fTime !== false && is_int($fTime)) {
  437. $ft = filemtime($path);
  438. $offset = time()-$fTime;
  439. if($ft <= $offset) {
  440. unlink($path);
  441. }
  442. }
  443. else {
  444. unlink($path);
  445. }
  446. }
  447. }
  448. }
  449. closedir($handle);
  450. if($empty === false) {
  451. if(!rmdir($directory)) {
  452. return false;
  453. }
  454. }
  455. return true;
  456. }
  457. }
  458. /**
  459. * http_build_query with modify array
  460. * modify will add: key AND value not empty
  461. * modify will remove: only key with no value
  462. *
  463. * @param array $array
  464. * @param bool $modify
  465. * @return string
  466. */
  467. static function createFromParameterLinkQuery(array $array, $modify=false): string {
  468. $ret = '';
  469. if(!empty($modify)) {
  470. foreach($modify as $k=>$v) {
  471. if(empty($v)) {
  472. unset($array[$k]);
  473. }
  474. else {
  475. $array[$k] = $v;
  476. }
  477. }
  478. }
  479. if(!empty($array)) {
  480. $ret = http_build_query($array);
  481. }
  482. return $ret;
  483. }
  484. }