First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,188 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Token;
use Civi\ActionSchedule\Event\MailingQueryEvent;
use Civi\Token\Event\TokenRegisterEvent;
use Civi\Token\Event\TokenValueEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class AbstractTokenSubscriber
* @package Civi\Token
*
* AbstractTokenSubscriber is a base class which may be extended to
* implement tokens in a somewhat more concise fashion.
*
* To implement a new token handler based on this:
* 1. Create a subclass.
* 2. Override the constructor and set values for $entity and $tokenNames.
* 3. Implement the evaluateToken() method.
* 4. Optionally, override others:
* + checkActive()
* + prefetch()
* + alterActionScheduleMailing()
* 5. Register the new class with the event-dispatcher.
*
* Note: There's no obligation to use this base class. You could implement
* your own class anew -- just subscribe the proper events.
*/
abstract class AbstractTokenSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return array(
Events::TOKEN_REGISTER => 'registerTokens',
Events::TOKEN_EVALUATE => 'evaluateTokens',
\Civi\ActionSchedule\Events::MAILING_QUERY => 'alterActionScheduleQuery',
);
}
/**
* @var string
* Ex: 'contact' or profile' or 'employer'
*/
public $entity;
/**
* @var array
* Ex: array('viewUrl', 'editUrl').
*/
public $tokenNames;
/**
* @param $entity
* @param array $tokenNames
* Array(string $fieldName => string $label).
*/
public function __construct($entity, $tokenNames = array()) {
$this->entity = $entity;
$this->tokenNames = $tokenNames;
}
/**
* Determine whether this token-handler should be used with
* the given processor.
*
* To short-circuit token-processing in irrelevant contexts,
* override this.
*
* @param \Civi\Token\TokenProcessor $processor
* @return bool
*/
public function checkActive(\Civi\Token\TokenProcessor $processor) {
return TRUE;
}
/**
* Register the declared tokens.
*
* @param TokenRegisterEvent $e
* The registration event. Add new tokens using register().
*/
public function registerTokens(TokenRegisterEvent $e) {
if (!$this->checkActive($e->getTokenProcessor())) {
return;
}
foreach ($this->tokenNames as $name => $label) {
$e->register(array(
'entity' => $this->entity,
'field' => $name,
'label' => $label,
));
}
}
/**
* Alter the query which prepopulates mailing data
* for scheduled reminders.
*
* This is method is not always appropriate, but if you're specifically
* focused on scheduled reminders, it can be convenient.
*
* @param MailingQueryEvent $e
* The pending query which may be modified. See discussion on
* MailingQueryEvent::$query.
*/
public function alterActionScheduleQuery(MailingQueryEvent $e) {
}
/**
* Populate the token data.
*
* @param TokenValueEvent $e
* The event, which includes a list of rows and tokens.
*/
public function evaluateTokens(TokenValueEvent $e) {
if (!$this->checkActive($e->getTokenProcessor())) {
return;
}
$messageTokens = $e->getTokenProcessor()->getMessageTokens();
if (!isset($messageTokens[$this->entity])) {
return;
}
$activeTokens = array_intersect($messageTokens[$this->entity], array_keys($this->tokenNames));
$prefetch = $this->prefetch($e);
foreach ($e->getRows() as $row) {
foreach ((array) $activeTokens as $field) {
$this->evaluateToken($row, $this->entity, $field, $prefetch);
}
}
}
/**
* To perform a bulk lookup before rendering tokens, override this
* function and return the prefetched data.
*
* @param \Civi\Token\Event\TokenValueEvent $e
*
* @return mixed
*/
public function prefetch(TokenValueEvent $e) {
return NULL;
}
/**
* Evaluate the content of a single token.
*
* @param TokenRow $row
* The record for which we want token values.
* @param string $entity
* The name of the token entity.
* @param string $field
* The name of the token field.
* @param mixed $prefetch
* Any data that was returned by the prefetch().
* @return mixed
*/
public abstract function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL);
}

View file

@ -0,0 +1,25 @@
<?php
namespace Civi\Token\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Class TokenListEvent
* @package Civi\Token\Event
*/
class TokenEvent extends Event {
protected $tokenProcessor;
public function __construct($tokenProcessor) {
$this->tokenProcessor = $tokenProcessor;
}
/**
* @return \Civi\Token\TokenProcessor
*/
public function getTokenProcessor() {
return $this->tokenProcessor;
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Civi\Token\Event;
/**
* Class TokenRegisterEvent
* @package Civi\Token\Event
*
* The TokenRegisterEvent is fired when constructing a list of available
* tokens. Listeners may register by specifying the entity/field/label for the token.
*
* @code
* $ev->entity('profile')
* ->register('viewUrl', ts('Default Profile URL (View Mode)')
* ->register('editUrl', ts('Default Profile URL (Edit Mode)');
* $ev->register(array(
* 'entity' => 'profile',
* 'field' => 'viewUrl',
* 'label' => ts('Default Profile URL (View Mode)'),
* ));
* @endcode
*/
class TokenRegisterEvent extends TokenEvent {
/**
* Default values to put in new registrations.
*
* @var array
*/
protected $defaults;
public function __construct($tokenProcessor, $defaults) {
parent::__construct($tokenProcessor);
$this->defaults = $defaults;
}
/**
* Set the default entity name.
*
* @param string $entity
* @return TokenRegisterEvent
*/
public function entity($entity) {
$defaults = $this->defaults;
$defaults['entity'] = $entity;
return new TokenRegisterEvent($this->tokenProcessor, $defaults);
}
/**
* Register a new token.
*
* @param array|string $paramsOrField
* @param NULL|string $label
* @return TokenRegisterEvent
*/
public function register($paramsOrField, $label = NULL) {
if (is_array($paramsOrField)) {
$params = $paramsOrField;
}
else {
$params = array(
'field' => $paramsOrField,
'label' => $label,
);
}
$params = array_merge($this->defaults, $params);
$this->tokenProcessor->addToken($params);
return $this;
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Civi\Token\Event;
/**
* Class TokenRenderEvent
* @package Civi\Token\Event
*
* A TokenRenderEvent is fired after the TokenProcessor has rendered
* a message.
*
* The render event may be used for post-processing the text, but
* it's very difficult to do substantive work in a secure, robust
* way within this event. The event primarily exists to facilitate
* a transition of some legacy code.
*/
class TokenRenderEvent extends TokenEvent {
/**
* @var array|\ArrayAccess
*/
public $context;
/**
* @var array|\ArrayAccess
*
* The original message template.
*/
public $message;
/**
* @var \Civi\Token\TokenRow
*
* The record for which we're generating date
*/
public $row;
/**
* @var string
*
* The rendered string, with tokens replaced.
*/
public $string;
}

View file

@ -0,0 +1,45 @@
<?php
namespace Civi\Token\Event;
/**
* Class TokenValueEvent
* @package Civi\Token\Event
*
* A TokenValueEvent is fired to convert raw query data into mergeable
* tokens. For example:
*
* @code
* $event = new TokenValueEvent($myContext, 'text/html', array(
* array('contact_id' => 123),
* array('contact_id' => 456),
* ));
*
* // Compute tokens one row at a time.
* foreach ($event->getRows() as $row) {
* $row->setTokens('contact', array(
* 'profileUrl' => CRM_Utils_System::url('civicrm/profile/view', 'reset=1&gid=456&id=' . $row['contact_id']'),
* ));
* }
*
* // Compute tokens with a bulk lookup.
* $ids = implode(',', array_filter(CRM_Utils_Array::collect('contact_id', $event->getRows()), 'is_numeric'));
* $dao = CRM_Core_DAO::executeQuery("SELECT contact_id, foo, bar FROM foobar WHERE contact_id in ($ids)");
* while ($dao->fetch) {
* $row->setTokens('oddball', array(
* 'foo' => $dao->foo,
* 'bar' => $dao->bar,
* ));
* }
* @encode
*
*/
class TokenValueEvent extends TokenEvent {
/**
* @return \Traversable<TokenRow>
*/
public function getRows() {
return $this->tokenProcessor->getRows();
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Civi\Token;
class Events {
/**
* Create a list of supported tokens.
*
* @see \Civi\Token\Event\TokenRegisterEvent
*/
const TOKEN_REGISTER = 'civi.token.list';
/**
* Create a list of supported tokens.
*
* @see \Civi\Token\Event\TokenValueEvent
*/
const TOKEN_EVALUATE = 'civi.token.eval';
/**
* Perform post-processing on a rendered message.
*
* WARNING: It is difficult to develop robust,
* secure code using this stage. However, we need
* to support it during a transitional period
* while the token logic is reorganized.
*
* @see \Civi\Token\Event\TokenRenderEvent
*/
const TOKEN_RENDER = 'civi.token.render';
}

View file

@ -0,0 +1,133 @@
<?php
namespace Civi\Token;
use Civi\Token\Event\TokenRenderEvent;
use Civi\Token\Event\TokenValueEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class TokenCompatSubscriber
* @package Civi\Token
*
* This class provides a compatibility layer for using CRM_Utils_Token
* helpers within TokenProcessor.
*
* THIS IS NOT A GOOD EXAMPLE TO EMULATE. The class exists to two
* bridge two different designs. CRM_Utils_Token has some
* undesirable elements (like iterative token substitution).
* However, if you're refactor CRM_Utils_Token or improve the
* bridge, then it makes sense to update this class.
*/
class TokenCompatSubscriber implements EventSubscriberInterface {
/**
* @inheritDoc
*/
public static function getSubscribedEvents() {
return array(
Events::TOKEN_EVALUATE => 'onEvaluate',
Events::TOKEN_RENDER => 'onRender',
);
}
/**
* Load token data.
*
* @param TokenValueEvent $e
* @throws TokenException
*/
public function onEvaluate(TokenValueEvent $e) {
// For reasons unknown, replaceHookTokens requires a pre-computed list of
// hook *categories* (aka entities aka namespaces). We'll cache
// this in the TokenProcessor's context.
$hookTokens = array();
\CRM_Utils_Hook::tokens($hookTokens);
$categories = array_keys($hookTokens);
$e->getTokenProcessor()->context['hookTokenCategories'] = $categories;
$messageTokens = $e->getTokenProcessor()->getMessageTokens();
foreach ($e->getRows() as $row) {
/** @var int $contactId */
$contactId = $row->context['contactId'];
if (empty($row->context['contact'])) {
$params = array(
array('contact_id', '=', $contactId, 0, 0),
);
list($contact, $_) = \CRM_Contact_BAO_Query::apiQuery($params);
$contact = reset($contact); //CRM-4524
if (!$contact || is_a($contact, 'CRM_Core_Error')) {
// FIXME: Need to differentiate errors which kill the batch vs the individual row.
throw new TokenException("Failed to generate token data. Invalid contact ID: " . $row->context['contactId']);
}
//update value of custom field token
if (!empty($messageTokens['contact'])) {
foreach ($messageTokens['contact'] as $token) {
if (\CRM_Core_BAO_CustomField::getKeyID($token)) {
$contact[$token] = civicrm_api3('Contact', 'getvalue', array(
'return' => $token,
'id' => $contactId,
));
}
}
}
}
else {
$contact = $row->context['contact'];
}
if (!empty($row->context['tmpTokenParams'])) {
// merge activity tokens with contact array
// this is pretty weird.
$contact = array_merge($contact, $row->context['tmpTokenParams']);
}
$contactArray = !is_array($contactId) ? array($contactId => $contact) : $contact;
// Note: This is a small contract change from the past; data should be missing
// less randomly.
\CRM_Utils_Hook::tokenValues($contactArray,
(array) $contactId,
empty($row->context['mailingJob']) ? NULL : $row->context['mailingJob']->id,
$messageTokens,
$row->context['controller']
);
// merge the custom tokens in the $contact array
if (!empty($contactArray[$contactId])) {
$contact = array_merge($contact, $contactArray[$contactId]);
}
$row->context('contact', $contact);
}
}
/**
* Apply the various CRM_Utils_Token helpers.
*
* @param TokenRenderEvent $e
*/
public function onRender(TokenRenderEvent $e) {
$isHtml = ($e->message['format'] == 'text/html');
$useSmarty = !empty($e->context['smarty']);
$domain = \CRM_Core_BAO_Domain::getDomain();
$e->string = \CRM_Utils_Token::replaceDomainTokens($e->string, $domain, $isHtml, $e->message['tokens'], $useSmarty);
if (!empty($e->context['contact'])) {
$e->string = \CRM_Utils_Token::replaceContactTokens($e->string, $e->context['contact'], $isHtml, $e->message['tokens'], TRUE, $useSmarty);
// FIXME: This may depend on $contact being merged with hook values.
$e->string = \CRM_Utils_Token::replaceHookTokens($e->string, $e->context['contact'], $e->context['hookTokenCategories'], $isHtml, $useSmarty);
\CRM_Utils_Token::replaceGreetingTokens($e->string, $e->context['contact'], $e->context['contact']['contact_id'], NULL, $useSmarty);
}
if ($useSmarty) {
$smarty = \CRM_Core_Smarty::singleton();
$e->string = $smarty->fetch("string:" . $e->string);
}
}
}

View file

@ -0,0 +1,6 @@
<?php
namespace Civi\Token;
class TokenException extends \CRM_Core_Exception {
}

View file

@ -0,0 +1,251 @@
<?php
namespace Civi\Token;
use Civi\Token\Event\TokenRegisterEvent;
use Civi\Token\Event\TokenRenderEvent;
use Civi\Token\Event\TokenValueEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Traversable;
class TokenProcessor {
/**
* @var array
* Description of the context in which the tokens are being processed.
* Ex: Array('class'=>'CRM_Core_BAO_ActionSchedule', 'schedule' => $dao, 'mapping' => $dao).
* Ex: Array('class'=>'CRM_Mailing_BAO_MailingJob', 'mailing' => $dao).
*
* For lack of a better place, here's a list of known/intended context values:
*
* - controller: string, the class which is managing the mail-merge.
* - smarty: bool, whether to enable smarty support.
* - contactId: int, the main person/org discussed in the message.
* - contact: array, the main person/org discussed in the message.
* (Optional for performance tweaking; if omitted, will load
* automatically from contactId.)
* - actionSchedule: DAO, the rule which triggered the mailing
* [for CRM_Core_BAO_ActionScheduler].
*/
public $context;
/**
* @var EventDispatcherInterface
*/
protected $dispatcher;
/**
* @var array
* Each message is an array with keys:
* - string: Unprocessed message (eg "Hello, {display_name}.").
* - format: Media type (eg "text/plain").
* - tokens: List of tokens which are actually used in this message.
*/
protected $messages;
/**
* DO NOT access field this directly. Use TokenRow. This is
* marked as public only to benefit TokenRow.
*
* @var array
* Array(int $pos => array $keyValues);
*/
public $rowContexts;
/**
* DO NOT access field this directly. Use TokenRow. This is
* marked as public only to benefit TokenRow.
*
* @var array
* Ex: $rowValues[$rowPos][$format][$entity][$field] = 'something';
* Ex: $rowValues[3]['text/plain']['contact']['display_name'] = 'something';
*/
public $rowValues;
/**
* A list of available tokens
* @var array
* Array(string $dottedName => array('entity'=>string, 'field'=>string, 'label'=>string)).
*/
protected $tokens = NULL;
protected $next = 0;
/**
* @param EventDispatcherInterface $dispatcher
* @param array $context
*/
public function __construct($dispatcher, $context) {
$this->dispatcher = $dispatcher;
$this->context = $context;
}
/**
* Register a string for which we'll need to merge in tokens.
*
* @param string $name
* Ex: 'subject', 'body_html'.
* @param string $value
* Ex: '<p>Hello {contact.name}</p>'.
* @param string $format
* Ex: 'text/html'.
* @return TokenProcessor
*/
public function addMessage($name, $value, $format) {
$this->messages[$name] = array(
'string' => $value,
'format' => $format,
'tokens' => \CRM_Utils_Token::getTokens($value),
);
return $this;
}
/**
* Add a row of data.
*
* @return TokenRow
*/
public function addRow() {
$key = $this->next++;
$this->rowContexts[$key] = array();
$this->rowValues[$key] = array(
'text/plain' => array(),
'text/html' => array(),
);
return new TokenRow($this, $key);
}
/**
* @param array $params
* Array with keys:
* - entity: string, e.g. "profile".
* - field: string, e.g. "viewUrl".
* - label: string, e.g. "Default Profile URL (View Mode)".
* @return TokenProcessor
*/
public function addToken($params) {
$key = $params['entity'] . '.' . $params['field'];
$this->tokens[$key] = $params;
return $this;
}
/**
* @param string $name
* @return array
* Keys:
* - string: Unprocessed message (eg "Hello, {display_name}.").
* - format: Media type (eg "text/plain").
*/
public function getMessage($name) {
return $this->messages[$name];
}
/**
* Get a list of all tokens used in registered messages.
*
* @return array
*/
public function getMessageTokens() {
$tokens = array();
foreach ($this->messages as $message) {
$tokens = \CRM_Utils_Array::crmArrayMerge($tokens, $message['tokens']);
}
foreach (array_keys($tokens) as $e) {
$tokens[$e] = array_unique($tokens[$e]);
sort($tokens[$e]);
}
return $tokens;
}
public function getRow($key) {
return new TokenRow($this, $key);
}
/**
* @return \Traversable<TokenRow>
*/
public function getRows() {
return new TokenRowIterator($this, new \ArrayIterator($this->rowContexts));
}
/**
* Get the list of available tokens.
*
* @return array
* Ex: $tokens['event'] = array('location', 'start_date', 'end_date').
*/
public function getTokens() {
if ($this->tokens === NULL) {
$this->tokens = array();
$event = new TokenRegisterEvent($this, array('entity' => 'undefined'));
$this->dispatcher->dispatch(Events::TOKEN_REGISTER, $event);
}
return $this->tokens;
}
/**
* Compute and store token values.
*/
public function evaluate() {
$event = new TokenValueEvent($this);
$this->dispatcher->dispatch(Events::TOKEN_EVALUATE, $event);
return $this;
}
/**
* Render a message.
*
* @param string $name
* The name previously registered with addMessage().
* @param TokenRow|int $row
* The object or ID for the row previously registered with addRow().
* @return string
* Fully rendered message, with tokens merged.
*/
public function render($name, $row) {
if (!is_object($row)) {
$row = $this->getRow($row);
}
$message = $this->getMessage($name);
$row->fill($message['format']);
$useSmarty = !empty($row->context['smarty']);
// FIXME preg_callback.
$tokens = $this->rowValues[$row->tokenRow][$message['format']];
$flatTokens = array();
\CRM_Utils_Array::flatten($tokens, $flatTokens, '', '.');
$filteredTokens = array();
foreach ($flatTokens as $k => $v) {
$filteredTokens['{' . $k . '}'] = ($useSmarty ? \CRM_Utils_Token::tokenEscapeSmarty($v) : $v);
}
$event = new TokenRenderEvent($this);
$event->message = $message;
$event->context = $row->context;
$event->row = $row;
$event->string = strtr($message['string'], $filteredTokens);
$this->dispatcher->dispatch(Events::TOKEN_RENDER, $event);
return $event->string;
}
}
class TokenRowIterator extends \IteratorIterator {
protected $tokenProcessor;
/**
* @param TokenProcessor $tokenProcessor
* @param Traversable $iterator
*/
public function __construct(TokenProcessor $tokenProcessor, Traversable $iterator) {
parent::__construct($iterator); // TODO: Change the autogenerated stub
$this->tokenProcessor = $tokenProcessor;
}
public function current() {
return new TokenRow($this->tokenProcessor, parent::key());
}
}

View file

@ -0,0 +1,374 @@
<?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
);
}
}