]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Features/Dynamics.php
aecbc4be3fc83df775fe95f356a2997d1affce41
[galette.git] / galette / lib / Galette / Features / Dynamics.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Dynamics fields trait
7 *
8 * PHP version 5
9 *
10 * Copyright © 2017-2023 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category Features
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2017-2023 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.9dev - 2017-05-26
35 */
36
37 namespace Galette\Features;
38
39 use Throwable;
40 use Analog\Analog;
41 use Galette\DynamicFields\File;
42 use Galette\DynamicFields\Date;
43 use Galette\DynamicFields\Boolean;
44 use Galette\Entity\DynamicFieldsHandle;
45
46 /**
47 * Dynamics fields trait
48 *
49 * @category Features
50 * @name Dynamics
51 * @package Galette
52 * @author Johan Cwiklinski <johan@x-tnd.be>
53 * @copyright 2017-2023 The Galette Team
54 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
55 * @link http://galette.tuxfamily.org
56 * @since Available since 0.9dev - 2017-05-26
57 */
58
59 trait Dynamics
60 {
61 use Dependencies;
62
63 /** @var string */
64 protected $name_pattern = 'info_field_';
65
66 /** @var DynamicFieldsHandle */
67 protected $dynamics;
68
69 /**
70 * Load dynamic fields for member
71 *
72 * @return void
73 */
74 private function loadDynamicFields()
75 {
76 if (!property_exists($this, 'login')) {
77 global $login;
78 } else {
79 $login = $this->login;
80 }
81 $this->dynamics = new DynamicFieldsHandle($this->zdb, $login, $this);
82 }
83
84 /**
85 * Get dynamic fields
86 *
87 * @return DynamicFieldsHandle
88 */
89 public function getDynamicFields()
90 {
91 if (null === $this->dynamics) {
92 $this->loadDynamicFields();
93 }
94 return $this->dynamics;
95 }
96
97 /**
98 * Extract posted values for dynamic fields
99 *
100 * @param array $post Posted values
101 * @param array $required Array of required fields
102 * @param array $disabled Array of disabled fields
103 *
104 * @return bool
105 */
106 protected function dynamicsCheck(array $post, array $required, array $disabled)
107 {
108 if ($this->dynamics === null) {
109 Analog::log(
110 'Dynamics fields have not been loaded, cannot be checked. (from: ' . __METHOD__ . ')',
111 Analog::WARNING
112 );
113 $this->loadDynamicFields();
114 }
115
116 if ($post != null) {
117 $valid = true;
118 $fields = $this->dynamics->getFields();
119
120 $dynamic_fields = [];
121 //posted fields
122 foreach ($post as $key => $value) {
123 // if the field is enabled, and match patterns check it
124 if (isset($disabled[$key]) || substr($key, 0, 11) != $this->name_pattern) {
125 continue;
126 }
127
128 list($field_id, $val_index) = explode('_', str_replace($this->name_pattern, '', $key));
129 if (!is_numeric($field_id) || !is_numeric($val_index)) {
130 continue;
131 }
132
133 $dynamic_fields[$key] = [
134 'value' => $value,
135 'field_id' => $field_id,
136 'val_index' => $val_index
137 ];
138 }
139
140 //some fields may be mising in posted values (checkboxes)
141 foreach ($fields as $field) {
142 $pattern = '/' . $this->name_pattern . $field->getId() . '_(\d)/';
143 if ($field instanceof Boolean && !preg_grep($pattern, array_keys($dynamic_fields))) {
144 $dynamic_fields[$this->name_pattern . $field->getId() . '_1'] = [
145 'value' => '',
146 'field_id' => $field->getId(),
147 'val_index' => 1
148 ];
149 }
150 }
151
152 foreach ($dynamic_fields as $key => $dfield_values) {
153 $field_id = $dfield_values['field_id'];
154 $value = $dfield_values['value'];
155 $val_index = $dfield_values['val_index'];
156
157 if ($fields[$field_id]->isRequired() && (trim($value) === '' || $value == null)) {
158 $this->errors[] = str_replace(
159 '%field',
160 $fields[$field_id]->getName(),
161 _T('Missing required field %field')
162 );
163 } else {
164 if ($fields[$field_id] instanceof File) {
165 //delete checkbox
166 $filename = sprintf(
167 'member_%d_field_%d_value_%d',
168 $this->id,
169 $field_id,
170 $val_index
171 );
172 if (file_exists(GALETTE_FILES_PATH . $filename)) {
173 unlink(GALETTE_FILES_PATH . $filename);
174 }
175 $this->dynamics->setValue($this->id, $field_id, $val_index, '');
176 } else {
177 if ($fields[$field_id] instanceof Date && !empty(trim($value))) {
178 //check date format
179 try {
180 $d = \DateTime::createFromFormat(__("Y-m-d"), $value);
181 if ($d === false) {
182 //try with non localized date
183 $d = \DateTime::createFromFormat("Y-m-d", $value);
184 if ($d === false) {
185 throw new \Exception('Incorrect format');
186 }
187 }
188 } catch (Throwable $e) {
189 $valid = false;
190 Analog::log(
191 'Wrong date format. field: ' . $field_id .
192 ', value: ' . $value . ', expected fmt: ' .
193 __("Y-m-d") . ' | ' . $e->getMessage(),
194 Analog::INFO
195 );
196 $this->errors[] = str_replace(
197 array(
198 '%date_format',
199 '%field'
200 ),
201 array(
202 __("Y-m-d"),
203 $fields[$field_id]->getName()
204 ),
205 _T("- Wrong date format (%date_format) for %field!")
206 );
207 }
208 }
209 //actual field value
210 if ($value !== null && trim($value) !== '') {
211 $this->dynamics->setValue($this->id, $field_id, $val_index, $value);
212 } else {
213 $this->dynamics->unsetValue($this->id, $field_id, $val_index);
214 }
215 }
216 }
217 }
218
219 return $valid;
220 }
221 return false;
222 }
223
224 /**
225 * Stores dynamic fields
226 *
227 * @param bool $transaction True if a transaction already exists
228 *
229 * @return bool
230 */
231 protected function dynamicsStore($transaction = false)
232 {
233 if ($this->dynamics === null) {
234 Analog::log(
235 'Dynamics fields have not been loaded, cannot be stored. (from: ' . __METHOD__ . ')',
236 Analog::WARNING
237 );
238 $this->loadDynamicFields();
239 }
240 $return = $this->dynamics->storeValues($this->id, $transaction);
241 if (method_exists($this, 'updateModificationDate') && $this->dynamics->hasChanged()) {
242 $this->updateModificationDate();
243 }
244 return $return;
245 }
246
247 /**
248 * Store dynamic Files
249 *
250 * @param array $files Posted files
251 *
252 * @return void
253 */
254 protected function dynamicsFiles($files)
255 {
256 $this->loadDynamicFields();
257 $fields = $this->dynamics->getFields();
258 $store = false;
259
260 foreach ($files as $key => $file) {
261 if (substr($key, 0, 11) != $this->name_pattern) {
262 continue;
263 }
264
265 list($field_id, $val_index) = explode('_', str_replace($this->name_pattern, '', $key));
266 if (!is_numeric($field_id) || !is_numeric($val_index)) {
267 continue;
268 }
269
270 if (
271 $file['error'] == UPLOAD_ERR_NO_FILE
272 && $file['name'] == ''
273 && $file['tmp_name'] == ''
274 ) {
275 //not upload atempt.
276 continue;
277 } elseif ($file['error'] !== UPLOAD_ERR_OK) {
278 Analog::log("file upload error", Analog::ERROR);
279 continue;
280 }
281
282 $tmp_filename = $file['tmp_name'];
283 if ($tmp_filename == '') {
284 Analog::log("empty temporary filename", Analog::ERROR);
285 continue;
286 }
287
288 if (!is_uploaded_file($tmp_filename)) {
289 Analog::log("not an uploaded file", Analog::ERROR);
290 continue;
291 }
292
293 $max_size =
294 $fields[$field_id]->getSize() ?
295 $fields[$field_id]->getSize() * 1024 : File::DEFAULT_MAX_FILE_SIZE * 1024;
296 if ($file['size'] > $max_size) {
297 Analog::log(
298 "file too large: " . $file['size'] . " Ko, vs $max_size Ko allowed",
299 Analog::ERROR
300 );
301 $this->errors[] = preg_replace(
302 '|%d|',
303 $max_size,
304 _T("File is too big. Maximum allowed size is %dKo")
305 );
306 continue;
307 }
308
309 $new_filename = sprintf(
310 'member_%d_field_%d_value_%d',
311 $this->id,
312 $field_id,
313 $val_index
314 );
315 Analog::log("new file: $new_filename", Analog::DEBUG);
316
317 move_uploaded_file(
318 $tmp_filename,
319 GALETTE_FILES_PATH . $new_filename
320 );
321 $this->dynamics->setValue($this->id, $field_id, $val_index, $file['name']);
322 $store = true;
323 }
324
325 if ($store === true) {
326 $this->dynamicsStore();
327 }
328 }
329
330 /**
331 * Remove dynamic fields values
332 *
333 * @param bool $transaction True if a transaction already exists
334 *
335 * @return bool
336 */
337 protected function dynamicsRemove($transaction = false)
338 {
339 if ($this->dynamics === null) {
340 Analog::log(
341 'Dynamics fields have not been loaded, cannot be removed. (from: ' . __METHOD__ . ')',
342 Analog::WARNING
343 );
344 $this->loadDynamicFields();
345 }
346 $return = $this->dynamics->removeValues($this->id, $transaction);
347 return $return;
348 }
349
350
351
352 /**
353 * Get errors
354 *
355 * @return array
356 */
357 public function getErrors()
358 {
359 return $this->errors;
360 }
361
362 /**
363 * Validate data for dynamic fields
364 * Set valid data in current object, also resets errors list
365 *
366 * @param array $values Dynamic fields values
367 * @param string $prefix Prefix to replace, default to 'dynfield_'
368 *
369 * @return bool
370 */
371 public function dynamicsValidate($values, $prefix = 'dynfield_')
372 {
373 $dfields = [];
374 foreach ($values as $key => $value) {
375 $dfields[str_replace($prefix, $this->name_pattern, $key)] = $value;
376 }
377 return $this->dynamicsCheck($dfields, [], []);
378 }
379 }