/** Reassemble markup string and insert appropriate PHP code @return string @param $node mixed @public **/ function build($node) { $out = ''; if (is_array($node)) { foreach ($node as $nkey => $nval) { if (is_int($nkey)) { $out .= $this->build($nval); } else { $count = count($this->syms); switch ($nkey) { case '@attrib': case 'exclude': break; case 'include': // <include> directive if (!$this->isdef($nkey, $nval, array('href'))) { return; } $hvar = $nval['@attrib']['href']; if (isset($nval['@attrib']['if'])) { $ival = $nval['@attrib']['if']; $cond = $this->expr($ival); // Syntax check if ($cond == $ival) { trigger_error(sprintf(self::TEXT_AttribInvalid, 'if="' . addcslashes($ival, '"') . '"')); return; } } $doc = new F3markup($this->mime, $this->globals); $file = self::resolve($hvar); if ($hvar != $file) { self::$cache = FALSE; } $nested = FALSE; foreach (array_keys($this->syms) as $pvar) { if (strstr($hvar, $pvar)) { $nested = TRUE; } } if ($nested) { $inc_var = preg_split("/[\\s]*[}}{{][\\s]*/i", $hvar, -1, PREG_SPLIT_NO_EMPTY); foreach ($inc_var as &$pval) { if ($pval[0] == '@') { $pval = preg_replace(array('/<\\?php echo /', '/; \\?>/'), '', self::expr('{{' . $pval . '}}')); } else { $pval = self::stringify($pval); } } $text = '<?php echo Template::serve(' . implode('.', $inc_var) . ',\'text/html\',' . 'TRUE,' . self::stringify($this->syms) . '); ?>'; $out .= isset($ival) ? '<?php if (' . trim($cond) . '): ?>' . $text . '<?php endif; ?>' : $text; } else { foreach (self::split(self::$vars['GUI']) as $gui) { if (is_file($view = $gui . $file)) { $text = $doc->load(self::getfile($view), $this->syms); $out .= isset($ival) ? '<?php if (' . trim($cond) . '): ?>' . $text . '<?php endif; ?>' : $text; break; } } } break; case 'loop': // <loop> directive if (!$this->isdef($nkey, $nval, array('counter', 'from', 'to'))) { return; } $cvar = self::remix(preg_replace('/{{\\s*@(.+?)\\s*}}/', '\\1', $nval['@attrib']['counter'])); foreach ($nval['@attrib'] as $akey => $aval) { ${$akey[0] . 'att'} = $aval; ${$akey[0] . 'str'} = $this->expr($aval); // Syntax check if (${$akey[0] . 'str'} == $aval) { trigger_error(sprintf(self::TEXT_AttribInvalid, $akey . '="' . addcslashes($aval, '"') . '"')); return; } } unset($nval['@attrib']); $this->syms['_' . $cvar] = eval('return ' . $fstr . ';'); $out .= '<?php for (' . '$_' . $cvar . '=' . $fstr . ';' . '$_' . $cvar . '<=' . $tstr . ';' . '$_' . $cvar . '+=' . (isset($satt) ? $sstr : '1') . '): ?>' . $this->build($nval) . '<?php endfor; ?>'; break; case 'repeat': // <repeat> directive if (!$this->isdef($nkey, $nval, array('group')) && (!$this->isdef($nkey, $nval, array('key')) || !$this->isdef($nkey, $nval, array('value')))) { return; } $gval = $nval['@attrib']['group']; $gstr = trim($this->expr($gval)); // Syntax check if ($gstr == $gval) { trigger_error(sprintf(self::TEXT_AttribInvalid, 'group="' . addcslashes($gval, '"') . '"')); return; } foreach ($nval['@attrib'] as $akey => $aval) { ${$akey[0] . 'var'} = self::remix(preg_replace('/{{\\s*@(.+?)\\s*}}/', '\\1', $aval)); // Syntax check if (${$akey[0] . 'var'} == $aval) { trigger_error(sprintf(self::TEXT_AttribInvalid, $akey . '=' . '"' . addcslashes($aval, '"') . '"')); return; } } unset($nval['@attrib']); if (isset($vvar)) { $this->syms['_' . $vvar] = NULL; } else { $vvar = self::hash($gvar); } if (isset($kvar)) { $this->syms['_' . $kvar] = NULL; } if (isset($cvar)) { $this->syms['_' . $cvar] = NULL; } $out .= '<?php ' . (isset($cvar) ? '$_' . $cvar . '=0; ' : '') . 'if (is_array(' . $gstr . ')):' . 'foreach ((' . $gstr . '?:array()) as ' . (isset($kvar) ? '$_' . $kvar . '=>' : '') . '$_' . $vvar . '):' . (isset($cvar) ? '$_' . $cvar . '++; ' : '') . '?>' . $this->build($nval) . '<?php ' . 'endforeach;' . 'endif;' . '?>'; break; case 'check': // <check> directive if (!$this->isdef($nkey, $nval, array('if'))) { return; } $ival = $nval['@attrib']['if']; $cond = $this->expr($ival); // Syntax check if ($cond == $ival) { trigger_error(sprintf(self::TEXT_AttribInvalid, 'if="' . addcslashes($ival, '"') . '"')); return; } // Is <true> is defined ahead of <false>? foreach ($nval as $pos => $blk) { if (is_array($blk)) { foreach ($blk as $ckey => $cval) { if (preg_match('/(?:F3:)?' . '(?:true|false)/i', $ckey)) { ${$ckey[0] . 'block'} = array($pos, $blk); } } } } if (isset($tblock) && isset($fblock) && $tblock[0] > $fblock[0]) { // Swap <true> and <false> blocks // <false> is defined ahead of <true> list($nval[$tblock[0]], $nval[$fblock[0]]) = array($fblock[1], $tblock[1]); } $out .= '<?php if (' . trim($cond) . '): ?>' . $this->build($nval) . '<?php endif; ?>'; break; case 'true': // <true> block of <check> directive $out .= $this->build($nval); break; case 'false': // <false> block of <check> directive $out .= '<?php else: ?>' . $this->build($nval); break; default: // Custom template tag $out .= new $nkey($nval); break; } // Reset scope while (count($this->syms) > $count) { end($this->syms); unset($this->syms[key($this->syms)]); } } } } else { $out .= preg_match('/<\\?php/', $node) ? $node : $this->expr($node, TRUE); } return $out; }
/** Reassemble markup string and insert appropriate PHP code @return string @param $node mixed @public **/ function build($node) { $out = ''; if (is_array($node)) { foreach ($node as $nkey => $nval) { if (is_int($nkey)) { $out .= $this->build($nval); } else { $count = count($this->syms); switch ($nkey) { case 'include': // <include> directive if (!$this->isdef($nkey, $nval, array('href'))) { return; } $hvar = $nval['@attrib']['href']; if (isset($nval['@attrib']['if'])) { $ival = $nval['@attrib']['if']; $cond = $this->expr($ival); // Syntax check if ($cond == $ival) { trigger_error(sprintf(self::TEXT_AttribInvalid, 'if="' . addcslashes($ival, '"') . '"')); return; } } $doc = new F3markup($this->mime, $this->globals); $file = self::resolve($hvar); if ($hvar != $file) { $this->cache = FALSE; } $file = self::$vars['GUI'] . $file; if (is_file($file)) { $text = $doc->load(self::getfile($file), $this->syms); $out .= isset($ival) ? '<?php if (' . trim($cond) . '): ?>' . $text . '<?php endif; ?>' : $text; } break; case 'loop': // <loop> directive if (!$this->isdef($nkey, $nval, array('counter', 'from', 'to'))) { return; } $cvar = self::remix(preg_replace('/{{\\s*@(.+?)\\s*}}/', '\\1', $nval['@attrib']['counter'])); foreach ($nval['@attrib'] as $akey => $aval) { ${$akey[0] . 'att'} = $aval; ${$akey[0] . 'str'} = $this->expr($aval); // Syntax check if (${$akey[0] . 'str'} == $aval) { trigger_error(sprintf(self::TEXT_AttribInvalid, $akey . '="' . addcslashes($aval, '"') . '"')); return; } } unset($nval['@attrib']); $this->syms[] = $cvar; $out .= '<?php for (' . '$_' . $cvar . '=' . $fstr . ';' . '$_' . $cvar . '<=' . $tstr . ';' . '$_' . $cvar . '+=' . (isset($satt) ? $sstr : '1') . '): ?>' . $this->build($nval) . '<?php endfor; ?>'; break; case 'repeat': // <repeat> directive if (!$this->isdef($nkey, $nval, array('group')) && (!$this->isdef($nkey, $nval, array('key')) || !$this->isdef($nkey, $nval, array('value')))) { return; } $gval = $nval['@attrib']['group']; $gstr = $this->expr($gval); // Syntax check if ($gstr == $gval) { trigger_error(sprintf(self::TEXT_AttribInvalid, 'group="' . addcslashes($gval, '"') . '"')); return; } foreach ($nval['@attrib'] as $akey => $aval) { ${$akey[0] . 'var'} = self::remix(preg_replace('/{{\\s*@(.+?)\\s*}}/', '\\1', $aval)); // Syntax check if (${$akey[0] . 'var'} == $aval) { trigger_error(sprintf(self::TEXT_AttribInvalid, $akey . '=' . '"' . addcslashes($aval, '"') . '"')); return; } } unset($nval['@attrib']); if (isset($vvar)) { $this->syms[] = $vvar; } else { $vvar = self::hash($gvar); } if (isset($kvar)) { $this->syms[] = $kvar; } if (isset($cvar)) { $this->syms[] = $cvar; } $out .= '<?php ' . (isset($cvar) ? '$_' . $cvar . '=0; ' : '') . 'foreach ((' . trim($gstr) . '?:array()) as ' . (isset($kvar) ? '$_' . $kvar . '=>' : '') . '$_' . $vvar . '): ' . (isset($cvar) ? '$_' . $cvar . '++; ' : '') . '?>' . $this->build($nval) . '<?php endforeach; ?>'; break; case 'check': // <check> directive if (!$this->isdef($nkey, $nval, array('if'))) { return; } $ival = $nval['@attrib']['if']; $cond = $this->expr($ival); // Syntax check if ($cond == $ival) { trigger_error(sprintf(self::TEXT_AttribInvalid, 'if="' . addcslashes($ival, '"') . '"')); return; } // Is <true> is defined ahead of <false>? foreach ($nval as $pos => $blk) { if (is_array($blk)) { foreach ($blk as $ckey => $cval) { if (preg_match('/(?:F3:)?' . '(?:true|false)/i', $ckey)) { ${$ckey[0] . 'block'} = array($pos, $blk); } } } } if (isset($tblock) && isset($fblock) && $tblock[0] > $fblock[0]) { // Swap <true> and <false> blocks // <false> is defined ahead of <true> list($nval[$tblock[0]], $nval[$fblock[0]]) = array($fblock[1], $tblock[1]); } $out .= '<?php if (' . trim($cond) . '): ?>' . $this->build($nval) . '<?php endif; ?>'; break; case 'true': // <true> block of <check> directive $out .= $this->build($nval); break; case 'false': // <false> block of <check> directive $out .= '<?php else: ?>' . $this->build($nval); break; } // Reset scope $this->syms = array_slice($this->syms, 0, $count); } } } else { $out .= preg_match('/<\\?php/', $node) ? $node : $this->expr($node, TRUE); } return $out; }
/** Render template @return string @param $file string @param $mime string @param $globals boolean @param $syms array @public **/ static function serve($file, $mime = 'text/html', $globals = TRUE, $syms = array()) { $file = self::resolve($file); $found = FALSE; foreach (preg_split('/[\\|;,]/', self::$vars['GUI'], 0, PREG_SPLIT_NO_EMPTY) as $gui) { if (is_file($view = self::fixslashes($gui . $file))) { $found = TRUE; break; } } if (!$found) { trigger_error(sprintf(self::TEXT_Render, $file)); return ''; } if (PHP_SAPI != 'cli' && !headers_sent()) { // Send HTTP header with appropriate character set header(self::HTTP_Content . ': ' . $mime . '; ' . 'charset=' . self::$vars['ENCODING']); } $hash = 'tpl.' . self::hash($view); $cached = Cache::cached($hash); if ($cached && filemtime($view) < $cached) { if (self::$vars['CACHE']) { // Retrieve PHP-compiled template from cache $text = Cache::get($hash); } } else { // Parse raw template $doc = new F3markup($mime, $globals); $text = $doc->load(self::getfile($view), $syms); if (self::$vars['CACHE'] && $doc::$cache) { // Save PHP-compiled template to cache Cache::set($hash, $text); } } // Render in a sandbox $instance = new F3instance(); ob_start(); if (ini_get('allow_url_fopen') && ini_get('allow_url_include')) { // Stream wrap $instance->sandbox('data:text/plain,' . urlencode($text), $syms); } else { // Save PHP-equivalent file in temporary folder if (!is_dir(self::$vars['TEMP'])) { self::mkdir(self::$vars['TEMP']); } $temp = self::$vars['TEMP'] . $_SERVER['SERVER_NAME'] . '.' . $hash; if (!$cached || !is_file($temp) || filemtime($temp) < Cache::cached($view)) { // Create semaphore $hash = 'sem.' . self::hash($view); while ($cached = Cache::cached($hash)) { // Locked by another process usleep(mt_rand(0, 100)); } Cache::set($hash, TRUE); self::putfile($temp, $text); // Remove semaphore Cache::clear($hash); } $instance->sandbox($temp, $syms); } $out = ob_get_clean(); unset($instance); return self::$vars['TIDY'] ? self::tidy($out) : $out; }