$v ) { if ( !is_numeric($k) ) $k = "'" . addslashes($k) . "'"; $php .= $str . $k . " => " . phpize($v, false, $compact) . ","; } if ( !$compact ) $php = preg_replace("!\n!", "\n\t", $php); $php .= $str . ")"; } elseif ( is_numeric($data) ) $php = addslashes($data); elseif ( is_bool($data) ) $php = $data ? 'true' : 'false'; elseif ( is_null($data) ) $php = "null"; elseif ( is_string($data) ) $php = "'" . implode('\' . "\n" . \'', explode("\n", preg_replace( array('!\\\\!', '!\'!'), array('\\\\\\', '\\\''), $data ))) . "'"; elseif ( $data instanceof PhpizeInterface ) $php = $data->__toPhp($compact); else throw new Exception('Object does not implements PhpizeInterface : ' . get_class($data)); if ( $file ) file_put_contents($file, ""); return $php; } function sprinto( $str, $obj, $default = '') { $a = function ($matches) use ($obj, $default, &$set) { $data = (array) $obj; $path = preg_split('!\.!', $matches[1]); foreach ( $path as $step ) if ( array_key_exists($step, $data) ) $data = $data[$step]; else $data = ''; return $data; return !empty($data) ? $data : ( $default === null ? $matches[0] : $default ); }; return preg_replace_callback("!\{([a-zA-Z0-9_:\-\.]*)\}!", $a, $str); } function joinstr ( $array, $glue = ' ', $start = '', $end = '' ) { if ( $str = implode($glue, $array) ) return $start . $str . $end; return ''; } /* interface dataItem { function __construct($item); function lister($deepscan); } */ class DSDirectory { //var $root = $_SERVER['DOCUMENT_ROOT']; var $dir = '.'; var $targetDir = ''; var $listing = array(); function __construct($item, $target = '.') { $this->dir = $item . '/'; $tg = $this->makeAR(realpath($item), realpath($target)); if (empty($tg)) $tg = './'; $this->targetDir = $tg; } function chroot ($root) { $this->root = realpath($root); return $this->root; } function lister($deepscan, $deep = 1) { $dir = $this->targetDir; $this->listing = $this->recursiveList($dir, $deepscan, $deep++); return $this->listing; } function read($item) { /*if (!is_array($item)) return $this->readOne($item); $items = array(); foreach($item as $oneitem) { $items[] = $this->readOne($oneitem); } return $items; */ return $this->vector('readOne', array($item)); } function write($item, $data) { if (!is_array($item) && !is_array($data)) return $this->writeOne($item, $data); if (sizeof($item) != sizeof($data)) { echo "DSDirectory::write error $item and $data have a different length."; return false; } $items = array(); foreach($item as $key => $oneitem) { $items[] = $this->writeOne($oneitem, $data[$key]); } return array_combine($item, $data); //return $this->vector('writeOne', array($item, $data)); } function writeH($items) { $itemi = array(); foreach($items as $item) { $itemi[] = $this->writeOne($item['path'], $item['data']); } return $itemi; } private function makeAR($base, $target) { // both absolute $rel = ''; $sep = "![\\|/]!"; $bases = preg_split($sep, $base); $tg = preg_split($sep, $target); $b = min(sizeof($bases), sizeof($tg)); $i = 0; while ($i < $b && $bases[$i] == $tg[$i]) { $i++; } $z = $i; while (!empty($bases[$i])) { $rel .= "../"; $i++; } while (!empty($tg[$z])) { $rel .= $tg[$z] . "/"; $z++; } return $rel; // relative to base } private function vector($func, $arglist) { $cplx = false; $str = ''; $i = 0; foreach($arglist as $key => $item) { $cplx = $cplx || is_array($item); $str .= "\$arglist[$key], "; } $str = ereg_replace(", $", "", $str); if (!$cplx) return $this->$func($item); $items = array(); foreach (current($arglist) as $unitem) { $items[] = eval('$this->$func(' . $str . ');'); } return eval('array_combine(' . $str . ');'); } private function deleteOne($item) { if ( !is_dir($item) ) unlink($item); return true; } private function readOne($item) { $content = ''; $file = $this->dir . $item; $fd = fopen($file, "r"); while (!feof($fd)) { $content .= fgets($fd, 256); } fclose($fd); return $content; } private function writeOne($item, $data) { $path = pathinfo($item); echo $path['dirname'] . "\n"; if (!file_exists($path['dirname'])) { mkdir($path['dirname'], 077, 1); } $file = $item; $fd = fopen($file, "w"); fwrite($fd, $data); fclose($fd); return $data; } private function appendOne($item, $data) { $file = $this->dir . $item; $fd = fopen($file, "a"); fwrite($fd, $data); fclose($fd); return $data; } private function readOneHex($item) { $c = ''; $file = $this->dir . $item; $fid = fopen($file, 'r'); while (!feof($fid)) { $c .= bin2hex(fgets($fid, 2)); } fclose($fid); return $c; } function sizeFile($size) { if ($size < 1024) $size = $size . ' o'; else if ($size < 1024 * 1024) $size = (int)($size / 1024) . ' Ko'; else if ($size < 1024 * 1024 * 1024) $size = (int)($size / 1024 / 1024) . ' Ko'; else $size = (int)($size / 1024 / 1024 / 1024) . ' Mo'; return $size; } function synchronise() { } private function recursiveList($curr, $deepscan, $deep) { $deep--; $result = array(); $list = scandir($curr); if (!$list) return null; $isempty = true; $z = sizeof($list); foreach($list as $key => $elem) { if (!in_array($elem, array('..', '.'))) {if (is_dir($curr . $elem) and $elem != '.' and $elem != '..') { if ($deepscan) { if ( $deep < 1 ) $res = null; else $res = $this->recursiveList($curr . $elem . '/', 1, $deep); $tot = 0; if ($res){ foreach($res as $k => $it) { $tot += $it['size']; $it['size'] = $this->sizeFile($it['size']); } } $result[] = array( 'type' => 'dir', 'curr' => $curr, 'name' => $elem, 'size' => $tot, 'subC' => $res); } else { $result[] = array( 'type' => 'dir', 'curr' => $curr, 'name' => $elem, 'size' => 0 ); } $isempty = false; } elseif (!$deepscan || ($elem != '.' and $elem != '..')) { $a = preg_split('!\.!', $elem); $type = $a[sizeof($a) - 1]; if ($elem == '.' || $elem == '..') $type = 'dir'; $size = filesize($curr . $elem ); //$size = $this->sizeFile($size); $result[] = array( 'type' => $type, 'curr' => $curr, 'name' => $elem, 'size' => $size ); $isempty = false; } elseif ($elem == '.' || $elem == '..') { $result[] = array( 'type' => 'dir', 'curr' => $curr, 'name' => $elem, 'size' => 0 ); } } } if ($isempty) return null; return $result; } } function recursiveTpl($list) { $template = new Template('.'); $template->set_filenames(array('explorer' => 'dev2.html')); if (!$list) return false; foreach ($list as $row) { if ($row['type'] == 'dir') { if (isset($row['subC'])) $row['sub'] = recursiveTpl($row['subC']); $template->assign_block_vars("dir", $row); } else { $template->assign_block_vars("file", $row); } } $template->pparse('explorer'); return $template->xml; } // Immutable class Path implements IteratorAggregate { public static function get ( $path ) { return new self($path); } public static function conv ( $path ) { if ( is_string($path) ) { if ( !$path ) return array(); $abs = $path[0] === DIRECTORY_SEPARATOR ? array(null) : array(); $elements = array_filter(explode(DIRECTORY_SEPARATOR, $path)); return array_merge($abs, $elements); } elseif ( is_array($path) ) return $path; elseif ( $path instanceof self ) return $path->elements; else throw new InvalidArgumentException(); } private static function compute ( $elements, $target ) { foreach ( $target as $elem ) if ( $elem === '.' ); elseif ( $elem === '..' ) { if ( count($elements) && end($elements) && end($elements) !== '..' ) array_pop($elements); elseif (!count($elements) || end($elements) === '..' ) $elements[] = $elem; } elseif ( $elem || !$elements ) $elements[] = $elem; return $elements; } private $elements; public function __construct ( $path ) { $this->elements = self::compute(array(), self::conv($path)); } public function getIterator () { return new ArrayIterator($this->elements); } public function isAbsolute () { return count($this->elements) && !$this->elements; } public function append ( $path ) { $source = $this->elements; foreach ( func_get_args() as $path ) $source = self::compute($source, self::conv($path)); return new self($source); } public function pop ( $path ) { $source = $this->elements; $target = self::compute(array(), self::conv($path)); foreach ( func_get_args() as $path ) { while ( count($source) && count($target) && last($source) === last($target) ) { array_pop($source); array_pop($target); } if ( count($target) ) return false; } return new self($source); } public function prepend ( $path ) { $source = $this->elements; foreach ( func_get_args() as $path ) $source = self::compute(self::conv($path), $source); return new self($source); } public function shift ( $path ) { $source = $this->elements; $target = self::compute(array(), self::conv($path)); foreach ( func_get_args() as $path ) { while ( count($source) && count($target) && reset($source) === reset($target) ) { array_shift($source); array_shift($target); } if ( count($target) ) return false; } return new self($source); } public function absolute () { return array_key_exists(0, $this->elements) && !$this->elements[0]; } public function __toString () { $abs = count($this->elements) === 1 && array_key_exists(0, $this->elements) && !$this->elements[0] ? DIRECTORY_SEPARATOR : ''; return $abs . implode(DIRECTORY_SEPARATOR, $this->elements); } } class Url { static private $properties = array( 'scheme' => '', 'host' => '', 'port' => '', 'path' => '', 'dirname' => '', 'basename' => '', 'filename' => '', 'extension' => '', 'fragment' => '', 'query' => '', ); static public function get ( $url = '' ) { return new self($url); } static public function parse ( $url ) { $urlData = parse_url($url); $urlData = array_merge(self::$properties, $urlData, pathinfo($urlData['path'])); if ( isset($urlData['query']) ) parse_str($urlData['query'], $urlData['query']); return $urlData; } static public function build ( $urlData ) { if ( isset($urlData['query']) ) $urlData['query'] = http_build_str($urlData['query']); return http_build_url($urlData); } static public function override ( $urlData, $part, $value ) { $urlData[$part] = $value; $update = 0; switch ( $part ) { case 'path': $update = 1; break; case 'dirname': $urlData['path'] = preg_replace('!//++!', '/', $urlData['dirname'] . '/' . $urlData['basename']); break; case 'basename': $urlData['path'] = preg_replace('!//++!', '/', $urlData['dirname'] . '/' . $urlData['basename']); $update = 1; break; case 'filename': $urlData['path'] = preg_replace('!//++!', '/', $urlData['dirname'] . '/' . $urlData['filename'] . '.' . $urlData['extension']); $urlData['basename'] = $urlData['filename'] . ( $urlData['extension'] ? '.' . $urlData['extension'] : '' ); break; case 'extension': $urlData['path'] = preg_replace('!//++!', '/', $urlData['dirname'] . '/' . $urlData['filename'] . '.' . $urlData['extension']); $urlData['basename'] = $urlData['filename'] . ( $urlData['extension'] ? '.' . $urlData['extension'] : '' ); break; } if ( $update ) { $urlData['dirname'] = ''; $urlData['basename'] = ''; $urlData['filename'] = ''; $urlData['extension'] = ''; $urlData = array_merge($urlData, pathinfo($urlData['path'])); } return $urlData; } private $data = array(); public function __construct ( $urlData = array() ) { if ( $urlData instanceof self ) $urlData = $urlData->data; elseif ( is_string($urlData) ) $urlData = self::parse($urlData); elseif ( is_array($urlData) ) foreach ( $urlData as $p => $v ) $urlData = self::override($urlData, $p, $v); else throw SenseException::sprintf( 'Argument of type "%1$s" is invalid ! Type "string","array" or "Url" expected !', gettype($urlData) ); $this->data = $urlData; } public function __invoke ( $parts ) { $urlData = $this->data; foreach ( $parts as $part => $value ) { if ( !array_key_exists($part, self::$properties) ) throw new SenseException(sprintf( 'Property "%1$s" does not exist in Url instance !', $part )); $urlData = self::override($urlData, $part, $value); } return new self($urlData); } public function __call ( $name, $args ) { if ( !array_key_exists($name, self::$properties) ) throw new SenseException(sprintf( 'Property "%1$s" does not exist in Url instance !', $name )); return new self(self::override($this->data, $name, $args[0])); } public function &__get ( $offset ) { if ( !array_key_exists($offset, self::$properties) ) throw new SenseException(sprintf( 'Property "%1$s" does not exist in Url instance !', $offset )); return $this->data[$offset]; } public function __set ( $offset, $value ) { if ( !array_key_exists($offset, self::$properties) ) throw new SenseException(sprintf( 'Property "%1$s" does not exist in Url instance !', $offset )); $this->data = self::override($this->data, $offset, $value); return $value; } public function __unset ( $offset ) { if ( !array_key_exists($offset, self::$properties) ) throw new SenseException(sprintf( 'Property "%1$s" does not exist in Url instance !', $offset )); $this->$offset = ''; } public function __toString ( ) { return self::build($this->data); } } abstract class Controller { protected function __construct () { if ( empty($_SESSION[get_called_class()]) ) $_SESSION[get_called_class()] = array(); } protected function store ( $key, $value ) { $_SESSION[get_called_class()][$key] = $value; return $this; } protected function restore ( $key ) { return !empty($_SESSION[get_called_class()][$key]) ? $_SESSION[get_called_class()][$key] : null; } } // singleton class Auth extends Controller { private static $instance; private $service, $user; static public function get ( Service $service = null ) { if ( self::$instance ) return self::$instance; return self::$instance = new self($service); } protected function __construct ( Service $service ) { parent::__construct(); $this->service = $service; $this->user = $this->restore('user'); } public function getUser () { return $this->user; } // Action Interface public function login ( $user ) { // query for ( login, pass ) $users = $this->service; // no match ( login, pass ) if ( empty($users[$user['login']]) ) throw new Exception('Wrong user !'); if ( $users[$user['login']]['password'] !== $user['password'] ) throw new Exception('Wrong password !'); $this->user = $users[$user['login']]; $this->store('user', $this->user); } public function logout () { $this->user = null; $this->store('user', null); $_SESSION = array(); } } class EngineSystem { protected static $instances = array(); public static function get ( $name ) { if ( !empty(static::$instances[$name]) ) return static::$instances[$name]; throw new Exception(sprintf( 'EngineSystem "%1$s" not registered !', $name )); } public static function set ( $name, $engineClass, $root = '', $cache = '', $mkCache = false ) { if ( $mkCache && !file_exists($cache) ) mkdir($cache, 0777, 1); return static::$instances[$name] = new static($engineClass, $root, $cache); } protected $root = '', $cache = '', $engineClass = ''; protected function __construct ( $engineClass, $root, $cache ) { if ( !class_exists($engineClass) || !method_exists($engineClass, 'fromFile') ) throw new Exception(sprintf('Engine class %1$s does not exists or does not have a static "fromFile" method !', $engineClass)); if ( !file_exists($root) ) throw new Exception(sprintf('Root folder "%1$s" does not exists !', $root)); if ( $cache && !file_exists($cache) ) throw new Exception(sprintf('Cache folder "%1$s" does not exists !', $cache)); $this->root = realpath($root); $this->cache = realpath($cache); $this->engineClass = $engineClass; } public function load ( $file, $content = null ) { $engineClass = $this->engineClass; return $engineClass::fromFile($file, $this->root, $this->cache, $content); } public function exists ( $file ) { $root = Path::get($this->root); $file = $root->append($file); return $file->shift($root) && file_exists($file) && is_file($file); } public function getClass () { return $this->engineClass; } public function getRoot () { return $this->root; } public function getCache () { return $this->cache; } } abstract class Engine { abstract function run ( $data = array() ); protected static $instances = array(); public static function fromFile ( $file, $root = '', $cache = '', $content = '' ) { if ( !$file ) throw new Exception('No file provided !'); // if specified, $root must be an actual directory if ( $root && !( $root = realpath($root[0] === DIRECTORY_SEPARATOR ? $root : realpath('.') . DIRECTORY_SEPARATOR . $root) ) ) throw new Exception(sprintf('Directory "%1$s" for root not found !', $root)); // if specified, $cache must be an actual directory if ( $cache && !( $cache = realpath($cache[0] === DIRECTORY_SEPARATOR ? $cache : realpath('.') . DIRECTORY_SEPARATOR . $cache) ) ) throw new Exception(sprintf('Directory "%1$s" for cache not found !', $cache)); // if $cache is set, then $root too !! if ( $cache && !$root ) throw new Exception(sprintf('Directory cache "%1$" set but empty root directory provided !', $cache)); $file = Path::get($file); $filepath = $file->prepend('/', $root); $filemirror = $file->prepend($cache); $cachepath = Path::get($file . '.php')->prepend($cache); $basename = basename($file); $dirname = dirname($file); // Process Level cache : not instantiate the same file twice if ( !empty(static::$instances[get_called_class()][$filepath->__toString()]) ) return static::$instances[get_called_class()][$filepath->__toString()]; // prepare cache dirctories if ( $cache ) { $cacheDir = dirname($cachepath); if ( !file_exists($cacheDir) ) mkdir($cacheDir, 0777, 1); } /*#BEGIN debug#*/ if ( file_exists($filepath) ) $m = filemtime($filepath); elseif ( $content && ( !file_exists($filemirror) || $content === file_get_contents($filemirror) ) ) $m = -1; else $m = time(); if ( $cache && !file_exists($cachepath . $m) ) { if ( file_exists($filemirror) ) unlink($filemirror); if ( file_exists($cachepath) ) unlink($cachepath); } if ( $cache ) file_put_contents($cachepath . $m, $m); /*#END debug#*/ if ( $cache && file_exists($cachepath) ) { $instance = new static($dirname, $root, $cache, $basename); $instance->file = $filemirror; $instance->include = $cachepath; } else { if ( file_exists($filepath) ) $content = file_get_contents($filepath); elseif ( !$content ) throw new Exception(sprintf('File "%1$s" not found and no content provided !', $filepath)); if ( $cache ) file_put_contents($filemirror, $content); try { $instance = static::fromString($content, $dirname, $root, $cache, $basename); } catch ( Exception $e ) { throw new Exception(sprintf( 'Error during compilation of file "%1$s" !', $filepath ), 0, $e); } if ( $cache ) { file_put_contents($cachepath, "code . "\n?>"); $instance->include = $cachepath; $instance->code = ''; $instance->file = $filemirror; $instance->source = ''; } } return static::$instances[get_called_class()][$filepath->__toString()] = $instance; } protected // Source code $source = '', $file = '', // Compiled code $code = '', $include = '', // Env $dir = '', $root = '', $cache = '', $basename = ''; protected function __construct ( $dir, $root = '', $cache = '', $basename = '') { $this->dir = $dir; $this->root = $root; $this->cache = $cache; $this->basename = $basename; } public function getSource () { if ( $this->file ) return file_get_contents($this->file); return $this->source; } /*** Runtime API ***/ public function getDir () { return $this->dir; } public function getRoot () { return $this->root; } public function getCache () { return $this->cache; } public function getFile () { return $this->dir . '/' . $this->basename; } public function getBasename () { return $this->basename; } } class Grammar { static public function fromFile ( $file, $path = null ) { if ( !file_exists($file) || is_dir($file) ) throw new Exception('File not found : ' . $file); try { return self::fromString(file_get_contents($file), $path ? $path : dirname($file) ); } catch ( Exception $e ) { throw new Exception(sprintf( 'Error in file "%1$s" : "%2$s"', $file, $e->getMessage()), 0, $e ); } } static public function fromString ( $string, $path = '.' ) { return new self($string, $path); } private $path = '', $dummyString = '', $dummies = array(), $terminals = array(), $rules = array(), $firsts = array(), $follows = array(); private function __construct ( $string, $path = '.' ) { $this->path = realpath($path); $this->parseGrammar($string); } private function parseGrammar ( $string ) { $lines = preg_split('!\n!', $string); $seen = array(); $current = 0; $section = 0; while ( count($lines) ) { $line = array_shift($lines); $current++; if ( $line === '%%' ) { // section separator $section++; } elseif ( preg_match('!^(//.*)?$!', $line) ) { // one line comments and empty lines } elseif ( $section < 1 ) { // terminal if ( $dummy = $this->parseTerminal($line, $current) ) $this->dummies[$dummy] = $dummy; } elseif ( $section < 2 ) { // production if ( preg_match('!\#include\(\s*(.+)\s*\)\s*$!', $line, $m) ) { $seen = $this->mergeGrammar($m[1], $seen); } else $seen = array_merge($this->parseProduction($line, $current), $seen); } else break; // eof comments } if ( $this->dummies ) $this->dummyString = '(?:' . implode('|', $this->dummies) . ')++'; if ( $undefined = array_diff_key($seen, $this->terminals, $this->rules) ) throw new Exception(sprintf( 'Malformed Grammar : undefiner symbol "%1$s" on line "%2$s".', key($undefined), current($undefined) )); $this->findRecursions(); $this->firstSet(); $this->followSet(); } private function mergeGrammar ( $file, $seen ) { $g = self::fromFile($this->path . '/' . $file); foreach ( $g->dummies as $k => $v ) if ( empty($this->dummies[$k]) ) $this->dummies[$k] = $v; foreach ( $g->terminals as $k => $v ) if ( empty($this->terminals[$k]) ) $this->terminals[$k] = $v; foreach ( $g->rules as $k => $v ) { $seen[$k] = 1; if ( empty($this->rules[$k]) ) $this->rules[$k] = $v; else { $this->rules[$k]['seed'] |= $v['seed']; $this->rules[$k]['productions'] = array_merge($this->rules[$k]['productions'], $v['productions']); array_walk($this->rules[$k]['productions'], function ( &$value, $key ) { $value['idx'] = $key; }); } } return $seen; } private function parseTerminal ( $line, $current ) { if ( preg_match('!^((?:\!|\.)?)([^\s]++)\s+->\s+!', $line, $m)) { $terminal = preg_split('!\s+->\s+!', $line); $this->terminals[$m[2]] = array( 'type' => $m[2], 'regex' => $terminal[1], 'ignore' => $m[1] === '!', 'line' => $current, ); if ( $m[1] === '!' ) return $terminal[1]; } else throw new Exception(sprintf( 'Malformed input in grammar at line "%1$s" !', $current )); } private function parseProduction ( $line, $current ) { if ( preg_match('!^(\!?)([^\s]++)\s+(\*?->|<-)\s+!', $line, $m)) { $terminals = &$this->terminals; $seen = array(); $rule = preg_split('!\s*(\*?->|<-)\s*!', $line, 2, PREG_SPLIT_DELIM_CAPTURE); if ( empty($this->rules[$rule[0]]) ) $this->rules[$rule[0]] = array( 'type' => $rule[0], 'left' => 0, 'right' => 0, 'seed' => 0, 'productions' => array(), ); $this->rules[$rule[0]]['seed'] = $this->rules[$rule[0]]['seed'] || $rule[1] !== '->'; $i = 0; $this->rules[$rule[0]]['productions'][] = array( 'idx' => count($this->rules[$rule[0]]), 'type' => $rule[0], 'line' => $current, 'rule' => $rule[2], 'left' => 0, 'right' => 0, 'seed' => $rule[1] === '->' ? false : true, 'production' => $line, 'tokens' => array_map(function ($token) use ( &$seen, $current, &$i, &$terminals ) { $precedence = 0; $associative = 0; if ( preg_match('!^((?:[0-9]++)?)(<|>).++$!', $token, $m) ) { $token = preg_replace('!^([0-9]++)(<|>)!', '', $token); $precedence = (int) $m[1]; $associative = ( $m[2] === '<' ? 'left' : 'right' ); } if ( strlen($token) === 1 || !in_array($token[strlen($token) - 1], array('+', '*', '?', '.', '&', '!')) ) $token .= '.'; $seen[$type = substr($token, 0, strlen($token) - 1)] = $current; return array( 'idx' => $i++, 'type' => $type, 'repeat' => $token[strlen($token) - 1], 'precedence' => $precedence, 'associative' => $associative, ); }, array_filter(preg_split('!\s+!', $rule[2]))) ); return $seen; } else throw new Exception(sprintf( 'Malformed input in grammar at line "%1$s" !', $current )); } private function findRecursions () { foreach ( $this->rules as $name => $rule ) foreach ( $rule['productions'] as $idx => $production ) { if ( $this->recurse($name, $production) ) { $production['left'] = 1; $this->rules[$name]['left'] = 1; } if ( $this->recurse($name, $production, true) ) { $production['right'] = 1; $this->rules[$name]['right'] = 1; } $this->rules[$name]['productions'][$idx] = $production; } } private function recurse ( $rul, $prod, $right = false, $visited = array() ) { // to support filters if ( !empty($visited[$prod['type']]) ) return ( $prod['type'] === $rul ); $visited[$prod['type']] = 1; $tokens = $prod['tokens']; if ( $right ) $tokens = array_reverse($tokens); foreach ( $tokens as $token ) { if ( !empty($this->rules[$token['type']]) ) foreach ( $this->rules[$token['type']]['productions'] as $production ) if ( $this->recurse($rul, $production, $right, $visited) ) return true; if ( in_array($token['repeat'], array('.', '+')) ) return false; } return false; } private function firstSet () { foreach ( $this->rules as $name => $rule ) foreach ( $rule['productions'] as $idx => $production ) $this->firsts[$name] = array_merge( !empty($this->firsts[$name]) ? $this->firsts[$name] : array(), $this->first($production) ); } private function first ( $production, $visited = array() ) { if ( !empty($visited[$production['type']]) ) return array(); $visited[$production['type']] = 1; $firsts = array(); foreach ( $production['tokens'] as $token ) { if ( !empty($this->terminals[$token['type']]) ) $firsts[$token['type']] = $token; else foreach ( $this->rules[$token['type']]['productions'] as $production ) $firsts = array_merge($firsts, $this->first($production, $visited)); if ( in_array($token['repeat'], array('.', '+')) ) break; } return $firsts; } private function followSet () { foreach ( $this->rules as $name => $rule ) foreach ( $rule['productions'] as $idx => $production ) { if ( $production['left'] && !empty($this->rules[$production['tokens'][0]['type']]['left']) && !empty($production['tokens'][1]) ) { if ( empty($this->follows[$production['tokens'][0]['type']]) ) $this->follows[$production['tokens'][0]['type']] = array(); //~ $this->follows[$production['tokens'][0]['type']][$production['tokens'][1]['type']] = $production['tokens'][1]; $this->follows[$production['tokens'][0]['type']][] = $production['tokens'][1]; } } } public function getDummyString () { return $this->dummyString; } public function getTerminals () { return $this->terminals; } public function getRules () { return $this->rules; } public function getFirsts () { return $this->firsts; } public function getFollows () { return $this->follows; } public function __toString() { return print_r($this, 1); } } class ParserException extends Exception {} class SGParser { static public function fromFile ( $file, $translator = null ) { return new static(Grammar::fromFile($file), $translator); } static public function fromString ( $string, $translator = null ) { return new static(Grammar::fromString($string), $translator); } protected $debug = 0, $translator = null, $grammar = null, $dummies = null, $terminals = null, $rules = null, $firsts = null, $follows = null; protected function __construct ( Grammar $grammar, $translator = null ) { $this->grammar = $grammar; $this->dummies = $grammar->getDummyString(); $this->terminals = $grammar->getTerminals(); $this->rules = $grammar->getRules(); $this->firsts = $grammar->getFirsts(); $this->follows = $grammar->getFollows(); $this->translator = $translator; } public function setDebug ( $debug ) { $this->debug = $debug; return $this; } public function parseFile ( $file, $litteral = false ) { if ( !file_exists($file) ) throw new ParserException(sprintf('File "%1$s" does not exist !', $file)); try { return $this->parseString(file_get_contents($file), $litteral); } catch ( ParserException $e ) { throw new ParserException(sprintf( 'Error parsing file "%1$s" : "%2$s"', $file, $e->getMessage()), 0, $e ); } } public function parseString ( $string, $litteral = false, $symbol = 'S', $repeat = '.' ) { $parseState = $this->getState($string, $litteral); $tree = $this->parse($parseState, $symbol, $repeat); if ( !preg_match( sprintf('/^(%1$s)*$/', $this->dummies), $string = substr($string, $parseState['state']['position']) ) ) throw new ParserException("Unexpected end of input :" . $string); foreach ( $parseState as $k => $v ) unset($parseState[$k]); return $tree['nodes']; } public function parse ( &$parseState, $symbol = 'S', $repeat = '.' ) { try { $tree = $this->work($parseState, array( 'type' => $symbol, 'repeat' => $repeat, )); } catch ( ParserException $e ) { preg_match('![^\n]++$!', substr($parseState['string'], 0, $parseState['error']['position']), $m); throw new ParserException(sprintf( 'Error on line %1$u at position %2$u.', $parseState['error']['line'], !empty($m[0]) ? strlen($m[0]) : 0), 0, $parseState['error']['ParserException'] ); } return $tree; } protected function getState ( $string, $litteral ) { return array( 'parser' => $this, 'string' => $string, 'litteral' => $litteral, 'matches' => array(), 'error' => array( 'position' => 0, 'line' => 0, 'ParserException' => null, ), 'state' => array( 'line' => 1, 'position' => 0, 'deepness' => 0, 'operators' => array(), 'ntoken' => null, 'production' => null, ), 'memo' => array(), ); } protected function setState ( &$parseState, $oldState, $no = array() ) { foreach ( array_diff_key($oldState['state'], $no) as $k => $v ) if ( empty($no[$k]) ) $parseState['state'][$k] = $v; return $parseState; } protected function saveState ( &$parseState ) { $state = array(); foreach ( $parseState['state'] as $k => $v ) $state[$k] = $v; return array('state' => $state); } protected function getId ( $parseState, $token ) { return $token['type'] . ':' . $parseState['state']['position']; } protected function reportError ( &$parseState, $token, ParserException $e ) { if ( !$parseState['error']['ParserException'] || $parseState['state']['position'] > $parseState['error']['position'] ) { $parseState['error']['position'] = $parseState['state']['position']; $parseState['error']['line'] = $parseState['state']['line']; $parseState['error']['ParserException'] = $e; } } protected function match ( &$parseState, $token ) { $id = $this->getId($parseState, $token); if ( !empty($parseState['matches'][$id]) ) return $parseState['matches'][$id]['value']; if ( !empty($this->terminals[$token['type']]) ) return $parseState['matches'][$id]['value'] = preg_match(sprintf( '/^((?:%2$s)?)(%1$s)/S', $this->terminals[$token['type']]['regex'], $this->dummies), substr($parseState['string'], $parseState['state']['position']) ); foreach ( $this->firsts[$token['type']] as $k => $terminal ) if ( preg_match(sprintf( '/^((?:%2$s)?)(%1$s)/S', $this->terminals[$k]['regex'], $this->dummies), substr($parseState['string'], $parseState['state']['position']) )) return $parseState['matches'][$id]['value'] = true; return $parseState['matches'][$id]['value'] = false; } protected function consume ( &$parseState, $token ) { if ( $this->debug ) $indent = str_repeat("\t", $parseState['state']['deepness']); $id = $this->getId($parseState, $token); if ( !empty($parseState['memo'][$id]) ) { if ( $this->debug ) echo "\n\t\t$indent memo {$token['type']} : {$parseState['state']['position']} => {$parseState['memo'][$id]['position']}"; if ( $parseState['memo'][$id]['value'] ) $parseState['state']['position'] = $parseState['memo'][$id]['position']; return $parseState['memo'][$id]['value']; } $parseState['memo'][$id] = array( 'value' => null, 'position' => null, ); if ( !empty($this->terminals[$token['type']]) ) { $nodes = $this->eat($parseState, $token); } elseif ( $this->match($parseState, $token) ) { $nodes = $this->test($parseState, $token); } else $nodes = null; if ( $nodes && $this->translator ) $nodes = call_user_func($this->translator, $parseState, $token, $nodes); if ( empty($this->rules[$token['type']]['left']) ) $parseState['memo'][$id] = array( 'value' => $nodes, 'position' => $parseState['state']['position'], ); else unset($parseState['memo'][$id]); return $nodes; } protected function test ( &$parseState, $token, $growing = false ) { $parseState['state']['deepness']++; $state = $this->saveState($parseState); $id = $this->getId($parseState, $token); if ( $this->debug ) $indent = str_repeat("\t", $parseState['state']['deepness']); $nodes = array(); $error = false; //~ $match = array('pos' => 0, 'val' => null); foreach ( $this->rules[$token['type']]['productions'] as $i => $production ) if ( $growing || !$this->rules[$token['type']]['seed'] || $production['seed'] ) if ( !$growing || $growing && !$production['seed'] ) try { if ( $this->debug ) echo "\n\t$indent [ $id : {$production['production']}"; $error = false; $nodes = $this->production($parseState, $token, $production); if ( $this->debug ) echo "\nOOO\t$indent ]O $id : {$parseState['state']['position']}"; $parseState['state']['deepness']--; break; //~ if ( $parseState['state']['position'] > $match['pos'] ) { //~ $match['pos'] = $parseState['state']['position']; //~ $match['state'] = $this->saveState($parseState); //~ $match['val'] = $nodes; //~ } //~ $this->setState($parseState, $state); } catch ( ParserException $e ) { if ( $this->debug ) echo "\n###\t$indent ]# $id : {$parseState['state']['position']}"; $this->reportError($parseState, $token, $e); $error = true; $nodes = null; $this->setState($parseState, $state); } if ( !$error && $nodes && !$growing && !empty($this->rules[$token['type']]['seed']) ) { if ( $this->debug ) echo "\n###\t$indent try to grow : {$parseState['state']['position']}"; $nodes = $this->grow($parseState, $token, $state, $nodes); } elseif ( $this->debug ) echo "\n###\t$indent dont try to grow : {$parseState['state']['position']}"; return $nodes; } protected function doesGrow ( $parseState, $token, $follow, $precedence ) { if ( $this->match($parseState, $follow) && ( !empty($parseState['state']['ntoken']) && $this->match($parseState, $parseState['state']['ntoken']) && $follow['precedence'] < $parseState['state']['ntoken']['precedence'] ) ) return false; return $this->match($parseState, $follow) && ( empty($parseState['state']['production']['right']) || !empty($parseState['state']['production']['tokens'][$token['idx'] + 1]) || $follow['precedence'] > $precedence || $follow['precedence'] === $precedence && $follow['associative'] === 'right' ); } protected function grow ( &$parseState, $token, $oldState, $nodes ) { $precedence = 0; if ( $c = count($parseState['state']['operators']) ) $precedence = $parseState['state']['operators'][$c - 1]['precedence']; if ( $this->debug ) $indent = str_repeat("\t", $parseState['state']['deepness']); $oid = $this->getId($oldState, $token); $id = $this->getId($parseState, $token); if ( $this->debug ) echo "\n\t$indent { grow : $oid => $id"; $match = false; if ( !empty($this->follows[$token['type']]) ) foreach ( $this->follows[$token['type']] as $type => $follow ) { if ( $this->doesGrow($parseState, $token, $follow, $precedence) ) { $match = true; break; } } if ( !$match ) { if ( $this->debug ) echo "\n\t$indent } # grow : $id cause not in followset "; return $nodes; } $oldNodes = $nodes; if ( $this->translator ) $nodes = call_user_func($this->translator, $parseState, $token, $nodes); $parseState['memo'][$this->getId($oldState, $token)] = array( 'value' => $nodes, 'position' => $parseState['state']['position'], ); $position = $parseState['state']['position']; $state = $this->saveState($parseState); $this->setState($parseState, $oldState); if ( $this->debug ) echo "\nGGG\t$indent grow procedure : $id"; $newNodes = $this->test($parseState, $token, true); if ( !$newNodes ) { if ( $position >= $parseState['state']['position'] ) $this->setState($parseState, $state); return $oldNodes; } $nodes = $newNodes; if ( $position < $parseState['state']['position'] ) $nodes = $this->grow($parseState, $token, $oldState, $nodes); if ( $this->debug ) echo "\nGGG\t$indent grow procedure successful : {$this->getId($parseState, $token)}"; return $nodes; } protected function production ( &$parseState, $token, $production ) { if ( $this->debug ) $indent = str_repeat("\t", $parseState['state']['deepness']); try { $prod = $parseState['state']['production']; $ntoken = $parseState['state']['ntoken']; $parseState['state']['production'] = $production; $nodes = array(); $op = 0; foreach ( $production['tokens'] as $j => $nextToken ) { if ( $this->debug ) echo "\n\t\t$indent { {$nextToken['type']}{$nextToken['repeat']} : {$parseState['state']['position']}"; if ( $nextToken['precedence'] ) { $op++; $parseState['state']['operators'][] = $nextToken; } $parseState['state']['ntoken'] = !empty($production['tokens'][$j+1]) ? $production['tokens'][$j+1] : $ntoken; $nodes[] = $this->work($parseState, $nextToken); if ( $this->debug ) echo "\nO\t\t$indent }O {$nextToken['type']}{$nextToken['repeat']} : {$parseState['state']['position']}"; } while ( $op-- ) { array_pop($parseState['state']['operators']); } $parseState['state']['production'] = $prod; $parseState['state']['ntoken'] = $ntoken; return $nodes; } catch ( ParserException $e ) { if ( $this->debug ) echo "\n#\t\t$indent }# {$nextToken['type']}{$nextToken['repeat']} : {$parseState['state']['position']}"; throw $e; } } protected function eat ( &$parseState, $token ) { if ( $this->debug ) $indent = str_repeat("\t", $parseState['state']['deepness']); $string = substr($parseState['string'], $parseState['state']['position']); $exp = sprintf( '/^((?:%2$s)?)(%1$s)/S', $this->terminals[$token['type']]['regex'], $this->dummies ); if ( preg_match($exp, $string, $m) ) { if ( $this->debug ) { $c = strlen($m[0]); echo "\n\t\t$indent consume {$token['type']} : {$parseState['state']['position']} : {$c}"; } $parseState['state']['position'] += strlen($m[0]); $parseState['state']['line'] += substr_count($m[0], "\n"); if ( $this->debug ) echo "\n\t\t$indent match {$token['type']} : {$parseState['state']['position']}"; $all = array_shift($m); $dummy = array_shift($m); $len = strlen($m[0]); return array( 'type' => $token['type'], 'line' => ( $parseState['state']['position'] - $len ) ? substr_count($parseState['string'], "\n", 0, $parseState['state']['position'] - $len) + 1 : 1, 'position' => $parseState['state']['position'] - $len, 'length' => $len, 'match' => !empty($m[1]) ? $m[1] : $m[0], 'litteral' => $parseState['litteral'] ? $all : '', 'matches' => $m, ); } else return null; } protected function work ( &$parseState, $token ) { $parseState['state']['deepness']++; $position = $parseState['state']['position']; switch ( $token['repeat'] ) { case '.' : $sub = $this->one($parseState, $token); break; case '?' : $sub = $this->oneAtMost($parseState, $token); break; case '+' : $sub = $this->oneAtLeast($parseState, $token); break; case '*' : $sub = $this->zeroAtLeast($parseState, $token); break; case '&' : $sub = $this->oneMore($parseState, $token); break; case '!' : $sub = $this->noMore($parseState, $token); break; } $parseState['state']['deepness']--; return array( 'type' => $token['type'], 'repeat' => $token['repeat'], 'line' => ( $position ? substr_count($parseState['string'], "\n", 0, $position) : 0 ) + 1, 'position' => $position, 'length' => $parseState['state']['position'] - $position, 'litteral' => $parseState['litteral'] ? substr($parseState['string'], $position, $parseState['state']['position'] - $position) : '', 'nodes' => $sub, ); } protected function one ( &$parseState, $token ) { // id if ( !$elem = $this->consume($parseState, $token) ) throw self::except($parseState, $token); return $elem; } protected function oneAtMost ( &$parseState, $token ) { // id? return $this->consume($parseState, $token); } protected function oneAtLeast ( &$parseState, $token ) { // id+ $nodes = array($this->one($parseState, $token)); while ( $elem = $this->consume($parseState, $token) ) $nodes[] = $elem; return $nodes; } protected function zeroAtLeast ( &$parseState, $token ) { // id* $nodes = array(); while ( $elem = $this->consume($parseState, $token) ) $nodes[] = $elem; return $nodes; } protected function oneMore ( &$parseState, $token ) { // id& $state = $this->saveState($parseState); if ( !$elem = $this->consume($parseState, $token) ) throw self::except($parseState, $token); $this->setState($parseState, $state); return $elem; } protected function noMore ( &$parseState, $token ) { // id! if ( $this->consume($parseState, $token, 1) ) throw self::except($parseState, $token); return true; } static protected function except ( &$parseState, $token, $notExpected = false ) { return new ParserException(sprintf( '%5$sxpected token : "%1$s" at position %2$s on line "%3$s" and col "%4$s" !', $token['type'], $parseState['state']['position'], 0, //substr_count($sub, "\n"), 0, //$parseState['state']['position'] - strrpos($sub, "\n") + 1, $notExpected ? 'Une' : 'E' )); } } class FileSystem { public static function chmod ( $file, $mode = 0 ) { if ( !file_exists($file) ) return false; $chmod = chmod($file, $mode); if ( !is_dir($file) ) return $chmod; $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $file, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($files as $fileinfo) { $path = $fileinfo->getRealPath(); $chmod &= chmod($path, $mode); } return $chmod; } public static function rm ( $file ) { $rm = 1; if ( file_exists($file) ) { if ( is_dir($file) ) { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $file, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($files as $fileinfo) { $path = $fileinfo->getRealPath(); if ( $fileinfo->isDir() ) $rm &= rmdir($path); else $rm &= unlink($path); } $rm &= rmdir($file); } else $rm &= unlink($file); } else return false; return $rm; } public static function zip ( $archive, $file, $exclude = null ) { if ( file_exists($file) ) { $zip = new ZipArchive(); $zip->open($archive, ZIPARCHIVE::CREATE); if ( $exclude ) $exclude = Path::get(realpath($exclude)); if ( is_dir($file) ) { $files = new RecursiveIteratorIterator( new RecursiveCallbackFilterIterator( new RecursiveDirectoryIterator( $file, RecursiveDirectoryIterator::SKIP_DOTS ), $exclude ? function ( $current, $key, $iterator) use ( $exclude ) { return !Path::get($current->getRealPath())->shift($exclude); } : function ( $current, $key, $iterator) { return true; } ), RecursiveIteratorIterator::SELF_FIRST ); $root = Path::get(realpath($file)); foreach ($files as $fileinfo) { $path = Path::get($fileinfo->getRealPath())->shift($root); if ( $fileinfo->isDir() ) $zip->addEmptyDir($path); else $zip->addFile($fileinfo->getRealPath(), $path); } } else $zip->addFile($file); $zip->close(); } else throw new Exception(sprintf('File "%1$s" does not exists !', $file)); } } class Preprocessor { private $noComment = false, $files = array(); public function __construct ( $noComment = false ) { $this->noComment = $noComment; } public function clear () { $this->files = array(); return $this; } public function addFile ( $file, $mode = '', $mirror = null ) { if ( !file_exists($file) ) throw new Exception(sprintf('"%1$s" is not a file or does not exist !', $file)); $content = $this->filter($file, array_filter(preg_split('!\s++!', $mode)), $this->noComment); $this->files[] = array( 'file' => $file, 'content' => $content, ); if ( $mirror ) $this->writeFile($mirror, $content); return $this; } public function addFiles ( $files ) { foreach ( $files as $file => $mode ) $this->addFile($file, $mode); return $this; } public function process ( $noComment = false ) { $str = ''; foreach ( $this->files as $file => $mode ) $str .= "\n" . self::filter($file, $mode, $noComment); return $this; } public function writeFile ( $file, $content ) { if ( !file_exists(dirname($file)) ) throw new Exception(sprintf('The directory for file "%1$s" does not exist !', $file)); file_put_contents($file, ""); return $this; } public function toFile ( $file ) { if ( !file_exists(dirname($file)) ) throw new Exception(sprintf('The directory for file "%1$s" does not exist !', $file)); $content = ''; foreach ( $this->files as $fileContent ) $content .= $fileContent['content']; file_put_contents($file, ""); return $this; } static private function filter ( $file, $mode, $noComment = false ) { $commentTokens = array(T_OPEN_TAG, T_CLOSE_TAG); if ( $noComment ) { $commentTokens[] = T_COMMENT; $commentTokens[] = T_DOC_COMMENT; } $m = array(); $state = ''; $str = ''; foreach ( token_get_all(file_get_contents($file)) as $token ) { if ( !empty($token[0]) && $token[0] === T_DIR ) $token = sprintf('"%1$s"', realpath(dirname($file))); if ( !$state ) { if ( $token[0] === T_COMMENT && preg_match('!^/\*\#BEGIN\s++((\!?)([a-zA-Z0-9]++))\s*\#\*/$!', $token[1], $m) && ( ( !in_array($m[3], $mode) && !$m[2] ) || ( in_array($m[3], $mode) && $m[2] ) ) ) { $state = $m[1]; if ( !$noComment ) $str .= $token[1]; } elseif ( !is_array($token) ) $str .= $token; elseif ( !in_array($token[0], $commentTokens) ) $str .= $token[1]; } elseif ( $token[0] === T_COMMENT ) { if ( preg_match('!^/\*\#END\s++((\!?)([a-zA-Z0-9]++))\s*\#\*/$!', $token[1], $m) ) if ( $m[1] == $state ) { if ( !$noComment ) $str .= $token[1]; $state = ''; } } elseif ( !$noComment ) $str .= str_repeat("\n", count(preg_split("!\n!", is_array($token) ? $token[1] : $token)) - 1); } return $str; } } class Process { public static function fork ( $func = null ) { if ( ( $pid = pcntl_fork() ) === -1 ) throw new Exception('Unable to fork !'); else return $func ? $func($pid) : $pid; } } class ArrayTimer extends ArrayObject { public static function get ( $period ) { $a = new static(); return $a->setPeriod($period); } private $period = 60, $min = 0; public function setPeriod ( $period, $min = 0 ) { $this->period = $period; $this->min = $min; return $this; } public function update ( $id, $delta = 0 ) { $this[$id] = time() + $delta; return $this; } public function offsetGet ( $id ) { if ( !$this->offsetExists($id) ) $this[$id] = time() - $this->period; return parent::offsetGet($id); } public function sleepTime ( $id ) { return max(0, $this->period + $this[$id] - time()); } public function sleepSkip ( $id ) { $skip = !$this->sleepTime($id); if ( $skip ) $this->update($id); return $skip; } public function sleep ( $id ) { $sleep = $this->sleepTime($id); if ( $sleep || $this->min ) sleep(max($sleep, $this->min)); return $this->update($id); } public function select () { $timer = $this; return new CallbackFilterIterator($this, function ( $value, $key, $it ) use ( $timer ) { return !$timer->sleepTime($key); }); } } class MemcachedTimer extends ArrayTimer { private $prefix = '', $cache = null, $period = 60, $min = 0; public function setPrefix ( $prefix ) { $this->cache = new Memcached(); $this->prefix = $prefix; return $this; } public function sleepSkip ( $id ) { $skip = !$this->sleepTime($id); if ( $skip ) $this->update($id); return $skip; } public function offsetGet ( $id ) { $value = $this->memcached->get($this->prefix . $id); if ( $value === false && $this->memcached->getResultCode() === Memcached::RES_NOTFOUND ) $value = $this[$id] = time() - $this->period; parent::offsetSet($value); return $value; } public function offsetSet ( $id, $value ) { if ( $this->memcached->set($this->prefix . $id, $value) ) { parent::offsetSet($id, $value); return $value; } throw new Exception('Memcached error ! Cannot set value !'); } public function offsetUnset ( $id ) { parent::offsetUnset($id); $this->memcached->delete($this->prefix . $id); } public function offsetExists ( $id ) { $value = $this->memcached->get($this->prefix . $id); if ( $value === false && $this->memcached->getResultCode() === Memcached::RES_NOTFOUND ) return false; return true; } } abstract class Expression { static protected $types = array(); public static function getParserExtension () { $context = get_called_class(); return function ( &$parseState, $token, $nodes ) use ( $context ) { return $context::parse($parseState, $token, $nodes); }; } public static function dispatch ( $type, $action ) { //~ echo get_called_class(), "\n"; if ( !empty(static::$types[$type][$action]) ) return static::$types[$type][$action]['value']; $class = get_called_class() . 'Type' . ucfirst($type); $delegate = get_called_class() . ucfirst($action); $method = $action . ucfirst($type); if ( class_exists($class) ) $caller = array( $class, $action ); elseif ( class_exists($delegate) && method_exists($delegate, $method) ) $caller = array( $delegate, $method ); elseif ( get_called_class() !== __CLASS__ ) $caller = call_user_func( array(get_parent_class(get_called_class()), 'dispatch'), $type, $action ); else $caller = null; return static::$types[$type][$action]['value'] = $caller; } public static function build ( $type ) { $args = func_get_args(); array_shift($args); if ( $func = static::dispatch($type, __FUNCTION__) ) return call_user_func_array( $func, $args ); else throw static::buildException($type, __FUNCTION__); } public static function parse ( &$parseState, $token, $nodes ) { if ( $func = static::dispatch($token['type'], __FUNCTION__) ) return call_user_func( $func, $parseState, $token, $nodes ); else return $nodes; } public static function serialize ( $ast ) { if ( $func = static::dispatch($ast['type'], __FUNCTION__) ) return call_user_func( $func, get_called_class(), $ast ); else throw static::buildException($type, __FUNCTION__); } public static function compile ( $ast ) { if ( $func = static::dispatch($ast['type'], __FUNCTION__) ) return call_user_func( $func, get_called_class(), $ast ); else throw static::buildException($ast['type'], __FUNCTION__); } public static function run ( $ast ) { if ( $func = static::dispatch($ast['type'], __FUNCTION__) ) return call_user_func( $func, get_called_class(), $ast ); else throw static::buildException($type, __FUNCTION__); } public static function fromData ( $data ) { if ( is_array($data) ) { $elements = array(); foreach ( $data as $k => $v ) $elements[] = array( static::fromData($k), static::fromData($v) ); return static::build($elements); } elseif ( is_float($data) ) return static::buildFloat($data); elseif ( is_numeric($data) ) return static::buildInteger($data); elseif ( is_bool($data) ) return $data ? static::buildTrue() : static::buildFalse(); elseif ( is_null($data) ) return static::buildNull(); elseif ( is_string($data) ) $code = "'" . implode('\' +. "\n" +. \'', explode("\n", addslashes($data))) . "'"; else throw new Exception('Object cannot be build from data : ' . get_class($data)); } public static function buildException ( $type, $action ) { return new Exception(sprintf( '%1$s error : type "%2$s" has no action "%3$s" !', get_called_class(), $type, $action )); } } class ExpressionAction { } abstract class ExpressionType { public static function parse ( &$parseState, $token, $nodes ) { throw static::buildException(__METHOD__); } public static function serialize ( $context, $ast ) { throw static::buildException(__METHOD__); } public static function compile ( $context, $ast ) { throw static::buildException(__METHOD__); } public static function run ( $context, $ast ) { throw static::buildException(__METHOD__); } private static function buildException ( $method ) { return new Exception(sprintf( 'Method "%3$s" of "%1$s" must be reimplemented in child class "%2$s" !', __CLASS__, get_called_class(), $method )); } } abstract class ExpressionBuild extends ExpressionAction { static protected $type = array(); public static function build ( $type ) { $args = func_get_args(); array_shift($args); return call_user_func_array( static::dispatch($type, __METHOD__), $args ); } } abstract class ExpressionCompile extends ExpressionAction { static protected $type = array(); public static function compile ( $ast ) { return call_user_func_array( static::dispatch($ast['type'], __METHOD__), get_called_class(), $ast ); } } abstract class ExpressionParse extends ExpressionAction { static protected $type = array(); public static function parse ( $cst, $token, $parseState ) { return call_user_func( static::dispatch($token['type'], __METHOD__), $cst, $token, $parseState ); } public static function parsePair ( &$parseState, $token, $nodes ) { return array( $nodes[0]['nodes'], $nodes[2]['nodes'], ); } public static function parsePairList ( &$parseState, $token, $nodes ) { return array_merge( array($nodes[0]['nodes']), $nodes[1]['nodes'] ?: array() ); } public static function parsePairQueue ( &$parseState, $token, $nodes ) { return $nodes[1]['nodes']; } public static function parseExprList ( &$parseState, $token, $nodes ) { return array_merge( array($nodes[0]), $nodes[1]['nodes'] ?: array() ); } public static function parseExprQueue ( &$parseState, $token, $nodes ) { return $nodes[1]; } public static function parseExprs ( &$parseState, $token, $nodes ) { return $nodes[1]; } public static function parseExpr ( &$parseState, $token, $nodes ) { return $nodes[0]['nodes']; } public static function parseIsolation ( &$parseState, $token, $nodes ) { return $nodes[1]['nodes']; } public static function parseScalar ( &$parseState, $token, $nodes ) { return $nodes[0]['nodes']; } public static function parseIndex ( &$parseState, $token, $nodes ) { if ( $nodes[0]['type'] !== 'identifier' ) return $nodes[0]['nodes']; return call_user_func( array($parseState['parser'], 'parseString'), "'" . $nodes[0]['nodes']['value'] . "'", 1, 'splquotes', '.' ); } public static function parseFilter( &$parseState, $token, $nodes ) { $args = array($nodes[0]['nodes']); return array( 'type' => 'function', 'line' => $nodes[3]['line'], 'namespace' => $nodes[2]['nodes'], 'name' => $nodes[3]['nodes']['value'], 'method' => $nodes[4]['nodes'] ? $nodes[4]['nodes'][1]['nodes']['value'] : '', 'args' => empty($nodes[5]['nodes']['nodes']) ? $args : array_merge( $args, array_map( function ( $elem ) { return $elem['nodes']; }, $nodes[5]['nodes']['nodes'] ) ), ); } public static function parseSubspace ( &$parseState, $token, $nodes ) { return $nodes[0]; } public static function parseNamespace ( &$parseState, $token, $nodes ) { return ( $nodes[0]['nodes'] ? '\\' : '' ) . implode('\\', array_map( function ( $elem ) { return $elem['nodes']['value']; }, $nodes[1]['nodes'] )); } } abstract class ExpressionRun extends ExpressionAction { static protected $type = array(); public static function run ( $ast ) { return call_user_func_array( static::dispatch($ast['type'], __METHOD__), get_called_class(), $ast ); } } abstract class ExpressionSerialize extends ExpressionAction { static protected $type = array(); public static function serialize ( $ast ) { return call_user_func_array( static::dispatch($ast['type'], __METHOD__), get_called_class(), $ast ); } } class ExpressionTypeArrayAccess extends ExpressionType { public static function build ( $ast1, $ast2, $line = -1 ) { return array( 'type' => 'arrayAccess', 'line' => $line, 'operands' => array( $ast1, $ast2 ) ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[0]['nodes'], $nodes[2]['nodes'], $nodes[1]['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( '%1$s[%2$s]', $context::serialize($ast['operands'][0]), $context::serialize($ast['operands'][1]) ); } public static function compile ( $context, $ast ) { return sprintf( 'ExpressionTypeArrayAccess::runtime(%1$s, %2$s)', $context::compile($ast['operands'][0]), $context::compile($ast['operands'][1]) ); } public static function run ( $context, $ast ) { return static::runtime( $context::run($ast['operands'][0]), $context::run($ast['operands'][1]) ); } public static function runtime ( $elem, $index ) { if ( !$elem ) return null; if ( is_array($elem) && array_key_exists($index, $elem) ) return $elem[$index]; return null; } } class ExpressionTypeBinaryOp extends ExpressionType { public static function build ( $operator, $ast1, $ast2, $line = -1 ) { return array( 'type' => 'binaryOp', 'line' => $line, 'operator' => $operator, 'operands' => array( $ast1, $ast2, ) ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[1]['nodes']['match'], $nodes[0]['nodes'], $nodes[2]['nodes'], $nodes[1]['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( '( %2$s %1$s %3$s )', $ast['operator'], $context::serialize($ast['operands'][0]), $context::serialize($ast['operands'][1]) ); } public static function compile ( $context, $ast ) { switch ( $ast['operator'] ) { case '^': $str = 'pow(%2$s, %3$s)'; break; case '??': $str = '( %2$s ? %3$s : "" )'; break; case '::': $str = '( %2$s ?: %3$s )'; break; default: $str = '( %2$s %1$s %3$s )'; break; } return sprintf( $str, ( $ast['operator'] !== '+.' ) ? $ast['operator'] : '.', $context::compile($ast['operands'][0]), $context::compile($ast['operands'][1]) ); } public static function run ( $context, $ast ) { switch ( $ast['operator'] ) { case '^': return pow( $context::run($ast['operands'][0]), $context::run($ast['operands'][1]) ); break; case '??': if ( $context::run($ast['operands'][0]) ) return $context::run($ast['operands'][1]); return null; break; case '::': if ( !$context::run($ast['operands'][0]) ) return $context::run($ast['operands'][1]); return null; break; default: //~ $op1 = $context::compile($ast['operands'][0]); //~ $op2 = $context::compile($ast['operands'][1]); //~ return eval(sprintf('return %2$s %1$s %3$s;', $op1, $op2)); break; } } } class ExpressionTypeClosure extends ExpressionType { public static function build ( $headAst, $bodyAst, $line = -1 ) { return array( 'type' => 'closure', 'line' => $line, 'head' => $headAst, 'body' => $bodyAst, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[2]['nodes'] ? array_map(function ( $elem ) { return $elem['value']; }, $nodes[2]['nodes']) : array(), $nodes[5]['nodes'] ? array_map( function ( $elem ) { return array( 'return' => $elem[0]['nodes'] ? 1 : 0, 'expression' => $elem[1]['nodes'], ); }, $nodes[5]['nodes'] ) : array(), $nodes[0]['line'] ); } public static function serialize ( $context, $ast ) { die('closure serialize'); return sprintf( '( %1$s %2$s )', $ast['operator'], $context::serialize($ast['operand']) ); } public static function compile ( $context, $ast ) { return sprintf( 'function (%1$s) use ($data, $tmp, $runtime) {' . "\n\t" . '%3$s' . "\n\t" . '%2$s' . "\n}", implode(', ', array_map(function ( $elem ) { return '$' . $elem; }, $ast['head'])), implode("\n\t", array_map( function ( $elem ) use ( $context ) { return sprintf( ( $elem['return'] ? 'return ' : '' ) . '%1$s;', $context::compile($elem['expression']) ); }, $ast['body'] )), implode("\n\t", array_map(function ( $elem ) { return sprintf('$data["%1$s"] = $%1$s;', $elem); }, $ast['head'])) ); } public static function run ( $context, $ast ) { } } class ExpressionTypeDblquotes extends ExpressionType { public static function build ( $value, $line = -1 ) { return array( 'type' => 'dblquotes', 'line' => $line, 'value' => $value, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( eval('return "' . str_replace('$', '\$', $nodes['matches'][1]) . '";'), $nodes['line'] ); } public static function serialize ( $context, $ast ) { return "'" . implode('\' +. "\n" +. \'', explode("\n", preg_replace( array('!\\\\!', '!\'!'), array('\\\\\\', '\\\''), $ast['value'] ))) . "'"; } public static function compile ( $context, $ast ) { return "'" . implode('\' . "\n" . \'', explode("\n", preg_replace( array('!\\\\!', '!\'!'), array('\\\\\\', '\\\''), $ast['value'] ))) . "'"; } public static function run ( $context, $ast ) { return $ast['value']; } } class ExpressionTypeFalse extends ExpressionType { public static function build ( $line = -1 ) { return array( 'type' => 'false', 'line' => $line, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build($nodes['line']); } public static function serialize ( $context, $ast ) { return '$false'; } public static function compile ( $context, $ast ) { return 'false'; } public static function run ( $context, $ast ) { return false; } } class ExpressionTypeFloat extends ExpressionType { public static function build ( $value, $line = -1 ) { return array( 'type' => 'float', 'line' => $line, 'value' => $value ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes['matches'][0], $nodes['line'] ); } public static function serialize ( $context, $ast ) { return $ast['value']; } public static function compile ( $context, $ast ) { return $ast['value']; } public static function run ( $context, $ast ) { return $ast['value']; } } class ExpressionTypeFunction extends ExpressionType { public static function build ( $namespace, $name, $methodName, $argsAst, $line = -1 ) { return array( 'type' => 'function', 'line' => $line, 'namespace' => $namespace, 'name' => $name, 'method' => $methodName, 'args' => $argsAst ?: array(), ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[0]['nodes'], $nodes[1]['nodes']['value'], $nodes[2]['nodes'] ? $nodes[2]['nodes'][1]['nodes']['value'] : '', array_map( function ( $elem ) { return $elem['nodes']; }, $nodes[3]['nodes']['nodes'] ?: array() ), $nodes[2]['nodes'][0]['nodes']['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( $ast['method'] ? '%4$s%1$s->%2$s(%3$s)' : '%4$s%1$s(%3$s)', $ast['name'], $ast['method'], empty($ast['args']) ? '' : implode(', ', array_map( function ($elem) use ( $context ) { return $context::serialize($elem); }, $ast['args'] )), $ast['namespace'] ? $ast['namespace'] . '\\' : '' ); } public static function compile ( $context, $ast ) { if ( !$ast['namespace'] && !$ast['method'] && function_exists('sense_' . $ast['name']) ) $ast['name'] = 'sense_' . $ast['name']; $str = !$ast['method'] ? '%4$s%1$s(%3$s)' : ( $ast['method'] !== 'new' ? '%4$s%1$s::%2$s(%3$s)' : 'new %4$s%1$s (%3$s)' ); return sprintf( $str, $ast['name'], $ast['method'], empty($ast['args']) ? '' : implode(', ', array_map( function ($elem) use ( $context ) { return $context::compile($elem); }, $ast['args'] )), $ast['namespace'] ? $ast['namespace'] . '\\' : '' ); } public static function run ( $context, $ast ) { return eval('return ' . static::compile($ast) . ';'); /* $name = ( $ast['namespace'] ? $ast['namespace'] . '\\' : '' ) . $ast['name']; if ( $ast['method'] ) { if ( $ast['method'] === 'new' ) return eval('return ' . static::compile($ast) . ';'); $name = array($name, $ast['method']); } return call_user_func_array( $name, array_map( function ($elem) use ( $context ) { return $context::run($elem); }, $ast['args'] ) ); */ } } class ExpressionTypeIdentifier extends ExpressionType { public static function build ( $name, $line = -1 ) { return array( 'type' => 'identifier', 'line' => $line, 'value' => $name ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes['match'], $nodes['line'] ); } public static function serialize ( $context, $ast ) { return $ast['value']; } public static function compile ( $context, $ast ) { if ( $ast['value'] === '_' ) return '$data'; return 'ExpressionTypeArrayAccess::runtime($data, \'' . $ast['value'] . '\')'; } public static function run ( $context, $ast ) { } } class ExpressionTypeInteger extends ExpressionType { public static function build ( $value, $line = -1 ) { return array( 'type' => 'integer', 'line' => $line, 'value' => (int) $value ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[0]['nodes']['matches'][0], $nodes[0]['nodes']['line'] ); } public static function serialize ( $context, $ast ) { return $ast['value']; } public static function compile ( $context, $ast ) { return $ast['value']; } public static function run ( $context, $ast ) { return $ast['value']; } } class ExpressionTypeList extends ExpressionType { public static function build ( $elementsAst, $line = -1 ) { return array( 'type' => 'list', 'line' => $line, 'elements' => $elementsAst ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( array_map(function ( $elem ) { return $elem['nodes']; }, $nodes[1]['nodes']), $nodes[0]['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( '[%1$s]', implode(', ', array_map( function( $elem ) use ( $context ) { return $context::serialize($elem); }, $ast['elements'] )) ); } public static function compile ( $context, $ast ) { return sprintf( "array(\n\t%1\$s\n)", implode(",\n\t", array_map( function( $elem ) use ( $context ) { return implode("\n\t", explode("\n", $context::compile($elem))); }, $ast['elements'] )) ); } public static function run ( $context, $ast ) { return array_map( function ( $elem ) use ( $context ) { return $context::run($elem); }, $ast['elements'] ); } } class ExpressionTypeMap extends ExpressionType { public static function build ( $elementsAst, $line = -1 ) { return array( 'type' => 'map', 'line' => $line, 'elements' => $elementsAst ?: array() ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[1]['nodes'], $nodes[0]['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( '[%1$s]', implode(', ', array_map( function( $elem ) use ( $context ) { return sprintf( '%1$s : %2$s', implode("\n\t", explode("\n", $context::serialize($elem[0]))), implode("\n\t", explode("\n", $context::serialize($elem[1]))) ); }, $ast['elements'] )) ); } public static function compile ( $context, $ast ) { return sprintf( "array(\n\t%1\$s\n)", $ast['elements'] ? implode(",\n\t", array_map( function( $elem ) use ( $context ) { return sprintf( '%1$s => %2$s', implode("\n\t", explode("\n", $context::compile($elem[0]))), implode("\n\t", explode("\n", $context::compile($elem[1]))) ); }, $ast['elements'] )) : '' ); } public static function run ( $context, $ast ) { return array_map( function ( $elem ) use ( $context ) { return $context::run($elem); }, $ast['elements'] ); } } class ExpressionTypeMethod extends ExpressionType { public static function build ( $objectAst, $methodName, $argsAst, $line = -1 ) { return array( 'type' => 'method', 'line' => $line, 'object' => $objectAst, 'method' => $methodName, 'args' => $argsAst, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[0]['nodes'], $nodes[2]['nodes']['value'], $nodes[3]['nodes']['nodes'] ? array_map( function ( $elem ) { return $elem['nodes']; }, $nodes[3]['nodes']['nodes'] ) : array(), $nodes[2]['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( '%1$s.%2$s(%3$s)', $context::serialize($ast['object']), $ast['method'], empty($ast['args']) ? '' : implode(', ', array_map( function ($elem) use ( $context ) { return $context::serialize($elem); }, $ast['args'] )) ); } public static function compile ( $context, $ast ) { return sprintf( 'call_user_func_array(array(%1$s, \'%2$s\'), array(%3$s) )', $context::compile($ast['object']), $ast['method'], empty($ast['args']) ? '' : implode(', ', array_map( function ($elem) use ( $context ) { return $context::compile($elem); }, $ast['args'] )) ); } public static function run ( $context, $ast ) { return call_user_func_array( array($context::run($ast['object']), $ast['method']), empty($ast['args']) ? array() : implode(', ', array_map( function ($elem) use ( $context ) { return $context::run($elem); }, $ast['args'] )) ); } } class ExpressionTypeNull extends ExpressionType { public static function build ( $line = -1 ) { return array( 'type' => 'null', 'line' => $line, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build($nodes['line']); } public static function serialize ( $context, $ast ) { return '$null'; } public static function compile ( $context, $ast ) { return 'null'; } public static function run ( $context, $ast ) { return null; } } class ExpressionTypeSplquotes extends ExpressionType { public static function build ( $value, $line = -1 ) { return array( 'type' => 'splquotes', 'line' => $line, 'value' => $value, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( preg_replace( array('!\\\\\\\!', '!\\\\\'!'), array('\\', "'"), $nodes['matches'][1] ), $nodes['line'] ); } public static function serialize ( $context, $ast ) { return "'" . implode('\' +. "\n" +. \'', explode("\n", preg_replace( array('!\\\\!', '!\'!'), array('\\\\\\', '\\\''), $ast['value'] ))) . "'"; } public static function compile ( $context, $ast ) { //~ return "'" . preg_replace( //~ array('!\\\\!', '!\'!'), //~ array('\\\\\\', '\\\''), //~ $ast['value'] //~ ) . "'"; return "'" . implode('\' . "\n" . \'', explode("\n", preg_replace( array('!\\\\!', '!\'!'), array('\\\\\\', '\\\''), $ast['value'] ))) . "'"; } public static function run ( $context, $ast ) { return $ast['value']; } } class ExpressionTypeTernaryOp extends ExpressionType { public static function build ( $ast1, $ast2, $ast3, $line = -1 ) { return array( 'type' => 'ternaryOp', 'line' => $line, 'operands' => array( $ast1, $ast2, $ast3, ) ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[0]['nodes'], $nodes[2]['nodes'], $nodes[4]['nodes'], $nodes[1]['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( '( %1$s ? %2$s : %3$s )', $context::serialize($ast['operands'][0]), $context::serialize($ast['operands'][1]), $context::serialize($ast['operands'][2]) ); } public static function compile ( $context, $ast ) { return sprintf( '( %1$s ? %2$s : %3$s )', $context::compile($ast['operands'][0]), $context::compile($ast['operands'][1]), $context::compile($ast['operands'][2]) ); } public static function run ( $context, $ast ) { if ( $context::run($ast['operands'][0]) ) return $context::run($ast['operands'][1]); else return $context::run($ast['operands'][2]); } } class ExpressionTypeTrue extends ExpressionType { public static function build ( $line = -1 ) { return array( 'type' => 'true', 'line' => $line, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build($nodes['line']); } public static function serialize ( $context, $ast ) { return '$true'; } public static function compile ( $context, $ast ) { return 'true'; } public static function run ( $context, $ast ) { return true; } } class ExpressionTypeUnaryOp extends ExpressionType { public static function build ( $operator, $ast, $line = -1 ) { return array( 'type' => 'unaryOp', 'line' => $line, 'operator' => $operator, 'operand' => $ast, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build( $nodes[0]['nodes']['match'], $nodes[1]['nodes'], $nodes[0]['line'] ); } public static function serialize ( $context, $ast ) { return sprintf( '( %1$s %2$s )', $ast['operator'], $context::serialize($ast['operand']) ); } public static function compile ( $context, $ast ) { return sprintf( '( %1$s %2$s )', $ast['operator'], $context::compile($ast['operand']) ); } public static function run ( $context, $ast ) { switch ( $ast['operator'] ) { case '!': return !$context::run($ast['operand']); break; case '-': return -$context::run($ast['operand']); break; case '~': return ~$context::run($ast['operand']); break; } } } class Template extends Engine { public static function fromString ( $string, $dir = '', $root = '', $cache = '', $basename = '' ) { $instance = new static($dir, $root, $cache, $basename); $instance->code = SeTMLInterface::compileString($string); $instance->source = $string; return $instance; } /** * Rendering */ public function run ( $data = array() ) { if ( !( $data instanceOf SeTMLRuntime ) ) $data = new SeTMLRuntime($data); try { return $data->run( $this->include ? $this->include : $this->code, $this, $this->include ? 1 : 0 ); } catch ( Exception $e ) { throw new Exception('Error during program execution !', 0, $e); } throw new Exception('File has not been compiled ! Moreover, it should not be possible to be here !'); } } class SeTMLRuntime { public static function initState ($data) { return array( 'data' => $data, 'alloc' => array( '' => null, ), ); } /* execution environnement */ public $engine = null, $flow = null, $alloc = null, $frame = null, $blocks = array(), $stack = array(); public function __construct ( $data ) { $this->frame = self::initState($data); $this->flow = &$this->frame['alloc']['']; $this->alloc = &$this->frame['alloc']; } public function run ( $code, Engine $engine = null, $file = false ) { $en = $this->engine; $this->engine = $engine; $this->exec($this, $code, $file); $this->engine = $en; return $this; } private function exec ($runtime, $code, $file = false) { if ( $file ) include($code); else eval($code); } public function getData( $idx = false ) { if ( $idx !== false ) return $this->frame['alloc'][$idx]; return $this->frame['alloc']; } public function appendContent ( $data ) { if ( is_array($this->flow) ) { if ( empty($this->flow['']) ) $this->flow[''] = $data; else $this->flow[''] .= $data; } else $this->flow .= $data; return $this; } public function cpush ( &$pointer, $map = false ) { $this->stack[] = array( &$this->flow, &$this->alloc, ); $this->alloc = &$pointer; if ( is_array($map) ) { foreach ( $map as $k => $v ) $this->alloc[$k] = $v; $this->flow = &$pointer['']; } else $this->flow = &$pointer; return $this; } public function cpop () { $data = $this->frame['alloc']; $alloc = array_pop($this->stack); $this->flow = &$alloc[0]; $this->alloc = &$alloc[1]; return $data; } public function lpush ( $data = array() ) { $this->stack[] = array( &$this->flow, &$this->alloc, $this->frame['alloc'] ); $this->frame['alloc'] = $data; $this->flow = &$this->frame['alloc']['']; $this->alloc = &$this->frame['alloc']; } public function lpop () { $data = $this->frame['alloc']; $alloc = array_pop($this->stack); $this->flow = &$alloc[0]; $this->alloc = &$alloc[1]; $this->frame['alloc'] = $alloc[2]; return $data; } public function mpush ( $data = array() ) { $this->stack[] = array( &$this->flow, &$this->alloc, $this->frame, $this->blocks ); $this->frame = self::initState($data); } public function mpop () { $data = $this->frame; $frame = array_pop($this->stack); $this->flow = &$frame[0]; $this->alloc = &$frame[1]; $this->frame = $frame[2]; $this->blocks = array_merge($frame[3], array_diff_key($this->blocks, $frame[3])); return $data; } public function mergeFrame ( $frame ) { foreach ( $frame['alloc'] as $k => $v ) if ( $k !== '' ) $this->frame['alloc'][$k] = $v; $this->appendContent($frame['alloc']['']); } public function setBlock ( &$pointer, $block, $scope = array() ) { $pointer = array( $block, $scope ); } public function callBlock ( $pointer, $data = array() ) { if ( empty($pointer) ) throw new Exception(sprintf('RuntimeError block does not exists !')); $block = $pointer[0]; $this->mpush(array_merge($pointer[1], $data)); $block($this); $this->mergeFrame($this->mpop()); } public function includeTemplate ( $file, $data, $asis = false ) { $engine = $this->engine; $file = Path::get(preg_replace('!//+!', '/', preg_match('!^/!', $file) ? $file : $this->engine->getDir() . '/' . $file)); try { $tpl = Template::fromFile( $file, $this->engine->getRoot(), $this->engine->getCache() ); if ( $asis ) $this->appendContent($tpl->getSource()); else { $this->mpush($data); $tpl->run($this); $this->mergeFrame($this->mpop()); } } catch ( Exception $e ) { throw new Exception(sprintf( 'RuntimeError during inclusion of file "%1$s" in root directory' . "\n" . '"%2$s"' . "\n" . 'specified in file "%3$s"!', $file, $engine->getRoot(), $engine->getBasename() ), 0, $e); } } public function metaTemplate ( Closure $template, $cache = null ) { $engine = $this->engine; try { $data = $template($this); $this->mpush($data); if ( !$cache ) { Template::fromString( $data[''], $this->engine->getDir(), $this->engine->getRoot(), $this->engine->getCache() )->run($this); } else { Template::fromFile( Path::get($cache[0] === DIRECTORY_SEPARATOR ? $cache : $this->engine->getDir() . DIRECTORY_SEPARATOR . $cache), $this->engine->getRoot(), $this->engine->getCache(), $data[''] )->run($this); } $this->mergeFrame($this->mpop()); } catch ( Exception $e ) { throw new Exception(sprintf( 'RuntimeError during meta processing of file "%1$s" in root directory' . "\n" . '"%2$s"%3$s !', $engine->getBasename(), $engine->getRoot(), $cache ? ".\nMeta template available in mirror file " . $cache : '.' ), 0, $e); } } } abstract class SeTML extends Expression { static protected $types = array(); } /* #dirty Some things are dirty like usage of __DIR__ and a relative filepath setml.cnf */ class SeTMLInterface { static protected $parser = null; static public function parseString ( $string ) { if ( !static::$parser ) static::$parser = SGParser::fromFile( "/var/www/html/admin/app/libs/sense/Sensible/SeTML/Compiler" . '/setml.peg', SeTML::getParserExtension() ); return static::$parser->parseString($string, 1); } static public function compileString ( $string, SeTMLRuntime $runtime = null ) { return SeTML::compile( static::parseString( $string ) ); } } class SeTMLType extends ExpressionType { } abstract class SeTMLBuild extends ExpressionBuild { public static function buildText () { return array( 'type' => 'text', 'content' => func_get_args() ); } } abstract class SeTMLCompile extends ExpressionCompile { static public function compileText ( $context, $ast ) { return SeTMLTypeTag::compileText($context, $ast); } } // static reference to SeTML in parseText to rewrite abstract class SeTMLParse extends ExpressionParse { public static function parseNode ( &$parseState, $token, $nodes ) { return $nodes[0]['nodes']; } public static function parseVartag ( &$parseState, $token, $nodes ) { return array( 'type' => 'var', 'line' => $nodes[0]['line'], 'meta' => (int) $nodes[1]['nodes'][1]['nodes']['value'] * ( $nodes[1]['nodes'][0]['nodes'] ? -1 : 1 ), 'value' => $nodes[2]['nodes'], ); } public static function parseTextual ( &$parseState, $token, $nodes ) { return array( 'type' => 'text', 'line' => $nodes['line'], 'text' => $nodes['litteral'], ); } public static function parseSpace ( &$parseState, $token, $nodes ) { return array( 'type' => 'text', 'line' => $nodes['line'], 'text' => $nodes['litteral'], ); } public static function parseAuto(&$parseState, $token, $nodes) { return static::parseOpen( $parseState, $token, $nodes, 'auto' ); } public static function parseOpen( &$parseState, $token, $nodes, $type = 'open' ) { if ( !SeTMLTypeTag::tagExists($nodes[0]['nodes']['match']) ) return null; return SeTMLSpec::check(array_merge( array( 'type' => 'tag', 'name' => $nodes[0]['nodes']['match'], 'tag' => $type, 'line' => $nodes[0]['line'], ), is_array($nodes[1]['nodes']) ? $nodes[1]['nodes'] : array() )); } public static function parseClose(&$parseState, $token, $nodes) { if ( !SeTMLTypeTag::tagExists($nodes[0]['nodes']['match']) ) return false; return array( 'type' => 'tag', 'name' => $nodes[0]['nodes']['match'], 'tag' => 'close', 'line' => $nodes[0]['line'], ); } public static function parseParam ( &$parseState, $token, $nodes ) { if ( $nodes[0]['type'] === 'attribute' ) { $attributes = array(); foreach ( $nodes[0]['nodes'] as $attribute ) $attributes[$attribute['name']] = $attribute['value']; return array('attributes' => $attributes); } elseif ($nodes[0]['type'] === '(' ) return array('expression' => $nodes[1]['nodes']); return array('form' => $nodes[0]['nodes']); } public static function parseForm ( &$parseState, $token, $nodes ) { $form = array( 'type' => 'form', 'line' => $nodes[0]['line'], 'nodes' => array( $nodes[0]['nodes'], $nodes[2]['nodes'] ), ); if ( !empty($nodes[4]['nodes']) ) $form['nodes'][] = $nodes[4]['nodes']; if ( !empty($nodes[6]['nodes']) ) $form['nodes'][] = $nodes[6]['nodes']; return $form; } public static function parseAttribute ( &$parseState, $token, $nodes ) { return array( 'type' => 'attribute', 'line' => $nodes[0]['line'], 'name' => $nodes[0]['nodes']['value'], 'value' => !empty($nodes[1]) ? $nodes[2]['nodes'] : 1, ); } } abstract class SeTMLRun extends ExpressionRun {} abstract class SeTMLSerialize extends ExpressionSerialize {} class SeTMLTypeMagic extends SeTMLType { public static function build ( $magic, $line = -1 ) { return array( 'type' => 'magic', 'value' => $magic, 'line' => $line, ); } public static function parse ( &$parseState, $token, $nodes ) { return static::build($nodes['match'], $nodes['line']); } public static function serialize ( $context, $ast ) { return '$' . $ast['value']; } public static function compile ( $context, $ast ) { switch ( $ast['value'] ) { case 'root': return '$runtime->engine->getRoot()'; break; case 'dir': return '$runtime->engine->getDir()'; break; case 'file': return '$runtime->engine->getBasename()'; break; case 'path': return '$runtime->engine->getFile()'; break; case 'line': return $ast['line']; break; case 'session': return '$_SESSION'; break; case 'server': return '$_SERVER'; break; case 'get': return '$_GET'; break; case 'post': return '$_POST'; break; case 'request': return '$_REQUEST'; break; case 'cookie': return '$_COOKIE'; break; case 'files': return '$_FILES'; break; default: throw new Exception('Magic constant not available : ' . $ast['value']); break; } } public static function run ( $context, $ast ) { return null; } } abstract class SeTMLTypeS extends SeTMLType { static public function build ( $ast, $line = -1 ) { return array( 'type' => 'S', 'line' => -1, 'nodes' => $ast, ); } static public function parse ( &$parseState, $token, $nodes ) { $ast = array('nodes' => array()); $here = array(&$ast); foreach ( $nodes[0]['nodes'] as $token ) if ( in_array($token['type'], array('text', 'var')) ) { static::addText ( $here[count($here) - 1], $token ); } else switch ( $token['tag'] ) { case 'auto': $here[count($here) - 1]['nodes'][] = $token; break; case 'open': $now = count($here) - 1; $count = count($here[$now]['nodes']); $token['nodes'] = array(); $here[$now]['nodes'][] = $token; $here[] = &$here[$now]['nodes'][$count]; break; case 'close': if ( count($here) < 2 ) throw new Exception(sprintf( 'Close tag "%1$s" found on line %2$s but no open tag was found !', $token['name'], $token['line'] )); $oldToken = array_pop($here); if ( $oldToken['name'] !== $token['name'] ) throw new Exception(sprintf( 'Close tag "%1$s" found on line %3$s instead of "%2$s" expected opened on line %4$s !', $token['name'], $oldToken['name'], $token['line'], $oldToken['line'] )); break; } if ( count($here) > 1 ) throw new Exception(sprintf( 'Tag "%1$s" opened on line "%2$s" has not been closed !', $here[count($here) - 1]['name'], $here[count($here) - 1]['line'] )); //~ print_r($ast['nodes']); //~ die(); return static::build($ast['nodes']); } static public function addText ( &$ast, $node ) { if ( !( $c = count($ast['nodes']) ) || $ast['nodes'][$c - 1]['type'] !== 'text' ) return $ast['nodes'][] = array( 'type' => 'text', 'content' => array($node) ); $cc = count($ast['nodes'][$c - 1]['content']) - 1; if ( $ast['nodes'][$c - 1]['content'][$cc]['type'] === 'text' && $node['type'] === 'text' ) $ast['nodes'][$c - 1]['content'][$cc]['text'] .= $node['text']; else $ast['nodes'][$c - 1]['content'][] = $node; } static public function serialize ( $context, $ast ) { } static public function compile ( $context, $ast ) { $code = array(static::open($ast)); foreach ( $ast['nodes'] as $node ) $code[] = $context::compile($node); $code[] = static::close($ast); return implode("\n", array_filter($code)); } static public function run ( $context, $ast ) { } static public function open ( $ast ) { return 'if ( !isset($tmp) ) $tmp = array();' . "\n" . 'if ( !isset($data) ) $data = &$runtime->frame["data"];'; } static public function close ( $ast ) { return ''; } } abstract class SeTMLTypeTag extends SeTMLType { static public function build ( $name, $type, $attributes = array(), $content = array(), $text = '', $line = -1 ) { return array( 'type' => 'tag', 'name' => $name, 'tag' => $type, 'line' => -1, 'text' => $text, 'attributes' => $attributes, 'expression' => $expression, 'form' => $form, 'content' => $content, ); } static public function parse ( &$parseState, $token, $nodes ) { return $nodes; $ast = array(); $here = array(&$ast); foreach ( $nodes[0]['nodes'] as $token ) { switch ( $token['type'] ) { case 'auto': $here[count($here) - 1][] = array( 'type' => 'auto', 'name' => $token['name'], 'token' => $token, ); break; case 'open': $now = count($here) - 1; $count = count($here[$now]); $here[$now][] = array( 'type' => 'tag', 'name' => $token['name'], 'token' => $token, 'nodes' => array(), ); $here[] = &$here[$now][$count]['nodes']; break; case 'close': array_pop($here); break; case 'text': $here[count($here) - 1][] = array( 'type' => 'text', 'token' => $token, ); break; } } return $ast; } static public function serialize ( $context, $ast ) { $attributes = array(); if ( !empty($ast['attributes']) ) foreach ( $ast['attributes'] as $n => $v ) $attributes[] = sprintf( '%1$s%2$s', $n, is_scalar($v) ? '' : '=' . $context::serialize($v) ); $code = sprintf( '<[%3$s%1$s%4$s%2$s]>', $ast['name'], $ast['tag'] === 'auto' ? '/' : '', $ast['tag'] === 'close' ? '/' : '', $ast['tag'] === 'close' ? '' : ' ' . implode(' ', $attributes) ); return $code; } static public function compile ( $context, $ast ) { $class = 'SeTMLTypeTag' . ucfirst($ast['name']); $type = $ast['tag']; return $class::$type($context, $ast); } static public function run ( $context, $ast ) { $class = 'SeTMLTypeTag' . ucfirst($ast['name']); return $class::run($context, $ast); } static public function auto ( $context, $ast ) { throw new Exception(sprintf( 'SeTMLTypeTag method "%2$s" not supported for tag "%1$s" !', $ast['name'], __METHOD__ )); } static public function open ( $context, $ast ) { throw new Exception(sprintf( 'SeTMLTypeTag method "%2$s" not supported for tag "%1$s" !', $ast['name'], __METHOD__ )); } static public function tagExists ( $tagName ) { $className = 'SeTMLTypeTag' . ucfirst($tagName); if ( class_exists($className) ) return $className; } static public function compileValue ( $value ) { return sprintf( '$runtime->appendContent(' . "\n\t" . '%1$s' . "\n" . ');', preg_replace("!\n!", "\n\t", $value) ); } static public function getShareAttribute ( $context, $tag, $attribute = 'share' ) { return empty($tag['attributes'][$attribute]) ? 'array()' : ( $tag['attributes'][$attribute] === 1 ? $context::compile($context::build('identifier', '_')) : $context::compile($tag['attributes'][$attribute]) ); } static public function getAsisAttribute ( $context, $tag, $attribute = 'asis' ) { return empty($tag['attributes'][$attribute]) ? 0 : ( $tag['attributes'][$attribute] === 1 ? 1 : $context::run($tag['attributes'][$attribute]) ); } static public function getMetaAttribute ( $context, $tag, $attribute = 'meta' ) { return empty($tag['attributes'][$attribute]) ? 0 : ( $tag['attributes'][$attribute] === 1 ? 1 : $context::run($tag['attributes'][$attribute]) ); } static public function compileText ( $context, $ast ) { return static::compileValue(implode(" . \n", array_map(function ( $elem ) use ( $context ) { if ( $elem['type'] === 'text' ) $code = $context::compile($context::build('splquotes', $elem['text'])); elseif ( !$elem['meta'] ) $code = $context::compile($elem['value']); else $code = $context::compile($context::build('splquotes', sprintf( !$elem['meta'] ? '{%2$s}' : '{%1$s, %2$s}', $elem['meta'] > 0 ? $elem['meta'] - 1 : $elem['meta'], $context::serialize($elem['value']) ))); return $code; }, $ast['content']))); } static public function compileTextAsText ( $context, $ast ) { return static::compileValue(implode(" . \n", array_map(function ( $elem ) use ( $context ) { if ( $elem['type'] === 'text' ) $code = $context::compile($context::build('splquotes', $elem['text'])); else $code = $context::compile($context::build('splquotes', sprintf( !$elem['meta'] ? '{%2$s}' : '{%1$s, %2$s}', $elem['meta'], $context::serialize($elem['value']) ))); return $code; }, $ast['content']))); } static public function compileTagAsText ( $context, $ast ) { if ( $ast['type'] === 'text' ) return static::compileTextAsText($context, $ast); $code = array(); foreach ( $ast['nodes'] as $node ) { if ( $node['type'] === 'text' ) $code[] = static::compileTextAsText($context, $node); elseif ( $node['tag'] === 'auto' ) { $code[] = $context::serialize($node); } elseif ( $node['tag'] === 'open' ) { $code[] = static::compileValue($context::compile($context::build('splquotes', $context::serialize($node)))) . "\n"; $code[] = static::compileTagAsText($context, $node); $node['tag'] = 'close'; $code[] = static::compileValue($context::compile($context::build('splquotes', $context::serialize($node)))) . "\n"; } else throw new Exception(sprintf('Error in file "%1$s" on line %2$s', __FILE__, __LINE__)); } return implode("\n", $code); } static public function compileTextAsMacro ( $context, $ast ) { return static::compileValue(implode(" . \n", array_map(function ( $elem ) use ( $context ) { if ( $elem['type'] === 'text' ) $code = $context::compile($context::build('splquotes', $elem['text'])); elseif ( $elem['meta'] === -1 ) $code = $context::compile($elem['value']); else $code = $context::compile($context::build('splquotes', sprintf( !$elem['meta'] ? '{%2$s}' : '{%1$s, %2$s}', $elem['meta'] < 0 ? $elem['meta'] + 1 : $elem['meta'], $context::serialize($elem['value']) ))); return $code; }, $ast['content']))); } static public function compileTagAsMacro ( $context, $ast ) { if ( $ast['type'] === 'text' ) return static::compileTextAsMacro($context, $ast); $code = array(); foreach ( $ast['nodes'] as $node ) { if ( $node['type'] === 'text' ) $code[] = static::compileTextAsMacro($context, $node); elseif ( $node['tag'] === 'auto' ) { $code[] = static::compileValue($context::compile($context::build('splquotes', $context::serialize($node)))); } elseif ( $node['tag'] === 'open' ) { $code[] = static::compileValue($context::compile($context::build('splquotes', $context::serialize($node)))); $code[] = static::compileTagAsMacro($context, $node); $node['tag'] = 'close'; $code[] = static::compileValue($context::compile($context::build('splquotes', $context::serialize($node)))); } else throw new Exception(sprintf('Error in file "%1$s" on line %2$s', __FILE__, __LINE__)); } return implode("\n", $code); } public static function compileIndex( $context, $ast, $root, $noRoot = false ) { if ( $ast['type'] === 'arrayAccess' ) { return sprintf( '%1$s[%2$s]', static::compileIndex($context, $ast['operands'][0], $root, 1), $ast['operands'][1]['type'] === 'index' ? "'" . $ast['operands'][1]['nodes']['nodes']['match'] . "'" : $context::compile($ast['operands'][1]) ); } else { if ( !$noRoot && $ast['value'] === '_' ) return $root; return sprintf( '%1$s["%2$s"]', $root, $ast['value'] ); } throw new Exception('Error : cannot convert tag param to index type !'); } public static function compileSub ( $context, $tag ) { $code = array(); foreach ( $tag['nodes'] as $node ) $code[] = implode("\n\t", explode("\n", $context::compile($node))); return implode("\n\t", $code); } } class SeTMLSpec { static private $parser, $spec = array(); static private function getParser () { if ( self::$parser ) return self::$parser; return self::$parser = SGParser::fromFile("/var/www/html/admin/app/libs/sense/Sensible/SeTML/Compiler/Types/Spec" . '/spec.peg', function ( &$parseState, $token, $elem ) { switch ( $token['type'] ) { case 'S' : $tags = array(); foreach ( $elem[0]['nodes'] as $tag ) $tags[$tag['name']] = $tag; return $tags; break; case 'tag' : $attributes = $forms = $expression = array(); foreach ( $elem[5]['nodes'] as $param ) if ( $param[0]['nodes']['type'] === 'attribute' ) $attributes[$param[0]['nodes']['name']] = $param[0]['nodes']; elseif ( $param[0]['nodes']['type'] === 'expression' ) $expression = $param[0]['nodes']; else $forms[] = $param[0]['nodes']; $tag = array('expression' => $expression, 'forms' => $forms, 'attributes' => $attributes); $tags = array('open' => $tag, 'auto' => $tag); $tags['open']['type'] = 'open'; $tags['auto']['type'] = 'auto'; foreach ( $elem[6]['nodes'] as $tag ) { if ( !$tag['expression'] ) $tag['expression'] = $expression; $tag['forms'] = array_merge($forms, $tag['forms']); $tag['attributes'] = array_merge($attributes, $tag['attributes']); $tags[$tag['type']] = $tag; } if ( $elem[0]['nodes'] === 'auto' ) unset($tags['open']); elseif ( $elem[0]['nodes'] === 'noauto' ) unset($tags['auto']); return array( 'type' => $elem[0]['nodes'], 'name' => $elem[1]['nodes'], 'description' => $elem[4]['nodes'], 'tags' => $tags, ); break; case 'subTag' : $attributes = $forms = $expression = array(); foreach ( $elem[5]['nodes'] as $param ) if ( $param[0]['nodes']['type'] === 'attribute' ) $attributes[$param[0]['nodes']['name']] = $param[0]['nodes']; elseif ( $param[0]['nodes']['type'] === 'expression' ) $expression = $param[0]['nodes']; else $forms[] = $param[0]['nodes']; return array( 'type' => $elem[1]['nodes'][0]['nodes']['match'], 'description' => $elem[4]['nodes'], 'expression' => $expression, 'forms' => $forms, 'attributes' => $attributes, ); break; case 'expression' : return array( 'type' => 'expression', 'description' => $elem[4]['nodes'], 'name' => $elem[2]['nodes'], ); break; case 'form' : return array( 'type' => 'form', 'description' => $elem[4]['nodes'], 'values' => $elem[2]['nodes'], ); break; case 'attribute' : $value = $elem[3]['nodes']; return array( 'type' => 'attribute', 'description' => $elem[6]['nodes'], 'name' => $elem[1]['nodes'], 'value' => $value, 'required' => empty($elem[4]['nodes']) && !in_array($value, array( 'mixed', 'flag', 'integer', 'relative' )), ); break; case 'description' : return $elem[2]['nodes']['match']; break; case 'type' : return $elem[0]['nodes']['match']; break; case 'name' : return $elem['match']; break; } return $elem; }); } public static function getSpecs () { if ( empty(self::$spec) ) self::loadSpecs(); return self::$spec; } private static function loadSpecs () { $parser = self::getParser(); $dir = sprintf('%1$s/../Tags/Spec/', "/var/www/html/admin/app/libs/sense/Sensible/SeTML/Compiler/Types/Spec"); foreach ( scandir($dir) as $file ) if ( preg_match('!\.spec$!', $file) ) try { self::$spec = array_merge( self::$spec, $parser->parseFile($file = sprintf('%1$s/../Tags/Spec/%2$s', "/var/www/html/admin/app/libs/sense/Sensible/SeTML/Compiler/Types/Spec", $file)) ); } catch ( Exception $e ) { throw new Exception(sprintf( 'Parse Error on specification file "%1$s" with message "%2$s" !', $file, $e->getMessage() ), 0); } } static public function check ( $tag ) { if ( empty(self::$spec) ) self::loadSpecs(); if ( !empty(self::$spec[$tag['name']]) ) { if ( self::$spec[$tag['name']]['type'] === 'noauto' && $tag['tag'] === 'auto' ) throw new Exception(sprintf( 'Error on line "%1$s" : tag "%2$s" must not be autoclosed !', $tag['line'], $tag['name'] )); elseif ( self::$spec[$tag['name']]['type'] === 'auto' && $tag['tag'] === 'open' ) throw new Exception(sprintf( 'Error on line "%1$s" : tag "%2$s" must be autoclosed !', $tag['line'], $tag['name'] )); } if ( !empty(self::$spec[$tag['name']]['tags'][$tag['tag']]) ) { $spec = self::$spec[$tag['name']]['tags'][$tag['tag']]; if ( !empty($tag['expression']) ) $tag = self::expressionToAttributes($tag, $spec); unset($tag['expression']); if ( !empty($tag['form']) ) $tag = self::formToAttributes($tag, $spec); unset($tag['form']); foreach ( $spec['attributes'] as $name => $value ) { if ( !self::checkValue( !empty($tag['attributes'][$name]) ? $tag['attributes'][$name] : null, $value )) throw new Exception(sprintf( 'Error on line "%1$s" in tag "%2$s" : attribute "%3$s" must be of type "%4$s" !', $tag['line'], $tag['name'], $name, $value['value'] )); } if ( !empty($tag['attributes']) && $c = count($diff = array_diff_key($tag['attributes'], $spec['attributes'])) ) throw new Exception(sprintf( 'Error on line "%1$s" in tag "%2$s" : attribute%5$s "%3$s" do%4$s not exist !', $tag['line'], $tag['name'], implode('", "', array_keys($diff)), $c > 1 ? '' : 'es', $c > 1 ? 's' : '' )); } if ( !empty($tag['attributes']) ) ksort($tag['attributes']); return $tag; } public static function checkValue ( $value, $type ) { if ( !$value ) return !$type['required']; $scalar = is_scalar($value); return !( $scalar && in_array($type['value'], array('index', 'value')) || !$scalar && $type['value'] === 'flag' || !$scalar && in_array($type['value'], array('relative', 'integer')) && !self::checkInteger($value, $type['value'] === 'relative') || $type['value'] === 'index' && !self::checkIndex($value) ); } public static function checkInteger ( $value, $subZero = false ) { if ( $subZero && $value['type'] === 'unaryOp' && $value['operator'] === '-' ) $value = $value['operand']; return $value['type'] === 'integer'; } public static function checkIndex ( $value ) { if ( $value['type'] === 'arrayAccess' ) return self::checkIndex($value['operands'][0]); return in_array($value['type'], array('identifier', 'integer', 'splquotes', 'dblquotes')); } static public function expressionToAttributes ( $tag, $spec ) { if ( !$spec['expression'] ) throw new Exception(sprintf( 'Tag "%1$s" on line "%2$s" must not have expression attribute !', $tag['name'], $tag['line'] )); $tag['attributes'] = array( $spec['expression']['name'] => $tag['expression'] ); return $tag; } static public function formToAttributes ( $tag, $spec ) { if ( !$spec['forms'] ) throw new Exception(sprintf( 'Tag "%1$s" on line "%2$s" must not have formal attributes !', $tag['name'], $tag['line'] )); $c = count($tag['form']['nodes']); foreach ( $spec['forms'] as $v ) { if ( count($v['values']) !== $c ) continue; foreach ( $v['values'] as $k => $value ) $tag['attributes'][$value] = $tag['form']['nodes'][$k]; break; } if ( empty($tag['attributes']) ) throw new Exception(sprintf( 'Tag "%1$s" on line "%2$s" has malformed formal attributes !', $tag['name'], $tag['line'] )); return $tag; } } class SeTMLTypeTagAffect extends SeTMLTypeTag { static public function auto ( $context, $tag ) { return sprintf( '%1$s %3$s %2$s;', static::compileIndex($context, $tag['attributes']['name'], '$runtime->frame["data"]'), $context::compile($tag['attributes']['value']), !empty($tag['attributes']['append']) ? '.=' : '=' ); } static public function open( $context, $tag ) { return sprintf( '$runtime->lpush(%1$s);' . "\n\t" . '%2$s' . "\n" . '$runtime->frame["data"] = array_merge($data, $runtime->lpop());', static::getShareAttribute($context, $tag), str_replace("\n", "\n\t", static::compilesub($context, $tag)) ); } } class SeTMLTypeTagAsis extends SeTMLTypeTag { static protected $tagName = 'asis'; static public function open ( $context, $tag ) { $code = $pcode = ''; $meta = static::getMetaAttribute($context, $tag); if ( $meta < -1 || 0 < $meta ) { $tag['attributes']['meta'] = $context::build('integer', $meta + ( $meta > 0 ? -1 : 1 )); $code = static::compileValue($context::compile($context::build('splquotes', $context::serialize($tag)))) . "\n"; $tag['tag'] = 'close'; $pcode = "\n" . static::compileValue($context::compile($context::build('splquotes', $context::serialize($tag)))); } if ( $meta < 0 ) return $code . static::compileTagAsMacro($context, $tag) . $pcode; return $code . static::compileTagAsText($context, $tag) . $pcode; } } class SeTMLTypeTagBlock extends SeTMLTypeTag { static public function open ( $context, $tag ) { return sprintf( '$runtime->setBlock(%2$s, function ( $runtime ) {' . "\n\t" . '%1$s' . '%5$s' . '%3$s}, %4$s);', implode("\n\t", explode("\n", SeTMLTypeS::open($tag))), static::compileIndex($context, $tag['attributes']['name'], '$runtime->blocks', 1), implode("\n\t", explode("\n", SeTMLTypeS::close($tag))), self::getShareAttribute($context, $tag), static::compilesub($context, $tag) ); } } class SeTMLTypeTagCall extends SeTMLTypeTag { static public function auto ( $context, $tag ) { return sprintf( '$runtime->callBlock(%1$s, %2$s);', self::compileIndex($context, $tag['attributes']['name'], '$runtime->blocks', 1), self::getShareAttribute($context, $tag) ); } static public function open ( $context, $tag ) { return sprintf( '$runtime->lpush(%1$s);' . '%3$s' . '$runtime->callBlock(%2$s, $runtime->lpop());', self::getShareAttribute($context, $tag), self::compileIndex($context, $tag['attributes']['name'], '$runtime->blocks', 1), static::compilesub($context, $tag) ); } } class SeTMLTypeTagComment extends SeTMLTypeTag { static public function open ( $context, $tag ) { return ''; } } class SeTMLTypeTagContent extends SeTMLTypeTag { static public function auto ( $context, $tag ) { return sprintf( '%1$s %3$s %2$s;', static::compileIndex($context, $tag['attributes']['name'], '$runtime->alloc'), $context::compile($tag['attributes']['value']), !empty($tag['attributes']['append']) ? '.=' : '=' ); } static public function open ( $context, $tag ) { $str = empty($tag['attributes']['replace']) ? '' : '%1$s = "";' . "\n"; $asis = static::getMetaAttribute($context, $tag, 'asis'); return sprintf( $str . '$runtime->cpush(%1$s);' . "\n\t" . '%2$s' . "\n" . '$runtime->cpop();', static::compileIndex($context, $tag['attributes']['name'], '$runtime->alloc'), ( $asis === 0 ) ? static::compilesub($context, $tag) : ( $asis > 0 ? static::compileTagAsText($context, $tag) : static::compileTagAsMacro($context, $tag) ) ); } } class SeTMLTypeTagElseif extends SeTMLTypeTag { static public function auto ( $context, $tag ) { return sprintf( '} elseif ( %1$s ) {', $context::compile($tag['attributes']['condition']) ); } } class SeTMLTypeTagElse extends SeTMLTypeTag { static public function auto ( $context, $tag ) { return "} else {"; } } class SeTMLTypeTagForeach extends SeTMLTypeTag { static public function open ( $context, $tag ) { $str = ''; if ( !empty($tag['attributes']['key']) ) $str = sprintf( "\n\t" . '%1$s = $k;', static::compileIndex($context, $tag['attributes']['key'], '$data') ); if ( !empty($tag['attributes']['transform']) ) $str .= sprintf( "\n\t" . '%1$s = %2$s;', static::compileIndex($context, $tag['attributes']['value'], '$data'), $context::compile($tag['attributes']['transform']) ); return sprintf( 'if ( ( $tmp[\'%2$s\'] = %1$s ) && count($tmp[\'%2$s\']) )' . "\n" . 'foreach ( $tmp[\'%2$s\'] as $k => $v ) {' . "\n\t" . '%3$s = $v;' . $str . "\n\t" . '%4$s' . "\n}", $context::compile($tag['attributes']['list']), uniqid(), static::compileIndex($context, $tag['attributes']['value'], '$data'), static::compilesub($context, $tag) ); } } class SeTMLTypeTagFor extends SeTMLTypeTag { static public function open ( $context, $tag ) { return sprintf( 'for ( %1$s = %2$s; %1$s < %3$s; %1$s++ ) {' . "\n\t" . '%4$s' . "\n}", static::compileIndex($context, $tag['attributes']['value'], '$data'), $context::compile($tag['attributes']['start']), $context::compile($tag['attributes']['end']), static::compilesub($context, $tag) ); } static public function opesn ( &$state, $tag ) { return sprintf( '$tmp["%5$s"] = %3$s;' . "\n" . '$tmp["%5$s-step"] = %4$s;' . "\n" . 'if ( %2$s < %3$s )' ."\n\t" . 'for ( %1$s = %2$s; %1$s < $tmp["%5$s"]; %1$s += $tmp["%5$s-step"] ) {', SeTMLExpression::compileIndex($tag['attributes']['value']['ast'], '$data'), $tag['attributes']['start']['code'], $tag['attributes']['end']['code'], !empty($tag['attributes']['step']) ? $tag['attributes']['step']['code'] : 1, uniqid() ); } static public function close ( &$state, $tag ) { return "}"; } } class SeTMLTypeTagIf extends SeTMLTypeTag { static public function open ( $context, $tag ) { $code = array(); foreach ( $tag['nodes'] as $node ) $code[] = $context::compile($node); return sprintf( 'if ( %1$s ) {' . "\n\t" . '%2$s' . "\n" . '}', $context::compile($tag['attributes']['condition']), implode("\n\t", str_replace("\n", "\n\t", $code)) ); } } class SeTMLTypeTagInclude extends SeTMLTypeTag { static public function auto ( $context, $tag ) { return sprintf( '$runtime->includeTemplate(%1$s, %2$s, 0);', $context::compile($tag['attributes']['file']), self::getShareAttribute($context, $tag) ); } static public function open( $context, $tag ) { return sprintf( '$runtime->lpush(%1$s);' . "\n\t" . '%3$s' . "\n" . '$runtime->includeTemplate(%2$s, $runtime->lpop(), 0);', self::getShareAttribute($context, $tag), $context::compile($tag['attributes']['file']), static::compilesub($context, $tag) ); } } class SeTMLTypeTagMacro extends SeTMLTypeTag { } class SeTMLTypeTagMap extends SeTMLTypeTag { static public function open ( $context, $tag ) { return sprintf( ( empty($tag['attributes']['replace']) ? '' : 'unset(%1$s);' . "\n") . '$runtime->cpush(%1$s, %2$s);' . "\n" . '%3$s' . "\n" . '$runtime->cpop();', static::compileIndex($context, $tag['attributes']['name'], '$runtime->alloc'), static::getShareAttribute($context, $tag), static::compilesub($context, $tag) ); } } class SeTMLTypeTagMeta extends SeTMLTypeTag { static public function open ( $context, $tag ) { return sprintf( '$runtime->metaTemplate(function ( $runtime ) {' . "\n\t" . '%1$s' . "\n\t" . '$runtime->lpush(%2$s);' . "\n\t" . '%3$s' . "\n\t" . 'return $runtime->lpop();' . "\n" . '}, %4$s);', implode("\n\t", explode("\n", SeTMLTypeS::open($context))), static::getShareAttribute($context, $tag), static::compilesub($context, $tag), empty($tag['attributes']['cache']) ? 'null' : $context::compile($tag['attributes']['cache']) ); } } abstract class Entity { protected static $type, $subType, $rootType, $parentType, $cache = array(), $entities = array(); final public static function getType ( $root = false ) { if ( !empty(static::$type) ) return static::$type; $class = static::getSubType(); return static::$type = ( $parent = get_parent_class($class) ) && ( __CLASS__ !== $parent ) ? $parent::getType() : $class; } final public static function getSubType () { if ( static::$subType ) return static::$subType; $class = get_called_class(); if ( $class === __CLASS__ ) throw new Exception('Cannot call static method on Entity !'); return static::$subType = $class; } final public static function getParentType ( $root = false ) { if ( !$root ) return static::$parentType; elseif ( static::$rootType ) return static::$rootType; return static::$rootType = ( $parent = static::$parentType ) ? $parent::getParentType($root) : static::getSubType(); } final public static function getEntityData ( $id ) { return static::getEntity($id)->getData(); } final public static function getEntity ( $id ) { if ( !empty(self::$entities[$id]) ) return self::$entities[$id]; $path = explode(':', $id); $type = $path[0]; return $type::get($path[1]); } final public static function get ( $fullName ) { $ctype = static::getType(); $id = $ctype . ':' . $fullName; //~ echo sprintf( //~ '%1$s::%2$s(%3$s)' . "\n", //~ get_called_class(), //~ __METHOD__, //~ $fullName //~ ); if ( !empty(self::$entities[$id]) ) return self::$entities[$id]; if ( !( $instance = $ctype::getInstance($fullName) ) ) return null; self::$entities[$instance->getId()] = $instance; self::$entities[$instance->getId(1)] = $instance; return $instance; } protected static function getInstance ( $fullName ) { //~ echo sprintf( //~ '%1$s::%2$s(%3$s)' . "\n", //~ get_called_class(), //~ __METHOD__, //~ $fullName //~ ); $path = explode('.', $fullName); $name = array_pop($path); $type = static::getType(); $parentType = $type::getParentType(); $parent = $parentType::get(implode('.', $path)); $data = $parent->getChildrenData($type, $name); if ( !$data ) return null; $data['name'] = $name; $subType = $data['type']; if ( !$subType ) $subType = static::getSubType(); return new $subType( $data, $parent ); } protected $collections = array(), $names = array(), $ids = array(), $parent, $data, $root; protected function __construct ( $data, $parent = null ) { $this->data = $data; $this->parent = $parent; } public function __destruct () { unset(self::$entities[$this->getId()]); unset(self::$entities[$this->getId(1)]); foreach ( $this->data as $k => $v ) unset($this->data[$k]); } final public function __get ( $type ) { $type = $type::getType(); if ( !empty($this->collections[$type]) ) return $this->collections[$type]; return $this->collections[$type] = new ChildrenTypeEntityIteratorAggregate($this, $type); } final public function __toString () { return $this->getId(1); } final public function getName ( $full = false ) { if ( !empty($this->names[$full]) ) return $this->names[$full]; if ( empty($this->data['name'])) print_r($this); return $this->names[$full] = ( $full && ( $parent = $this->getParent() ) ? $parent->getName(1) . '.' : '' ) . $this->data['name']; } final public function getId ( $canonical = false ) { if ( !empty($this->ids[$canonical]) ) return $this->ids[$canonical]; return $this->ids[$canonical] = sprintf( '%1$s:%2$s', $canonical ? static::getType() : static::getSubType(), $this->getName(true) ); } final public function getData ( $offset = null ) { if ( $offset && array_key_exists($offset, $this->data) ) return $this->data[$offset]; elseif ( $offset ) return null; return $this->data; } final public function getParent ( $root = false ) { if ( !$root ) return $this->parent; elseif ( $this->root ) return $this->root; return $this->root = $this->parent ? $this->parent->getParent(1) : $this; } final public function getChild ( $type, $name ) { return $type::get($this->getName(1) . '.' . $name); } final public function getChildrenData ( $type = null, $name = null ) { //~ echo sprintf( //~ '%1$s::%2$s(%3$s, %4$s)' . "\n", //~ get_called_class(), //~ __METHOD__, //~ $type, //~ $name //~ ); if ( !$type ) return !empty($this->data['children']) ? $this->data['children'] : null; elseif ( $name && !empty($this->data['children'][$type][$name]) ) return $this->data['children'][$type][$name]; elseif ( $name ) return null; return !empty($this->data['children'][$type]) ? $this->data['children'][$type] : array(); } final protected function setCache ( $offset, $value ) { self::$cache[$this->getId(1)][$offset] = $value; return $this; } final protected function getCache ( $offset ) { return self::$cache[$this->getId(1)][$offset]; } final public function getChildrenIterator () { return new ChildrenEntityIterator($this); } final public function getParentIterator () { return new ParenEntityIterator($this); } } class EntityObject implements ArrayAccess, IteratorAggregate { private $model, $data; public function __construct ( Model $model, $data = array() ) { $this->model = $model; $this->data = $data; } public function getModel () { return $this->model; } public function getData () { return $this->data; } public function setData ( $data = array() ) { $this->data = $data; return $this; } public function apply () { $this->data = $this->model->apply($this->data); return $this; } public function invalid () { return $this->model->invalid($this->data); } public function getState ( $service = '' ) { return $this->model->getState($this->data, $service); } public function offsetSet ( $offset, $value ) { return $this->data[$offset] = $value; } public function offsetGet ( $offset ) { return !empty($this->data[$offset]) ? $this->data[$offset] : null; } public function offsetExists ( $offset ) { return array_key_exists($offre, $this->data); } public function offsetUnset ( $offset ) { unset($this->data[$offset]); } public function getIterator () { return new ArrayIterator($this->data); } } abstract class Element { private static $systemType; final public static function getSystemType ( $root = false ) { if ( static::$systemType ) return static::$systemType; $class = get_called_class(); return static::$systemType = lcfirst(preg_replace('!$Element!', '', ( $parent = get_parent_class($class) ) && ( __CLASS__ !== $parent ) ? $parent::getSystemType() : $class)); } public static function getDefinition () { throw new Exception('Children class must reimplement Element::getSchema !'); } public static function getSchema ( $params ) { throw new Exception('Children class must reimplement Element::getParams !'); } public static function invalid ( $schema, $value, $data = array() ) { if ( is_null($value) && !empty($schema['null']) ) return false; if ( is_null($value) ) return 'Value should not be null !'; return false; } public static function apply ( $schema, $value, $data = array() ) { if ( $error = static::invalid($schema, $value, $data) ) { $value = array_key_exists('default', $schema) ? $schema['default'] : null; if ( is_null($value) && empty($schema['null']) ) throw new Exception('No default to set for missing value for type ' . $schema['type'] . "\n" . $error); return $value; } return $value; } } abstract class Provider extends Entity implements IteratorAggregate { protected static $type, $subType, $rootType, $parentType, $providers = array(); final public static function addProviderDir ( $dir, $cache = null ) { static::checkDirectories($dir, $cache); foreach ( scandir($dir) as $name ) if ( file_exists($dir . '/' . $name . '/provider.php') ) { if ( $cache && !file_exists($cache . '/' . $name) ) mkdir($cache . '/' . $name, 0777, 1); self::set($name, $dir . '/' . $name, $cache ? $cache . '/' . $name : null); } } final public static function set ( $name, $dir, $cache = '' ) { if ( !empty(self::$providers[$name]) ) throw new Exception(sprintf( 'Provider "%1$s" already set !', $name )); static::checkDirectories($dir, $cache); self::$providers[$name] = array($dir, $cache); } final public static function checkDirectories ( $folder, $cache = null ) { if ( !( $exists = file_exists($folder) ) || !is_dir($folder) ) throw new Exception(sprintf( $exists ? 'Directory "%1$s" doest not exists ! It is a file !' : 'Directory "%1$s" doest not exists !', $folder )); if ( $cache && ( !( $exists = file_exists($cache) ) || !is_dir($cache) ) ) throw new Exception(sprintf( $exists ? 'Directory "%1$s" doest not exists ! It is a file !' : 'Directory "%1$s" doest not exists !', $cache )); } protected static function getInstance ( $name ) { //~ echo sprintf( //~ '%1$s::%2$s(%3$s)' . "\n", //~ get_called_class(), //~ __METHOD__, //~ $name //~ ); if ( empty(self::$providers[$name]) ) throw new Exception(sprintf( 'Provider "%1$s" not registered !', $name )); $data = include(self::$providers[$name][0] . '/provider.php'); $data['name'] = $name; $class = $data['type']; return new $class($data); } abstract protected function build (); protected function __construct ( $data, $parent = null ) { parent::__construct($data, $parent); $this->load(); } public function getIterator () { return $this->__get('Model'); } protected function load () { // if not in cache if ( !$this->loadFromCache() ) { // if no model structure found $this->loadFromFiles(); $this->build(); $this->saveToFiles(); // merge data for faster access and cache $this->saveToCache(); } return $this; } private function loadFromFiles () { $loaded = false; $name = $this->getName(); if ( $dir = self::$providers[$name][0] ) { foreach ( scandir($dir) as $file ) if ( preg_match('!^([a-zA-Z0-9_]++)\.([a-zA-Z0-9_]++)\.php$!', $file, $m) ) { $loaded = true; $this->data['children'][$m[1]][$m[2]] = include($dir . '/' . $file); } } /*#BEGIN debug#*/ return false; /*#END debug#*/ return $loaded; } private function saveToFiles ( ) { $name = $this->getName(); if ( $dir = self::$providers[$name][0] ) { $dir .= '/auto'; if ( !file_exists($dir) ) mkdir($dir, 0777, 1); $provider = $this->data; unset($provider['children']); phpize($provider, $dir . '/provider.php'); foreach ( $this->data['children'] as $type => $instances ) foreach ( $instances as $k => $instance ) phpize($instance, sprintf( '%1$s/%2$s.%3$s.php', $dir, $type, $k )); } //~ if ( $dir = self::$providers[$name][1] ) { //~ if ( !file_exists($dir = dirname($this->cachePath) . '/' . $this->getName()) ) //~ mkdir($dir, 0770, 1); //~ foreach ( $props as $f ) //~ phpize($this->$f, $dir . '/' . $f . '.php'); //~ } return $this; } private function loadFromCache ( ) { if ( $cache = self::$providers[$name = $this->getName()][1] ) { $cacheFile = sprintf( '%1$s/provider.php', $cache ); /*#BEGIN debug#*/ //~ // clear the cache if it is. if ( file_exists($cacheFile) ) unlink($cacheFile); /*#END debug#*/ $cached = null; if ( file_exists($cacheFile) ) $cached = include($cacheFile); // restore the cache if ( $cached ) { $this->data = $cached; $cached = null; return true; } } return false; } private function saveToCache ( ) { // Cache the merged model data if ( $cache = self::$providers[$name = $this->getName()][1] ) { $cacheFile = sprintf( '%1$s/provider.php', $cache ); phpize($this->data, $cacheFile); } return $this; } } class Role extends Entity { protected static $type, $subType, $rootType, $parentType = 'Provider'; public function getSource () { return $this->getParent()->getChild('Model', $this->data['source']); } public function getTarget () { return $this->getParent()->getChild('Model', $this->data['target']); } } class Model extends Entity implements IteratorAggregate { protected static $type, $subType, $rootType, $parentType = 'Provider'; protected function __construct ( $data, Provider $provider ) { parent::__construct($data, $provider); // extends model if ( !empty($this->data['extends']) ) { if ( !$parentData = $provider->getChildrenData('Model', $this->data['extends']) ) throw new Exception(sprintf( 'Parent Model "%3$s" for Child Model "%2$s" in Provider "%1$s" does not exists !', $provider->getName(), $this->data['name'], $this->data['extends'] )); // extends services $this->data['children']['Service'] = array_merge( !empty($parentData['children']['Service']) ? $parentData['children']['Service'] : array(), !empty($this->data['children']['Service']) ? $this->data['children']['Service'] : array() ); // extends views $this->data['children']['View'] = array_merge( !empty($parentData['children']['View']) ? $parentData['children']['View'] : array(), !empty($this->data['children']['View']) ? $this->data['children']['View'] : array() ); } } public function getIterator () { return $this->__get('Field'); } public function apply () { $errors = array(); foreach ( $this->model->Field as $name => $field ) if ( $field->apply($this->data) ) return $this; } public function invalid () { $errors = array(); foreach ( $this->model->Field as $name => $field ) if ( $error = $field->invalid( !empty($this->data[$field->getName()]) ? $this->data[$field->getName()] : null ) );// $errors[] = $error; return $errors; } public function getState ( $service, $data = array() ) { if ( $service ) return $this->getModel()->Service->$service->getState($data); throw new Exception(sprintf( 'Method "%1$s->%2$s" must be reimplemented in child class "%3$s" !', __CLASS__, __METHOD__, $this::getSubType() )); } public function getEntityObject ( $data = array() ) { return new EntityObject($this, $data); } } class Field extends Entity { protected static $type, $subType, $rootType, $parentType = 'Model'; protected function __construct ( $data, Model $parent = null ) { parent::__construct($data, $parent); } public function invalid ( $value, $data = array() ) { $class = 'Element' . ucfirst($this->data['value']['type']); return $class::invalid($this->data['value'], $value, $data); } public function apply ( $value, $data = array() ) { $class = 'Element' . ucfirst($this->data['value']['type']); return $class::apply($this->data['value'], $value, $data); } public function unaryOperator ( $operator, $arg ) { $class = 'Element' . ucfirst($this->data['value']['type']); return $class::unaryOperator($this->data['value'], $operator, $arg); } public function binaryOperator ( $operator, $arg ) { $class = 'Element' . ucfirst($this->data['value']['type']); return $class::binaryOperator($this->data['value'], $operator, $arg1, $arg2); } } abstract class Index extends Entity implements IteratorAggregate { protected static $type, $subType, $rootType, $parentType = 'Model'; public function getIterator () { return $this->__get('Field'); } final public function getField ( $name ) { if ( empty($this->data['fields'][$name]) ) return null; return Field::get($this->getParent()->getName(1) . '.' . $name); } final public function getFieldsData ( $name = null ) { if ( $name && !empty($this->data['fields'][$name]) ) return $this->data['fields'][$name]; elseif ( $name ) return null; return $this->data['fields']; } final public function getPositionField ( $position ) { if ( empty($this->data['fields'][$position]) ) return null; return $this->getField($this->data['fields'][$position]); } final public function getPositionFieldsData ( $position = null ) { if ( $position && !empty($this->data['fields'][$position]) ) return $this->data['fields'][$this->data['fields'][$position]]; elseif ( $name ) return null; return $this->data['fields']; } } class View extends Entity { protected static $type, $subType, $rootType, $parentType = 'Model'; protected function __construct ( $data, Model $model ) { //~ echo $model . ":\n"; //~ print_r($data); //~ echo "\n"; parent::__construct($data, $model); $fields = $this->getParent()->getChildrenData('Field'); unset($fields['id']); $f = array(); foreach ( $fields as $n => $field ) $f[$n] = 0; $ff = array(); foreach ( $fields as $n => $field ) $ff[$n] = 1; if ( $this->data['list'] === '*' ) $this->data['list'] = $f; elseif ( is_string($this->data['list']) ) { $tokens = preg_split('!\s+!', $this->data['list']); if ( $tokens && $tokens[0] === '*' ) { $first = array_shift($tokens); $this->data['list'] = $f; } else $this->data['list'] = array(); while ( count($tokens) ) { $a = array_shift($tokens); $b = explode(':', array_shift($tokens)); if ( $a === '-' ) { unset($this->data['list'][$b[0]]); } elseif ( $model->getChild('Field', $b[0]) ) { $this->data['list'][$b[0]] = empty($b[1]) ? null : ( $b[1] === '1' ? 1 : 0 ); } } } if ( $this->data['edit'] === '*' ) $this->data['edit'] = $ff; elseif ( is_string($this->data['edit']) ) { $tokens = preg_split('!\s+!', $this->data['edit']); if ( $tokens && $tokens[0] === '*' ) { $first = array_shift($tokens); $this->data['edit'] = $ff; } else $this->data['edit'] = array(); //~ $tokens = preg_split('!\s!', $this->data['edit']); //~ $first = array_shift($tokens); //~ $this->data['edit'] = $ff; while ( count($tokens) ) { $a = array_shift($tokens); $b = explode(':', array_shift($tokens)); if ( $a === '-' ) { unset($this->data['edit'][$b[0]]); } elseif ( array_key_exists($b[0], $ff) ) { $this->data['edit'][$b[0]] = ( !isset($b[1]) || $b[1] === '1' ) ? 1 : null; } } } //~ print_r($this->data); } } abstract class Service extends Entity { protected static $type, $subType, $rootType, $parentType = 'Model'; public function getState ( EntityObject $dataObject ) { return $this->getParent()->getState($dataObject); } } define('INT_32_MAX', pow(2, 32)); define('INT_32_MIN', -INT_32_MAX - 1); class ElementInteger extends Element { const _MIN = INT_32_MIN, _MAX = INT_32_MAX; private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= self::getSchema(array( 'min' => self::getSchema(array('min' => self::_MIN, 'max' => self::_MAX, 'default' => self::_MIN, 'null' => true)), 'max' => self::getSchema(array('min' => self::_MIN, 'max' => self::_MAX, 'default' => self::_MAX, 'null' => true)), 'default' => ElementBoolean::getSchema(array('default' => null, 'null' => true)), 'null' => ElementBoolean::getSchema(array('default' => true, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'integer'; return $params; } public static function invalid ( $schema, $value, $data = array() ) { if ( $error = parent::invalid($schema, $value, $data) ) return $error; if ( is_null($value) || is_int($value) ) return false; return 'Value is not an integer !'; } public static function apply ( $schema, $value, $data = array() ) { if ( !$value ) $value = 0; else $value = (int) $value; return (int) parent::apply($schema, $value, $data); } } class ElementBoolean extends Element { private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= self::getSchema(array( 'default' => self::getSchema(array('default' => null, 'null' => true)), 'null' => self::getSchema(array('default' => null, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'boolean'; return $params; } public static function invalid ( $schema, $value, $data = array() ) { if ( $error = parent::invalid($schema, $value, $data) ) return $error; if ( is_null($value) || is_bool($value) ) return false; return 'Value is not a boolean !'; } public static function apply ( $schema, $value, $data = array() ) { return (bool) $value; return (bool) parent::apply($schema, $value, $data); } } class ElementString extends Element { private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= self::getSchema(array( 'minlength' => ElementInteger::getSchema(array('default' => false, 'null' => true)), 'maxlength' => ElementInteger::getSchema(array('default' => false, 'null' => true)), 'default' => ElementBoolean::getSchema(array('default' => false, 'null' => true)), 'null' => ElementBoolean::getSchema(array('default' => true, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'string'; return $params; } public static function invalid ( $schema, $value, $data = array() ) { if ( $error = parent::invalid($schema, $value, $data) ) return $error; if ( is_null($value) || is_string($value) ) return false; return 'Value is not a string !'; } public static function apply ( $schema, $value, $data = array() ) { return (string) parent::apply($schema, $value, $data); } } class ElementSet extends Element { private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= static::getSchema(array( 'values' => self::getParams(array('default' => null, 'null' => true)), 'default' => ElementBoolean::getParams(array('default' => null, 'null' => true)), 'null' => ElementBoolean::getParams(array('default' => true, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'set'; return $params; } public static function invalid ( $schema, $value, $data = array() ) { if ( $error = parent::invalid($value, $data) ) return $error; if ( is_null($value) || is_array($value) ) return false; return 'Value is not a set !'; } } class ElementEnum extends ElementSet { private static $schema; public static function getSchema ( $params = array() ) { $params['type'] = 'enum'; return $params; } public static function invalid ( $schema, $value, $data = array() ) { if ( $error = parent::invalid($value, $data) ) return $error; if ( is_null($value) || is_string($value) ) return false; return 'Value is not an enum !'; } } class ElementImage extends ElementString { private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= self::getSchema(array( 'minlength' => ElementInteger::getSchema(array('default' => false, 'null' => true)), 'maxlength' => ElementInteger::getSchema(array('default' => false, 'null' => true)), 'default' => ElementBoolean::getSchema(array('default' => false, 'null' => true)), 'null' => ElementBoolean::getSchema(array('default' => true, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'image'; return $params; } } class ElementDate extends ElementString { private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= self::getSchema(array( 'format' => ElementString::getSchema(array('default' => 'Y-m-d', 'null' => 'false')), 'default' => ElementBoolean::getSchema(array('default' => false, 'null' => true)), 'null' => ElementBoolean::getSchema(array('default' => true, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'date'; return $params; } } class ElementRichText extends ElementString { } class ElementText extends ElementString { } class ElementRelation extends Element { const _MIN = INT_32_MIN, _MAX = INT_32_MAX; private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= self::getSchema(array( 'min' => self::getSchema(array('min' => self::_MIN, 'max' => self::_MAX, 'default' => self::_MIN, 'null' => true)), 'max' => self::getSchema(array('min' => self::_MIN, 'max' => self::_MAX, 'default' => self::_MAX, 'null' => true)), 'default' => ElementBoolean::getSchema(array('default' => null, 'null' => true)), 'null' => ElementBoolean::getSchema(array('default' => true, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'integer'; return $params; } public static function invalid ( $schema, $value, $data = array() ) { if ( $error = parent::invalid($schema, $value, $data) ) return $error; if ( is_null($value) || is_int($value) ) return false; return 'Value is not an integer !'; } public static function apply ( $schema, $value, $data = array() ) { return $value; if ( !$value ) $value = 0; else $value = (int) $value; return (int) parent::apply($schema, $value, $data); } } class ElementFile extends ElementString { private static $schema; public static function getDefinition () { if ( self::$schema ) return static::$schema; return static::$schema= self::getSchema(array( 'minlength' => ElementInteger::getSchema(array('default' => false, 'null' => true)), 'maxlength' => ElementInteger::getSchema(array('default' => false, 'null' => true)), 'default' => ElementBoolean::getSchema(array('default' => false, 'null' => true)), 'null' => ElementBoolean::getSchema(array('default' => true, 'null' => true)) )); } public static function getSchema ( $params = array() ) { $params['type'] = 'file'; return $params; } } class ParentEntityIterator implements Iterator { private $entity, $current; public function __construct ( Entity $entity ) { $this->entity = $entity; $this->current = $entity; } public function rewind () { $this->current = $this->entity; } public function valid () { return $this->current ? true : false; } public function current () { return $this->current; } public function key () { return $this->current->getId(1); } public function next () { $this->current = $this->current->getParent(); } } class ChildrenEntityIterator extends ArrayIterator implements RecursiveIterator { private $entity; public function __construct ( Entity $entity ) { $this->entity = $entity; $data = array(); foreach ( $this->entity->getChildrenData() as $type => $entities ) foreach ( $entities as $name => $entity ) $data[] = array($type, $name); parent::__construct($data); } public function current () { $current = parent::current(); return $this->entity->getChild($current[0], $current[1]); } public function key () { return $this->current()->getId(1); } public function hasChildren () { return count($this->current()->getChildrenData()) ? true : false; } public function getChildren () { return new self($this->current()); } } class ChildrenTypeEntityIterator extends ArrayIterator { private $entity, $type; public function __construct ( Entity $entity, $type ) { $this->entity = $entity; $this->type = $type; parent::__construct(array_keys($this->entity->getChildrenData($this->type))); } public function current () { return $this->entity->getChild($this->type, parent::current()); } public function key () { return $this->current()->getId(1); } } class ChildrenTypeEntityIteratorAggregate implements IteratorAggregate { private $entity, $type; public function __construct ( Entity $entity, $type ) { $this->entity = $entity; $this->type = $type; } public function __get ( $name ) { return $this->entity->getChild($this->type, $name); } public function getIterator () { return new ChildrenTypeEntityIterator($this->entity, $this->type); } } class PHPProvider extends Provider implements ArrayAccess, IteratorAggregate { protected static $subType; protected $database = array(); protected function __construct ( $data, $parent = null ) { $file = $data['root'] . '/' . $data['file']; if ( file_exists($file) ) $this->database = include($file); else phpize(array(), $file); parent::__construct($data); } public function __destruct () { $file = $this->data['root'] . '/' . $this->data['file']; phpize($this->database, $file); parent::__destruct(); } protected function build () { return $this; } public function offsetExists ( $offset ) { return array_key_exists($offset, $this->database); } public function offsetSet ( $offset, $value ) { return $this->database[$offset] = $value; } public function &offsetGet ( $offset ) { if ( array_key_exists($offset, $this->database) ) return $this->database[$offset]; return null; } public function offsetUnset ( $offset ) { unset($this->database[$offset]); } public function getIterator () { return new ArrayIterator($this->database); } } class PHPService extends Service implements ArrayAccess, IteratorAggregate { protected static $subType; protected $provider; protected function __construct ( $data, Model $parent ) { parent::__construct($data, $parent); $this->provider = $parent->getParent(); $this->modelName = $parent->getName(); } public function offsetExists ( $offset ) { return array_key_exists($offset, $this->provider[$this->modelName]); } public function offsetSet ( $offset, $value ) { return $this->provider[$this->modelName][$offset] = $value; } public function &offsetGet ( $offset ) { if ( !array_key_exists($offset, $this->provider[$this->modelName]) ) $this->provider[$this->modelName][$offset] = array(); return $this->provider[$this->modelName][$offset]; } public function offsetUnset ( $offset ) { unset($this->provider[$this->modelName][$offset]); } public function getIterator () { return new ArrayIterator($this->provider[$this->modelName]); } } class MySQLProvider extends Provider { protected static $subType, $buildQueries = array( 'update' => 'ALTER TABLE `%1$s` CHANGE `%2$s` `%2$s` %3$s %4$s COMMENT \'%5$s\'', 'tables' => 'SHOW TABLES;', 'fields' => 'SHOW FULL FIELDS FROM `%1$s`;', 'indexes' => 'SHOW INDEX FROM `%1$s`;', 'fkeys' => 'SELECT * FROM `information_schema`.`KEY_COLUMN_USAGE` AS kcu WHERE kcu.`TABLE_SCHEMA` = "%1$s" AND kcu.`TABLE_NAME` = "%2$s" AND kcu.`REFERENCED_COLUMN_NAME` IS NOT NULL;', ); protected $link = null; protected function __construct ( $data, $parent = null ) { $this->open($data); parent::__construct($data); } public function updateFields () { foreach ( $this->Model as $mid => $m ) if ( !$m->getData('virtual') ) { $fields = array(); $sql = $this->query(sprintf('SHOW FIELDS FROM %1$s', $m->getName())); foreach ( $sql[0] as $sqlf ) $fields[$sqlf['Field']] = $sqlf; foreach ( $fields as $fid => $field ) if ( $fid !== 'id' ) { $this->query(sprintf( self::$buildQueries['update'], $m->getName(), $fid, $field['Type'], $field['Null'] === 'NO' ? 'NOT NULL' : 'NULL', $this->link->real_escape_string(json_encode($m->Field->$fid->getData('value'))) )) . "\n"; } } } protected function build () { $tablesList = $this->query(self::$buildQueries['tables']); if ( !count($tablesList[0]) ) throw new Exception('DB Error : DB is empty !'); foreach ( $tablesList[0] as $table ) { $tableName = $table['Tables_in_' . $this->data['dbname']]; // model $this->data['children']['Model'][$tableName] = MySQLTable::build( $tableName, !empty($this->data['children']['Model'][$tableName]) ? $this->data['children']['Model'][$tableName] : array() ); // Schema $fieldsList = $this->query(sprintf(self::$buildQueries['fields'], $tableName)); $this->data['children']['Model'][$tableName] = MySQLTable::buildSchema( $tableName, $this->data['children']['Model'][$tableName], $fieldsList[0] ); // Indexes $indexesList = $this->query(sprintf(self::$buildQueries['indexes'], $tableName)); $this->data['children']['Model'][$tableName]['children']['Index'] = MySQLIndex::build( $tableName, !empty($this->data['children']['Model'][$tableName]['children']['Index']) ? $this->data['children']['Model'][$tableName]['children']['Index'] : array(), $indexesList[0] ); // Roles $rolesList = $this->query(sprintf(self::$buildQueries['fkeys'], $this->data['dbname'], $tableName)); $this->data['children']['Role'] = MySQLRole::build( !empty($this->data['children']['Role']) ? $this->data['children']['Role'] : array(), $rolesList[0] ); } return $this; } public function open ( $data = null ) { if ( !$data ) $data = $this->data; if ( !$this->link ) { $link = new Mysqli( $data['host'], $data['user'], $data['password'], $data['dbname'] ); if ( $link->connect_errno ) throw new Exception(sprintf( 'Error connecting to database "%1$s".' . "\n" . 'MysqlErrorCode : %2$s : ' . "\n" . '%3$s', $data['dbname'], $link->connect_errno, $link->connect_error )); $this->link = $link; $this->setOptions($data); } return $this; } public function close () { if ( $this->link ) { $this->link->close(); $this->link = null; } return $this; } public function setOptions ( $options ) { if ( !empty($options['charset']) ) $this->link->set_charset($options['charset']); } public function getLink () { return $this->link; } public function escape ( $str ) { if ( $str === null ) return 'NULL'; elseif ( $str === false ) return 'FALSE'; elseif ( $str === true ) return 'TRUE'; elseif ( $str === 0 ) return '0'; elseif ( is_int($str) || is_float($str) ) return $str; //~ elseif ( !$str ) //~ return '""'; return '\'' . $this->link->real_escape_string($str) . '\''; } public function getEscaper () { $self = $this; return function ( $str ) use ( $self ) { return $self->escape($str); }; } public function queryValue ( $sql, $context = null ) { $a = $this->queryObject($sql); return !empty($a) ? end($a) : null; } public function queryObject ( $sql, $context = null ) { $a = $this->queryList($sql); return !empty($a) ? end($a) : null; } public function queryList ( $sql, $context = null ) { $a = $this->query($sql); return end($a); } public function query ( $sql, $context = null ) { if ( !$this->link->multi_query($sql) ) throw new Exception(sprintf( 'Error querying the database "%1$s".' . "\n" . 'MysqlErrorCode : %2$s ' . "\nMySQL Error Message : " . '%3$s' . "\n" . 'SQL : %4$s', $this->data['dbname'], $this->link->errno, $this->link->error, $sql )); $i = 1; $results = array(); do { $r = $this->link->use_result(); if ( $r instanceof Mysqli_Result) { //~ $results[] = $r->fetch_all(MYSQLI_ASSOC); $rows = array(); while ( $row = $r->fetch_assoc() ) $rows[] = $row; $results[] = $rows; $r->free(); } else $results[] = $r; } while ( $this->link->more_results() && $this->link->next_result() ); return $results; } } class MySQLRole extends Role { protected static $parentType = 'MySQLProvider', $subType; static public function build ( $roles, $sqlRoles ) { foreach ( $sqlRoles as $sqlFKey ) $roles[$sqlFKey['CONSTRAINT_NAME']] = array( 'type' => 'MySQLRole', 'name' => $sqlFKey['CONSTRAINT_NAME'], 'source' => $sqlFKey['TABLE_NAME'], 'target' => $sqlFKey['REFERENCED_TABLE_NAME'], 'links' => array( array( 'source' => $sqlFKey['COLUMN_NAME'], 'target' => $sqlFKey['REFERENCED_COLUMN_NAME'], 'op' => '=', ) ), ); return $roles; } } class MySQLTable extends Model { protected static $parentType = 'MySQLProvider', $subType; static public function build ( $tableName, $model ) { return array_merge( array( 'type' => get_called_class(), 'name' => $tableName, 'extends' => '_MySQLTable', 'desc' => array( 'gender' => null, 'singular' => $tableName, 'plural' => $tableName, ), 'children' => array(), ), $model ); } static public function buildSchema ( $tableName, $model, $sqlFields ) { foreach ( $sqlFields as $field ) { $model['children']['Field'][$field['Field']] = MySQLField::build( !empty($model['children']['Field'][$field['Field']]) ? $model['children']['Field'][$field['Field']] : array(), $field ); } return $model; } } class MySQLField extends Field { private static $valueTypes = array(); protected static $parentType = 'MySQLTable', $subType; public static function build ( $field, $sqlField ) { $sqlField['Null'] = ( $sqlField['Null'] === 'NO' ) ? false : true; $value = self::getFieldValue($sqlField); return array_merge( array( 'type' => static::getSubType(), 'name' => $sqlField['Field'], 'desc' => array( 'gender' => null, 'singular' => $sqlField['Field'], 'plural' => '', ), 'value' => $value, //~ 'sql' => $sqlField, ), $field ); } static public function addValueType( $regex, $closure ) { return self::$valueTypes[$regex] = $closure; } static public function getFieldValue ( $field ) { if ( preg_match('!^{!', $field['Comment']) ) return json_decode($field['Comment'], 1); $m = array(); $fld = array(); foreach ( self::$valueTypes as $k => $f ) if ( preg_match($k, $field['Type'], $m) ) $fld = $f($field, $m); if ( !$fld ) $fld = array( 'default' => $field['Default'], 'null' => $field['Null'], ); if ( $field['Comment'] ) $fld['type'] = $field['Comment']; return $fld; } public function toSql ( $object ) { $value = array_key_exists($this->getName(), $object) ? $object[$this->getName()] : null; $value = $this->apply($value, $object); return $this->getParent()->getParent()->escape($value); } } MySQLField::addValueType('!(BIT|BOOLEAN)!i', $bool = function ($f, $m) { return ElementBoolean::getSchema(array( 'default' => $f['Default'], 'null' => $f['Null'] )); }); MySQLField::addValueType('!date!i', function ($f, $m) { return ElementDate::getSchema(array( 'format' => 'Y-m-d', 'default' => null, )); }); MySQLField::addValueType('!^(.*)INT((?:\(\d+\))?)((?: UNSIGNED)?)!i', function ($f, $m) use( $bool ) { if ( $m[1] === 'tiny' ) return $bool($f, $m); return ElementInteger::getSchema(array( 'min' => $m[3] ? 0 : ElementInteger::_MIN, 'max' => ElementInteger::_MAX, 'default' => $f['Default'], 'null' => $f['Null'] )); }); MySQLField::addValueType('!^(.*)(CHAR)\((.*)\)$!i', function ($f, $m) { return ElementString::getSchema(array( 'minlength' => $m[1] ? 0 : $m[3], 'maxlength' => $m[3], 'default' => $f['Default'], 'null' => $f['Null'] )); }); MySQLField::addValueType('!(.*)TEXT!i', function ($f, $m) { return ElementString::getSchema(array( 'minlength' => 0, 'maxlength' => 0, 'default' => $f['Default'], 'null' => $f['Null'] )); }); MySQLField::addValueType('!(.*)BLOB!i', function ($f, $m) { return ElementString::getSchema(array( 'minlength' => 0, 'maxlength' => 0, 'default' => $f['Default'], 'null' => $f['Null'] )); }); MySQLField::addValueType('!(ENUM)\((.*)\)!i', function ($f, $m) { return ElementEnum::getSchema(array( 'values' => array_map(function ($arg) { return preg_replace('!\'\'!', '\'', preg_replace('!^\'|\'$!', '', $arg));}, explode(',', $m[2])), 'desc' => array_map(function ($arg) { return preg_replace('!\'\'!', '\'', preg_replace('!^\'|\'$!', '', $arg));}, explode(',', $m[2])), 'default' => $f['Default'], 'null' => false )); }); MySQLField::addValueType('!(SET)\((.*)\)!i', function ($f, $m) { return ElementEnum::getSchema(array( 'values' => array_map(function ($arg) { return preg_replace('!\'\'!', '\'', preg_replace('!^\'|\'$!', '', $arg));}, explode(',', $m[2])), 'desc' => array_map(function ($arg) { return preg_replace('!\'\'!', '\'', preg_replace('!^\'|\'$!', '', $arg));}, explode(',', $m[2])), 'default' => explode(',', preg_replace('!\'!', '', $f['Default'])), 'null' => true )); }); /* /* MySQLField::addValueType('!(DECIMAL|FLOAT|REAL|DOUBLE)!i', function ($f, $m) { return array( 'Element' => 'float', 'default' => $f['Default'], ); }); MySQLField::addValueType('!(ENUM|SET)\((.*)\)!i', function ($f, $m) { return array( 'Element' => $t = strtolower($m[1]), 'values' => array_map(function ($arg) { return preg_replace('!\'\'!', '\'', preg_replace('!^\'|\'$!', '', $arg));}, explode(',', $m[2])), 'default' => ( $t == 'set' ) ? explode(',', preg_replace('!\'!', '', $f['Default'])) : $f['Default'], ); }); MySQLField::addValueType('!(.*BINARY\((.*)\)|(.*)BLOB)!i', function ($f, $m) { return array( 'Element' => empty($m[2]) ? 'blob' : '', 'size' => (int) $m[2], 'default' => $f['Default'], ); }); MySQLField::addValueType('!datetime!i', function ($f, $m) { return array( 'Element' => 'datetime', 'format' => 'Y-m-d', 'formatjs' => 'yy-mm-dd', 'timeformat' => 'H:i:s', 'timeformatjs' => 'hh:mm:ss', 'timezone' => date_default_timezone_get(), 'default' => null, ); }); */ class MySQLIndex extends Index { protected static $parentType = 'MySQLTable', $subType; static public function build ( $tableName, $indexes, $sqlIndexes ) { foreach ( $sqlIndexes as $sqlIndex ) { if ( empty($indexes[$sqlIndex['Key_name']]) ) $indexes[$sqlIndex['Key_name']] = array( 'type' => 'MySQLIndex', 'model' => $tableName, 'name' => $sqlIndex['Key_name'], 'unique' => !$sqlIndex['Non_unique'], 'fields' => array( $sqlIndex['Seq_in_index'] => $sqlIndex['Column_name'], $sqlIndex['Column_name'] => array( 'position' => $sqlIndex['Seq_in_index'], 'op' => empty($indexes) || empty($indexes[$sqlIndex['Key_name']]['fields'][$sqlIndex['Column_name']]) ? '=' : $indexes[$sqlIndex['Key_name']]['fields'][$sqlIndex['Column_name']]['op'], ), ), ); else { $indexes[$sqlIndex['Key_name']]['fields'][$sqlIndex['Seq_in_index']] = $sqlIndex['Column_name']; $indexes[$sqlIndex['Key_name']]['fields'][$sqlIndex['Column_name']] = array( 'name' => $sqlIndex['Column_name'], 'position' => $sqlIndex['Seq_in_index'], 'op' => empty($indexes[$sqlIndex['Key_name']]) || empty($indexes[$sqlIndex['Key_name']]['fields'][$sqlIndex['Column_name']]) ? '=' : $indexes[$sqlIndex['Key_name']]['fields'][$sqlIndex['Column_name']]['op'], ); } } return $indexes; } } class ArrayService extends Service implements ArrayAccess, Countable, IteratorAggregate { private $key; public function __construct ( $data, Model $model, $filter = null ) { parent::__construct($data, $model); $this->provider = $this->getParent()->getParent(); $key = $this->getParent()->Index->PRIMARY; $error = ''; if ( !$key ) $error = 'Index:%1$s.PRIMARY does not exists !'; elseif ( count($key->getFieldsData()) !== 2 ) $error = 'Index:%1$s.PRIMARY must have exactly 1 field !'; elseif ( !$key->getData('unique') ) $error = 'Index:%1$s.PRIMARY must be unique !'; if ( $error ) throw new Exception(sprintf($error, $model->getName(1))); if ( $this->getParent()->getName() === 'users' ) $this->key = $model->Field->login; else $this->key = $key->getPositionField(1); } public function add ( $value ) { return $this->offsetSet(null, $value); } public function offsetSet ( $offset, $value ) { $fields = array(); $values = array(); $update = array(); $value[$this->key->getName()] = $offset; foreach ( $this->getParent()->Field as $field ) { $name = $field->getName(); if ( array_key_exists($name, $value) && ($sql = $field->toSql($value)) ) { $fields[] = $name; $values[] = $sql; if ( $name !== $this->key->getNAme() ) $update[] = sprintf( '`%1$s` = %2$s', $name, $sql ); } } return $this->getParent()->getParent()->queryObject($sql = sprintf( 'INSERT INTO `%1$s` ( `%2$s` ) VALUES ( %3$s ) ON DUPLICATE KEY UPDATE %4$s;' . 'SELECT * FROM `%1$s` WHERE %5$s = LAST_INSERT_ID() LIMIT 1;', $this->getParent()->getName(), implode('`, `', $fields), implode(', ', $values), implode(', ', $update), $this->key->getName() )); } public function offsetGet ( $offset ) { return $this->getParent()->getParent()->queryObject(sprintf( 'SELECT * FROM `%1$s` WHERE `%2$s` = %3$s LIMIT 1;', $this->getParent()->getName(), $this->key->getName(), $this->provider->escape($offset) )); } public function offsetUnset ( $offset ) { return $this->getParent()->getParent()->queryObject(sprintf( 'DELETE FROM `%1$s` WHERE `%2$s` = %3$s LIMIT 1;', $this->getParent()->getName(), $this->key->getName(), $this->provider->escape($offset) )); } public function offsetExists ( $offset ) { return $this->offsetGet($offset) ? true : false; } public function count () { return $this->getParent()->getParent()->queryValue(sprintf( 'SELECT COUNT(*) FROM `%1$s`;', $this->getParent()->getName() )); } public function max () { return $this->getParent()->getParent()->queryValue(sprintf( 'SELECT MAX(%2$s) FROM `%1$s`;', $this->getParent()->getName(), $this->key->getName() )); } public function min () { return $this->getParent()->getParent()->queryValue(sprintf( 'SELECT MIN(%2$s) FROM `%1$s`;', $this->getParent()->getName(), $this->key->getName() )); } public function sum () { return $this->getParent()->getParent()->queryValue(sprintf( 'SELECT SUM(%2$s) FROM `%1$s`;', $this->getParent()->getName(), $this->key->getName() )); } public function avg () { return $this->getParent()->getParent()->queryValue(sprintf( 'SELECT AVG(%2$s) FROM `%1$s`;', $this->getParent()->getName(), $this->key->getName() )); } public function getIterator () { return new ArrayIterator($this->getParent()->getParent()->queryList(sprintf( 'SELECT * FROM `%1$s`;', $this->getParent()->getName(), $this->key->getName() ))); } } class MySQLServiceCrud extends Service { private $key; public function __construct ( $data, Model $model, $filter = null ) { parent::__construct($data, $model); $this->provider = $this->getParent()->getParent(); $key = $this->getParent()->Index->PRIMARY; $error = ''; if ( !$key ) $error = 'Index:%1$s.PRIMARY does not exists !'; elseif ( count($key->getFieldsData()) !== 2 ) $error = 'Index:%1$s.PRIMARY must have exactly 1 field !'; elseif ( !$key->getData('unique') ) $error = 'Index:%1$s.PRIMARY must be unique !'; if ( $error ) throw new Exception(sprintf($error, $model->getName(1))); $this->key = $key->getPositionField(1); } public function getKey () { return $this->key; } public function set ( $object = array() ) { if ( !empty($object[$this->key->getName()]) ) return $this->update($object); return $this->create($object); } public function create ( $object = array() ) { //~ print_r($object); $fields = array(); $values = array(); unset($object[$this->key->getName()]); foreach ( $this->getParent()->Field as $field ) { $name = $field->getName(); if ( array_key_exists($name, $object) && ($sql = $field->toSql($object)) ) { $fields[] = $name; $values[] = $sql; } } return $this->getParent()->getParent()->queryObject(($sql = sprintf( 'INSERT INTO `%1$s` ( `%2$s` ) VALUES ( %3$s );' . 'SELECT * FROM `%1$s` WHERE %4$s = LAST_INSERT_ID() LIMIT 1;', $this->getParent()->getName(), implode('`, `', $fields), implode(', ', $values), $this->key->getName() ))); } public function update ( $object = array() ) { $update = array(); foreach ( $this->getParent()->Field as $field ) { $name = $field->getName(); if ( array_key_exists($name, $object) && ($sql = $field->toSql($object)) ) { if ( $name !== $this->key->getName() ) $update[] = sprintf( '`%1$s` = %2$s', $name, $sql ); } } return $this->getParent()->getParent()->queryObject(($sql = sprintf( 'UPDATE `%1$s` SET %2$s WHERE %3$s = %4$s;' . 'SELECT * FROM `%1$s` WHERE %3$s = %4$s LIMIT 1;', $this->getParent()->getName(), implode(', ', $update), $this->key->getName(), $this->key->toSql($object) ))); } public function delete ( $object = array() ) { $this->getParent()->getParent()->queryObject(($sql = sprintf( 'DELETE FROM `%1$s` WHERE %2$s = %3$s; LIMIT 1', $this->getParent()->getName(), $this->key->getName(), $this->key->toSql($object) ))); return null; } public function read ( $object = array(), $order = array('position' => 0, 'date' => 1), $limit = array() ) { $fields = $this->getParent()->Field; $where = array(); foreach ( $object as $ff => $v ) { $not = $ff[0] === '!'; $f = $not ? substr($ff, 1) : $ff; if ( $fields->$f ) $where[$f] = sprintf( '`%1$s` %3$s %2$s', $f, $fields->$f->toSql(array_merge($object, array($f => $object[$ff]))), $not ? '!=' : '=' ); } $o = array(); foreach ( $order as $f => $v ) if ( $fields->$f ) $o[$f] = sprintf( '`%1$s` %2$s', $f, $v ? 'DESC' : 'ASC' ); return $this->getParent()->getParent()->queryList((sprintf( 'SELECT * FROM %1$s WHERE ( %2$s ) ORDER BY %3$s LIMIT %4$s, %5$s;', $this->getParent()->getName(), $where ? implode(' AND ', $where) : 1, $o ? implode(', ', $o) : $this->key->getName() . ' ASC', !empty($limit['offset']) ? $limit['offset'] : 0, !empty($limit['length']) ? $limit['length'] : 10000 ))); } public function readObject ( $object = array(), $order = array('position' => 0, 'date' => 1), $limit = array() ) { $a = $this->read($object, $order, $limit); return $a ? $a[0] : $a; } } class MySQLOrderService extends Service { private $sql = array( 'sorted' => 'SELECT ( COUNT(DISTINCT {index}) = COUNT(*) ) AND ( MIN({index}) = 0 ) FROM {table} WHERE {clause}', 'update' => 'UPDATE {table} SET {index} = {position} WHERE {clauseId}', 'list' => 'SELECT {id} FROM {table} WHERE {clause} ORDER BY {index}', ); public function __construct ( $data, Model $model, $filter = null ) { if ( empty($data['fields']) ) throw new Exception('No Fields specified for ORDER Service !'); parent::__construct($data, $model); print_r($data); print_r($this->sql); $self = $this; $d = array( 'table' => $self->getParent()->getName(), 'index' => $data['fields'][count($data['fields']) - 1], 'clause' => '', ); $this->sql = array_map(function ( $e ) use ( $self, $data ) { return sprinto($e, $data); }, $this->sql); } public function move ( $source, $target ) { $this->resort($source); $source = $this->getParent()->Service->crud->readObject($source); $target = $this->getParent()->Service->crud->readObject($target); } } /** * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ /** * Thrown when an API call returns an exception. * * @author Naitik Shah */ class FacebookApiException extends Exception { /** * The result from the API server that represents the exception information. */ protected $result; /** * Make a new API Exception with the given result. * * @param array $result The result from the API server */ public function __construct($result) { $this->result = $result; $code = isset($result['error_code']) ? $result['error_code'] : 0; if (isset($result['error_description'])) { // OAuth 2.0 Draft 10 style $msg = $result['error_description']; } else if (isset($result['error']) && is_array($result['error'])) { // OAuth 2.0 Draft 00 style $msg = $result['error']['message']; } else if (isset($result['error_msg'])) { // Rest server style $msg = $result['error_msg']; } else { $msg = 'Unknown Error. Check getResult()'; } parent::__construct($msg, $code); } /** * Return the associated result object returned by the API server. * * @return array The result from the API server */ public function getResult() { return $this->result; } /** * Returns the associated type for the error. This will default to * 'Exception' when a type is not available. * * @return string */ public function getType() { if (isset($this->result['error'])) { $error = $this->result['error']; if (is_string($error)) { // OAuth 2.0 Draft 10 style return $error; } else if (is_array($error)) { // OAuth 2.0 Draft 00 style if (isset($error['type'])) { return $error['type']; } } } return 'Exception'; } /** * To make debugging easier. * * @return string The string representation of the error */ public function __toString() { $str = $this->getType() . ': '; if ($this->code != 0) { $str .= $this->code . ': '; } return $str . $this->message; } } /** * Provides access to the Facebook Platform. This class provides * a majority of the functionality needed, but the class is abstract * because it is designed to be sub-classed. The subclass must * implement the four abstract methods listed at the bottom of * the file. * * @author Naitik Shah */ abstract class BaseFacebook { /** * Version. */ const VERSION = '3.1.1'; /** * Default options for curl. */ public static $CURL_OPTS = array( CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 60, CURLOPT_USERAGENT => 'facebook-php-3.1', ); /** * List of query parameters that get automatically dropped when rebuilding * the current URL. */ protected static $DROP_QUERY_PARAMS = array( 'code', 'state', 'signed_request', ); /** * Maps aliases to Facebook domains. */ public static $DOMAIN_MAP = array( 'api' => 'https://api.facebook.com/', 'api_video' => 'https://api-video.facebook.com/', 'api_read' => 'https://api-read.facebook.com/', 'graph' => 'https://graph.facebook.com/', 'graph_video' => 'https://graph-video.facebook.com/', 'www' => 'https://www.facebook.com/', ); /** * The Application ID. * * @var string */ protected $appId; /** * The Application App Secret. * * @var string */ protected $appSecret; /** * The ID of the Facebook user, or 0 if the user is logged out. * * @var integer */ protected $user; /** * The data from the signed_request token. */ protected $signedRequest; /** * A CSRF state variable to assist in the defense against CSRF attacks. */ protected $state; /** * The OAuth access token received in exchange for a valid authorization * code. null means the access token has yet to be determined. * * @var string */ protected $accessToken = null; /** * Indicates if the CURL based @ syntax for file uploads is enabled. * * @var boolean */ protected $fileUploadSupport = false; /** * Initialize a Facebook Application. * * The configuration: * - appId: the application ID * - secret: the application secret * - fileUpload: (optional) boolean indicating if file uploads are enabled * * @param array $config The application configuration */ public function __construct($config) { $this->setAppId($config['appId']); $this->setAppSecret($config['secret']); if (isset($config['fileUpload'])) { $this->setFileUploadSupport($config['fileUpload']); } $state = $this->getPersistentData('state'); if (!empty($state)) { $this->state = $this->getPersistentData('state'); } } /** * Set the Application ID. * * @param string $appId The Application ID * @return BaseFacebook */ public function setAppId($appId) { $this->appId = $appId; return $this; } /** * Get the Application ID. * * @return string the Application ID */ public function getAppId() { return $this->appId; } /** * Set the App Secret. * * @param string $apiSecret The App Secret * @return BaseFacebook * @deprecated */ public function setApiSecret($apiSecret) { $this->setAppSecret($apiSecret); return $this; } /** * Set the App Secret. * * @param string $appSecret The App Secret * @return BaseFacebook */ public function setAppSecret($appSecret) { $this->appSecret = $appSecret; return $this; } /** * Get the App Secret. * * @return string the App Secret * @deprecated */ public function getApiSecret() { return $this->getAppSecret(); } /** * Get the App Secret. * * @return string the App Secret */ public function getAppSecret() { return $this->appSecret; } /** * Set the file upload support status. * * @param boolean $fileUploadSupport The file upload support status. * @return BaseFacebook */ public function setFileUploadSupport($fileUploadSupport) { $this->fileUploadSupport = $fileUploadSupport; return $this; } /** * Get the file upload support status. * * @return boolean true if and only if the server supports file upload. */ public function getFileUploadSupport() { return $this->fileUploadSupport; } /** * DEPRECATED! Please use getFileUploadSupport instead. * * Get the file upload support status. * * @return boolean true if and only if the server supports file upload. */ public function useFileUploadSupport() { return $this->getFileUploadSupport(); } /** * Sets the access token for api calls. Use this if you get * your access token by other means and just want the SDK * to use it. * * @param string $access_token an access token. * @return BaseFacebook */ public function setAccessToken($access_token) { $this->accessToken = $access_token; return $this; } /** * Determines the access token that should be used for API calls. * The first time this is called, $this->accessToken is set equal * to either a valid user access token, or it's set to the application * access token if a valid user access token wasn't available. Subsequent * calls return whatever the first call returned. * * @return string The access token */ public function getAccessToken() { if ($this->accessToken !== null) { // we've done this already and cached it. Just return. return $this->accessToken; } // first establish access token to be the application // access token, in case we navigate to the /oauth/access_token // endpoint, where SOME access token is required. $this->setAccessToken($this->getApplicationAccessToken()); $user_access_token = $this->getUserAccessToken(); if ($user_access_token) { $this->setAccessToken($user_access_token); } return $this->accessToken; } /** * Determines and returns the user access token, first using * the signed request if present, and then falling back on * the authorization code if present. The intent is to * return a valid user access token, or false if one is determined * to not be available. * * @return string A valid user access token, or false if one * could not be determined. */ protected function getUserAccessToken() { // first, consider a signed request if it's supplied. // if there is a signed request, then it alone determines // the access token. $signed_request = $this->getSignedRequest(); if ($signed_request) { // apps.facebook.com hands the access_token in the signed_request if (array_key_exists('oauth_token', $signed_request)) { $access_token = $signed_request['oauth_token']; $this->setPersistentData('access_token', $access_token); return $access_token; } // the JS SDK puts a code in with the redirect_uri of '' if (array_key_exists('code', $signed_request)) { $code = $signed_request['code']; $access_token = $this->getAccessTokenFromCode($code, ''); if ($access_token) { $this->setPersistentData('code', $code); $this->setPersistentData('access_token', $access_token); return $access_token; } } // signed request states there's no access token, so anything // stored should be cleared. $this->clearAllPersistentData(); return false; // respect the signed request's data, even // if there's an authorization code or something else } $code = $this->getCode(); if ($code && $code != $this->getPersistentData('code')) { $access_token = $this->getAccessTokenFromCode($code); if ($access_token) { $this->setPersistentData('code', $code); $this->setPersistentData('access_token', $access_token); return $access_token; } // code was bogus, so everything based on it should be invalidated. $this->clearAllPersistentData(); return false; } // as a fallback, just return whatever is in the persistent // store, knowing nothing explicit (signed request, authorization // code, etc.) was present to shadow it (or we saw a code in $_REQUEST, // but it's the same as what's in the persistent store) return $this->getPersistentData('access_token'); } /** * Retrieve the signed request, either from a request parameter or, * if not present, from a cookie. * * @return string the signed request, if available, or null otherwise. */ public function getSignedRequest() { if (!$this->signedRequest) { if (isset($_REQUEST['signed_request'])) { $this->signedRequest = $this->parseSignedRequest( $_REQUEST['signed_request']); } else if (isset($_COOKIE[$this->getSignedRequestCookieName()])) { $this->signedRequest = $this->parseSignedRequest( $_COOKIE[$this->getSignedRequestCookieName()]); } } return $this->signedRequest; } /** * Get the UID of the connected user, or 0 * if the Facebook user is not connected. * * @return string the UID if available. */ public function getUser() { if ($this->user !== null) { // we've already determined this and cached the value. return $this->user; } return $this->user = $this->getUserFromAvailableData(); } /** * Determines the connected user by first examining any signed * requests, then considering an authorization code, and then * falling back to any persistent store storing the user. * * @return integer The id of the connected Facebook user, * or 0 if no such user exists. */ protected function getUserFromAvailableData() { // if a signed request is supplied, then it solely determines // who the user is. $signed_request = $this->getSignedRequest(); if ($signed_request) { if (array_key_exists('user_id', $signed_request)) { $user = $signed_request['user_id']; $this->setPersistentData('user_id', $signed_request['user_id']); return $user; } // if the signed request didn't present a user id, then invalidate // all entries in any persistent store. $this->clearAllPersistentData(); return 0; } $user = $this->getPersistentData('user_id', $default = 0); $persisted_access_token = $this->getPersistentData('access_token'); // use access_token to fetch user id if we have a user access_token, or if // the cached access token has changed. $access_token = $this->getAccessToken(); if ($access_token && $access_token != $this->getApplicationAccessToken() && !($user && $persisted_access_token == $access_token)) { $user = $this->getUserFromAccessToken(); if ($user) { $this->setPersistentData('user_id', $user); } else { $this->clearAllPersistentData(); } } return $user; } /** * Get a Login URL for use with redirects. By default, full page redirect is * assumed. If you are using the generated URL with a window.open() call in * JavaScript, you can pass in display=popup as part of the $params. * * The parameters: * - redirect_uri: the url to go to after a successful login * - scope: comma separated list of requested extended perms * * @param array $params Provide custom parameters * @return string The URL for the login flow */ public function getLoginUrl($params=array()) { $this->establishCSRFTokenState(); $currentUrl = $this->getCurrentUrl(); // if 'scope' is passed as an array, convert to comma separated list $scopeParams = isset($params['scope']) ? $params['scope'] : null; if ($scopeParams && is_array($scopeParams)) { $params['scope'] = implode(',', $scopeParams); } return $this->getUrl( 'www', 'dialog/oauth', array_merge(array( 'client_id' => $this->getAppId(), 'redirect_uri' => $currentUrl, // possibly overwritten 'state' => $this->state), $params)); } /** * Get a Logout URL suitable for use with redirects. * * The parameters: * - next: the url to go to after a successful logout * * @param array $params Provide custom parameters * @return string The URL for the logout flow */ public function getLogoutUrl($params=array()) { return $this->getUrl( 'www', 'logout.php', array_merge(array( 'next' => $this->getCurrentUrl(), 'access_token' => $this->getAccessToken(), ), $params) ); } /** * Get a login status URL to fetch the status from Facebook. * * The parameters: * - ok_session: the URL to go to if a session is found * - no_session: the URL to go to if the user is not connected * - no_user: the URL to go to if the user is not signed into facebook * * @param array $params Provide custom parameters * @return string The URL for the logout flow */ public function getLoginStatusUrl($params=array()) { return $this->getUrl( 'www', 'extern/login_status.php', array_merge(array( 'api_key' => $this->getAppId(), 'no_session' => $this->getCurrentUrl(), 'no_user' => $this->getCurrentUrl(), 'ok_session' => $this->getCurrentUrl(), 'session_version' => 3, ), $params) ); } /** * Make an API call. * * @return mixed The decoded response */ public function api(/* polymorphic */) { $args = func_get_args(); if (is_array($args[0])) { return $this->_restserver($args[0]); } else { return call_user_func_array(array($this, '_graph'), $args); } } /** * Constructs and returns the name of the cookie that * potentially houses the signed request for the app user. * The cookie is not set by the BaseFacebook class, but * it may be set by the JavaScript SDK. * * @return string the name of the cookie that would house * the signed request value. */ protected function getSignedRequestCookieName() { return 'fbsr_'.$this->getAppId(); } /** * Constructs and returns the name of the coookie that potentially contain * metadata. The cookie is not set by the BaseFacebook class, but it may be * set by the JavaScript SDK. * * @return string the name of the cookie that would house metadata. */ protected function getMetadataCookieName() { return 'fbm_'.$this->getAppId(); } /** * Get the authorization code from the query parameters, if it exists, * and otherwise return false to signal no authorization code was * discoverable. * * @return mixed The authorization code, or false if the authorization * code could not be determined. */ protected function getCode() { if (isset($_REQUEST['code'])) { if ($this->state !== null && isset($_REQUEST['state']) && $this->state === $_REQUEST['state']) { // CSRF state has done its job, so clear it $this->state = null; $this->clearPersistentData('state'); return $_REQUEST['code']; } else { self::errorLog('CSRF state token does not match one provided.'); return false; } } return false; } /** * Retrieves the UID with the understanding that * $this->accessToken has already been set and is * seemingly legitimate. It relies on Facebook's Graph API * to retrieve user information and then extract * the user ID. * * @return integer Returns the UID of the Facebook user, or 0 * if the Facebook user could not be determined. */ protected function getUserFromAccessToken() { try { $user_info = $this->api('/me'); return $user_info['id']; } catch (FacebookApiException $e) { return 0; } } /** * Returns the access token that should be used for logged out * users when no authorization code is available. * * @return string The application access token, useful for gathering * public information about users and applications. */ protected function getApplicationAccessToken() { return $this->appId.'|'.$this->appSecret; } /** * Lays down a CSRF state token for this process. * * @return void */ protected function establishCSRFTokenState() { if ($this->state === null) { $this->state = md5(uniqid(mt_rand(), true)); $this->setPersistentData('state', $this->state); } } /** * Retrieves an access token for the given authorization code * (previously generated from www.facebook.com on behalf of * a specific user). The authorization code is sent to graph.facebook.com * and a legitimate access token is generated provided the access token * and the user for which it was generated all match, and the user is * either logged in to Facebook or has granted an offline access permission. * * @param string $code An authorization code. * @return mixed An access token exchanged for the authorization code, or * false if an access token could not be generated. */ protected function getAccessTokenFromCode($code, $redirect_uri = null) { if (empty($code)) { return false; } if ($redirect_uri === null) { $redirect_uri = $this->getCurrentUrl(); } try { // need to circumvent json_decode by calling _oauthRequest // directly, since response isn't JSON format. $access_token_response = $this->_oauthRequest( $this->getUrl('graph', '/oauth/access_token'), $params = array('client_id' => $this->getAppId(), 'client_secret' => $this->getAppSecret(), 'redirect_uri' => $redirect_uri, 'code' => $code)); } catch (FacebookApiException $e) { // most likely that user very recently revoked authorization. // In any event, we don't have an access token, so say so. return false; } if (empty($access_token_response)) { return false; } $response_params = array(); parse_str($access_token_response, $response_params); if (!isset($response_params['access_token'])) { return false; } return $response_params['access_token']; } /** * Invoke the old restserver.php endpoint. * * @param array $params Method call object * * @return mixed The decoded response object * @throws FacebookApiException */ protected function _restserver($params) { // generic application level parameters $params['api_key'] = $this->getAppId(); $params['format'] = 'json-strings'; $result = json_decode($this->_oauthRequest( $this->getApiUrl($params['method']), $params ), true); // results are returned, errors are thrown if (is_array($result) && isset($result['error_code'])) { $this->throwAPIException($result); } if ($params['method'] === 'auth.expireSession' || $params['method'] === 'auth.revokeAuthorization') { $this->destroySession(); } return $result; } /** * Return true if this is video post. * * @param string $path The path * @param string $method The http method (default 'GET') * * @return boolean true if this is video post */ protected function isVideoPost($path, $method = 'GET') { if ($method == 'POST' && preg_match("/^(\/)(.+)(\/)(videos)$/", $path)) { return true; } return false; } /** * Invoke the Graph API. * * @param string $path The path (required) * @param string $method The http method (default 'GET') * @param array $params The query/post data * * @return mixed The decoded response object * @throws FacebookApiException */ protected function _graph($path, $method = 'GET', $params = array()) { if (is_array($method) && empty($params)) { $params = $method; $method = 'GET'; } $params['method'] = $method; // method override as we always do a POST if ($this->isVideoPost($path, $method)) { $domainKey = 'graph_video'; } else { $domainKey = 'graph'; } $result = json_decode($this->_oauthRequest( $this->getUrl($domainKey, $path), $params ), true); // results are returned, errors are thrown if (is_array($result) && isset($result['error'])) { $this->throwAPIException($result); } return $result; } /** * Make a OAuth Request. * * @param string $url The path (required) * @param array $params The query/post data * * @return string The decoded response object * @throws FacebookApiException */ protected function _oauthRequest($url, $params) { if (!isset($params['access_token'])) { $params['access_token'] = $this->getAccessToken(); } // json_encode all params values that are not strings foreach ($params as $key => $value) { if (!is_string($value)) { $params[$key] = json_encode($value); } } return $this->makeRequest($url, $params); } /** * Makes an HTTP request. This method can be overridden by subclasses if * developers want to do fancier things or use something other than curl to * make the request. * * @param string $url The URL to make the request to * @param array $params The parameters to use for the POST body * @param CurlHandler $ch Initialized curl handle * * @return string The response text */ protected function makeRequest($url, $params, $ch=null) { if (!$ch) { $ch = curl_init(); } $opts = self::$CURL_OPTS; if ($this->getFileUploadSupport()) { $opts[CURLOPT_POSTFIELDS] = $params; } else { $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&'); } $opts[CURLOPT_URL] = $url; // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait // for 2 seconds if the server does not support this header. if (isset($opts[CURLOPT_HTTPHEADER])) { $existing_headers = $opts[CURLOPT_HTTPHEADER]; $existing_headers[] = 'Expect:'; $opts[CURLOPT_HTTPHEADER] = $existing_headers; } else { $opts[CURLOPT_HTTPHEADER] = array('Expect:'); } curl_setopt_array($ch, $opts); $result = curl_exec($ch); if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT self::errorLog('Invalid or no certificate authority found, '. 'using bundled information'); curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/fb_ca_chain_bundle.crt'); $result = curl_exec($ch); } if ($result === false) { $e = new FacebookApiException(array( 'error_code' => curl_errno($ch), 'error' => array( 'message' => curl_error($ch), 'type' => 'CurlException', ), )); curl_close($ch); throw $e; } curl_close($ch); return $result; } /** * Parses a signed_request and validates the signature. * * @param string $signed_request A signed token * @return array The payload inside it or null if the sig is wrong */ protected function parseSignedRequest($signed_request) { list($encoded_sig, $payload) = explode('.', $signed_request, 2); // decode the data $sig = self::base64UrlDecode($encoded_sig); $data = json_decode(self::base64UrlDecode($payload), true); if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') { self::errorLog('Unknown algorithm. Expected HMAC-SHA256'); return null; } // check sig $expected_sig = hash_hmac('sha256', $payload, $this->getAppSecret(), $raw = true); if ($sig !== $expected_sig) { self::errorLog('Bad Signed JSON signature!'); return null; } return $data; } /** * Build the URL for api given parameters. * * @param $method String the method name. * @return string The URL for the given parameters */ protected function getApiUrl($method) { static $READ_ONLY_CALLS = array('admin.getallocation' => 1, 'admin.getappproperties' => 1, 'admin.getbannedusers' => 1, 'admin.getlivestreamvialink' => 1, 'admin.getmetrics' => 1, 'admin.getrestrictioninfo' => 1, 'application.getpublicinfo' => 1, 'auth.getapppublickey' => 1, 'auth.getsession' => 1, 'auth.getsignedpublicsessiondata' => 1, 'comments.get' => 1, 'connect.getunconnectedfriendscount' => 1, 'dashboard.getactivity' => 1, 'dashboard.getcount' => 1, 'dashboard.getglobalnews' => 1, 'dashboard.getnews' => 1, 'dashboard.multigetcount' => 1, 'dashboard.multigetnews' => 1, 'data.getcookies' => 1, 'events.get' => 1, 'events.getmembers' => 1, 'fbml.getcustomtags' => 1, 'feed.getappfriendstories' => 1, 'feed.getregisteredtemplatebundlebyid' => 1, 'feed.getregisteredtemplatebundles' => 1, 'fql.multiquery' => 1, 'fql.query' => 1, 'friends.arefriends' => 1, 'friends.get' => 1, 'friends.getappusers' => 1, 'friends.getlists' => 1, 'friends.getmutualfriends' => 1, 'gifts.get' => 1, 'groups.get' => 1, 'groups.getmembers' => 1, 'intl.gettranslations' => 1, 'links.get' => 1, 'notes.get' => 1, 'notifications.get' => 1, 'pages.getinfo' => 1, 'pages.isadmin' => 1, 'pages.isappadded' => 1, 'pages.isfan' => 1, 'permissions.checkavailableapiaccess' => 1, 'permissions.checkgrantedapiaccess' => 1, 'photos.get' => 1, 'photos.getalbums' => 1, 'photos.gettags' => 1, 'profile.getinfo' => 1, 'profile.getinfooptions' => 1, 'stream.get' => 1, 'stream.getcomments' => 1, 'stream.getfilters' => 1, 'users.getinfo' => 1, 'users.getloggedinuser' => 1, 'users.getstandardinfo' => 1, 'users.hasapppermission' => 1, 'users.isappuser' => 1, 'users.isverified' => 1, 'video.getuploadlimits' => 1); $name = 'api'; if (isset($READ_ONLY_CALLS[strtolower($method)])) { $name = 'api_read'; } else if (strtolower($method) == 'video.upload') { $name = 'api_video'; } return self::getUrl($name, 'restserver.php'); } /** * Build the URL for given domain alias, path and parameters. * * @param $name string The name of the domain * @param $path string Optional path (without a leading slash) * @param $params array Optional query parameters * * @return string The URL for the given parameters */ protected function getUrl($name, $path='', $params=array()) { $url = self::$DOMAIN_MAP[$name]; if ($path) { if ($path[0] === '/') { $path = substr($path, 1); } $url .= $path; } if ($params) { $url .= '?' . http_build_query($params, null, '&'); } return $url; } /** * Returns the Current URL, stripping it of known FB parameters that should * not persist. * * @return string The current URL */ protected function getCurrentUrl() { if (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { $protocol = 'https://'; } else { $protocol = 'http://'; } $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; $parts = parse_url($currentUrl); $query = ''; if (!empty($parts['query'])) { // drop known fb params $params = explode('&', $parts['query']); $retained_params = array(); foreach ($params as $param) { if ($this->shouldRetainParam($param)) { $retained_params[] = $param; } } if (!empty($retained_params)) { $query = '?'.implode($retained_params, '&'); } } // use port if non default $port = isset($parts['port']) && (($protocol === 'http://' && $parts['port'] !== 80) || ($protocol === 'https://' && $parts['port'] !== 443)) ? ':' . $parts['port'] : ''; // rebuild return $protocol . $parts['host'] . $port . $parts['path'] . $query; } /** * Returns true if and only if the key or key/value pair should * be retained as part of the query string. This amounts to * a brute-force search of the very small list of Facebook-specific * params that should be stripped out. * * @param string $param A key or key/value pair within a URL's query (e.g. * 'foo=a', 'foo=', or 'foo'. * * @return boolean */ protected function shouldRetainParam($param) { foreach (self::$DROP_QUERY_PARAMS as $drop_query_param) { if (strpos($param, $drop_query_param.'=') === 0) { return false; } } return true; } /** * Analyzes the supplied result to see if it was thrown * because the access token is no longer valid. If that is * the case, then we destroy the session. * * @param $result array A record storing the error message returned * by a failed API call. */ protected function throwAPIException($result) { $e = new FacebookApiException($result); switch ($e->getType()) { // OAuth 2.0 Draft 00 style case 'OAuthException': // OAuth 2.0 Draft 10 style case 'invalid_token': // REST server errors are just Exceptions case 'Exception': $message = $e->getMessage(); if ((strpos($message, 'Error validating access token') !== false) || (strpos($message, 'Invalid OAuth access token') !== false) || (strpos($message, 'An active access token must be used') !== false) ) { $this->destroySession(); } break; } throw $e; } /** * Prints to the error log if you aren't in command line mode. * * @param string $msg Log message */ protected static function errorLog($msg) { // disable error log if we are running in a CLI environment // @codeCoverageIgnoreStart if (php_sapi_name() != 'cli') { error_log($msg); } // uncomment this if you want to see the errors on the page // print 'error_log: '.$msg."\n"; // @codeCoverageIgnoreEnd } /** * Base64 encoding that doesn't need to be urlencode()ed. * Exactly the same as base64_encode except it uses * - instead of + * _ instead of / * * @param string $input base64UrlEncoded string * @return string */ protected static function base64UrlDecode($input) { return base64_decode(strtr($input, '-_', '+/')); } /** * Destroy the current session */ public function destroySession() { $this->accessToken = null; $this->signedRequest = null; $this->user = null; $this->clearAllPersistentData(); // Javascript sets a cookie that will be used in getSignedRequest that we // need to clear if we can $cookie_name = $this->getSignedRequestCookieName(); if (array_key_exists($cookie_name, $_COOKIE)) { unset($_COOKIE[$cookie_name]); if (!headers_sent()) { // The base domain is stored in the metadata cookie if not we fallback // to the current hostname $base_domain = '.'. $_SERVER['HTTP_HOST']; $metadata = $this->getMetadataCookie(); if (array_key_exists('base_domain', $metadata) && !empty($metadata['base_domain'])) { $base_domain = $metadata['base_domain']; } setcookie($cookie_name, '', 0, '/', $base_domain); } else { self::errorLog( 'There exists a cookie that we wanted to clear that we couldn\'t '. 'clear because headers was already sent. Make sure to do the first '. 'API call before outputing anything' ); } } } /** * Parses the metadata cookie that our Javascript API set * * @return an array mapping key to value */ protected function getMetadataCookie() { $cookie_name = $this->getMetadataCookieName(); if (!array_key_exists($cookie_name, $_COOKIE)) { return array(); } // The cookie value can be wrapped in "-characters so remove them $cookie_value = trim($_COOKIE[$cookie_name], '"'); if (empty($cookie_value)) { return array(); } $parts = explode('&', $cookie_value); $metadata = array(); foreach ($parts as $part) { $pair = explode('=', $part, 2); if (!empty($pair[0])) { $metadata[urldecode($pair[0])] = (count($pair) > 1) ? urldecode($pair[1]) : ''; } } return $metadata; } /** * Each of the following four methods should be overridden in * a concrete subclass, as they are in the provided Facebook class. * The Facebook class uses PHP sessions to provide a primitive * persistent store, but another subclass--one that you implement-- * might use a database, memcache, or an in-memory cache. * * @see Facebook */ /** * Stores the given ($key, $value) pair, so that future calls to * getPersistentData($key) return $value. This call may be in another request. * * @param string $key * @param array $value * * @return void */ abstract protected function setPersistentData($key, $value); /** * Get the data for $key, persisted by BaseFacebook::setPersistentData() * * @param string $key The key of the data to retrieve * @param boolean $default The default value to return if $key is not found * * @return mixed */ abstract protected function getPersistentData($key, $default = false); /** * Clear the data with $key from the persistent storage * * @param string $key * @return void */ abstract protected function clearPersistentData($key); /** * Clear all data from the persistent storage * * @return void */ abstract protected function clearAllPersistentData(); } /** * Copyright 2011 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ /** * Extends the BaseFacebook class with the intent of using * PHP sessions to store user ids and access tokens. */ class Facebook extends BaseFacebook { /** * Identical to the parent constructor, except that * we start a PHP session to store the user ID and * access token if during the course of execution * we discover them. * * @param Array $config the application configuration. * @see BaseFacebook::__construct in facebook.php */ public function __construct($config) { if (!session_id()) { session_start(); } parent::__construct($config); } protected static $kSupportedKeys = array('state', 'code', 'access_token', 'user_id'); /** * Provides the implementations of the inherited abstract * methods. The implementation uses PHP sessions to maintain * a store for authorization codes, user ids, CSRF states, and * access tokens. */ protected function setPersistentData($key, $value) { if (!in_array($key, self::$kSupportedKeys)) { self::errorLog('Unsupported key passed to setPersistentData.'); return; } $session_var_name = $this->constructSessionVariableName($key); $_SESSION[$session_var_name] = $value; } protected function getPersistentData($key, $default = false) { if (!in_array($key, self::$kSupportedKeys)) { self::errorLog('Unsupported key passed to getPersistentData.'); return $default; } $session_var_name = $this->constructSessionVariableName($key); return isset($_SESSION[$session_var_name]) ? $_SESSION[$session_var_name] : $default; } protected function clearPersistentData($key) { if (!in_array($key, self::$kSupportedKeys)) { self::errorLog('Unsupported key passed to clearPersistentData.'); return; } $session_var_name = $this->constructSessionVariableName($key); unset($_SESSION[$session_var_name]); } protected function clearAllPersistentData() { foreach (self::$kSupportedKeys as $key) { $this->clearPersistentData($key); } } protected function constructSessionVariableName($key) { return implode('_', array('fb', $this->getAppId(), $key)); } } // vim: foldmethod=marker /* Generic exception class */ class OAuthException extends Exception { // pass } class OAuthConsumer { public $key; public $secret; function __construct($key, $secret, $callback_url=NULL) { $this->key = $key; $this->secret = $secret; $this->callback_url = $callback_url; } function __toString() { return "OAuthConsumer[key=$this->key,secret=$this->secret]"; } } class OAuthToken { // access tokens and request tokens public $key; public $secret; /** * key = the token * secret = the token secret */ function __construct($key, $secret) { $this->key = $key; $this->secret = $secret; } /** * generates the basic string serialization of a token that a server * would respond to request_token and access_token calls with */ function to_string() { return "oauth_token=" . OAuthUtil::urlencode_rfc3986($this->key) . "&oauth_token_secret=" . OAuthUtil::urlencode_rfc3986($this->secret); } function __toString() { return $this->to_string(); } } /** * A class for implementing a Signature Method * See section 9 ("Signing Requests") in the spec */ abstract class OAuthSignatureMethod { /** * Needs to return the name of the Signature Method (ie HMAC-SHA1) * @return string */ abstract public function get_name(); /** * Build up the signature * NOTE: The output of this function MUST NOT be urlencoded. * the encoding is handled in OAuthRequest when the final * request is serialized * @param OAuthRequest $request * @param OAuthConsumer $consumer * @param OAuthToken $token * @return string */ abstract public function build_signature($request, $consumer, $token); /** * Verifies that a given signature is correct * @param OAuthRequest $request * @param OAuthConsumer $consumer * @param OAuthToken $token * @param string $signature * @return bool */ public function check_signature($request, $consumer, $token, $signature) { $built = $this->build_signature($request, $consumer, $token); return $built == $signature; } } /** * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] * where the Signature Base String is the text and the key is the concatenated values (each first * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' * character (ASCII code 38) even if empty. * - Chapter 9.2 ("HMAC-SHA1") */ class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { function get_name() { return "HMAC-SHA1"; } public function build_signature($request, $consumer, $token) { $base_string = $request->get_signature_base_string(); $request->base_string = $base_string; $key_parts = array( $consumer->secret, ($token) ? $token->secret : "" ); $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); $key = implode('&', $key_parts); return base64_encode(hash_hmac('sha1', $base_string, $key, true)); } } /** * The PLAINTEXT method does not provide any security protection and SHOULD only be used * over a secure channel such as HTTPS. It does not use the Signature Base String. * - Chapter 9.4 ("PLAINTEXT") */ class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { public function get_name() { return "PLAINTEXT"; } /** * oauth_signature is set to the concatenated encoded values of the Consumer Secret and * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is * empty. The result MUST be encoded again. * - Chapter 9.4.1 ("Generating Signatures") * * Please note that the second encoding MUST NOT happen in the SignatureMethod, as * OAuthRequest handles this! */ public function build_signature($request, $consumer, $token) { $key_parts = array( $consumer->secret, ($token) ? $token->secret : "" ); $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); $key = implode('&', $key_parts); $request->base_string = $key; return $key; } } /** * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a * verified way to the Service Provider, in a manner which is beyond the scope of this * specification. * - Chapter 9.3 ("RSA-SHA1") */ abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod { public function get_name() { return "RSA-SHA1"; } // Up to the SP to implement this lookup of keys. Possible ideas are: // (1) do a lookup in a table of trusted certs keyed off of consumer // (2) fetch via http using a url provided by the requester // (3) some sort of specific discovery code based on request // // Either way should return a string representation of the certificate protected abstract function fetch_public_cert(&$request); // Up to the SP to implement this lookup of keys. Possible ideas are: // (1) do a lookup in a table of trusted certs keyed off of consumer // // Either way should return a string representation of the certificate protected abstract function fetch_private_cert(&$request); public function build_signature($request, $consumer, $token) { $base_string = $request->get_signature_base_string(); $request->base_string = $base_string; // Fetch the private key cert based on the request $cert = $this->fetch_private_cert($request); // Pull the private key ID from the certificate $privatekeyid = openssl_get_privatekey($cert); // Sign using the key $ok = openssl_sign($base_string, $signature, $privatekeyid); // Release the key resource openssl_free_key($privatekeyid); return base64_encode($signature); } public function check_signature($request, $consumer, $token, $signature) { $decoded_sig = base64_decode($signature); $base_string = $request->get_signature_base_string(); // Fetch the public key cert based on the request $cert = $this->fetch_public_cert($request); // Pull the public key ID from the certificate $publickeyid = openssl_get_publickey($cert); // Check the computed signature against the one passed in the query $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); // Release the key resource openssl_free_key($publickeyid); return $ok == 1; } } class OAuthRequest { private $parameters; private $http_method; private $http_url; // for debug purposes public $base_string; public static $version = '1.0'; public static $POST_INPUT = 'php://input'; function __construct($http_method, $http_url, $parameters=NULL) { $parameters or $parameters = array(); $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); $this->parameters = $parameters; $this->http_method = $http_method; $this->http_url = $http_url; } /** * attempt to build up a request from what was passed to the server */ public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) { $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https'; $http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; $http_method or $http_method = $_SERVER['REQUEST_METHOD']; // We weren't handed any parameters, so let's find the ones relevant to // this request. // If you run XML-RPC or similar you should use this to provide your own // parsed parameter-list if (!$parameters) { // Find request headers $request_headers = OAuthUtil::get_headers(); // Parse the query-string to find GET parameters $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); // It's a POST request of the proper content-type, so parse POST // parameters and add those overriding any duplicates from GET if ($http_method == "POST" && strstr($request_headers["Content-Type"], "application/x-www-form-urlencoded") ) { $post_data = OAuthUtil::parse_parameters( file_get_contents(self::$POST_INPUT) ); $parameters = array_merge($parameters, $post_data); } // We have a Authorization-header with OAuth data. Parse the header // and add those overriding any duplicates from GET or POST if (substr($request_headers['Authorization'], 0, 6) == "OAuth ") { $header_parameters = OAuthUtil::split_header( $request_headers['Authorization'] ); $parameters = array_merge($parameters, $header_parameters); } } return new OAuthRequest($http_method, $http_url, $parameters); } /** * pretty much a helper function to set up the request */ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) { $parameters or $parameters = array(); $defaults = array("oauth_version" => OAuthRequest::$version, "oauth_nonce" => OAuthRequest::generate_nonce(), "oauth_timestamp" => OAuthRequest::generate_timestamp(), "oauth_consumer_key" => $consumer->key); if ($token) $defaults['oauth_token'] = $token->key; $parameters = array_merge($defaults, $parameters); return new OAuthRequest($http_method, $http_url, $parameters); } public function set_parameter($name, $value, $allow_duplicates = true) { if ($allow_duplicates && isset($this->parameters[$name])) { // We have already added parameter(s) with this name, so add to the list if (is_scalar($this->parameters[$name])) { // This is the first duplicate, so transform scalar (string) // into an array so we can add the duplicates $this->parameters[$name] = array($this->parameters[$name]); } $this->parameters[$name][] = $value; } else { $this->parameters[$name] = $value; } } public function get_parameter($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } public function get_parameters() { return $this->parameters; } public function unset_parameter($name) { unset($this->parameters[$name]); } /** * The request parameters, sorted and concatenated into a normalized string. * @return string */ public function get_signable_parameters() { // Grab all parameters $params = $this->parameters; // Remove oauth_signature if present // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") if (isset($params['oauth_signature'])) { unset($params['oauth_signature']); } return OAuthUtil::build_http_query($params); } /** * Returns the base string of this request * * The base string defined as the method, the url * and the parameters (normalized), each urlencoded * and the concated with &. */ public function get_signature_base_string() { $parts = array( $this->get_normalized_http_method(), $this->get_normalized_http_url(), $this->get_signable_parameters() ); $parts = OAuthUtil::urlencode_rfc3986($parts); return implode('&', $parts); } /** * just uppercases the http method */ public function get_normalized_http_method() { return strtoupper($this->http_method); } /** * parses the url and rebuilds it to be * scheme://host/path */ public function get_normalized_http_url() { $parts = parse_url($this->http_url); $port = !empty($parts['port']) ? $parts['port'] : ''; $scheme = $parts['scheme']; $host = $parts['host']; $path = !empty($parts['path']) ? $parts['path'] : ''; $port or $port = ($scheme == 'https') ? '443' : '80'; if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) { $host = "$host:$port"; } return "$scheme://$host$path"; } /** * builds a url usable for a GET request */ public function to_url() { $post_data = $this->to_postdata(); $out = $this->get_normalized_http_url(); if ($post_data) { $out .= '?'.$post_data; } return $out; } /** * builds the data one would send in a POST request */ public function to_postdata() { return OAuthUtil::build_http_query($this->parameters); } /** * builds the Authorization: header */ public function to_header($realm=null) { $first = true; if($realm) { $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; $first = false; } else $out = 'Authorization: OAuth'; $total = array(); foreach ($this->parameters as $k => $v) { if (substr($k, 0, 5) != "oauth") continue; if (is_array($v)) { throw new OAuthException('Arrays not supported in headers'); } $out .= ($first) ? ' ' : ','; $out .= OAuthUtil::urlencode_rfc3986($k) . '="' . OAuthUtil::urlencode_rfc3986($v) . '"'; $first = false; } return $out; } public function __toString() { return $this->to_url(); } public function sign_request($signature_method, $consumer, $token) { $this->set_parameter( "oauth_signature_method", $signature_method->get_name(), false ); $signature = $this->build_signature($signature_method, $consumer, $token); $this->set_parameter("oauth_signature", $signature, false); } public function build_signature($signature_method, $consumer, $token) { $signature = $signature_method->build_signature($this, $consumer, $token); return $signature; } /** * util function: current timestamp */ private static function generate_timestamp() { return time(); } /** * util function: current nonce */ private static function generate_nonce() { $mt = microtime(); $rand = mt_rand(); return md5($mt . $rand); // md5s look nicer than numbers } } class OAuthServer { protected $timestamp_threshold = 300; // in seconds, five minutes protected $version = '1.0'; // hi blaine protected $signature_methods = array(); protected $data_store; function __construct($data_store) { $this->data_store = $data_store; } public function add_signature_method($signature_method) { $this->signature_methods[$signature_method->get_name()] = $signature_method; } // high level functions /** * process a request_token request * returns the request token on success */ public function fetch_request_token(&$request) { $this->get_version($request); $consumer = $this->get_consumer($request); // no token required for the initial token request $token = NULL; $this->check_signature($request, $consumer, $token); // Rev A change $callback = $request->get_parameter('oauth_callback'); $new_token = $this->data_store->new_request_token($consumer, $callback); return $new_token; } /** * process an access_token request * returns the access token on success */ public function fetch_access_token(&$request) { $this->get_version($request); $consumer = $this->get_consumer($request); // requires authorized request token $token = $this->get_token($request, $consumer, "request"); $this->check_signature($request, $consumer, $token); // Rev A change $verifier = $request->get_parameter('oauth_verifier'); $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); return $new_token; } /** * verify an api call, checks all the parameters */ public function verify_request(&$request) { $this->get_version($request); $consumer = $this->get_consumer($request); $token = $this->get_token($request, $consumer, "access"); $this->check_signature($request, $consumer, $token); return array($consumer, $token); } // Internals from here /** * version 1 */ private function get_version(&$request) { $version = $request->get_parameter("oauth_version"); if (!$version) { // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. // Chapter 7.0 ("Accessing Protected Ressources") $version = '1.0'; } if ($version !== $this->version) { throw new OAuthException("OAuth version '$version' not supported"); } return $version; } /** * figure out the signature with some defaults */ private function get_signature_method(&$request) { $signature_method = $request->get_parameter("oauth_signature_method"); if (!$signature_method) { // According to chapter 7 ("Accessing Protected Ressources") the signature-method // parameter is required, and we can't just fallback to PLAINTEXT throw new OAuthException('No signature method parameter. This parameter is required'); } if (!in_array($signature_method, array_keys($this->signature_methods))) { throw new OAuthException( "Signature method '$signature_method' not supported " . "try one of the following: " . implode(", ", array_keys($this->signature_methods)) ); } return $this->signature_methods[$signature_method]; } /** * try to find the consumer for the provided request's consumer key */ private function get_consumer(&$request) { $consumer_key = $request->get_parameter("oauth_consumer_key"); if (!$consumer_key) { throw new OAuthException("Invalid consumer key"); } $consumer = $this->data_store->lookup_consumer($consumer_key); if (!$consumer) { throw new OAuthException("Invalid consumer"); } return $consumer; } /** * try to find the token for the provided request's token key */ private function get_token(&$request, $consumer, $token_type="access") { $token_field = $request->get_parameter('oauth_token'); $token = $this->data_store->lookup_token( $consumer, $token_type, $token_field ); if (!$token) { throw new OAuthException("Invalid $token_type token: $token_field"); } return $token; } /** * all-in-one function to check the signature on a request * should guess the signature method appropriately */ private function check_signature(&$request, $consumer, $token) { // this should probably be in a different method $timestamp = $request->get_parameter('oauth_timestamp'); $nonce = $request->get_parameter('oauth_nonce'); $this->check_timestamp($timestamp); $this->check_nonce($consumer, $token, $nonce, $timestamp); $signature_method = $this->get_signature_method($request); $signature = $request->get_parameter('oauth_signature'); $valid_sig = $signature_method->check_signature( $request, $consumer, $token, $signature ); if (!$valid_sig) { throw new OAuthException("Invalid signature"); } } /** * check that the timestamp is new enough */ private function check_timestamp($timestamp) { if( ! $timestamp ) throw new OAuthException( 'Missing timestamp parameter. The parameter is required' ); // verify that timestamp is recentish $now = time(); if (abs($now - $timestamp) > $this->timestamp_threshold) { throw new OAuthException( "Expired timestamp, yours $timestamp, ours $now" ); } } /** * check that the nonce is not repeated */ private function check_nonce($consumer, $token, $nonce, $timestamp) { if( ! $nonce ) throw new OAuthException( 'Missing nonce parameter. The parameter is required' ); // verify that the nonce is uniqueish $found = $this->data_store->lookup_nonce( $consumer, $token, $nonce, $timestamp ); if ($found) { throw new OAuthException("Nonce already used: $nonce"); } } } class OAuthDataStore { function lookup_consumer($consumer_key) { // implement me } function lookup_token($consumer, $token_type, $token) { // implement me } function lookup_nonce($consumer, $token, $nonce, $timestamp) { // implement me } function new_request_token($consumer, $callback = null) { // return a new token attached to this consumer } function new_access_token($token, $consumer, $verifier = null) { // return a new access token attached to this consumer // for the user associated with this token if the request token // is authorized // should also invalidate the request token } } class OAuthUtil { public static function urlencode_rfc3986($input) { if (is_array($input)) { return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input); } else if (is_scalar($input)) { return str_replace( '+', ' ', str_replace('%7E', '~', rawurlencode($input)) ); } else { return ''; } } // This decode function isn't taking into consideration the above // modifications to the encoding process. However, this method doesn't // seem to be used anywhere so leaving it as is. public static function urldecode_rfc3986($string) { return urldecode($string); } // Utility function for turning the Authorization: header into // parameters, has to do some unescaping // Can filter out any non-oauth parameters if needed (default behaviour) public static function split_header($header, $only_allow_oauth_parameters = true) { $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/'; $offset = 0; $params = array(); while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) { $match = $matches[0]; $header_name = $matches[2][0]; $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0]; if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) { $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content); } $offset = $match[1] + strlen($match[0]); } if (isset($params['realm'])) { unset($params['realm']); } return $params; } // helper to try to sort out headers for people who aren't running apache public static function get_headers() { if (function_exists('apache_request_headers')) { // we need this to get the actual Authorization: header // because apache tends to tell us it doesn't exist $headers = apache_request_headers(); // sanitize the output of apache_request_headers because // we always want the keys to be Cased-Like-This and arh() // returns the headers in the same case as they are in the // request $out = array(); foreach( $headers AS $key => $value ) { $key = str_replace( " ", "-", ucwords(strtolower(str_replace("-", " ", $key))) ); $out[$key] = $value; } } else { // otherwise we don't have apache and are just going to have to hope // that $_SERVER actually contains what we need $out = array(); if( isset($_SERVER['CONTENT_TYPE']) ) $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; if( isset($_ENV['CONTENT_TYPE']) ) $out['Content-Type'] = $_ENV['CONTENT_TYPE']; foreach ($_SERVER as $key => $value) { if (substr($key, 0, 5) == "HTTP_") { // this is chaos, basically it is just there to capitalize the first // letter of every word that is not an initial HTTP and strip HTTP // code from przemek $key = str_replace( " ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) ); $out[$key] = $value; } } } return $out; } // This function takes a input like a=b&a=c&d=e and returns the parsed // parameters like this // array('a' => array('b','c'), 'd' => 'e') public static function parse_parameters( $input ) { if (!isset($input) || !$input) return array(); $pairs = explode('&', $input); $parsed_parameters = array(); foreach ($pairs as $pair) { $split = explode('=', $pair, 2); $parameter = OAuthUtil::urldecode_rfc3986($split[0]); $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; if (isset($parsed_parameters[$parameter])) { // We have already recieved parameter(s) with this name, so add to the list // of parameters with this name if (is_scalar($parsed_parameters[$parameter])) { // This is the first duplicate, so transform scalar (string) into an array // so we can add the duplicates $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); } $parsed_parameters[$parameter][] = $value; } else { $parsed_parameters[$parameter] = $value; } } return $parsed_parameters; } public static function build_http_query($params) { if (!$params) return ''; // Urlencode both keys and values $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); $values = OAuthUtil::urlencode_rfc3986(array_values($params)); $params = array_combine($keys, $values); // Parameters are sorted by name, using lexicographical byte value ordering. // Ref: Spec: 9.1.1 (1) uksort($params, 'strcmp'); $pairs = array(); foreach ($params as $parameter => $value) { if (is_array($value)) { // If two or more parameters share the same name, they are sorted by their value // Ref: Spec: 9.1.1 (1) natsort($value); foreach ($value as $duplicate_value) { $pairs[] = $parameter . '=' . $duplicate_value; } } else { $pairs[] = $parameter . '=' . $value; } } // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) // Each name-value pair is separated by an '&' character (ASCII code 38) return implode('&', $pairs); } } /* * Abraham Williams (abraham@abrah.am) http://abrah.am * * The first PHP Library to support OAuth for Twitter's REST API. */ /** * Twitter OAuth class */ class TwitterOAuth { /* Contains the last HTTP status code returned. */ public $http_code; /* Contains the last API call. */ public $url; /* Set up the API root URL. */ public $host = "https://api.twitter.com/1.1/"; /* Set timeout default. */ public $timeout = 30; /* Set connect timeout. */ public $connecttimeout = 30; /* Verify SSL Cert. */ public $ssl_verifypeer = FALSE; /* Respons format. */ public $format = 'json'; /* Decode returned json data. */ public $decode_json = TRUE; /* Contains the last HTTP headers returned. */ public $http_info; /* Set the useragnet. */ public $useragent = 'TwitterOAuth v0.2.0-beta2'; /* Immediately retry the API call if the response was not successful. */ //public $retry = TRUE; /** * Set API URLS */ function accessTokenURL() { return 'https://api.twitter.com/oauth/access_token'; } function authenticateURL() { return 'https://api.twitter.com/oauth/authenticate'; } function authorizeURL() { return 'https://api.twitter.com/oauth/authorize'; } function requestTokenURL() { return 'https://api.twitter.com/oauth/request_token'; } /** * Debug helpers */ function lastStatusCode() { return $this->http_status; } function lastAPICall() { return $this->last_api_call; } /** * construct TwitterOAuth object */ function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) { $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); if (!empty($oauth_token) && !empty($oauth_token_secret)) { $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret); } else { $this->token = NULL; } } /** * Get a request_token from Twitter * * @returns a key/value array containing oauth_token and oauth_token_secret */ function getRequestToken($oauth_callback = NULL) { $parameters = array(); if (!empty($oauth_callback)) { $parameters['oauth_callback'] = $oauth_callback; } $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters); $token = OAuthUtil::parse_parameters($request); $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); return $token; } /** * Get the authorize URL * * @returns a string */ function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) { if (is_array($token)) { $token = $token['oauth_token']; } if (empty($sign_in_with_twitter)) { return $this->authorizeURL() . "?oauth_token={$token}"; } else { return $this->authenticateURL() . "?oauth_token={$token}"; } } /** * Exchange request token and secret for an access token and * secret, to sign API calls. * * @returns array("oauth_token" => "the-access-token", * "oauth_token_secret" => "the-access-secret", * "user_id" => "9436992", * "screen_name" => "abraham") */ function getAccessToken($oauth_verifier = FALSE) { $parameters = array(); if (!empty($oauth_verifier)) { $parameters['oauth_verifier'] = $oauth_verifier; } $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters); $token = OAuthUtil::parse_parameters($request); $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); return $token; } /** * One time exchange of username and password for access token and secret. * * @returns array("oauth_token" => "the-access-token", * "oauth_token_secret" => "the-access-secret", * "user_id" => "9436992", * "screen_name" => "abraham", * "x_auth_expires" => "0") */ function getXAuthToken($username, $password) { $parameters = array(); $parameters['x_auth_username'] = $username; $parameters['x_auth_password'] = $password; $parameters['x_auth_mode'] = 'client_auth'; $request = $this->oAuthRequest($this->accessTokenURL(), 'POST', $parameters); $token = OAuthUtil::parse_parameters($request); $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); return $token; } /** * GET wrapper for oAuthRequest. */ function get($url, $parameters = array()) { $response = $this->oAuthRequest($url, 'GET', $parameters); if ($this->format === 'json' && $this->decode_json) { return json_decode($response); } return $response; } /** * POST wrapper for oAuthRequest. */ function post($url, $parameters = array()) { $response = $this->oAuthRequest($url, 'POST', $parameters); if ($this->format === 'json' && $this->decode_json) { return json_decode($response); } return $response; } /** * DELETE wrapper for oAuthReqeust. */ function delete($url, $parameters = array()) { $response = $this->oAuthRequest($url, 'DELETE', $parameters); if ($this->format === 'json' && $this->decode_json) { return json_decode($response); } return $response; } /** * Format and sign an OAuth / API request */ function oAuthRequest($url, $method, $parameters) { if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) { $url = "{$this->host}{$url}.{$this->format}"; } $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters); $request->sign_request($this->sha1_method, $this->consumer, $this->token); switch ($method) { case 'GET': return $this->http($request->to_url(), 'GET'); default: return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata()); } } /** * Make an HTTP request * * @return API results */ function http($url, $method, $postfields = NULL) { $this->http_info = array(); $ci = curl_init(); /* Curl settings */ curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent); curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout); curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:')); curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer); curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader')); curl_setopt($ci, CURLOPT_HEADER, FALSE); switch ($method) { case 'POST': curl_setopt($ci, CURLOPT_POST, TRUE); if (!empty($postfields)) { curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields); } break; case 'DELETE': curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE'); if (!empty($postfields)) { $url = "{$url}?{$postfields}"; } } curl_setopt($ci, CURLOPT_URL, $url); $response = curl_exec($ci); $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); $this->url = $url; curl_close ($ci); return $response; } /** * Get the header info to store. */ function getHeader($ch, $header) { $i = strpos($header, ':'); if (!empty($i)) { $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); $value = trim(substr($header, $i + 2)); $this->http_header[$key] = $value; } return strlen($header); } } ?>