PageRenderTime 28ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/web.php

https://github.com/XioNoX/Benevolus
PHP | 509 lines | 380 code | 12 blank | 117 comment | 78 complexity | c04e2ce514819c37df7ddce089b729aa MD5 | raw file
  1. <?php
  2. /**
  3. Web pack for the PHP Fat-Free Framework
  4. The contents of this file are subject to the terms of the GNU General
  5. Public License Version 3.0. You may not use this file except in
  6. compliance with the license. Any of the license terms and conditions
  7. can be waived if you get permission from the copyright holder.
  8. Copyright (c) 2009-2011 F3::Factory
  9. Bong Cosca <bong.cosca@yahoo.com>
  10. @package Expansion
  11. @version 2.0.5
  12. **/
  13. //! Web pack
  14. class Web extends Base {
  15. //@{ Locale-specific error/exception messages
  16. const
  17. TEXT_Minify='Unable to minify %s';
  18. //@}
  19. const
  20. //! Carriage return/line feed sequence
  21. EOL="\r\n";
  22. /**
  23. Return a URL/filesystem-friendly version of string
  24. @return string
  25. @param $text string
  26. @param $maxlen integer
  27. **/
  28. static function slug($text,$maxlen=0) {
  29. $out=preg_replace('/([^\w]|-)+/','-',
  30. trim(strtr(str_replace('\'','',$text),
  31. self::$vars['DIACRITICS'])));
  32. return trim(strtolower($maxlen?substr($out,0,$maxlen):$out),'-');
  33. }
  34. /**
  35. Strip Javascript/CSS files of extraneous whitespaces and comments;
  36. Return combined output as a minified string
  37. @return string
  38. @param $base string
  39. @param $files array
  40. @param $echo bool
  41. @public
  42. **/
  43. static function minify($base,array $files,$echo=TRUE) {
  44. preg_match('/\.(js|css)$/',$files[0],$ext);
  45. if (!$ext[1])
  46. // Not a JavaSript/CSS file
  47. return $echo?NULL:FALSE;
  48. $mime=array(
  49. 'js'=>'application/x-javascript',
  50. 'css'=>'text/css'
  51. );
  52. $path=self::fixslashes($base);
  53. foreach ($files as $file)
  54. if (!is_file($path.$file)) {
  55. trigger_error(sprintf(self::TEXT_Minify,$file));
  56. return $echo?NULL:FALSE;
  57. }
  58. $src='';
  59. foreach ($files as $file) {
  60. $stats=&self::ref('STATS');
  61. $stats['FILES']['minified']
  62. [basename($file)]=filesize($path.$file);
  63. // Rewrite relative URLs in CSS
  64. $src.=preg_replace_callback(
  65. '/\b(?<=url)\(([\"\'])*([^\1]+?)\1\)/',
  66. function($url) use($path,$file) {
  67. // Ignore absolute URLs
  68. if (preg_match('/https?:/',$url[2]))
  69. return $url[0];
  70. $fdir=dirname($file);
  71. $rewrite=explode(
  72. '/',$path.($fdir!='.'?$fdir.'/':'').$url[2]
  73. );
  74. $i=0;
  75. while ($i<count($rewrite))
  76. // Analyze each URL segment
  77. if ($i>0 &&
  78. $rewrite[$i]=='..' &&
  79. $rewrite[$i-1]!='..') {
  80. // Simplify URL
  81. unset($rewrite[$i],$rewrite[$i-1]);
  82. $rewrite=array_values($rewrite);
  83. $i--;
  84. }
  85. else
  86. $i++;
  87. // Reconstruct simplified URL
  88. return
  89. '('.implode('/',array_merge($rewrite,array())).')';
  90. },
  91. // Retrieve CSS/Javascript file
  92. self::getfile($path.$file)
  93. );
  94. }
  95. $ptr=0;
  96. $dst='';
  97. while ($ptr<strlen($src)) {
  98. if ($src[$ptr]=='/') {
  99. // Presume it's a regex pattern
  100. $regex=TRUE;
  101. if ($ptr>0) {
  102. // Backtrack and validate
  103. $ofs=$ptr;
  104. while ($ofs>0) {
  105. $ofs--;
  106. // Pattern should be preceded by parenthesis,
  107. // colon or assignment operator
  108. if ($src[$ofs]=='(' || $src[$ofs]==':' ||
  109. $src[$ofs]=='=') {
  110. while ($ptr<strlen($src)) {
  111. $str=strstr(substr($src,$ptr+1),'/',TRUE);
  112. if (!strlen($str) && $src[$ptr-1]!='/' ||
  113. strpos($str,"\n")!==FALSE) {
  114. // Not a regex pattern
  115. $regex=FALSE;
  116. break;
  117. }
  118. $dst.='/'.$str;
  119. $ptr+=strlen($str)+1;
  120. if ($src[$ptr-1]!='\\' ||
  121. $src[$ptr-2]=='\\') {
  122. $dst.='/';
  123. $ptr++;
  124. break;
  125. }
  126. }
  127. break;
  128. }
  129. elseif ($src[$ofs]!="\t" && $src[$ofs]!=' ') {
  130. // Not a regex pattern
  131. $regex=FALSE;
  132. break;
  133. }
  134. }
  135. if ($regex && $ofs<1)
  136. $regex=FALSE;
  137. }
  138. if (!$regex || $ptr<1) {
  139. if (substr($src,$ptr+1,2)=='*@') {
  140. // Conditional block
  141. $str=strstr(substr($src,$ptr+3),'@*/',TRUE);
  142. $dst.='/*@'.$str.$src[$ptr].'@*/';
  143. $ptr+=strlen($str)+6;
  144. }
  145. elseif ($src[$ptr+1]=='*') {
  146. // Multiline comment
  147. $str=strstr(substr($src,$ptr+2),'*/',TRUE);
  148. $ptr+=strlen($str)+4;
  149. }
  150. elseif ($src[$ptr+1]=='/') {
  151. // Single-line comment
  152. $str=strstr(substr($src,$ptr+2),"\n",TRUE);
  153. $ptr+=strlen($str)+2;
  154. }
  155. else {
  156. // Division operator
  157. $dst.=$src[$ptr];
  158. $ptr++;
  159. }
  160. }
  161. continue;
  162. }
  163. if ($src[$ptr]=='\'' || $src[$ptr]=='"') {
  164. $match=$src[$ptr];
  165. // String literal
  166. while ($ptr<strlen($src)) {
  167. $str=strstr(substr($src,$ptr+1),$src[$ptr],TRUE);
  168. $dst.=$match.$str;
  169. $ptr+=strlen($str)+1;
  170. if ($src[$ptr-1]!='\\' || $src[$ptr-2]=='\\') {
  171. $dst.=$match;
  172. $ptr++;
  173. break;
  174. }
  175. }
  176. continue;
  177. }
  178. if (ctype_space($src[$ptr])) {
  179. $last=substr($dst,-1);
  180. $ofs=$ptr+1;
  181. if ($ofs+1<strlen($src)) {
  182. while (ctype_space($src[$ofs]))
  183. $ofs++;
  184. if (preg_match('/[\w%][\w'.
  185. // IE is sensitive about certain spaces in CSS
  186. ($ext[1]=='css'?'#\-*\.':'').'$]/',$last.$src[$ofs]))
  187. $dst.=$src[$ptr];
  188. }
  189. $ptr=$ofs;
  190. }
  191. else {
  192. $dst.=$src[$ptr];
  193. $ptr++;
  194. }
  195. }
  196. if ($echo) {
  197. if (PHP_SAPI!='cli' && !headers_sent())
  198. header(self::HTTP_Content.': '.$mime[$ext[1]].'; '.
  199. 'charset='.self::$vars['ENCODING']);
  200. echo $dst;
  201. die;
  202. }
  203. return $dst;
  204. }
  205. /**
  206. Convert seconds to frequency (in words)
  207. @return integer
  208. @param $secs string
  209. @public
  210. **/
  211. static function frequency($secs) {
  212. $freq['hourly']=3600;
  213. $freq['daily']=86400;
  214. $freq['weekly']=604800;
  215. $freq['monthly']=2592000;
  216. foreach ($freq as $key=>$val)
  217. if ($secs<=$val)
  218. return $key;
  219. return 'yearly';
  220. }
  221. /**
  222. Send HTTP/S request to another host; Follow 30x redirects (default);
  223. Forward headers received (if specified) and return content
  224. @return mixed
  225. @param $pattern string
  226. @param $query string
  227. @param $reqhdrs array
  228. @param $follow bool
  229. @param $forward bool
  230. @public
  231. **/
  232. static function http(
  233. $pattern,$query='',$reqhdrs=array(),$follow=TRUE,$forward=FALSE) {
  234. self::$vars['HEADERS']=array();
  235. // Check if valid route pattern
  236. list($method,$route)=explode(' ',$pattern,2);
  237. // Content divider
  238. $div=chr(0);
  239. $url=parse_url($route);
  240. if (!isset($url['path']))
  241. // Set to Web root
  242. $url['path']='/';
  243. if ($method!='GET') {
  244. if (isset($url['query']) && $url['query']) {
  245. // Non-GET method; Query is distinct from URI
  246. $query=$url['query'];
  247. $url['query']='';
  248. }
  249. }
  250. else {
  251. if ($query) {
  252. // GET method; Query is integral part of URI
  253. $url['query']=$query;
  254. $query='';
  255. }
  256. }
  257. // Set up host name and TCP port for socket connection
  258. if (isset($url['scheme']) && $url['scheme']=='https') {
  259. if (!isset($url['port']))
  260. $url['port']=443;
  261. $target='ssl://'.$url['host'];
  262. }
  263. else {
  264. if (!isset($url['port']))
  265. $url['port']=80;
  266. $target=$url['host'];
  267. }
  268. $socket=@fsockopen($target,$url['port'],$errno,$text);
  269. if (!$socket) {
  270. // Can't establish connection
  271. trigger_error($text);
  272. return FALSE;
  273. }
  274. // Set connection timeout parameters
  275. stream_set_blocking($socket,TRUE);
  276. stream_set_timeout($socket,ini_get('default_socket_timeout'));
  277. // Send HTTP request
  278. fputs($socket,
  279. $method.' '.(isset($url['path'])?$url['path']:'').
  280. (isset($url['query']) && $url['query']?
  281. ('?'.$url['query']):'').' '.
  282. 'HTTP/1.0'.self::EOL.
  283. self::HTTP_Host.': '.$url['host'].self::EOL.
  284. self::HTTP_Agent.': Mozilla/5.0 '.
  285. '(compatible;'.PHP_OS.')'.self::EOL.
  286. ($reqhdrs?
  287. (implode(self::EOL,$reqhdrs).self::EOL):'').
  288. ($method!='GET'?(
  289. 'Content-Type: '.
  290. 'application/x-www-form-urlencoded'.self::EOL.
  291. 'Content-Length: '.strlen($query).self::EOL):'').
  292. self::HTTP_AcceptEnc.': gzip'.self::EOL.
  293. self::HTTP_Connect.': close'.self::EOL.self::EOL.
  294. $query.self::EOL.self::EOL
  295. );
  296. $found=FALSE;
  297. $expires=FALSE;
  298. $gzip=FALSE;
  299. $rcvhdrs='';
  300. $info=stream_get_meta_data($socket);
  301. // Get headers and response
  302. $response='';
  303. while (!feof($socket) && !$info['timed_out']) {
  304. $response.=fgets($socket,4096); // MDFK97
  305. $info=stream_get_meta_data($socket);
  306. if (!$found && is_int(strpos($response,self::EOL.self::EOL))) {
  307. $found=TRUE;
  308. $rcvhdrs=strstr($response,self::EOL.self::EOL,TRUE);
  309. ob_start();
  310. if ($follow &&
  311. preg_match('/HTTP\/1\.\d\s30\d/',$rcvhdrs)) {
  312. // Redirection
  313. preg_match('/'.self::HTTP_Location.
  314. ':\s*(.+)/',$rcvhdrs,$loc);
  315. return self::http($method.' '.trim($loc[1]),
  316. $query,$reqhdrs);
  317. }
  318. foreach (explode(self::EOL,$rcvhdrs) as $hdr) {
  319. self::$vars['HEADERS'][]=$hdr;
  320. if (PHP_SAPI!='cli' && $forward)
  321. // Forward HTTP header
  322. header($hdr);
  323. elseif (preg_match('/^'.
  324. self::HTTP_Encoding.':\s*.*gzip/',$hdr))
  325. // Uncompress content
  326. $gzip=TRUE;
  327. }
  328. ob_end_flush();
  329. // Split content from HTTP response headers
  330. $response=substr(strstr($response,self::EOL.self::EOL),4);
  331. }
  332. }
  333. fclose($socket);
  334. if ($info['timed_out']) {
  335. trigger_error(self::TEXT_Timeout);
  336. return FALSE;
  337. }
  338. if (PHP_SAPI!='cli') {
  339. if ($gzip)
  340. $response=gzinflate(substr($response,10));
  341. }
  342. // Return content
  343. return $response;
  344. }
  345. /**
  346. Parse each URL recursively and generate sitemap
  347. @param $url string
  348. @public
  349. **/
  350. static function sitemap($url=NULL) {
  351. if (is_null($url))
  352. $url=self::$vars['BASE'].'/';
  353. if ($url[0]=='#' || isset(self::$vars['SITEMAP'][$url]) &&
  354. is_bool(self::$vars['SITEMAP'][$url]['status']))
  355. // Skip
  356. return;
  357. $parse=parse_url($url);
  358. if (isset($parse['scheme']) &&
  359. !preg_match('/https?:/',$parse['scheme']))
  360. return;
  361. $response=self::http('GET '.self::$vars['PROTOCOL'].'://'.
  362. $_SERVER['SERVER_NAME'].$url);
  363. if (!$response) {
  364. // No HTTP response
  365. self::$vars['SITEMAP'][$url]['status']=FALSE;
  366. return;
  367. }
  368. foreach (self::$vars['HEADERS'] as $header)
  369. if (preg_match('/HTTP\/\d\.\d\s(\d+)/',$header,$match) &&
  370. $match[1]!=200) {
  371. self::$vars['SITEMAP'][$url]['status']=FALSE;
  372. return;
  373. }
  374. $doc=new DOMDocument('1.0',self::$vars['ENCODING']);
  375. // Suppress errors caused by invalid HTML structures
  376. libxml_use_internal_errors(TRUE);
  377. if ($doc->loadHTML($response)) {
  378. // Valid HTML; add to sitemap
  379. if (!self::$vars['SITEMAP'][$url]['level'])
  380. // Web root
  381. self::$vars['SITEMAP'][$url]['level']=0;
  382. self::$vars['SITEMAP'][$url]['status']=TRUE;
  383. self::$vars['SITEMAP'][$url]['mod']=time();
  384. self::$vars['SITEMAP'][$url]['freq']=0;
  385. // Cached page
  386. $hash='url.'.self::hash('GET '.$url);
  387. $cached=Cache::cached($hash);
  388. if ($cached) {
  389. self::$vars['SITEMAP'][$url]['mod']=$cached['time'];
  390. self::$vars['SITEMAP'][$url]['freq']=$_SERVER['REQUEST_TTL'];
  391. }
  392. // Parse all links
  393. $links=$doc->getElementsByTagName('a');
  394. foreach ($links as $link) {
  395. $ref=$link->getAttribute('href');
  396. preg_match('/^http[s]*:\/\/([^\/$]+)/',$ref,$host);
  397. if (!empty($host) && $host[1]!=$_SERVER['SERVER_NAME'] ||
  398. !$ref || ($rel=$link->getAttribute('rel')) &&
  399. preg_match('/nofollow/',$rel))
  400. // Don't crawl this link!
  401. continue;
  402. if (!isset(self::$vars['SITEMAP'][$ref]))
  403. self::$vars['SITEMAP'][$ref]=array(
  404. 'level'=>self::$vars['SITEMAP'][$url]['level']+1,
  405. 'status'=>NULL
  406. );
  407. }
  408. // Parse each link
  409. $map=array_keys(self::$vars['SITEMAP']);
  410. array_walk($map,'self::sitemap');
  411. }
  412. unset($doc);
  413. if (!self::$vars['SITEMAP'][$url]['level']) {
  414. // Finalize sitemap
  415. $depth=1;
  416. while ($ref=current(self::$vars['SITEMAP']))
  417. // Find deepest level while iterating
  418. if (!$ref['status'])
  419. // Remove remote URLs and pages with errors
  420. unset(self::$vars['SITEMAP']
  421. [key(self::$vars['SITEMAP'])]);
  422. else {
  423. $depth=max($depth,$ref['level']+1);
  424. next(self::$vars['SITEMAP']);
  425. }
  426. // Create XML document
  427. $xml=simplexml_load_string(
  428. '<?xml version="1.0" encoding="'.
  429. self::$vars['ENCODING'].'"?>'.
  430. '<urlset xmlns='.
  431. '"http://www.sitemaps.org/schemas/sitemap/0.9"'.
  432. '/>'
  433. );
  434. $host=self::$vars['PROTOCOL'].'://'.$_SERVER['SERVER_NAME'];
  435. foreach (self::$vars['SITEMAP'] as $key=>$ref) {
  436. // Add new URL
  437. $item=$xml->addChild('url');
  438. // Add URL elements
  439. $item->addChild('loc',$host.$key);
  440. $item->addChild('lastMod',gmdate('c',$ref['mod']));
  441. $item->addChild('changefreq',
  442. self::frequency($ref['freq']));
  443. $item->addChild('priority',
  444. sprintf('%1.1f',1-$ref['level']/$depth));
  445. }
  446. // Send output
  447. if (PHP_SAPI!='cli' && !headers_sent())
  448. header(self::HTTP_Content.': application/xml; '.
  449. 'charset='.self::$vars['ENCODING']);
  450. $xml=dom_import_simplexml($xml)->ownerDocument;
  451. $xml->formatOutput=TRUE;
  452. echo $xml->saveXML();
  453. die;
  454. }
  455. }
  456. /**
  457. Return TRUE if HTTP request origin is AJAX
  458. @return bool
  459. @public
  460. **/
  461. static function isajax() {
  462. return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
  463. $_SERVER['HTTP_X_REQUESTED_WITH']=='XMLHttpRequest';
  464. }
  465. /**
  466. Class initializer
  467. @public
  468. **/
  469. static function onload() {
  470. if (!extension_loaded('sockets'))
  471. // Sockets extension required
  472. trigger_error(sprintf(self::TEXT_PHPExt,'sockets'));
  473. // Default translations
  474. $diacritics=array(
  475. 'À'=>'A','Á'=>'A','Â'=>'A','Ã'=>'A','Å'=>'A','Ä'=>'A','Æ'=>'AE',
  476. 'à'=>'a','á'=>'a','â'=>'a','ã'=>'a','å'=>'a','ä'=>'a','æ'=>'ae',
  477. 'Þ'=>'B','þ'=>'b','Č'=>'C','Ć'=>'C','Ç'=>'C','č'=>'c','ć'=>'c',
  478. 'ç'=>'c','Ď'=>'D','ð'=>'d','ď'=>'d','Đ'=>'Dj','đ'=>'dj','È'=>'E',
  479. 'É'=>'E','Ê'=>'E','Ë'=>'E','è'=>'e','é'=>'e','ê'=>'e','ë'=>'e',
  480. 'Ì'=>'I','Í'=>'I','Î'=>'I','Ï'=>'I','ì'=>'i','í'=>'i','î'=>'i',
  481. 'ï'=>'i','Ľ'=>'L','ľ'=>'l','Ñ'=>'N','Ň'=>'N','ñ'=>'n','ň'=>'n',
  482. 'Ò'=>'O','Ó'=>'O','Ô'=>'O','Õ'=>'O','Ø'=>'O','Ö'=>'O','Œ'=>'OE',
  483. 'ð'=>'o','ò'=>'o','ó'=>'o','ô'=>'o','õ'=>'o','ö'=>'o','œ'=>'oe',
  484. 'ø'=>'o','Ŕ'=>'R','Ř'=>'R','ŕ'=>'r','ř'=>'r','Š'=>'S','š'=>'s',
  485. 'ß'=>'ss','Ť'=>'T','ť'=>'t','Ù'=>'U','Ú'=>'U','Û'=>'U','Ü'=>'U',
  486. 'Ů'=>'U','ù'=>'u','ú'=>'u','û'=>'u','ü'=>'u','ů'=>'u','Ý'=>'Y',
  487. 'Ÿ'=>'Y','ý'=>'y','ý'=>'y','ÿ'=>'y','Ž'=>'Z','ž'=>'z'
  488. );
  489. self::$vars['DIACRITICS']=isset(self::$vars['DIACRITICS'])?
  490. array_merge($diacritics,self::$vars['DIACRITICS']):$diacritics;
  491. // Site structure
  492. self::$vars['SITEMAP']=NULL;
  493. }
  494. }