Merge pull request #22481 from eileenmcnaughton/deprecated
[civicrm-core.git] / Civi / Token / ImpliedContextSubscriber.php
1 <?php
2 namespace Civi\Token;
3
4 use Civi\Token\Event\TokenRegisterEvent;
5 use Civi\Token\Event\TokenValueEvent;
6 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7
8 /**
9 * Suppose you have `$context['participantId']`. You can infer a
10 * corresponding `$context['eventId']`. The ImpliedContextSubscriber reads
11 * values like `participantId` and fills-in values like `eventId`.
12 *
13 * @package Civi\Token
14 */
15 class ImpliedContextSubscriber implements EventSubscriberInterface {
16
17 public static function getSubscribedEvents() {
18 return [
19 'civi.token.list' => ['onRegisterTokens', 1000],
20 'civi.token.eval' => ['onEvaluateTokens', 1000],
21 ];
22 }
23
24 /**
25 * When listing tokens, ensure that implied data is visible.
26 *
27 * Ex: If `$context['participantId']` is part of the schema, then
28 * `$context['eventId']` will also be part of the schema.
29 *
30 * This fires early during the `civi.token.list` process to ensure that
31 * other listeners see the updated schema.
32 *
33 * @param \Civi\Token\Event\TokenRegisterEvent $e
34 */
35 public function onRegisterTokens(TokenRegisterEvent $e): void {
36 $tokenProc = $e->getTokenProcessor();
37 foreach ($this->findRelevantMappings($tokenProc) as $mapping) {
38 $tokenProc->addSchema($mapping['destEntityId']);
39 }
40 }
41
42 /**
43 * When evaluating tokens, ensure that implied data is loaded.
44 *
45 * Ex: If `$context['participantId']` is supplied, then lookup the
46 * corresponding `$context['eventId']`.
47 *
48 * This fires early during the `civi.token.list` process to ensure that
49 * other listeners see the autoloaded values.
50 *
51 * @param \Civi\Token\Event\TokenValueEvent $e
52 */
53 public function onEvaluateTokens(TokenValueEvent $e): void {
54 $tokenProc = $e->getTokenProcessor();
55 foreach ($this->findRelevantMappings($tokenProc) as $mapping) {
56 $getSrcId = function($row) use ($mapping) {
57 return $row->context[$mapping['srcEntityId']] ?? $row->context[$mapping['srcEntityRec']]['id'] ?? NULL;
58 };
59
60 $ids = [];
61 foreach ($tokenProc->getRows() as $row) {
62 $ids[] = $getSrcId($row);
63 }
64 $ids = \array_diff(\array_unique($ids), [NULL]);
65 if (empty($ids)) {
66 continue;
67 }
68
69 [$srcTable, $fkColumn] = explode('.', $mapping['fk']);
70 $fks = \CRM_Utils_SQL_Select::from($srcTable)
71 ->where('id in (#ids)', ['ids' => $ids])
72 ->select(['id', $fkColumn])
73 ->execute()
74 ->fetchMap('id', $fkColumn);
75
76 $tokenProc->addSchema($mapping['destEntityId']);
77 foreach ($tokenProc->getRows() as $row) {
78 $srcId = $getSrcId($row);
79 if ($srcId && empty($row->context[$mapping['destEntityId']])) {
80 $row->context($mapping['destEntityId'], $fks[$srcId]);
81 }
82 }
83 }
84 }
85
86 /**
87 * Are there any context-fields for which we should do lookups?
88 *
89 * Ex: If the `$tokenProcessor` has the `participantId`s, then we would want
90 * to know any rules that involve `participantId`. But we don't need to know
91 * rules that involve `contributionId`.
92 *
93 * @param \Civi\Token\TokenProcessor $tokenProcessor
94 */
95 private function findRelevantMappings(TokenProcessor $tokenProcessor): iterable {
96 $schema = $tokenProcessor->context['schema'];
97 yield from [];
98 foreach ($this->getMappings() as $mapping) {
99 if (in_array($mapping['srcEntityRec'], $schema) || in_array($mapping['srcEntityId'], $schema)) {
100 yield $mapping;
101 }
102 }
103 }
104
105 private function getMappings(): iterable {
106 yield [
107 'srcEntityId' => 'participantId',
108 'srcEntityRec' => 'participant',
109 'fk' => 'civicrm_participant.event_id',
110 'destEntityId' => 'eventId',
111 ];
112 }
113
114 }