10692 lines
243 KiB
PHP
Executable File
10692 lines
243 KiB
PHP
Executable File
<?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);
|
||
}
|
||
}
|
||
|
||
?>
|