First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
188
sites/all/modules/civicrm/Civi/Token/AbstractTokenSubscriber.php
Normal file
188
sites/all/modules/civicrm/Civi/Token/AbstractTokenSubscriber.php
Normal 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);
|
||||
|
||||
}
|
25
sites/all/modules/civicrm/Civi/Token/Event/TokenEvent.php
Normal file
25
sites/all/modules/civicrm/Civi/Token/Event/TokenEvent.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
32
sites/all/modules/civicrm/Civi/Token/Events.php
Normal file
32
sites/all/modules/civicrm/Civi/Token/Events.php
Normal 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';
|
||||
|
||||
}
|
133
sites/all/modules/civicrm/Civi/Token/TokenCompatSubscriber.php
Normal file
133
sites/all/modules/civicrm/Civi/Token/TokenCompatSubscriber.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
6
sites/all/modules/civicrm/Civi/Token/TokenException.php
Normal file
6
sites/all/modules/civicrm/Civi/Token/TokenException.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Civi\Token;
|
||||
|
||||
class TokenException extends \CRM_Core_Exception {
|
||||
|
||||
}
|
251
sites/all/modules/civicrm/Civi/Token/TokenProcessor.php
Normal file
251
sites/all/modules/civicrm/Civi/Token/TokenProcessor.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
374
sites/all/modules/civicrm/Civi/Token/TokenRow.php
Normal file
374
sites/all/modules/civicrm/Civi/Token/TokenRow.php
Normal 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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue