375 lines
9.7 KiB
PHP
375 lines
9.7 KiB
PHP
|
<?php
|
||
|
namespace Civi\Token;
|
||
|
|
||
|
/**
|
||
|
* Class TokenRow
|
||
|
* @package Civi\Token
|
||
|
*
|
||
|
* A TokenRow is a helper providing simplified access to the
|
||
|
* TokenProcessor.
|
||
|
*
|
||
|
* A TokenRow combines two elements:
|
||
|
* - context: This is backend data provided by the controller.
|
||
|
* - tokens: This is frontend data that can be mail-merged.
|
||
|
*
|
||
|
* The context and tokens can be accessed using either methods
|
||
|
* or attributes. The methods are appropriate for updates
|
||
|
* (and generally accept a mix of arrays), and the attributes
|
||
|
* are appropriate for reads.
|
||
|
*
|
||
|
* To update the context or the tokens, use the methods.
|
||
|
* Note that the methods are fairly flexible about accepting
|
||
|
* single values or arrays. If given an array, the values
|
||
|
* will be merged recursively.
|
||
|
*
|
||
|
* @code
|
||
|
* $row
|
||
|
* ->context('contact_id', 123)
|
||
|
* ->context(array('contact_id' => 123))
|
||
|
* ->tokens('profile', array('viewUrl' => 'http://example.com'))
|
||
|
* ->tokens('profile', 'viewUrl, 'http://example.com');
|
||
|
*
|
||
|
* echo $row->context['contact_id'];
|
||
|
* echo $row->tokens['profile']['viewUrl'];
|
||
|
*
|
||
|
* $row->tokens('profile', array(
|
||
|
* 'viewUrl' => 'http://example.com/view/' . urlencode($row->context['contact_id'];
|
||
|
* ));
|
||
|
* @endcode
|
||
|
*/
|
||
|
class TokenRow {
|
||
|
|
||
|
/**
|
||
|
* @var TokenProcessor
|
||
|
*/
|
||
|
public $tokenProcessor;
|
||
|
|
||
|
public $tokenRow;
|
||
|
|
||
|
public $format;
|
||
|
|
||
|
/**
|
||
|
* @var array|\ArrayAccess
|
||
|
* List of token values.
|
||
|
* Ex: array('contact' => array('display_name' => 'Alice')).
|
||
|
*/
|
||
|
public $tokens;
|
||
|
|
||
|
/**
|
||
|
* @var array|\ArrayAccess
|
||
|
* List of context values.
|
||
|
* Ex: array('controller' => 'CRM_Foo_Bar').
|
||
|
*/
|
||
|
public $context;
|
||
|
|
||
|
public function __construct(TokenProcessor $tokenProcessor, $key) {
|
||
|
$this->tokenProcessor = $tokenProcessor;
|
||
|
$this->tokenRow = $key;
|
||
|
$this->format('text/plain'); // Set a default.
|
||
|
$this->context = new TokenRowContext($tokenProcessor, $key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $format
|
||
|
* @return TokenRow
|
||
|
*/
|
||
|
public function format($format) {
|
||
|
$this->format = $format;
|
||
|
$this->tokens = &$this->tokenProcessor->rowValues[$this->tokenRow][$format];
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the value of a context element.
|
||
|
*
|
||
|
* @param string|array $a
|
||
|
* @param mixed $b
|
||
|
* @return TokenRow
|
||
|
*/
|
||
|
public function context($a = NULL, $b = NULL) {
|
||
|
if (is_array($a)) {
|
||
|
\CRM_Utils_Array::extend($this->tokenProcessor->rowContexts[$this->tokenRow], $a);
|
||
|
}
|
||
|
elseif (is_array($b)) {
|
||
|
\CRM_Utils_Array::extend($this->tokenProcessor->rowContexts[$this->tokenRow][$a], $b);
|
||
|
}
|
||
|
else {
|
||
|
$this->tokenProcessor->rowContexts[$this->tokenRow][$a] = $b;
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the value of a token.
|
||
|
*
|
||
|
* @param string|array $a
|
||
|
* @param string|array $b
|
||
|
* @param mixed $c
|
||
|
* @return TokenRow
|
||
|
*/
|
||
|
public function tokens($a = NULL, $b = NULL, $c = NULL) {
|
||
|
if (is_array($a)) {
|
||
|
\CRM_Utils_Array::extend($this->tokens, $a);
|
||
|
}
|
||
|
elseif (is_array($b)) {
|
||
|
\CRM_Utils_Array::extend($this->tokens[$a], $b);
|
||
|
}
|
||
|
elseif (is_array($c)) {
|
||
|
\CRM_Utils_Array::extend($this->tokens[$a][$b], $c);
|
||
|
}
|
||
|
elseif ($c === NULL) {
|
||
|
$this->tokens[$a] = $b;
|
||
|
}
|
||
|
else {
|
||
|
$this->tokens[$a][$b] = $c;
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the value of a custom field token.
|
||
|
*
|
||
|
* @param string $entity
|
||
|
* @param int $customFieldID
|
||
|
* @param int $entityID
|
||
|
* @return TokenRow
|
||
|
*/
|
||
|
public function customToken($entity, $customFieldID, $entityID) {
|
||
|
$customFieldName = "custom_" . $customFieldID;
|
||
|
$fieldValue = civicrm_api3($entity, 'getvalue', array(
|
||
|
'return' => $customFieldName,
|
||
|
'id' => $entityID,
|
||
|
));
|
||
|
|
||
|
// format the raw custom field value into proper display value
|
||
|
if ($fieldValue) {
|
||
|
$fieldValue = \CRM_Core_BAO_CustomField::displayValue($fieldValue, $customFieldID);
|
||
|
}
|
||
|
|
||
|
return $this->tokens($entity, $customFieldName, $fieldValue);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the value of a token. Apply formatting based on DB schema.
|
||
|
*
|
||
|
* @param string $tokenEntity
|
||
|
* @param string $tokenField
|
||
|
* @param string $baoName
|
||
|
* @param array $baoField
|
||
|
* @param mixed $fieldValue
|
||
|
* @return TokenRow
|
||
|
* @throws \CRM_Core_Exception
|
||
|
*/
|
||
|
public function dbToken($tokenEntity, $tokenField, $baoName, $baoField, $fieldValue) {
|
||
|
if ($fieldValue === NULL || $fieldValue === '') {
|
||
|
return $this->tokens($tokenEntity, $tokenField, '');
|
||
|
}
|
||
|
|
||
|
$fields = $baoName::fields();
|
||
|
if (!empty($fields[$baoField]['pseudoconstant'])) {
|
||
|
$options = $baoName::buildOptions($baoField, 'get');
|
||
|
return $this->format('text/plain')->tokens($tokenEntity, $tokenField, $options[$fieldValue]);
|
||
|
}
|
||
|
|
||
|
switch ($fields[$baoField]['type']) {
|
||
|
case \CRM_Utils_Type::T_DATE + \CRM_Utils_Type::T_TIME:
|
||
|
return $this->format('text/plain')->tokens($tokenEntity, $tokenField, \CRM_Utils_Date::customFormat($fieldValue));
|
||
|
|
||
|
case \CRM_Utils_Type::T_MONEY:
|
||
|
// Is this something you should ever use? Seems like you need more context
|
||
|
// to know which currency to use.
|
||
|
return $this->format('text/plain')->tokens($tokenEntity, $tokenField, \CRM_Utils_Money::format($fieldValue));
|
||
|
|
||
|
case \CRM_Utils_Type::T_STRING:
|
||
|
case \CRM_Utils_Type::T_BOOLEAN:
|
||
|
case \CRM_Utils_Type::T_INT:
|
||
|
case \CRM_Utils_Type::T_TEXT:
|
||
|
return $this->format('text/plain')->tokens($tokenEntity, $tokenField, $fieldValue);
|
||
|
|
||
|
}
|
||
|
|
||
|
throw new \CRM_Core_Exception("Cannot format token for field '$baoField' in '$baoName'");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Auto-convert between different formats
|
||
|
*
|
||
|
* @param string $format
|
||
|
*
|
||
|
* @return TokenRow
|
||
|
*/
|
||
|
public function fill($format = NULL) {
|
||
|
if ($format === NULL) {
|
||
|
$format = $this->format;
|
||
|
}
|
||
|
|
||
|
if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/html'])) {
|
||
|
$this->tokenProcessor->rowValues[$this->tokenRow]['text/html'] = array();
|
||
|
}
|
||
|
if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'])) {
|
||
|
$this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'] = array();
|
||
|
}
|
||
|
|
||
|
$htmlTokens = &$this->tokenProcessor->rowValues[$this->tokenRow]['text/html'];
|
||
|
$textTokens = &$this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'];
|
||
|
|
||
|
switch ($format) {
|
||
|
case 'text/html':
|
||
|
// Plain => HTML.
|
||
|
foreach ($textTokens as $entity => $values) {
|
||
|
foreach ($values as $field => $value) {
|
||
|
if (!isset($htmlTokens[$entity][$field])) {
|
||
|
// CRM-18420 - Activity Details Field are enclosed within <p>,
|
||
|
// hence if $body_text is empty, htmlentities will lead to
|
||
|
// conversion of these tags resulting in raw HTML.
|
||
|
if ($entity == 'activity' && $field == 'details') {
|
||
|
$htmlTokens[$entity][$field] = $value;
|
||
|
}
|
||
|
else {
|
||
|
$htmlTokens[$entity][$field] = htmlentities($value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'text/plain':
|
||
|
// HTML => Plain.
|
||
|
foreach ($htmlTokens as $entity => $values) {
|
||
|
foreach ($values as $field => $value) {
|
||
|
if (!isset($textTokens[$entity][$field])) {
|
||
|
$textTokens[$entity][$field] = html_entity_decode(strip_tags($value));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new \RuntimeException("Invalid format");
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render a message.
|
||
|
*
|
||
|
* @param string $name
|
||
|
* The name previously registered with TokenProcessor::addMessage.
|
||
|
* @return string
|
||
|
* Fully rendered message, with tokens merged.
|
||
|
*/
|
||
|
public function render($name) {
|
||
|
return $this->tokenProcessor->render($name, $this);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class TokenRowContext
|
||
|
* @package Civi\Token
|
||
|
*
|
||
|
* Combine the row-context and general-context into a single array-like facade.
|
||
|
*/
|
||
|
class TokenRowContext implements \ArrayAccess, \IteratorAggregate, \Countable {
|
||
|
|
||
|
/**
|
||
|
* @var TokenProcessor
|
||
|
*/
|
||
|
protected $tokenProcessor;
|
||
|
|
||
|
protected $tokenRow;
|
||
|
|
||
|
/**
|
||
|
* Class constructor.
|
||
|
*
|
||
|
* @param array $tokenProcessor
|
||
|
* @param array $tokenRow
|
||
|
*/
|
||
|
public function __construct($tokenProcessor, $tokenRow) {
|
||
|
$this->tokenProcessor = $tokenProcessor;
|
||
|
$this->tokenRow = $tokenRow;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Does offset exist.
|
||
|
*
|
||
|
* @param mixed $offset
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public function offsetExists($offset) {
|
||
|
return
|
||
|
isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset])
|
||
|
|| isset($this->tokenProcessor->context[$offset]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get offset.
|
||
|
*
|
||
|
* @param string $offset
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function &offsetGet($offset) {
|
||
|
if (isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset])) {
|
||
|
return $this->tokenProcessor->rowContexts[$this->tokenRow][$offset];
|
||
|
}
|
||
|
if (isset($this->tokenProcessor->context[$offset])) {
|
||
|
return $this->tokenProcessor->context[$offset];
|
||
|
}
|
||
|
$val = NULL;
|
||
|
return $val;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set offset.
|
||
|
*
|
||
|
* @param string $offset
|
||
|
* @param mixed $value
|
||
|
*/
|
||
|
public function offsetSet($offset, $value) {
|
||
|
$this->tokenProcessor->rowContexts[$this->tokenRow][$offset] = $value;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unset offset.
|
||
|
*
|
||
|
* @param mixed $offset
|
||
|
*/
|
||
|
public function offsetUnset($offset) {
|
||
|
unset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get iterator.
|
||
|
*
|
||
|
* @return \ArrayIterator
|
||
|
*/
|
||
|
public function getIterator() {
|
||
|
return new \ArrayIterator($this->createMergedArray());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Count.
|
||
|
*
|
||
|
* @return int
|
||
|
*/
|
||
|
public function count() {
|
||
|
return count($this->createMergedArray());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create merged array.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
protected function createMergedArray() {
|
||
|
return array_merge(
|
||
|
$this->tokenProcessor->rowContexts[$this->tokenRow],
|
||
|
$this->tokenProcessor->context
|
||
|
);
|
||
|
}
|
||
|
|
||
|
}
|