/lib/web.php
PHP | 509 lines | 380 code | 12 blank | 117 comment | 78 complexity | c04e2ce514819c37df7ddce089b729aa MD5 | raw file
- <?php
- /**
- Web pack for the PHP Fat-Free Framework
- The contents of this file are subject to the terms of the GNU General
- Public License Version 3.0. You may not use this file except in
- compliance with the license. Any of the license terms and conditions
- can be waived if you get permission from the copyright holder.
- Copyright (c) 2009-2011 F3::Factory
- Bong Cosca <bong.cosca@yahoo.com>
- @package Expansion
- @version 2.0.5
- **/
- //! Web pack
- class Web extends Base {
- //@{ Locale-specific error/exception messages
- const
- TEXT_Minify='Unable to minify %s';
- //@}
- const
- //! Carriage return/line feed sequence
- EOL="\r\n";
- /**
- Return a URL/filesystem-friendly version of string
- @return string
- @param $text string
- @param $maxlen integer
- **/
- static function slug($text,$maxlen=0) {
- $out=preg_replace('/([^\w]|-)+/','-',
- trim(strtr(str_replace('\'','',$text),
- self::$vars['DIACRITICS'])));
- return trim(strtolower($maxlen?substr($out,0,$maxlen):$out),'-');
- }
- /**
- Strip Javascript/CSS files of extraneous whitespaces and comments;
- Return combined output as a minified string
- @return string
- @param $base string
- @param $files array
- @param $echo bool
- @public
- **/
- static function minify($base,array $files,$echo=TRUE) {
- preg_match('/\.(js|css)$/',$files[0],$ext);
- if (!$ext[1])
- // Not a JavaSript/CSS file
- return $echo?NULL:FALSE;
- $mime=array(
- 'js'=>'application/x-javascript',
- 'css'=>'text/css'
- );
- $path=self::fixslashes($base);
- foreach ($files as $file)
- if (!is_file($path.$file)) {
- trigger_error(sprintf(self::TEXT_Minify,$file));
- return $echo?NULL:FALSE;
- }
- $src='';
- foreach ($files as $file) {
- $stats=&self::ref('STATS');
- $stats['FILES']['minified']
- [basename($file)]=filesize($path.$file);
- // Rewrite relative URLs in CSS
- $src.=preg_replace_callback(
- '/\b(?<=url)\(([\"\'])*([^\1]+?)\1\)/',
- function($url) use($path,$file) {
- // Ignore absolute URLs
- if (preg_match('/https?:/',$url[2]))
- return $url[0];
- $fdir=dirname($file);
- $rewrite=explode(
- '/',$path.($fdir!='.'?$fdir.'/':'').$url[2]
- );
- $i=0;
- while ($i<count($rewrite))
- // Analyze each URL segment
- if ($i>0 &&
- $rewrite[$i]=='..' &&
- $rewrite[$i-1]!='..') {
- // Simplify URL
- unset($rewrite[$i],$rewrite[$i-1]);
- $rewrite=array_values($rewrite);
- $i--;
- }
- else
- $i++;
- // Reconstruct simplified URL
- return
- '('.implode('/',array_merge($rewrite,array())).')';
- },
- // Retrieve CSS/Javascript file
- self::getfile($path.$file)
- );
- }
- $ptr=0;
- $dst='';
- while ($ptr<strlen($src)) {
- if ($src[$ptr]=='/') {
- // Presume it's a regex pattern
- $regex=TRUE;
- if ($ptr>0) {
- // Backtrack and validate
- $ofs=$ptr;
- while ($ofs>0) {
- $ofs--;
- // Pattern should be preceded by parenthesis,
- // colon or assignment operator
- if ($src[$ofs]=='(' || $src[$ofs]==':' ||
- $src[$ofs]=='=') {
- while ($ptr<strlen($src)) {
- $str=strstr(substr($src,$ptr+1),'/',TRUE);
- if (!strlen($str) && $src[$ptr-1]!='/' ||
- strpos($str,"\n")!==FALSE) {
- // Not a regex pattern
- $regex=FALSE;
- break;
- }
- $dst.='/'.$str;
- $ptr+=strlen($str)+1;
- if ($src[$ptr-1]!='\\' ||
- $src[$ptr-2]=='\\') {
- $dst.='/';
- $ptr++;
- break;
- }
- }
- break;
- }
- elseif ($src[$ofs]!="\t" && $src[$ofs]!=' ') {
- // Not a regex pattern
- $regex=FALSE;
- break;
- }
- }
- if ($regex && $ofs<1)
- $regex=FALSE;
- }
- if (!$regex || $ptr<1) {
- if (substr($src,$ptr+1,2)=='*@') {
- // Conditional block
- $str=strstr(substr($src,$ptr+3),'@*/',TRUE);
- $dst.='/*@'.$str.$src[$ptr].'@*/';
- $ptr+=strlen($str)+6;
- }
- elseif ($src[$ptr+1]=='*') {
- // Multiline comment
- $str=strstr(substr($src,$ptr+2),'*/',TRUE);
- $ptr+=strlen($str)+4;
- }
- elseif ($src[$ptr+1]=='/') {
- // Single-line comment
- $str=strstr(substr($src,$ptr+2),"\n",TRUE);
- $ptr+=strlen($str)+2;
- }
- else {
- // Division operator
- $dst.=$src[$ptr];
- $ptr++;
- }
- }
- continue;
- }
- if ($src[$ptr]=='\'' || $src[$ptr]=='"') {
- $match=$src[$ptr];
- // String literal
- while ($ptr<strlen($src)) {
- $str=strstr(substr($src,$ptr+1),$src[$ptr],TRUE);
- $dst.=$match.$str;
- $ptr+=strlen($str)+1;
- if ($src[$ptr-1]!='\\' || $src[$ptr-2]=='\\') {
- $dst.=$match;
- $ptr++;
- break;
- }
- }
- continue;
- }
- if (ctype_space($src[$ptr])) {
- $last=substr($dst,-1);
- $ofs=$ptr+1;
- if ($ofs+1<strlen($src)) {
- while (ctype_space($src[$ofs]))
- $ofs++;
- if (preg_match('/[\w%][\w'.
- // IE is sensitive about certain spaces in CSS
- ($ext[1]=='css'?'#\-*\.':'').'$]/',$last.$src[$ofs]))
- $dst.=$src[$ptr];
- }
- $ptr=$ofs;
- }
- else {
- $dst.=$src[$ptr];
- $ptr++;
- }
- }
- if ($echo) {
- if (PHP_SAPI!='cli' && !headers_sent())
- header(self::HTTP_Content.': '.$mime[$ext[1]].'; '.
- 'charset='.self::$vars['ENCODING']);
- echo $dst;
- die;
- }
- return $dst;
- }
- /**
- Convert seconds to frequency (in words)
- @return integer
- @param $secs string
- @public
- **/
- static function frequency($secs) {
- $freq['hourly']=3600;
- $freq['daily']=86400;
- $freq['weekly']=604800;
- $freq['monthly']=2592000;
- foreach ($freq as $key=>$val)
- if ($secs<=$val)
- return $key;
- return 'yearly';
- }
- /**
- Send HTTP/S request to another host; Follow 30x redirects (default);
- Forward headers received (if specified) and return content
- @return mixed
- @param $pattern string
- @param $query string
- @param $reqhdrs array
- @param $follow bool
- @param $forward bool
- @public
- **/
- static function http(
- $pattern,$query='',$reqhdrs=array(),$follow=TRUE,$forward=FALSE) {
- self::$vars['HEADERS']=array();
- // Check if valid route pattern
- list($method,$route)=explode(' ',$pattern,2);
- // Content divider
- $div=chr(0);
- $url=parse_url($route);
- if (!isset($url['path']))
- // Set to Web root
- $url['path']='/';
- if ($method!='GET') {
- if (isset($url['query']) && $url['query']) {
- // Non-GET method; Query is distinct from URI
- $query=$url['query'];
- $url['query']='';
- }
- }
- else {
- if ($query) {
- // GET method; Query is integral part of URI
- $url['query']=$query;
- $query='';
- }
- }
- // Set up host name and TCP port for socket connection
- if (isset($url['scheme']) && $url['scheme']=='https') {
- if (!isset($url['port']))
- $url['port']=443;
- $target='ssl://'.$url['host'];
- }
- else {
- if (!isset($url['port']))
- $url['port']=80;
- $target=$url['host'];
- }
- $socket=@fsockopen($target,$url['port'],$errno,$text);
- if (!$socket) {
- // Can't establish connection
- trigger_error($text);
- return FALSE;
- }
- // Set connection timeout parameters
- stream_set_blocking($socket,TRUE);
- stream_set_timeout($socket,ini_get('default_socket_timeout'));
- // Send HTTP request
- fputs($socket,
- $method.' '.(isset($url['path'])?$url['path']:'').
- (isset($url['query']) && $url['query']?
- ('?'.$url['query']):'').' '.
- 'HTTP/1.0'.self::EOL.
- self::HTTP_Host.': '.$url['host'].self::EOL.
- self::HTTP_Agent.': Mozilla/5.0 '.
- '(compatible;'.PHP_OS.')'.self::EOL.
- ($reqhdrs?
- (implode(self::EOL,$reqhdrs).self::EOL):'').
- ($method!='GET'?(
- 'Content-Type: '.
- 'application/x-www-form-urlencoded'.self::EOL.
- 'Content-Length: '.strlen($query).self::EOL):'').
- self::HTTP_AcceptEnc.': gzip'.self::EOL.
- self::HTTP_Connect.': close'.self::EOL.self::EOL.
- $query.self::EOL.self::EOL
- );
- $found=FALSE;
- $expires=FALSE;
- $gzip=FALSE;
- $rcvhdrs='';
- $info=stream_get_meta_data($socket);
- // Get headers and response
- $response='';
- while (!feof($socket) && !$info['timed_out']) {
- $response.=fgets($socket,4096); // MDFK97
- $info=stream_get_meta_data($socket);
- if (!$found && is_int(strpos($response,self::EOL.self::EOL))) {
- $found=TRUE;
- $rcvhdrs=strstr($response,self::EOL.self::EOL,TRUE);
- ob_start();
- if ($follow &&
- preg_match('/HTTP\/1\.\d\s30\d/',$rcvhdrs)) {
- // Redirection
- preg_match('/'.self::HTTP_Location.
- ':\s*(.+)/',$rcvhdrs,$loc);
- return self::http($method.' '.trim($loc[1]),
- $query,$reqhdrs);
- }
- foreach (explode(self::EOL,$rcvhdrs) as $hdr) {
- self::$vars['HEADERS'][]=$hdr;
- if (PHP_SAPI!='cli' && $forward)
- // Forward HTTP header
- header($hdr);
- elseif (preg_match('/^'.
- self::HTTP_Encoding.':\s*.*gzip/',$hdr))
- // Uncompress content
- $gzip=TRUE;
- }
- ob_end_flush();
- // Split content from HTTP response headers
- $response=substr(strstr($response,self::EOL.self::EOL),4);
- }
- }
- fclose($socket);
- if ($info['timed_out']) {
- trigger_error(self::TEXT_Timeout);
- return FALSE;
- }
- if (PHP_SAPI!='cli') {
- if ($gzip)
- $response=gzinflate(substr($response,10));
- }
- // Return content
- return $response;
- }
- /**
- Parse each URL recursively and generate sitemap
- @param $url string
- @public
- **/
- static function sitemap($url=NULL) {
- if (is_null($url))
- $url=self::$vars['BASE'].'/';
- if ($url[0]=='#' || isset(self::$vars['SITEMAP'][$url]) &&
- is_bool(self::$vars['SITEMAP'][$url]['status']))
- // Skip
- return;
- $parse=parse_url($url);
- if (isset($parse['scheme']) &&
- !preg_match('/https?:/',$parse['scheme']))
- return;
- $response=self::http('GET '.self::$vars['PROTOCOL'].'://'.
- $_SERVER['SERVER_NAME'].$url);
- if (!$response) {
- // No HTTP response
- self::$vars['SITEMAP'][$url]['status']=FALSE;
- return;
- }
- foreach (self::$vars['HEADERS'] as $header)
- if (preg_match('/HTTP\/\d\.\d\s(\d+)/',$header,$match) &&
- $match[1]!=200) {
- self::$vars['SITEMAP'][$url]['status']=FALSE;
- return;
- }
- $doc=new DOMDocument('1.0',self::$vars['ENCODING']);
- // Suppress errors caused by invalid HTML structures
- libxml_use_internal_errors(TRUE);
- if ($doc->loadHTML($response)) {
- // Valid HTML; add to sitemap
- if (!self::$vars['SITEMAP'][$url]['level'])
- // Web root
- self::$vars['SITEMAP'][$url]['level']=0;
- self::$vars['SITEMAP'][$url]['status']=TRUE;
- self::$vars['SITEMAP'][$url]['mod']=time();
- self::$vars['SITEMAP'][$url]['freq']=0;
- // Cached page
- $hash='url.'.self::hash('GET '.$url);
- $cached=Cache::cached($hash);
- if ($cached) {
- self::$vars['SITEMAP'][$url]['mod']=$cached['time'];
- self::$vars['SITEMAP'][$url]['freq']=$_SERVER['REQUEST_TTL'];
- }
- // Parse all links
- $links=$doc->getElementsByTagName('a');
- foreach ($links as $link) {
- $ref=$link->getAttribute('href');
- preg_match('/^http[s]*:\/\/([^\/$]+)/',$ref,$host);
- if (!empty($host) && $host[1]!=$_SERVER['SERVER_NAME'] ||
- !$ref || ($rel=$link->getAttribute('rel')) &&
- preg_match('/nofollow/',$rel))
- // Don't crawl this link!
- continue;
- if (!isset(self::$vars['SITEMAP'][$ref]))
- self::$vars['SITEMAP'][$ref]=array(
- 'level'=>self::$vars['SITEMAP'][$url]['level']+1,
- 'status'=>NULL
- );
- }
- // Parse each link
- $map=array_keys(self::$vars['SITEMAP']);
- array_walk($map,'self::sitemap');
- }
- unset($doc);
- if (!self::$vars['SITEMAP'][$url]['level']) {
- // Finalize sitemap
- $depth=1;
- while ($ref=current(self::$vars['SITEMAP']))
- // Find deepest level while iterating
- if (!$ref['status'])
- // Remove remote URLs and pages with errors
- unset(self::$vars['SITEMAP']
- [key(self::$vars['SITEMAP'])]);
- else {
- $depth=max($depth,$ref['level']+1);
- next(self::$vars['SITEMAP']);
- }
- // Create XML document
- $xml=simplexml_load_string(
- '<?xml version="1.0" encoding="'.
- self::$vars['ENCODING'].'"?>'.
- '<urlset xmlns='.
- '"http://www.sitemaps.org/schemas/sitemap/0.9"'.
- '/>'
- );
- $host=self::$vars['PROTOCOL'].'://'.$_SERVER['SERVER_NAME'];
- foreach (self::$vars['SITEMAP'] as $key=>$ref) {
- // Add new URL
- $item=$xml->addChild('url');
- // Add URL elements
- $item->addChild('loc',$host.$key);
- $item->addChild('lastMod',gmdate('c',$ref['mod']));
- $item->addChild('changefreq',
- self::frequency($ref['freq']));
- $item->addChild('priority',
- sprintf('%1.1f',1-$ref['level']/$depth));
- }
- // Send output
- if (PHP_SAPI!='cli' && !headers_sent())
- header(self::HTTP_Content.': application/xml; '.
- 'charset='.self::$vars['ENCODING']);
- $xml=dom_import_simplexml($xml)->ownerDocument;
- $xml->formatOutput=TRUE;
- echo $xml->saveXML();
- die;
- }
- }
- /**
- Return TRUE if HTTP request origin is AJAX
- @return bool
- @public
- **/
- static function isajax() {
- return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
- $_SERVER['HTTP_X_REQUESTED_WITH']=='XMLHttpRequest';
- }
- /**
- Class initializer
- @public
- **/
- static function onload() {
- if (!extension_loaded('sockets'))
- // Sockets extension required
- trigger_error(sprintf(self::TEXT_PHPExt,'sockets'));
- // Default translations
- $diacritics=array(
- 'À'=>'A','Á'=>'A','Â'=>'A','Ã'=>'A','Å'=>'A','Ä'=>'A','Æ'=>'AE',
- 'à'=>'a','á'=>'a','â'=>'a','ã'=>'a','å'=>'a','ä'=>'a','æ'=>'ae',
- 'Þ'=>'B','þ'=>'b','Č'=>'C','Ć'=>'C','Ç'=>'C','č'=>'c','ć'=>'c',
- 'ç'=>'c','Ď'=>'D','ð'=>'d','ď'=>'d','Đ'=>'Dj','đ'=>'dj','È'=>'E',
- 'É'=>'E','Ê'=>'E','Ë'=>'E','è'=>'e','é'=>'e','ê'=>'e','ë'=>'e',
- 'Ì'=>'I','Í'=>'I','Î'=>'I','Ï'=>'I','ì'=>'i','í'=>'i','î'=>'i',
- 'ï'=>'i','Ľ'=>'L','ľ'=>'l','Ñ'=>'N','Ň'=>'N','ñ'=>'n','ň'=>'n',
- 'Ò'=>'O','Ó'=>'O','Ô'=>'O','Õ'=>'O','Ø'=>'O','Ö'=>'O','Œ'=>'OE',
- 'ð'=>'o','ò'=>'o','ó'=>'o','ô'=>'o','õ'=>'o','ö'=>'o','œ'=>'oe',
- 'ø'=>'o','Ŕ'=>'R','Ř'=>'R','ŕ'=>'r','ř'=>'r','Š'=>'S','š'=>'s',
- 'ß'=>'ss','Ť'=>'T','ť'=>'t','Ù'=>'U','Ú'=>'U','Û'=>'U','Ü'=>'U',
- 'Ů'=>'U','ù'=>'u','ú'=>'u','û'=>'u','ü'=>'u','ů'=>'u','Ý'=>'Y',
- 'Ÿ'=>'Y','ý'=>'y','ý'=>'y','ÿ'=>'y','Ž'=>'Z','ž'=>'z'
- );
- self::$vars['DIACRITICS']=isset(self::$vars['DIACRITICS'])?
- array_merge($diacritics,self::$vars['DIACRITICS']):$diacritics;
- // Site structure
- self::$vars['SITEMAP']=NULL;
- }
- }