Merge pull request #18576 from eileenmcnaughton/iso
[civicrm-core.git] / Civi / Api4 / Service / Schema / SchemaMapBuilder.php
CommitLineData
19b53e5b
C
1<?php
2
380f3545
TO
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 */
12
13/**
14 *
15 * @package CRM
ca5cec67 16 * @copyright CiviCRM LLC https://civicrm.org/licensing
380f3545
TO
17 */
18
19
19b53e5b
C
20namespace Civi\Api4\Service\Schema;
21
22use Civi\Api4\Entity;
23use Civi\Api4\Event\Events;
24use Civi\Api4\Event\SchemaMapBuildEvent;
25use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
26use Civi\Api4\Service\Schema\Joinable\Joinable;
27use Symfony\Component\EventDispatcher\EventDispatcherInterface;
19b53e5b 28use CRM_Core_DAO_AllCoreTables as AllCoreTables;
19b53e5b
C
29
30class SchemaMapBuilder {
31 /**
32 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
33 */
34 protected $dispatcher;
35 /**
36 * @var array
37 */
38 protected $apiEntities;
39
40 /**
41 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
42 */
43 public function __construct(EventDispatcherInterface $dispatcher) {
44 $this->dispatcher = $dispatcher;
fe806431 45 $this->apiEntities = array_keys((array) Entity::get(FALSE)->addSelect('name')->execute()->indexBy('name'));
19b53e5b
C
46 }
47
48 /**
49 * @return SchemaMap
50 */
51 public function build() {
52 $map = new SchemaMap();
53 $this->loadTables($map);
54
55 $event = new SchemaMapBuildEvent($map);
56 $this->dispatcher->dispatch(Events::SCHEMA_MAP_BUILD, $event);
57
58 return $map;
59 }
60
61 /**
62 * Add all tables and joins
63 *
64 * @param SchemaMap $map
65 */
66 private function loadTables(SchemaMap $map) {
67 /** @var \CRM_Core_DAO $daoName */
68 foreach (AllCoreTables::get() as $daoName => $data) {
69 $table = new Table($data['table']);
60677581
CW
70 foreach ($daoName::fields() as $fieldData) {
71 $this->addJoins($table, $fieldData['name'], $fieldData);
19b53e5b
C
72 }
73 $map->addTable($table);
74 if (in_array($data['name'], $this->apiEntities)) {
75 $this->addCustomFields($map, $table, $data['name']);
76 }
77 }
78
79 $this->addBackReferences($map);
80 }
81
82 /**
83 * @param Table $table
84 * @param string $field
85 * @param array $data
86 */
87 private function addJoins(Table $table, $field, array $data) {
1b8e3bc8 88 $fkClass = $data['FKClassName'] ?? NULL;
19b53e5b
C
89
90 // can there be multiple methods e.g. pseudoconstant and fkclass
91 if ($fkClass) {
92 $tableName = AllCoreTables::getTableForClass($fkClass);
1b8e3bc8 93 $fkKey = $data['FKKeyColumn'] ?? 'id';
19b53e5b
C
94 $alias = str_replace('_id', '', $field);
95 $joinable = new Joinable($tableName, $fkKey, $alias);
96 $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE);
97 $table->addTableLink($field, $joinable);
98 }
19b53e5b
C
99 }
100
101 /**
102 * Loop through existing links and provide link from the other side
103 *
104 * @param SchemaMap $map
105 */
106 private function addBackReferences(SchemaMap $map) {
107 foreach ($map->getTables() as $table) {
108 foreach ($table->getTableLinks() as $link) {
19b53e5b
C
109 $target = $map->getTableByName($link->getTargetTable());
110 $tableName = $link->getBaseTable();
5e327f37 111 // Exclude custom field tables
6fe7bdee 112 if (strpos($link->getTargetTable(), 'civicrm_value_') !== 0 && strpos($link->getBaseTable(), 'civicrm_value_') !== 0) {
5e327f37
CW
113 $plural = str_replace('civicrm_', '', $this->getPlural($tableName));
114 $joinable = new Joinable($tableName, $link->getBaseColumn(), $plural);
115 $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY);
116 $target->addTableLink($link->getTargetColumn(), $joinable);
117 }
19b53e5b
C
118 }
119 }
120 }
121
122 /**
123 * Simple implementation of pluralization.
124 * Could be replaced with symfony/inflector
125 *
126 * @param string $singular
127 *
128 * @return string
129 */
130 private function getPlural($singular) {
131 $last_letter = substr($singular, -1);
132 switch ($last_letter) {
133 case 'y':
134 return substr($singular, 0, -1) . 'ies';
135
136 case 's':
137 return $singular . 'es';
138
139 default:
140 return $singular . 's';
141 }
142 }
143
144 /**
145 * @param \Civi\Api4\Service\Schema\SchemaMap $map
146 * @param \Civi\Api4\Service\Schema\Table $baseTable
147 * @param string $entity
148 */
149 private function addCustomFields(SchemaMap $map, Table $baseTable, $entity) {
150 // Don't be silly
151 if (!array_key_exists($entity, \CRM_Core_SelectValues::customGroupExtends())) {
152 return;
153 }
154 $queryEntity = (array) $entity;
155 if ($entity == 'Contact') {
156 $queryEntity = ['Contact', 'Individual', 'Organization', 'Household'];
157 }
158 $fieldData = \CRM_Utils_SQL_Select::from('civicrm_custom_field f')
159 ->join('custom_group', 'INNER JOIN civicrm_custom_group g ON g.id = f.custom_group_id')
160 ->select(['g.name as custom_group_name', 'g.table_name', 'g.is_multiple', 'f.name', 'label', 'column_name', 'option_group_id'])
161 ->where('g.extends IN (@entity)', ['@entity' => $queryEntity])
162 ->where('g.is_active')
163 ->where('f.is_active')
164 ->execute();
165
166 $links = [];
167
168 while ($fieldData->fetch()) {
169 $tableName = $fieldData->table_name;
170
171 $customTable = $map->getTableByName($tableName);
172 if (!$customTable) {
173 $customTable = new Table($tableName);
174 }
175
19b53e5b
C
176 $map->addTable($customTable);
177
178 $alias = $fieldData->custom_group_name;
179 $links[$alias]['tableName'] = $tableName;
180 $links[$alias]['isMultiple'] = !empty($fieldData->is_multiple);
181 $links[$alias]['columns'][$fieldData->name] = $fieldData->column_name;
5e327f37
CW
182
183 // Add backreference
184 if (!empty($fieldData->is_multiple)) {
185 $joinable = new Joinable($baseTable->getName(), 'id', AllCoreTables::convertEntityNameToLower($entity));
186 $customTable->addTableLink('entity_id', $joinable);
187 }
19b53e5b
C
188 }
189
190 foreach ($links as $alias => $link) {
6fe7bdee 191 $joinable = new CustomGroupJoinable($link['tableName'], $alias, $link['isMultiple'], $link['columns']);
19b53e5b
C
192 $baseTable->addTableLink('id', $joinable);
193 }
194 }
195
196}