Merge pull request #23100 from civicrm/5.48
[civicrm-core.git] / CRM / Core / Form / EntityFormTrait.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 trait CRM_Core_Form_EntityFormTrait {
18
19 /**
20 * The id of the object being edited / created.
21 *
22 * @var int
23 */
24 public $_id;
25
26 /**
27 * The entity subtype ID (eg. for Relationship / Activity)
28 *
29 * @var int
30 */
31 protected $_entitySubTypeId = NULL;
32
33 /**
34 * Get entity fields for the entity to be added to the form.
35 *
36 * @return array
37 */
38 public function getEntityFields() {
39 return $this->entityFields;
40 }
41
42 /**
43 * Explicitly declare the form context.
44 */
45 public function getDefaultContext() {
46 return 'create';
47 }
48
49 /**
50 * Get entity fields for the entity to be added to the form.
51 *
52 * @return string
53 */
54 public function getDeleteMessage() {
55 return $this->deleteMessage;
56 }
57
58 /**
59 * Set the delete message.
60 *
61 * We do this from the constructor in order to do a translation.
62 */
63 public function setDeleteMessage() {
64 }
65
66 /**
67 * Set entity fields to be assigned to the form.
68 */
69 protected function setEntityFields() {
70 }
71
72 /**
73 * Get the entity id being edited.
74 *
75 * @return int|null
76 */
77 public function getEntityId() {
78 return $this->_id;
79 }
80
81 /**
82 * Set the entity ID
83 *
84 * @param int $id The entity ID
85 */
86 public function setEntityId($id) {
87 $this->_id = $id;
88 }
89
90 /**
91 * Should custom data be suppressed on this form.
92 *
93 * @return bool
94 */
95 protected function isSuppressCustomData() {
96 return FALSE;
97 }
98
99 /**
100 * Get the entity subtype ID being edited
101 *
102 * @return int|null
103 */
104 public function getEntitySubTypeId() {
105 return $this->_entitySubTypeId;
106 }
107
108 /**
109 * Set the entity subtype ID being edited
110 *
111 * @param $subTypeId
112 */
113 public function setEntitySubTypeId($subTypeId) {
114 $this->_entitySubTypeId = $subTypeId;
115 }
116
117 /**
118 * If the custom data is in the submitted data (eg. added via ajax loaded form) add to form.
119 */
120 public function addCustomDataToForm() {
121 if ($this->isSuppressCustomData()) {
122 return TRUE;
123 }
124 $customisableEntities = CRM_Core_SelectValues::customGroupExtends();
125 if (isset($customisableEntities[$this->getDefaultEntity()])) {
126 CRM_Custom_Form_CustomData::addToForm($this, $this->getEntitySubTypeId());
127 }
128 }
129
130 /**
131 * Build the form object.
132 */
133 public function buildQuickEntityForm() {
134 if ($this->isDeleteContext()) {
135 $this->buildDeleteForm();
136 return;
137 }
138 $this->applyFilter('__ALL__', 'trim');
139 $this->addEntityFieldsToTemplate();
140 foreach ($this->entityFields as $index => $fields) {
141 $this->entityFields[$index] = array_merge([
142 'template' => '',
143 'help' => [],
144 'pre_html_text' => '',
145 'post_html_text' => '',
146 'description' => '',
147 'documentation_link' => '',
148 ], $fields);
149 }
150 $this->assign('entityFields', $this->entityFields);
151 $this->assign('entityID', $this->getEntityId());
152 $this->assign('entityInClassFormat', strtolower(str_replace('_', '-', $this->getDefaultEntity())));
153 $this->assign('entityTable', CRM_Core_DAO_AllCoreTables::getTableForClass(CRM_Core_DAO_AllCoreTables::getFullName($this->getDefaultEntity())));
154 $this->addCustomDataToForm();
155 $this->addFormButtons();
156
157 if ($this->isViewContext()) {
158 $this->freeze();
159 }
160 }
161
162 /**
163 * Build the form for any deletion.
164 */
165 protected function buildDeleteForm() {
166 $this->assign('deleteMessage', $this->getDeleteMessage());
167 $this->addFormButtons();
168 }
169
170 /**
171 * Add relevant buttons to the form.
172 */
173 protected function addFormButtons() {
174 if ($this->isViewContext() || $this->_action & CRM_Core_Action::PREVIEW) {
175 $this->addButtons([
176 [
177 'type' => 'cancel',
178 'name' => ts('Done'),
179 'isDefault' => TRUE,
180 ],
181 ]);
182 }
183 else {
184 $this->addButtons([
185 [
186 'type' => 'next',
187 'name' => $this->isDeleteContext() ? ts('Delete') : ts('Save'),
188 'isDefault' => TRUE,
189 ],
190 [
191 'type' => 'cancel',
192 'name' => ts('Cancel'),
193 ],
194 ]);
195 }
196 }
197
198 /**
199 * Get the defaults for the entity.
200 */
201 protected function getEntityDefaults() {
202 $defaults = $moneyFields = [];
203
204 if (!$this->isDeleteContext() &&
205 $this->getEntityId()) {
206 $params = ['id' => $this->getEntityId()];
207 $baoName = $this->_BAOName;
208 $baoName::retrieve($params, $defaults);
209 }
210 foreach ($this->entityFields as $entityFieldName => $fieldSpec) {
211 $value = CRM_Utils_Request::retrieveValue($fieldSpec['name'], $this->getValidationTypeForField($fieldSpec['name']));
212 if ($value !== FALSE && $value !== NULL) {
213 $defaults[$fieldSpec['name']] = $value;
214 }
215 // Store a list of fields with money formatters
216 if (CRM_Utils_Array::value('formatter', $fieldSpec) == 'crmMoney') {
217 $moneyFields[] = $entityFieldName;
218 }
219 }
220 if (!empty($defaults['currency'])) {
221 // If we have a money formatter we need to pass the specified currency or it will render as the default
222 foreach ($moneyFields as $entityFieldName) {
223 $this->entityFields[$entityFieldName]['formatterParam'] = $defaults['currency'];
224 }
225 }
226
227 // Assign again as we may have modified above
228 $this->assign('entityFields', $this->entityFields);
229 return $defaults;
230 }
231
232 /**
233 * Get the validation rule to apply to a function.
234 *
235 * Alphanumeric is designed to always be safe & for now we just return
236 * that but in future we can use tighter rules for types like int, bool etc.
237 *
238 * @param string $fieldName
239 *
240 * @return string|int|bool
241 */
242 protected function getValidationTypeForField($fieldName) {
243 switch ($this->metadata[$fieldName]['type']) {
244 case CRM_Utils_Type::T_BOOLEAN:
245 return 'Boolean';
246
247 default:
248 return 'Alphanumeric';
249 }
250 }
251
252 /**
253 * Set translated fields.
254 *
255 * This function is called from the class constructor, allowing us to set
256 * fields on the class that can't be set as properties due to need for
257 * translation or other non-input specific handling.
258 */
259 protected function setTranslatedFields() {
260 $this->setEntityFields();
261 $this->setDeleteMessage();
262 $metadata = civicrm_api3($this->getDefaultEntity(), 'getfields', ['action' => 'create']);
263 $this->metadata = $metadata['values'];
264 foreach ($this->metadata as $fieldName => $spec) {
265 if (isset($this->entityFields[$fieldName])) {
266 if ($spec['localizable']) {
267 $this->entityFields[$fieldName]['is_add_translate_dialog'] = TRUE;
268 }
269 if (empty($spec['html']['type'])) {
270 $this->entityFields[$fieldName]['not-auto-addable'] = TRUE;
271 }
272 }
273 }
274 }
275
276 /**
277 * Add defined entity field to template.
278 */
279 protected function addEntityFieldsToTemplate() {
280 foreach ($this->getEntityFields() as $fieldSpec) {
281 if (empty($fieldSpec['not-auto-addable'])) {
282 $element = $this->addField($fieldSpec['name'], [], CRM_Utils_Array::value('required', $fieldSpec), FALSE);
283 if (!empty($fieldSpec['is_freeze'])) {
284 $element->freeze();
285 }
286 }
287 }
288 }
289
290 /**
291 * Is the form being used in the context of a deletion.
292 *
293 * (For some reason rather than having separate forms Civi overloads one form).
294 *
295 * @return bool
296 */
297 protected function isDeleteContext() {
298 return ($this->_action & CRM_Core_Action::DELETE);
299 }
300
301 /**
302 * Is the form being used in the context of a view.
303 *
304 * @return bool
305 */
306 protected function isViewContext() {
307 return ($this->_action & CRM_Core_Action::VIEW);
308 }
309
310 protected function setEntityFieldsMetadata() {
311 foreach ($this->entityFields as $field => &$props) {
312 if (!empty($props['not-auto-addable'])) {
313 // We can't load this field using metadata
314 continue;
315 }
316 if ($field != 'id' && $this->isDeleteContext()) {
317 // Delete forms don't generally present any fields to edit
318 continue;
319 }
320 // Resolve action.
321 if (empty($props['action'])) {
322 $props['action'] = $this->getApiAction();
323 }
324 $fieldSpec = civicrm_api3($this->getDefaultEntity(), 'getfield', $props);
325 $fieldSpec = $fieldSpec['values'];
326 if (!isset($props['description']) && isset($fieldSpec['description'])) {
327 $props['description'] = $fieldSpec['description'];
328 }
329 }
330 }
331
332 }