4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
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 |
10 +--------------------------------------------------------------------+
12 namespace Civi\Api4\Generic
;
14 use Civi\API\Exception\NotImplementedException
;
15 use Civi\Api4\Utils\ReflectionUtils
;
18 * Base class for all api entities.
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`).
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.
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.
31 * An entity which extends this class directly must, at minimum, implement the `getFields` action.
33 * @see https://lab.civicrm.org/extensions/api4example
35 abstract class AbstractEntity
{
38 * @param bool $checkPermissions
39 * @return \Civi\Api4\Action\GetActions
41 public static function getActions($checkPermissions = TRUE) {
42 return (new \Civi\Api4\Action\
GetActions(static::getEntityName(), __FUNCTION__
))
43 ->setCheckPermissions($checkPermissions);
47 * @return \Civi\Api4\Generic\BasicGetFieldsAction
49 abstract public static function getFields();
52 * @return \Civi\Api4\Generic\CheckAccessAction
54 public static function checkAccess() {
55 return new CheckAccessAction(self
::getEntityName(), __FUNCTION__
);
59 * Returns a list of permissions needed to access the various actions in this api.
63 public static function permissions() {
64 $permissions = \CRM_Core_Permission
::getEntityActionPermissions();
66 // For legacy reasons the permissions are keyed by lowercase entity name
67 $lcentity = \CRM_Core_DAO_AllCoreTables
::convertEntityNameToLower(self
::getEntityName());
68 // Merge permissions for this entity with the defaults
69 return ($permissions[$lcentity] ??
[]) +
$permissions['default'];
73 * Get entity name from called class
77 protected static function getEntityName() {
78 return self
::stripNamespace(static::class);
82 * Overridable function to return a localized title for this entity.
85 * Whether to return a plural title.
88 protected static function getEntityTitle($plural = FALSE) {
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);
95 * Overridable function to return menu paths related to this entity.
99 protected static function getEntityPaths() {
104 * Magic method to return the action object for an api.
106 * @param string $action
108 * @return AbstractAction
109 * @throws NotImplementedException
111 public static function __callStatic($action, $args) {
112 $entity = static::getEntityName();
113 $nameSpace = str_replace('Civi\Api4\\', 'Civi\Api4\Action\\', static::class);
114 // Find class for this action
115 $entityAction = "$nameSpace\\" . ucfirst($action);
116 if (class_exists($entityAction)) {
117 $actionObject = new $entityAction($entity, $action);
118 if (isset($args[0]) && $args[0] === FALSE) {
119 $actionObject->setCheckPermissions(FALSE);
123 throw new NotImplementedException("Api $entity $action version 4 does not exist.");
125 return $actionObject;
129 * Reflection function called by Entity::get()
131 * @see \Civi\Api4\Action\Entity\Get
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}
134 public static function getInfo() {
135 $entityName = static::getEntityName();
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',
148 // Add info for entities with a corresponding DAO
149 $dao = \CRM_Core_DAO_AllCoreTables
::getFullName($info['name']);
151 $info['paths'] = $dao::getEntityPaths();
152 $info['primary_key'] = $dao::$_primaryKey;
153 $info['icon'] = $dao::$_icon;
154 $info['label_field'] = $dao::$_labelField;
157 foreach (ReflectionUtils
::getTraits(static::class) as $trait) {
158 $info['type'][] = self
::stripNamespace($trait);
160 $reflection = new \
ReflectionClass(static::class);
161 $info = array_merge($info, ReflectionUtils
::getCodeDocs($reflection, NULL, ['entity' => $info['name']]));
163 $info['description'] = $dao::getEntityDescription() ??
$info['description'] ??
NULL;
165 unset($info['package'], $info['method']);
170 * Remove namespace prefix from a class name
172 * @param string $className
175 private static function stripNamespace($className) {
176 return substr($className, strrpos($className, '\\') +
1);