<?php
class
H2o_Context
implements
ArrayAccess {
public
$safeClass
=
array
(
'stdClass'
,
'BlockContext'
);
public
$scopes
;
public
$options
;
public
$autoescape
= true;
private
$arrayMethods
=
array
(
'first'
=> 0,
'last'
=> 1,
'length'
=> 2,
'size'
=> 3);
static
$lookupTable
=
array
();
function
__construct(
$context
=
array
(),
$options
=
array
()){
if
(
is_object
(
$context
))
$context
= get_object_vars(
$context
);
$this
->scopes =
array
(
$context
);
if
(isset(
$options
[
'safeClass'
]))
$this
->safeClass =
array_merge
(
$this
->safeClass,
$options
[
'safeClass'
]);
if
(isset(
$options
[
'autoescape'
]))
$this
->autoescape =
$options
[
'autoescape'
];
$this
->options =
$options
;
}
function
push(
$layer
=
array
()){
return
array_unshift
(
$this
->scopes,
$layer
);
}
function
pop() {
if
(!isset(
$this
->scopes[1]))
throw
new
Exception(
'cannnot pop from empty stack'
);
return
array_shift
(
$this
->scopes);
}
function
offsetExists(
$offset
) {
foreach
(
$this
->scopes
as
$layer
) {
if
(isset(
$layer
[
$offset
]))
return
true;
}
return
false;
}
function
offsetGet(
$key
) {
foreach
(
$this
->scopes
as
$layer
) {
if
(isset(
$layer
[
$key
]))
return
$layer
[
$key
];
}
return
;
}
function
offsetSet(
$key
,
$value
) {
if
(
strpos
(
$key
,
'.'
) > -1)
throw
new
Exception(
'cannot set non local variable'
);
return
$this
->scopes[0][
$key
] =
$value
;
}
function
offsetUnset(
$key
) {
foreach
(
$this
->scopes
as
$layer
) {
if
(isset(
$layer
[
$key
])) unset(
$layer
[
$key
]);
}
}
function
extend(
$context
) {
$this
->scopes[0] =
array_merge
(
$this
->scopes[0],
$context
);
}
function
set(
$key
,
$value
) {
return
$this
->offsetSet(
$key
,
$value
);
}
function
get(
$key
) {
return
$this
->offsetGet(
$key
);
}
function
isDefined(
$key
) {
return
$this
->offsetExists(
$key
);
}
function
resolve(
$var
) {
#
if
$var
is
array
- it contains filters to apply
$filters
=
array
();
if
(
is_array
(
$var
) ) {
$name
=
array_shift
(
$var
);
$filters
= isset(
$var
[
'filters'
])?
$var
[
'filters'
] :
array
();
}
else
$name
=
$var
;
$result
= null;
# Lookup basic types, null, boolean, numeric
and
string
# Variable starts with : (:users.name) to short-circuit lookup
if
(
$name
[0] ===
':'
) {
$object
=
$this
->getVariable(
substr
(
$name
, 1));
if
(!
is_null
(
$object
))
$result
=
$object
;
}
else
{
if
(
$name
===
'true'
) {
$result
= true;
}
elseif
(
$name
===
'false'
) {
$result
= false;
}
elseif
(preg_match(
'/^-?\d+(\.\d+)?$/'
,
$name
,
$matches
)) {
$result
= isset(
$matches
[1])?
floatval
(
$name
) :
intval
(
$name
);
}
elseif
(preg_match(
'/^"([^"\\\\]*(?:\\.[^"\\\\]*)*)"|'
.
'\'([^\'\\\\]*(?:\\.[^\'\\\\]*)*)\'$/'
,
$name
)) {
$result
=
stripcslashes
(
substr
(
$name
, 1, -1));
}
}
if
(!
empty
(self::
$lookupTable
) &&
$result
== Null) {
$result
=
$this
->externalLookup(
$name
);
}
$result
=
$this
->applyFilters(
$result
,
$filters
);
return
$result
;
}
function
getVariable(
$name
) {
# Local variables. this gives
as
a bit of performance improvement
if
(!
strpos
(
$name
,
'.'
))
return
$this
[
$name
];
# Prepare
for
Big lookup
$parts
=
explode
(
'.'
,
$name
);
$object
=
$this
[
array_shift
(
$parts
)];
# Lookup context
foreach
(
$parts
as
$part
) {
if
(
is_array
(
$object
)
or
$object
instanceof
ArrayAccess) {
if
(isset(
$object
[
$part
])) {
$object
=
$object
[
$part
];
}
elseif
(
$part
===
'first'
) {
$object
= isset(
$object
[0])?
$object
[0]:null;
}
elseif
(
$part
===
'last'
) {
$last
=
count
(
$object
)-1;
$object
= isset(
$object
[
$last
])?
$object
[
$last
]:null;
}
elseif
(
$part
===
'size'
or
$part
===
'length'
) {
return
count
(
$object
);
}
else
{
return
null;
}
}
elseif
(
is_object
(
$object
)) {
if
(isset(
$object
->
$part
))
$object
=
$object
->
$part
;
elseif
(
is_callable
(
array
(
$object
,
$part
))) {
$methodAllowed
= in_array(get_class(
$object
),
$this
->safeClass) ||
(isset(
$object
->h2o_safe) && (
$object
->h2o_safe === true || in_array(
$part
,
$object
->h2o_safe)
)
);
$object
=
$methodAllowed
?
$object
->
$part
() : null;
}
else
return
null;
}
else
return
null;
}
return
$object
;
}
function
applyFilters(
$object
,
$filters
) {
foreach
(
$filters
as
$filter
) {
$name
=
substr
(
array_shift
(
$filter
), 1);
$args
=
$filter
;
if
(isset(h2o::
$filters
[
$name
])) {
foreach
(
$args
as
$i
=>
$argument
) {
# name args
if
(
is_array
(
$argument
)) {
foreach
(
$argument
as
$n
=>
$arg
) {
$args
[
$i
][
$n
] =
$this
->resolve(
$arg
);
}
}
else
{
# resolve argument values
$args
[
$i
] =
$this
->resolve(
$argument
);
}
}
array_unshift
(
$args
,
$object
);
$object
= call_user_func_array(h2o::
$filters
[
$name
],
$args
);
}
}
return
$object
;
}
function
escape(
$value
,
$var
) {
$safe
= false;
$filters
= (
is_array
(
$var
) && isset(
$var
[
'filters'
]))?
$var
[
'filters'
] :
array
();
foreach
(
$filters
as
$filter
) {
$name
=
substr
(
array_shift
(
$filter
), 1);
$safe
= !
$safe
&& (
$name
===
'safe'
);
$escaped
=
$name
===
'escape'
;
}
$should_escape
=
$this
->autoescape || isset(
$escaped
) &&
$escaped
;
if
( (
$should_escape
&& !
$safe
)) {
$value
= htmlspecialchars(
$value
);
}
return
$value
;
}
function
externalLookup(
$name
) {
if
(!
empty
(self::
$lookupTable
)) {
foreach
(self::
$lookupTable
as
$lookup
) {
$tmp
= call_user_func_array(
$lookup
,
array
(
$name
,
$this
));
if
(
$tmp
!== null)
return
$tmp
;
}
}
return
null;
}
}
class
BlockContext {
var
$h2o_safe
=
array
(
'name'
,
'depth'
,
'super'
);
var
$block
,
$index
;
private
$context
;
function
__construct(
$block
,
$context
,
$index
) {
$this
->block =&
$block
;
$this
->context =
$context
;
$this
->index =
$index
;
}
function
name() {
return
$this
->block->name;
}
function
depth() {
return
$this
->index;
}
function
super() {
$stream
=
new
StreamWriter;
$this
->block->parent->render(
$this
->context,
$stream
,
$this
->index+1);
return
$stream
->close();
}
function
__toString() {
return
"[BlockContext : {$this->block->name}, {$this->block->filename}]"
;
}
}
?>