cyrilleinvalides/choupas/www/admin/app/cache/debug/libs/libs.php

10692 lines
243 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
interface PhpizeInterface {
public function __toPhp( $compact = false );
}
function html ($a, $encoding = 'UTF-8', $flag = null ) {
if ( is_null($flag) )
$flag = ENT_COMPAT;
return htmlentities($a, $flag, $encoding);
}
function phpize( $data, $file = null, $compact = false ) {
if ( is_array($data) ) {
$str = $compact ? '' : "\n";
$php = "array(";
foreach ( $data as $k => $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, "<?php\n\nreturn " . $php . "\n\n?>");
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, "<?php\n" . $instance->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, "<?php\n" . $content . "\n?>");
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, "<?php\n" . $content . "\n?>");
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 <naitik@facebook.com>
*/
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 <naitik@facebook.com>
*/
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);
}
}
?>