/** Parse all directives and render HTML/XML template @return mixed @param $_file string @param $_ishtml boolean @param $_path string @public **/ public static function serve($_file, $_ishtml = TRUE, $_path = NULL) { if (is_null($_path)) { $_path = self::fixSlashes(self::$global['GUI']); } // Remove <F3::exclude> blocks $_text = preg_replace('/<(?:F3:)?exclude>.*?<\\/(?:F3:)?exclude>/is', '', self::embed($_file, $_path)); if (!preg_match('/<.+>/s', $_text)) { // Plain text return self::resolve($_text); } // Initialize XML tree $_tree = new XMLTree('1.0', self::$global['ENCODING']); // Suppress errors caused by invalid HTML structures libxml_use_internal_errors($_ishtml); // Populate XML tree if ($_ishtml) { // HTML template; Keep track of existing tags so those // added by libxml can be removed later $_tags = array('/<!DOCTYPE\\s+html.*?>\\h*\\v*/is', '/<[\\/]?html.*?>\\h*\\v*/is', '/<[\\/]?head.*?>\\h*\\v*/is', '/<[\\/]?body.*?>\\h*\\v*/is'); $_undef = array(); foreach ($_tags as $_regex) { if (!preg_match($_regex, $_text)) { $_undef[] = $_regex; } } $_tree->loadHTML($_text); } else { // XML template $_tree->loadXML($_text, LIBXML_COMPACT | LIBXML_NOERROR); } // Prepare for XML tree traversal $_tree->fragment = $_tree->createDocumentFragment(); $_2ndp = FALSE; $_tree->traverse(function () use($_tree, &$_2ndp) { $_node =& $_tree->nodeptr; $_tag = $_node->tagName; $_next = $_node; $_parent = $_node->parentNode; // Node removal flag $_remove = FALSE; if ($_tag == 'repeat') { // Process <F3:repeat> directive $_inner = $_tree->innerHTML($_node); if ($_inner) { // Process attributes foreach ($_node->attributes as $_attr) { preg_match('/{?@(\\w+(\\[[^\\]]+\\]|\\.\\w+)*)}?/', $_attr->value, $_cap); $_name = $_attr->name; if (!$_cap[1] || isset($_cap[2]) && $_name != 'group') { // Invalid attribute F3::$global['CONTEXT'] = $_attr->value; trigger_error(F3::TEXT_Attrib); return; } elseif ($_name == 'key') { $_kvar = '/@' . $_cap[1] . '\\b/'; } elseif ($_name == 'index') { $_ivar = '/@' . $_cap[1] . '\\b/'; } elseif ($_name == 'group') { $_gcap = '@' . $_cap[1]; $_gvar = F3::get($_cap[1]); } } if (is_array($_gvar) && count($_gvar)) { ob_start(); // Iterate thru group elements foreach (array_keys($_gvar) as $_key) { echo preg_replace($_ivar, $_gcap . '[\'' . $_key . '\']', isset($_kvar) ? preg_replace($_kvar, '\'' . $_key . '\'', $_inner) : $_inner); } $_block = ob_get_contents(); ob_end_clean(); if (strlen($_block)) { $_tree->fragment->appendXML($_block); // Insert fragment before current node $_next = $_parent->insertBefore($_tree->fragment, $_node); } } } $_remove = TRUE; } elseif ($_tag == 'check' && !$_2ndp) { // Found <F3:check> directive $_2ndp = TRUE; } elseif (strpos($_tag, '-')) { // Process custom template directive list($_class, $_method) = explode('-', $_tag); // Invoke template directive handler call_user_func(array($_class, $_method), $_tree); $_remove = TRUE; } if ($_remove) { // Find next node if ($_node->isSameNode($_next)) { $_next = $_node->nextSibling ? $_node->nextSibling : $_parent; } // Remove current node $_parent->removeChild($_node); // Replace with next node $_node = $_next; } }); if ($_2ndp) { // Second pass; Template contains <F3:check> directive $_tree->traverse(function () use($_tree) { $_node =& $_tree->nodeptr; $_parent = $_node->parentNode; $_tag = $_node->tagName; // Process <F3:check> directive if ($_tag == 'check') { $_cond = var_export((bool) F3::resolve(rawurldecode($_node->getAttribute('if'))), TRUE); ob_start(); foreach ($_node->childNodes as $_child) { if ($_child->nodeType == XML_ELEMENT_NODE && preg_match('/' . $_cond . '/i', $_child->tagName)) { echo $_tree->innerHTML($_child) ?: ''; } } $_block = ob_get_contents(); ob_end_clean(); if (strlen($_block)) { $_tree->fragment->appendXML($_block); $_parent->insertBefore($_tree->fragment, $_node); } // Remove current node $_parent->removeChild($_node); // Re-process parent node $_node = $_parent; } }); } if ($_ishtml) { // Fix empty HTML tags $_text = preg_replace('/<((?:' . self::HTML_Tags . ')\\b.*?)\\/?>/is', '<$1/>', self::resolve(rawurldecode($_tree->saveHTML()))); // Remove tags inserted by libxml foreach ($_undef as $_regex) { $_text = preg_replace($_regex, '', $_text); } } else { $_text = self::xmlEncode(self::resolve(rawurldecode($_tree->saveXML())), TRUE); } return $_text; }
/** Parse each URL recursively and generate sitemap @param $_url string @public **/ public static function sitemap($_url = '/') { $_map =& F3::$global['SITEMAP']; if (array_key_exists($_url, $_map) && $_map[$_url]['status'] !== NULL) { // Already crawled return; } preg_match('/^http[s]*:\\/\\/([^\\/$]+)/', $_url, $_host); if (!empty($_host) && $_host[1] != $_SERVER['SERVER_NAME']) { // Remote URL $_map[$_url]['status'] = FALSE; return; } F3::$global['QUIET'] = TRUE; F3::mock('GET ' . $_url); F3::run(); // Check if an error occurred or no HTTP response if (F3::$global['ERROR'] || !F3::$global['RESPONSE']) { $_map[$_url]['status'] = FALSE; // Reset error flag for next page unset(F3::$global['ERROR']); return; } $_doc = new XMLTree('1.0', F3::$global['ENCODING']); if ($_doc->loadHTML(F3::$global['RESPONSE'])) { // Valid HTML; add to sitemap if (!$_map[$_url]['level']) { // Web root $_map[$_url]['level'] = 0; } $_map[$_url]['status'] = TRUE; $_map[$_url]['mod'] = time(); $_map[$_url]['freq'] = 0; // Cached page $_hash = 'url.' . F3::hashCode('GET ' . $_url); $_cached = Cache::cached($_hash); if ($_cached) { $_map[$_url]['mod'] = $_cached['time']; $_map[$_url]['freq'] = $_SERVER['REQUEST_TTL']; } // Parse all links $_links = $_doc->getElementsByTagName('a'); foreach ($_links as $_link) { $_ref = $_link->getAttribute('href'); $_rel = $_link->getAttribute('rel'); if (!$_ref || $_rel && preg_match('/nofollow/', $_rel)) { // Don't crawl this link! continue; } if (!array_key_exists($_ref, $_map)) { $_map[$_ref] = array('level' => $_map[$_url]['level'] + 1, 'status' => NULL); } } // Parse each link array_walk(array_keys($_map), 'self::sitemap'); } unset($_doc); if (!$_map[$_url]['level']) { // Finalize sitemap $_depth = 1; while ($_ref = current($_map)) { // Find depest level while iterating if (!$_ref['status']) { // Remove remote URLs and pages with errors unset($_map[key($_map)]); } else { $_depth = max($_depth, $_ref['level'] + 1); next($_map); } } // Create XML document $_xml = simplexml_load_string('<?xml version="1.0" encoding="' . F3::$global['ENCODING'] . '"?>' . '<urlset xmlns="' . 'http://www.sitemaps.org/schemas/sitemap/0.9' . '"/>'); $_host = 'http://' . $_SERVER['SERVER_NAME']; foreach ($_map as $_key => $_ref) { // Add new URL $_item = $_xml->addChild('url'); // Add URL elements $_item->addChild('loc', $_host . $_key); $_item->addChild('lastMod', date('c', $_ref['mod'])); $_item->addChild('changefreq', self::frequency($_ref['freq'])); $_item->addChild('priority', sprintf('%1.1f', 1 - $_ref['level'] / $_depth)); } // Send output F3::$global['QUIET'] = FALSE; if (PHP_SAPI != 'cli' && !headers_sent()) { header(F3::HTTP_Content . ': application/xhtml+xml; ' . 'charset=' . F3::$global['ENCODING']); } $_xml = dom_import_simplexml($_xml)->ownerDocument; $_xml->formatOutput = TRUE; echo $_xml->saveXML(); } }
/** Parse all directives and render HTML/XML template @return mixed @param $_file string @param $_ishtml boolean @param $_path string @public **/ public static function serve($_file, $_ishtml = TRUE, $_path = NULL) { if (is_null($_path)) { $_path = self::fixSlashes(self::$global['GUI']); } // Remove <F3::exclude> blocks $_text = preg_replace('/<(?:F3:)?exclude>.*?<\\/(?:F3:)?exclude>/is', '', self::embed($_file, $_path)); if (preg_match('/<.+>/s', $_text)) { // Initialize XML tree $_tree = new XMLTree('1.0', self::$global['ENCODING']); // Suppress errors caused by invalid HTML structures libxml_use_internal_errors($_ishtml); // Populate XML tree if ($_ishtml) { // HTML template; Remember defined tags $_deftags = array('/<!DOCTYPE\\s+html.*?>\\h*\\v*/is' => FALSE, '/<[\\/]?html.*?>\\h*\\v*/is' => FALSE, '/<[\\/]?head.*?>\\h*\\v*/is' => FALSE, '/<[\\/]?body.*?>\\h*\\v*/is' => FALSE); foreach ($_deftags as $_regex => &$_tag) { $_tag = preg_match($_regex, $_text); } // Destroy reference unset($_tag); $_tree->loadHTML($_text); } else { // XML template $_tree->loadXML($_text, LIBXML_COMPACT | LIBXML_NOERROR); } // Prepare for XML tree traversal $_tree->fragment = $_tree->createDocumentFragment(); $_2ndp = FALSE; $_tree->traverse(function () use($_tree, &$_2ndp) { $_node =& $_tree->nodeptr; $_tag = $_node->tagName; $_next = $_node; $_parent = $_node->parentNode; // Node removal flag $_remove = FALSE; if ($_tag == 'repeat') { // Process <F3:repeat> directive $_inner = $_tree->innerHTML($_node); if ($_inner) { foreach ($_node->attributes as $_attr) { preg_match('/\\{*@(\\w+\\b(\\[[^\\]]+\\]|\\.\\w+\\b)*)\\}*/', $_attr->value, $_cap); if (!$_cap[1] || isset($_cap[2]) && $_attr->name != 'group') { // Invalid attribute F3::$global['CONTEXT'] = $_attr->value; trigger_error(F3::TEXT_Attrib); return; } if ($_attr->name == 'key') { $_kvar = '/@' . $_cap[1] . '\\b/'; } elseif ($_attr->name == 'index') { $_ivar = '/@' . $_cap[1] . '\\b/'; } elseif ($_attr->name == 'group') { $_gcap = '@' . $_cap[1]; $_gvar = F3::get($_cap[1]); } } if (is_array($_gvar) && count($_gvar)) { $_block = ''; // Iterate thru group elements foreach (array_keys($_gvar) as $_key) { $_block .= preg_replace($_ivar, $_gcap . '[\'' . $_key . '\']', isset($_kvar) ? preg_replace($_kvar, var_export($_key, TRUE), $_inner) : $_inner); } if (isset($_block[0])) { $_tree->fragment->appendXML($_block); // Insert fragment before current node $_next = $_parent->insertBefore($_tree->fragment, $_node); } } } $_remove = TRUE; } elseif ($_tag == 'check' && !$_2ndp) { // Found <F3:check> directive $_2ndp = TRUE; } elseif (strpos($_tag, '-')) { // Process custom template directive list($_class, $_method) = explode('-', $_tag); $_found = FALSE; if (!class_exists($_class, FALSE)) { foreach (explode('|', F3::$global['AUTOLOAD']) as $_auto) { $_file = $_auto . $_class . '.php'; // Case-insensitive check for file presence $_glob = glob(dirname($_file) . '/*.php'); $_fkey = array_search(strtolower($_file), array_map('strtolower', $_glob)); if (is_int($_fkey)) { include $_glob[$_fkey]; if (method_exists($_class, 'onLoad')) { call_user_func(array($_class, 'onLoad')); } $_found = TRUE; break; } } } else { $_found = TRUE; } if ($_found) { // Invoke template directive handler call_user_func(array($_class, $_method), $_tree); $_remove = TRUE; } } if ($_remove) { // Find next node if ($_node->isSameNode($_next)) { $_next = $_node->nextSibling ? $_node->nextSibling : $_parent; } // Remove current node $_parent->removeChild($_node); // Replace with next node $_node = $_next; } }); if ($_2ndp) { // Second pass; Template contains <F3:check> directive $_tree->traverse(function () use($_tree) { $_node =& $_tree->nodeptr; $_parent = $_node->parentNode; $_tag = $_node->tagName; // Process <F3:check> directive if ($_tag == 'check') { $_cond = var_export((bool) F3::resolve(rawurldecode($_node->getAttribute('if'))), TRUE); $_block = ''; foreach ($_node->childNodes as $_child) { if ($_child->nodeType != XML_TEXT_NODE && $_child->tagName == $_cond) { $_inner = $_tree->innerHTML($_child); if ($_inner) { // Replacement $_block .= $_inner; } } } if (isset($_block[0])) { $_tree->fragment->appendXML($_block); $_parent->insertBefore($_tree->fragment, $_node); } // Remove current node $_parent->removeChild($_node); // Re-process parent node $_node = $_parent; } }); } $_text = self::resolve(rawurldecode($_ishtml ? $_tree->saveHTML() : $_tree->saveXML())); if ($_ishtml) { // Fix empty HTML tags $_text = preg_replace('/<((?:area|base|br|col|frame|hr|img|input|' . 'isindex|link|meta|param).*?)\\/?>/is', '<$1/>', $_text); // Remove tags inserted by libxml foreach ($_deftags as $_regex => $_tag) { if (!$_tag) { $_text = preg_replace($_regex, '', $_text); } } } else { $_text = self::xmlEncode($_text, TRUE); } unset($_tree); } else { // Plain text $_text = self::resolve($_text); } // Remove control characters except whitespaces return preg_replace('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/', '', $_text); }