Merge pull request #17480 from tunbola/email-template-perms
[civicrm-core.git] / Civi / Core / CiviEventInspector.php
CommitLineData
bdd65b5c 1<?php
ec84755a 2namespace Civi\Core;
bdd65b5c
TO
3
4/**
ec84755a 5 * Class CiviEventInspector
bdd65b5c 6 *
6f1818fd 7 * The event inspector is a development tool which provides metadata about events.
bdd65b5c
TO
8 * It can be used for code-generators and documentation-generators.
9 *
0b882a86 10 * ```
ec84755a 11 * $i = new CiviEventInspector();
6f1818fd 12 * print_r(CRM_Utils_Array::collect('name', $i->getAll()));
0b882a86 13 * ```
bdd65b5c 14 *
513d9c29
TO
15 * An event definition includes these fields:
16 * - type: string, required. Ex: 'hook' or 'object'
17 * - name: string, required. Ex: 'hook_civicrm_post' or 'civi.dao.postInsert'
18 * - class: string, required. Ex: 'Civi\Core\Event\GenericHookEvent'.
19 * - signature: string, required FOR HOOKS. Ex: '$first, &$second'.
20 * - fields: array, required FOR HOOKS. List of hook parameters.
21 * - stub: ReflectionMethod, optional. An example function with docblocks/inputs.
22 *
bdd65b5c
TO
23 * Note: The inspector is only designed for use in developer workflows, such
24 * as code-generation and inspection. It should be not called by regular
25 * runtime logic.
26 */
ec84755a 27class CiviEventInspector {
bdd65b5c
TO
28
29 /**
30 * Register the default hooks defined by 'CRM_Utils_Hook'.
31 *
32 * @param \Civi\Core\Event\GenericHookEvent $e
47e7c2f8 33 * @see \CRM_Utils_Hook::eventDefs()
bdd65b5c 34 */
6f1818fd 35 public static function findBuiltInEvents(\Civi\Core\Event\GenericHookEvent $e) {
c64f69d9 36 $skipList = ['singleton'];
bdd65b5c 37 $e->inspector->addStaticStubs('CRM_Utils_Hook', 'hook_civicrm_',
6f1818fd
TO
38 function ($eventDef, $method) use ($skipList) {
39 return in_array($method->name, $skipList) ? NULL : $eventDef;
bdd65b5c
TO
40 });
41 }
42
43 /**
44 * @var array
6f1818fd 45 * Array(string $name => array $eventDef).
bdd65b5c 46 *
6f1818fd 47 * Ex: $eventDefs['hook_civicrm_foo']['description_html'] = 'Hello world';
bdd65b5c 48 */
6f1818fd 49 protected $eventDefs;
bdd65b5c
TO
50
51 /**
6f1818fd 52 * Perform a scan to identify/describe all events.
bdd65b5c
TO
53 *
54 * @param bool $force
ec84755a 55 * @return CiviEventInspector
bdd65b5c
TO
56 */
57 public function build($force = FALSE) {
6f1818fd 58 if ($force || $this->eventDefs === NULL) {
c64f69d9 59 $this->eventDefs = [];
47e7c2f8 60 \CRM_Utils_Hook::eventDefs($this);
6f1818fd 61 ksort($this->eventDefs);
bdd65b5c
TO
62 }
63 return $this;
64 }
65
66 /**
6f1818fd 67 * Get a list of all events.
bdd65b5c
TO
68 *
69 * @return array
6f1818fd
TO
70 * Array(string $name => array $eventDef).
71 * Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world';
bdd65b5c
TO
72 */
73 public function getAll() {
74 $this->build();
6f1818fd 75 return $this->eventDefs;
bdd65b5c
TO
76 }
77
78 /**
6f1818fd
TO
79 * Find any events that match a pattern.
80 *
81 * @param string $regex
82 * @return array
83 * Array(string $name => array $eventDef).
84 * Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world';
85 */
86 public function find($regex) {
87 $this->build();
0d0031a0 88 return array_filter($this->eventDefs, function ($e) use ($regex) {
6f1818fd
TO
89 return preg_match($regex, $e['name']);
90 });
91 }
92
93 /**
94 * Get the definition of one event.
bdd65b5c
TO
95 *
96 * @param string $name
97 * Ex: 'hook_civicrm_alterSettingsMetaData'.
98 * @return array
6f1818fd 99 * Ex: $result['description_html'] = 'Hello world';
bdd65b5c
TO
100 */
101 public function get($name) {
102 $this->build();
6f1818fd 103 return $this->eventDefs[$name];
bdd65b5c
TO
104 }
105
106 /**
6f1818fd 107 * @param $eventDef
bdd65b5c
TO
108 * @return bool
109 * TRUE if valid.
110 */
6f1818fd 111 public function validate($eventDef) {
0d0031a0
TO
112 if (!is_array($eventDef) || empty($eventDef['name']) || !isset($eventDef['type'])) {
113 return FALSE;
114 }
115
c64f69d9 116 if (!in_array($eventDef['type'], ['hook', 'object'])) {
0d0031a0
TO
117 return FALSE;
118 }
119
120 if ($eventDef['type'] === 'hook') {
121 if (!isset($eventDef['signature']) || !is_array($eventDef['fields'])) {
122 return FALSE;
123 }
124 }
125
126 return TRUE;
bdd65b5c
TO
127 }
128
129 /**
6f1818fd 130 * Add a new event definition.
bdd65b5c 131 *
6f1818fd 132 * @param array $eventDef
ec84755a 133 * @return CiviEventInspector
bdd65b5c 134 */
6f1818fd 135 public function add($eventDef) {
2e1f50d6 136 $name = $eventDef['name'] ?? NULL;
6f1818fd 137
0d0031a0
TO
138 if (!isset($eventDef['type'])) {
139 $eventDef['type'] = preg_match('/^hook_/', $eventDef['name']) ? 'hook' : 'object';
6f1818fd 140 }
bdd65b5c 141
0d0031a0 142 if ($eventDef['type'] === 'hook' && empty($eventDef['signature'])) {
6f1818fd 143 $eventDef['signature'] = implode(', ', array_map(
bdd65b5c
TO
144 function ($field) {
145 $sigil = $field['ref'] ? '&$' : '$';
146 return $sigil . $field['name'];
147 },
6f1818fd 148 $eventDef['fields']
bdd65b5c
TO
149 ));
150 }
151
6f1818fd
TO
152 if (TRUE !== $this->validate($eventDef)) {
153 throw new \CRM_Core_Exception("Failed to register event ($name). Invalid definition.");
bdd65b5c
TO
154 }
155
6f1818fd 156 $this->eventDefs[$name] = $eventDef;
bdd65b5c
TO
157 return $this;
158 }
159
0d0031a0
TO
160 /**
161 * Scan a Symfony event class for metadata, and add it.
162 *
163 * @param string $event
164 * Ex: 'civi.api.authorize'.
165 * @param string $className
166 * Ex: 'Civi\API\Event\AuthorizeEvent'.
167 * @return CiviEventInspector
168 */
169 public function addEventClass($event, $className) {
c64f69d9 170 $this->add([
0d0031a0
TO
171 'name' => $event,
172 'class' => $className,
c64f69d9 173 ]);
0d0031a0
TO
174 return $this;
175 }
176
bdd65b5c
TO
177 /**
178 * Scan a class for hook stubs, and add all of them.
179 *
180 * @param string $className
181 * The name of a class which contains static stub functions.
182 * Ex: 'CRM_Utils_Hook'.
183 * @param string $prefix
184 * A prefix to apply to all hook names.
185 * Ex: 'hook_civicrm_'.
186 * @param null|callable $filter
187 * An optional function to filter/rewrite the metadata for each hook.
ec84755a 188 * @return CiviEventInspector
bdd65b5c 189 */
6f1818fd 190 public function addStaticStubs($className, $prefix, $filter = NULL) {
ec84755a 191 $class = new \ReflectionClass($className);
bdd65b5c 192
ec84755a 193 foreach ($class->getMethods(\ReflectionMethod::IS_STATIC) as $method) {
bdd65b5c
TO
194 if (!isset($method->name)) {
195 continue;
196 }
197
c64f69d9 198 $eventDef = [
bdd65b5c 199 'name' => $prefix . $method->name,
ec84755a 200 'description_html' => $method->getDocComment() ? \CRM_Admin_Page_APIExplorer::formatDocBlock($method->getDocComment()) : '',
c64f69d9 201 'fields' => [],
bdd65b5c 202 'class' => 'Civi\Core\Event\GenericHookEvent',
e1d66df7 203 'stub' => $method,
c64f69d9 204 ];
bdd65b5c
TO
205
206 foreach ($method->getParameters() as $parameter) {
c64f69d9 207 $eventDef['fields'][$parameter->getName()] = [
bdd65b5c
TO
208 'name' => $parameter->getName(),
209 'ref' => (bool) $parameter->isPassedByReference(),
210 // WISHLIST: 'type' => 'mixed',
c64f69d9 211 ];
bdd65b5c
TO
212 }
213
214 if ($filter !== NULL) {
6f1818fd
TO
215 $eventDef = $filter($eventDef, $method);
216 if ($eventDef === NULL) {
bdd65b5c
TO
217 continue;
218 }
219 }
220
6f1818fd 221 $this->add($eventDef);
bdd65b5c
TO
222 }
223
224 return $this;
225 }
226
227}