You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
396 lines
12 KiB
396 lines
12 KiB
<?php
|
|
/*********************************************************************
|
|
class.variable.php
|
|
|
|
Variable replacer
|
|
|
|
Used to parse, resolve and replace variables.
|
|
|
|
Peter Rotich <peter@osticket.com>
|
|
Copyright (c) 2006-2013 osTicket
|
|
http://www.osticket.com
|
|
|
|
Released under the GNU General Public License WITHOUT ANY WARRANTY.
|
|
See LICENSE.TXT for details.
|
|
|
|
vim: expandtab sw=4 ts=4 sts=4:
|
|
**********************************************************************/
|
|
|
|
class VariableReplacer {
|
|
|
|
var $start_delim;
|
|
var $end_delim;
|
|
|
|
var $objects = array();
|
|
var $variables = array();
|
|
var $extras = array();
|
|
|
|
var $errors;
|
|
|
|
function __construct($start_delim='(?:%{|%%7B)', $end_delim='(?:}|%7D)') {
|
|
|
|
$this->start_delim = $start_delim;
|
|
$this->end_delim = $end_delim;
|
|
}
|
|
|
|
function setError($error) {
|
|
$this->errors[] = $error;
|
|
}
|
|
|
|
function getErrors() {
|
|
return $this->errors;
|
|
}
|
|
|
|
function getObj($tag) {
|
|
return @$this->objects[$tag];
|
|
}
|
|
|
|
function assign($var, $val='') {
|
|
|
|
if($val && is_object($val)) {
|
|
$this->objects[$var] = $val;
|
|
} elseif($var && is_array($var)) {
|
|
foreach($var as $k => $v)
|
|
$this->assign($k, $v);
|
|
} elseif($var) {
|
|
$this->variables[$var] = $val;
|
|
}
|
|
}
|
|
|
|
function getVar($obj, $var) {
|
|
|
|
if (!$obj)
|
|
return "";
|
|
|
|
// Order or resolving %{... .tag.remainder}
|
|
// 1. $obj[$tag]
|
|
// 2. $obj->tag
|
|
// 3. $obj->getVar(tag)
|
|
// 4. $obj->getTag()
|
|
@list($tag, $remainder) = explode('.', $var ?: '', 2);
|
|
$tag = mb_strtolower($tag);
|
|
$rv = null;
|
|
|
|
if (!is_object($obj)) {
|
|
if ($tag && is_array($obj) && array_key_exists($tag, $obj))
|
|
$rv = $obj[$tag];
|
|
else
|
|
// Not able to continue the lookup
|
|
return '';
|
|
}
|
|
else {
|
|
if (!$var) {
|
|
if (method_exists($obj, 'asVar'))
|
|
return call_user_func(array($obj, 'asVar'), $this);
|
|
elseif (method_exists($obj, '__toString'))
|
|
return (string) $obj;
|
|
}
|
|
if (method_exists($obj, 'getVar')) {
|
|
$rv = $obj->getVar($tag, $this);
|
|
}
|
|
if (!isset($rv) && property_exists($obj, $tag)) {
|
|
$rv = $obj->{$tag};
|
|
}
|
|
if (!isset($rv) && is_callable(array($obj, 'get'.ucfirst($tag)))) {
|
|
$rv = call_user_func(array($obj, 'get'.ucfirst($tag)));
|
|
}
|
|
}
|
|
|
|
// Recurse with $rv
|
|
if (is_object($rv) || $remainder)
|
|
return $this->getVar($rv, $remainder);
|
|
|
|
return $rv;
|
|
}
|
|
|
|
function replaceVars($input) {
|
|
|
|
// Preserve existing extras
|
|
if ($input instanceof TextWithExtras)
|
|
$this->extras = $input->extras;
|
|
|
|
if($input && is_array($input))
|
|
return array_map(array($this, 'replaceVars'), $input);
|
|
|
|
if(!($vars=$this->_parse($input)))
|
|
return $input;
|
|
|
|
$text = str_replace(array_keys($vars), array_values($vars), $input);
|
|
if ($this->extras) {
|
|
return new TextWithExtras($text, $this->extras);
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
function _resolveVar($var) {
|
|
|
|
//Variable already memoized?
|
|
if($var && @isset($this->variables[$var]))
|
|
return $this->variables[$var];
|
|
|
|
$parts = explode('.', $var, 2);
|
|
try {
|
|
if ($parts && ($obj=$this->getObj($parts[0])))
|
|
return $this->getVar($obj, $parts[1]);
|
|
}
|
|
catch (OOBContent $content) {
|
|
$type = $content->getType();
|
|
$existing = @$this->extras[$type] ?: array();
|
|
$this->extras[$type] = array_merge($existing, $content->getContent());
|
|
return $content->asVar();
|
|
}
|
|
|
|
if ($parts[0] && @isset($this->variables[$parts[0]])) { //root override
|
|
if (is_array($this->variables[$parts[0]])
|
|
&& isset($this->variables[$parts[0]][$parts[1]]))
|
|
return $this->variables[$parts[0]][$parts[1]];
|
|
|
|
return $this->variables[$parts[0]];
|
|
}
|
|
|
|
//Unknown object or variable - leavig it alone.
|
|
$this->setError(sprintf(__('Unknown object for "%s" tag'), $var));
|
|
return false;
|
|
}
|
|
|
|
function _parse($text) {
|
|
|
|
$input = $text;
|
|
$result = array();
|
|
if(!preg_match_all('/'.$this->start_delim.'([A-Za-z_][\w._]+)'.$this->end_delim.'/',
|
|
$input, $result))
|
|
return null;
|
|
|
|
$vars = array();
|
|
foreach($result[0] as $k => $v) {
|
|
if(isset($vars[$v])) continue;
|
|
// Format::html_balance() may urlencode() the contents here
|
|
$val=$this->_resolveVar(rawurldecode($result[1][$k]));
|
|
if($val!==false)
|
|
$vars[$v] = $val;
|
|
}
|
|
|
|
return $vars;
|
|
}
|
|
|
|
static function compileScope($scope, $recurse=5, $exclude=false) {
|
|
$items = array();
|
|
foreach ($scope as $name => $info) {
|
|
if ($exclude === $name)
|
|
continue;
|
|
if ($recurse && is_array($info) && isset($info['class'])) {
|
|
$items[$name] = $info['desc'];
|
|
foreach (static::compileScope($info['class']::getVarScope(), $recurse-1,
|
|
@$info['exclude'] ?: $name)
|
|
as $name2=>$desc) {
|
|
$items["{$name}.{$name2}"] = $desc;
|
|
}
|
|
}
|
|
if (!is_array($info)) {
|
|
$items[$name] = $info;
|
|
}
|
|
}
|
|
return $items;
|
|
}
|
|
|
|
static function compileFormScope($form) {
|
|
$items = array();
|
|
foreach ($form->getFields() as $f) {
|
|
if (!($name = $f->get('name')))
|
|
continue;
|
|
if (!$f->isStorable() || !$f->hasData())
|
|
continue;
|
|
|
|
$desc = $f->getLocal('label');
|
|
if (($class = $f->asVarType()) && class_exists($class)) {
|
|
$desc = array('desc' => $desc, 'class' => $class);
|
|
}
|
|
$items[$name] = $desc;
|
|
foreach (VariableReplacer::compileFieldScope($f) as $name2=>$desc) {
|
|
$items["$name.$name2"] = $desc;
|
|
}
|
|
}
|
|
return $items;
|
|
}
|
|
|
|
static function compileFieldScope($field, $recurse=2, $exclude=false) {
|
|
$items = array();
|
|
if (!$field->hasSubFields())
|
|
return $items;
|
|
|
|
foreach ($field->getSubFields() as $f) {
|
|
if (!($name = $f->get('name')))
|
|
continue;
|
|
if ($exclude === $name)
|
|
continue;
|
|
$items[$name] = $f->getLabel();
|
|
if ($recurse) {
|
|
foreach (static::compileFieldScope($f, $recurse-1, $name)
|
|
as $name2=>$desc) {
|
|
if (($class = $f->asVarType()) && class_exists($class)) {
|
|
$desc = array('desc' => $desc, 'class' => $class);
|
|
}
|
|
$items["$name.$name2"] = $desc;
|
|
}
|
|
}
|
|
}
|
|
return $items;
|
|
}
|
|
|
|
static function getContextForRoot($root) {
|
|
switch ($root) {
|
|
case 'cannedresponse':
|
|
$roots = array('ticket');
|
|
break;
|
|
|
|
case 'fa:send_email':
|
|
// FIXME: Make this pluggable
|
|
require_once INCLUDE_DIR . 'class.filter_action.php';
|
|
return FA_SendEmail::getVarScope();
|
|
|
|
default:
|
|
if ($info = Page::getContext($root)) {
|
|
$roots = $info;
|
|
break;
|
|
}
|
|
|
|
// Get the context for an email template
|
|
if ($tpl_info = EmailTemplateGroup::getTemplateDescription($root))
|
|
$roots = $tpl_info['context'];
|
|
}
|
|
|
|
if (!$roots)
|
|
return false;
|
|
|
|
$contextTypes = array(
|
|
'activity' => array('class' => 'ThreadActivity', 'desc' => __('Type of recent activity')),
|
|
'assignee' => array('class' => 'Staff', 'desc' => __('Assigned Agent / Team')),
|
|
'assigner' => array('class' => 'Staff', 'desc' => __('Agent performing the assignment')),
|
|
'comments' => __('Assign/transfer comments'),
|
|
'link' => __('Access link'),
|
|
'message' => array('class' => 'MessageThreadEntry', 'desc' => 'Message from the EndUser'),
|
|
'note' => array('class' => 'NoteThreadEntry', 'desc' => __('Internal Note')),
|
|
'poster' => array('class' => 'User', 'desc' => 'EndUser or Agent originating the message'),
|
|
// XXX: This could be EndUser -or- Staff object
|
|
'recipient' => array('class' => 'TicketUser', 'desc' => 'Message recipient'),
|
|
'response' => array('class' => 'ResponseThreadEntry', 'desc' => __('Outgoing response')),
|
|
'signature' => 'Selected staff or department signature',
|
|
'staff' => array('class' => 'Staff', 'desc' => 'Agent originating the activity'),
|
|
'ticket' => array('class' => 'Ticket', 'desc' => 'The ticket'),
|
|
'task' => array('class' => 'Task', 'desc' => 'The task'),
|
|
'user' => array('class' => 'User', 'desc' => __('Message recipient')),
|
|
);
|
|
$context = array();
|
|
foreach ($roots as $C=>$desc) {
|
|
// $desc may be either the root or the description array
|
|
if (is_array($desc))
|
|
$context[$C] = $desc;
|
|
else
|
|
$context[$desc] = $contextTypes[$desc];
|
|
}
|
|
$global = osTicket::getVarScope();
|
|
return self::compileScope($context + $global);
|
|
}
|
|
}
|
|
|
|
class PlaceholderList
|
|
/* implements TemplateVariable */ {
|
|
var $items;
|
|
|
|
function __construct($items) {
|
|
$this->items = $items;
|
|
}
|
|
|
|
function asVar() {
|
|
$items = array();
|
|
foreach ($this->items as $I) {
|
|
if (method_exists($I, 'asVar')) {
|
|
$items[] = $I->asVar();
|
|
}
|
|
else {
|
|
$items[] = (string) $I;
|
|
}
|
|
}
|
|
return implode(',', $items);
|
|
}
|
|
|
|
function getVar($tag) {
|
|
$items = array();
|
|
foreach ($this->items as $I) {
|
|
if (is_object($I) && method_exists($I, 'get'.ucfirst($tag))) {
|
|
$items[] = call_user_func(array($I, 'get'.ucfirst($tag)));
|
|
}
|
|
elseif (method_exists($I, 'getVar')) {
|
|
$items[] = $I->getVar($tag);
|
|
}
|
|
}
|
|
if (count($items) == 1) {
|
|
return $items[0];
|
|
}
|
|
return new static(array_filter($items));
|
|
}
|
|
|
|
function __toString() {
|
|
return $this->asVar();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exception used in the variable replacement process to indicate non text
|
|
* content (such as attachments)
|
|
*/
|
|
class OOBContent extends Exception {
|
|
var $type;
|
|
var $content;
|
|
var $text;
|
|
|
|
const FILES = 'files';
|
|
|
|
function __construct($type, $content, $asVar='') {
|
|
$this->type = $type;
|
|
$this->content = $content;
|
|
$this->text = $asVar;
|
|
}
|
|
|
|
function getType() { return $this->type; }
|
|
function getContent() { return $this->content; }
|
|
function asVar() { return $this->text; }
|
|
}
|
|
|
|
/**
|
|
* Simple wrapper to represent a rendered or partially rendered template
|
|
* with extra content such as attachments
|
|
*/
|
|
class TextWithExtras {
|
|
var $text = '';
|
|
var $extras;
|
|
|
|
function __construct($text, array $extras) {
|
|
$this->setText($text);
|
|
$this->extras = $extras;
|
|
}
|
|
|
|
function setText($text) {
|
|
try {
|
|
$this->text = (string) $text;
|
|
}
|
|
catch (Exception $e) {
|
|
throw new InvalidArgumentException('String type is required', 0, $e);
|
|
}
|
|
}
|
|
|
|
function __toString() {
|
|
return $this->text;
|
|
}
|
|
|
|
function getAttachments() {
|
|
return $this->extras[OOBContent::FILES] ?: array();
|
|
}
|
|
}
|
|
|
|
interface TemplateVariable {
|
|
// function asVar(); — not absolutely required
|
|
// function getVar($name, $parser); — not absolutely required
|
|
static function getVarScope();
|
|
}
|
|
?>
|