Commit | Line | Data |
---|---|---|
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 |
20 | namespace Civi\Api4\Service\Schema; |
21 | ||
22 | use Civi\Api4\Entity; | |
23 | use Civi\Api4\Event\Events; | |
24 | use Civi\Api4\Event\SchemaMapBuildEvent; | |
25 | use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable; | |
26 | use Civi\Api4\Service\Schema\Joinable\Joinable; | |
27 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
19b53e5b | 28 | use CRM_Core_DAO_AllCoreTables as AllCoreTables; |
19b53e5b C |
29 | |
30 | class 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 | } |