Merge pull request #24117 from civicrm/5.52
[civicrm-core.git] / Civi / Api4 / Generic / AbstractEntity.php
CommitLineData
19b53e5b 1<?php
380f3545
TO
2
3/*
4 +--------------------------------------------------------------------+
41498ac5 5 | Copyright CiviCRM LLC. All rights reserved. |
380f3545 6 | |
41498ac5
TO
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
380f3545
TO
10 +--------------------------------------------------------------------+
11 */
19b53e5b
C
12namespace Civi\Api4\Generic;
13
14use Civi\API\Exception\NotImplementedException;
449c4e6b 15use Civi\Api4\Utils\ReflectionUtils;
19b53e5b
C
16
17/**
18 * Base class for all api entities.
19 *
9bafff7c
CW
20 * This is the most generic of 3 possible base classes for an APIv4 Entity
21 * (the other 2, which extend this class, are `BasicEntity` and `DAOEntity`).
19b53e5b 22 *
9bafff7c
CW
23 * Implementing an API by extending this class directly is appropriate when it does not implement
24 * all of the CRUD actions, or only a subset like `get` without `create`, `update` or `delete`;
25 * for example the RelationshipCache entity.
19b53e5b 26 *
9bafff7c
CW
27 * For all other APIs that do implement CRUD it is recommended to use:
28 * 1. `DAOEntity` for all entities with a DAO (sql table).
29 * 2. `BasicEntity` for all others, e.g. file-based entities.
30 *
31 * An entity which extends this class directly must, at minimum, implement the `getFields` action.
c2adedc1
CW
32 *
33 * @see https://lab.civicrm.org/extensions/api4example
19b53e5b
C
34 */
35abstract class AbstractEntity {
36
37 /**
6764a9d3 38 * @param bool $checkPermissions
19b53e5b
C
39 * @return \Civi\Api4\Action\GetActions
40 */
6764a9d3 41 public static function getActions($checkPermissions = TRUE) {
9f338c09 42 return (new \Civi\Api4\Action\GetActions(static::getEntityName(), __FUNCTION__))
6764a9d3 43 ->setCheckPermissions($checkPermissions);
19b53e5b
C
44 }
45
46 /**
c0c668ce 47 * @return \Civi\Api4\Generic\BasicGetFieldsAction
19b53e5b 48 */
c0c668ce 49 abstract public static function getFields();
19b53e5b 50
929a9585
CW
51 /**
52 * @return \Civi\Api4\Generic\CheckAccessAction
53 */
54 public static function checkAccess() {
24a919da 55 return new CheckAccessAction(static::getEntityName(), __FUNCTION__);
929a9585
CW
56 }
57
19b53e5b
C
58 /**
59 * Returns a list of permissions needed to access the various actions in this api.
60 *
61 * @return array
62 */
63 public static function permissions() {
64 $permissions = \CRM_Core_Permission::getEntityActionPermissions();
65
66 // For legacy reasons the permissions are keyed by lowercase entity name
24a919da 67 $lcentity = \CRM_Core_DAO_AllCoreTables::convertEntityNameToLower(static::getEntityName());
19b53e5b 68 // Merge permissions for this entity with the defaults
74c303ca 69 return ($permissions[$lcentity] ?? []) + $permissions['default'];
19b53e5b
C
70 }
71
72 /**
73 * Get entity name from called class
74 *
75 * @return string
76 */
77 protected static function getEntityName() {
90908aac 78 return self::stripNamespace(static::class);
19b53e5b
C
79 }
80
449c4e6b
CW
81 /**
82 * Overridable function to return a localized title for this entity.
83 *
7b66c3b5
AH
84 * @param bool $plural
85 * Whether to return a plural title.
449c4e6b
CW
86 * @return string
87 */
7b66c3b5 88 protected static function getEntityTitle($plural = FALSE) {
c5368b3b
CW
89 $name = static::getEntityName();
90 $dao = \CRM_Core_DAO_AllCoreTables::getFullName($name);
91 return $dao ? $dao::getEntityTitle($plural) : ($plural ? \CRM_Utils_String::pluralize($name) : $name);
449c4e6b
CW
92 }
93
a7bd99ff
CW
94 /**
95 * Overridable function to return menu paths related to this entity.
96 *
97 * @return array
98 */
99 protected static function getEntityPaths() {
100 return [];
101 }
102
19b53e5b
C
103 /**
104 * Magic method to return the action object for an api.
105 *
106 * @param string $action
6764a9d3 107 * @param array $args
19b53e5b
C
108 * @return AbstractAction
109 * @throws NotImplementedException
110 */
111 public static function __callStatic($action, $args) {
9f338c09 112 $entity = static::getEntityName();
eb378b8a 113 $nameSpace = str_replace('Civi\Api4\\', 'Civi\Api4\Action\\', static::class);
19b53e5b 114 // Find class for this action
eb378b8a 115 $entityAction = "$nameSpace\\" . ucfirst($action);
19b53e5b
C
116 if (class_exists($entityAction)) {
117 $actionObject = new $entityAction($entity, $action);
6764a9d3
CW
118 if (isset($args[0]) && $args[0] === FALSE) {
119 $actionObject->setCheckPermissions(FALSE);
120 }
19b53e5b
C
121 }
122 else {
123 throw new NotImplementedException("Api $entity $action version 4 does not exist.");
124 }
125 return $actionObject;
126 }
127
449c4e6b
CW
128 /**
129 * Reflection function called by Entity::get()
130 *
131 * @see \Civi\Api4\Action\Entity\Get
0b2471a8 132 * @return array{name: string, title: string, description: string, title_plural: string, type: string, paths: array, class: string, primary_key: array, searchable: string, dao: string, label_field: string, icon: string}
449c4e6b
CW
133 */
134 public static function getInfo() {
2ddaad96 135 $entityName = static::getEntityName();
0b2471a8
CW
136 $info = [
137 'name' => $entityName,
138 'title' => static::getEntityTitle(),
139 'title_plural' => static::getEntityTitle(TRUE),
140 'type' => [self::stripNamespace(get_parent_class(static::class))],
141 'paths' => static::getEntityPaths(),
142 'class' => static::class,
143 'primary_key' => ['id'],
144 // Entities without a @searchable annotation will default to secondary,
145 // which makes them visible in SearchKit but not at the top of the list.
146 'searchable' => 'secondary',
147 ];
148 // Add info for entities with a corresponding DAO
149 $dao = \CRM_Core_DAO_AllCoreTables::getFullName($info['name']);
150 if ($dao) {
151 $info['paths'] = $dao::getEntityPaths();
152 $info['primary_key'] = $dao::$_primaryKey;
153 $info['icon'] = $dao::$_icon;
154 $info['label_field'] = $dao::$_labelField;
155 $info['dao'] = $dao;
8b6b7576 156 $info['table_name'] = $dao::$_tableName;
0b2471a8
CW
157 }
158 foreach (ReflectionUtils::getTraits(static::class) as $trait) {
159 $info['type'][] = self::stripNamespace($trait);
160 }
aa0aca46 161 // Get DocBlock from APIv4 Entity class
0b2471a8 162 $reflection = new \ReflectionClass(static::class);
aa0aca46
CW
163 $docBlock = ReflectionUtils::getCodeDocs($reflection, NULL, ['entity' => $info['name']]);
164 // Convert docblock keys to snake_case
165 foreach ($docBlock as $key => $val) {
166 $docBlock[\CRM_Utils_String::convertStringToSnakeCase($key)] = $val;
167 }
168 // Filter docblock to only declared entity fields
169 foreach (\Civi\Api4\Entity::$entityFields as $field) {
170 if (isset($docBlock[$field['name']])) {
171 $val = $docBlock[$field['name']];
172 // Convert to array if data_type == Array
173 if (isset($field['data_type']) && $field['data_type'] === 'Array' && is_string($val)) {
174 $val = \CRM_Core_DAO::unSerializeField($val, \CRM_Core_DAO::SERIALIZE_COMMA);
175 }
176 $info[$field['name']] = $val;
177 }
178 }
179
0b2471a8
CW
180 if ($dao) {
181 $info['description'] = $dao::getEntityDescription() ?? $info['description'] ?? NULL;
2562d09a 182 }
076fe09a 183
aa0aca46 184 return $info;
449c4e6b
CW
185 }
186
90908aac
CW
187 /**
188 * Remove namespace prefix from a class name
189 *
190 * @param string $className
191 * @return string
192 */
193 private static function stripNamespace($className) {
194 return substr($className, strrpos($className, '\\') + 1);
195 }
196
19b53e5b 197}