PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / helpers / libraries / mvc / table.php
vikappointments / site / helpers / libraries / mvc Last commit date
controllers 3 days ago controller.php 3 days ago index.html 3 days ago model.php 3 days ago table.php 3 days ago view.php 3 days ago
table.php
568 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * This class implements helpful methods for abstract tables.
16 *
17 * @since 1.7
18 */
19 class JTableVAP extends JTable
20 {
21 /**
22 * A list of fields that requires a validation.
23 * Children classes must inherit this property in
24 * order to include additional fields within the
25 * validation process.
26 *
27 * @var array
28 */
29 protected $_requiredFields = array();
30
31 /**
32 * Flag used to overwrite the "update nulls" argument
33 * received by the store method without having to
34 * override it.
35 *
36 * @var boolean
37 */
38 protected $_updateNulls = false;
39
40 /**
41 * Flag used to temporarily prevent the storage of the
42 * user state while binding the table.
43 *
44 * @var boolean
45 */
46 public $_setUserState = true;
47
48 /**
49 * Method to provide a shortcut to binding, checking and storing a Table instance to the database table.
50 *
51 * The method will check a row in once the data has been stored and if an ordering filter is present will attempt to reorder
52 * the table rows based on the filter. The ordering filter is an instance property name. The rows that will be reordered
53 * are those whose value matches the Table instance for the property specified.
54 *
55 * @param array|object $src An associative array or object to bind to the Table instance.
56 * @param string $orderingFilter Filter for the order updating.
57 * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
58 *
59 * @return boolean True on success.
60 */
61 public function save($src, $orderingFilter = '', $ignore = '')
62 {
63 $result = true;
64
65 // create "before save" event
66 $event = 'onBeforeSave' . ucfirst($this->getName());
67
68 try
69 {
70 /**
71 * Trigger event to allow the plugins to bind the object that
72 * is going to be saved. The event to use is built as:
73 * onBeforeSave[TABLE_NAME], where [TABLE_NAME] is the name of
74 * file in which the table child is written.
75 *
76 * @param mixed &$src The array/object to bind.
77 * @param JTable $table The table instance.
78 *
79 * @return boolean False to abort saving.
80 *
81 * @throws Exception It is possible to throw an exception to abort
82 * the saving process and return a readable message.
83 *
84 * @since 1.7
85 */
86 if (VAPFactory::getEventDispatcher()->false($event, array(&$src, $this)))
87 {
88 // abort in case a plugin returned false
89 $result = false;
90 }
91 }
92 catch (Exception $e)
93 {
94 // register the error thrown by the plugin and abort
95 $this->setError($e);
96
97 $result = false;
98 }
99
100 // always store the specified data before binding
101 if ($src)
102 {
103 $this->setUserStateData($src);
104 }
105
106 // dispatch parent to complete saving
107 return $result && parent::save($src, $orderingFilter, $ignore);
108 }
109
110 /**
111 * Method to perform sanity checks on the Table instance properties to
112 * ensure they are safe to store in the database.
113 *
114 * @return boolean True if the instance is sane and able to be stored in the database.
115 */
116 public function check()
117 {
118 $pk = $this->getKeyName();
119
120 // iterate required fields
121 foreach ($this->_requiredFields as $col)
122 {
123 if (isset($this->{$col}))
124 {
125 // parameter given, make sure it is not an empty string
126 $blank = is_scalar($this->{$col}) && !strlen(trim($this->{$col}));
127 }
128 else
129 {
130 // parameter missing, mark as mandatory only in case of insert
131 $blank = !$this->{$pk};
132 }
133
134 // in case the property was specified, make sure it is not an empty string
135 if ($blank)
136 {
137 // register error message
138 $this->setError(JText::sprintf('VAP_MISSING_REQ_FIELD', $col));
139
140 // unsafe record
141 return false;
142 }
143 }
144
145 // safe record
146 return true;
147 }
148
149 /**
150 * Method to store a row in the database from the Table instance properties.
151 *
152 * If a primary key value is set the row with that primary key value will be updated with the instance property values.
153 * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance.
154 *
155 * @param boolean $updateNulls True to update fields even if they are null.
156 *
157 * @return boolean True on success.
158 */
159 public function store($updateNulls = false)
160 {
161 $pk = $this->getKeyName();
162
163 $is_new = empty($this->{$pk});
164
165 if (!empty($this->_updateNulls))
166 {
167 // force update of NULL columns
168 $updateNulls = true;
169
170 // reset internal property
171 $this->_updateNulls = false;
172 }
173
174 // invoke parent to store the record
175 if (!parent::store($updateNulls))
176 {
177 // do not proceed in case of error
178 return false;
179 }
180
181 // always clean previous data after a successful saving
182 $this->setUserStateData(null);
183
184 // get customer data
185 $args = $this->getProperties();
186
187 // create "after save" event
188 $event = 'onAfterSave' . ucfirst($this->getName());
189
190 /**
191 * Trigger event to allow the plugins to make something after saving
192 * a record in the database. The event to use is built as:
193 * onAfterSave[TABLE_NAME], where [TABLE_NAME] is the name of
194 * file in which the table child is written.
195 *
196 * @param array $args The saved record.
197 * @param boolean $is_new True if the record was inserted.
198 * @param JTable $table The table instance.
199 *
200 * @return void
201 *
202 * @since 1.7
203 */
204 VAPFactory::getEventDispatcher()->trigger($event, array($args, $is_new, $this));
205
206 return true;
207 }
208
209 /**
210 * Method to delete one or more records.
211 *
212 * @param mixed $ids Either the record ID or a list of records.
213 *
214 * @return boolean True on success.
215 */
216 public function delete($ids = null)
217 {
218 if (!$ids)
219 {
220 return false;
221 }
222
223 // always treat the IDs as an array
224 $ids = (array) $ids;
225
226 $dispatcher = VAPFactory::getEventDispatcher();
227
228 // create "before delete" event
229 $event = 'onBeforeDelete' . ucfirst($this->getName());
230
231 try
232 {
233 /**
234 * Trigger event to allow the plugins to make something before deleting
235 * one or more records from the database. The event to use is built as:
236 * onBeforeDelete[TABLE_NAME], where [TABLE_NAME] is the name of the
237 * file in which the table child is written.
238 *
239 * @param array $ids An array of IDs to delete.
240 * @param JTable $table The table instance.
241 *
242 * @return boolean False to abort delete.
243 *
244 * @throws Exception It is possible to throw an exception to abort
245 * the delete process and return a readable message.
246 *
247 * @since 1.7
248 */
249 if ($dispatcher->false($event, array($ids, $this)))
250 {
251 // a plugin aborted this action
252 return false;
253 }
254 }
255 catch (Exception $e)
256 {
257 // register the error thrown by the plugin and abort
258 $this->setError($e);
259
260 return false;
261 }
262
263 $dbo = JFactory::getDbo();
264
265 // delete records
266 $q = $dbo->getQuery(true)
267 ->delete($dbo->qn($this->getTableName()))
268 ->where($dbo->qn($this->getKeyName()) . ' IN (' . implode(',', $ids) . ')');
269
270 $dbo->setQuery($q);
271 $dbo->execute();
272
273 if (!$dbo->getAffectedRows())
274 {
275 // nothing to delete
276 return false;
277 }
278
279 // create "after delete" event
280 $event = 'onAfterDelete' . ucfirst($this->getName());
281
282 // trigger a separated event for each ID in the list
283 foreach ($ids as $id)
284 {
285 /**
286 * Trigger event to allow the plugins to make something after deleting
287 * one or more records from the database. The event to use is built as:
288 * onAfterDelete[TABLE_NAME], where [TABLE_NAME] is the name of the
289 * file in which the table child is written.
290 *
291 * @param integer $id The deleted ID.
292 * @param JTable $table The table instance.
293 *
294 * @return void
295 *
296 * @since 1.7
297 */
298 $dispatcher->trigger($event, array($id, $this));
299 }
300
301 return true;
302 }
303
304 /**
305 * Method to set the publishing state for a row or list of rows in the database table.
306 *
307 * The method respects checked out rows by other users and will attempt to checkin rows that it can after adjustments are made.
308 *
309 * @param mixed $pks An optional array of primary key values to update. If not set the instance property value is used.
310 * @param integer $state The publishing state. eg. [0 = unpublished, 1 = published]
311 * @param integer $userId The user ID of the user performing the operation.
312 *
313 * @return boolean True on success; false if $pks is empty.
314 *
315 * @since 1.7.1
316 */
317 public function publish($pks = null, $state = 1, $userId = 0)
318 {
319 if (!$pks)
320 {
321 return false;
322 }
323
324 $pks = (array) $pks;
325
326 $dispatcher = VAPFactory::getEventDispatcher();
327
328 // create "before publish" event
329 $event = 'onBeforePublish' . ucfirst($this->getName());
330
331 try
332 {
333 /**
334 * Trigger event to allow the plugins to make something before publishing
335 * or unpublishing one or more records. The event to use is built as:
336 * onBeforePublish[TABLE_NAME], where [TABLE_NAME] is the name of the
337 * file in which the table child is written.
338 *
339 * @param mixed $pks An optional array of primary key values to update.
340 * @param integer $state The publishing state.
341 * @param JTable $table The table instance.
342 *
343 * @return boolean False to abort publishing.
344 *
345 * @throws Exception It is possible to throw an exception to abort the
346 * publishing process and return a readable message.
347 *
348 * @since 1.7.1
349 */
350 if ($dispatcher->false($event, array($pks, $state, $this)))
351 {
352 // a plugin aborted this action
353 return false;
354 }
355 }
356 catch (Exception $e)
357 {
358 // register the error thrown by the plugin and abort
359 $this->setError($e);
360
361 return false;
362 }
363
364 // publish through parent
365 if (!parent::publish($pks, $state, $userId))
366 {
367 // something went wrong...
368 return false;
369 }
370
371 // create "after publish" event
372 $event = 'onAfterPublish' . ucfirst($this->getName());
373
374 // trigger a separated event for each ID in the list
375 foreach ($pks as $id)
376 {
377 /**
378 * Trigger event to allow the plugins to make something after publishing
379 * or unpublishing one or more records. The event to use is built as:
380 * onAfterPublish[TABLE_NAME], where [TABLE_NAME] is the name of the
381 * file in which the table child is written.
382 *
383 * @param integer $id The ID of the updated record.
384 * @param integer $state The publishing state.
385 * @param JTable $table The table instance.
386 *
387 * @return void
388 *
389 * @since 1.7.1
390 */
391 $dispatcher->trigger($event, array($id, $state, $this));
392 }
393
394 return true;
395 }
396
397 /**
398 * Sets the relations between the given entry and the specified records list.
399 * In order to be used, the children class must declare the following properties:
400 * - _tbl_assoc_pk the assoc column of the primary table;
401 * - _tbl_assoc_fk the assoc column of the foreign table.
402 *
403 * @param mixed $id The assoc column of the primary table.
404 * @param array $records The assoc column of the foreign table.
405 *
406 * @return void
407 */
408 public function setRelation($id, array $records)
409 {
410 if (empty($id) || !isset($this->_tbl_assoc_pk) || !isset($this->_tbl_assoc_fk))
411 {
412 return;
413 }
414
415 $dbo = JFactory::getDbo();
416
417 // get existing records
418 $q = $dbo->getQuery(true)
419 ->select($dbo->qn($this->_tbl_assoc_fk))
420 ->from($dbo->qn($this->getTableName()))
421 ->where($dbo->qn($this->_tbl_assoc_pk) . ' = ' . (int) $id);
422
423 $dbo->setQuery($q);
424 $existing = $dbo->loadColumn();
425
426 // insert new records
427
428 $has = false;
429
430 $q = $dbo->getQuery(true)
431 ->insert($dbo->qn($this->getTableName()))
432 ->columns($dbo->qn(array($this->_tbl_assoc_pk, $this->_tbl_assoc_fk)));
433
434 foreach ($records as $s)
435 {
436 // make sure the record to push doesn't exist yet
437 if (!in_array($s, $existing))
438 {
439 $q->values($id . ', ' . $s);
440 $has = true;
441 }
442 }
443
444 if ($has)
445 {
446 $dbo->setQuery($q);
447 $dbo->execute();
448 }
449
450 // delete records
451
452 $delete = array();
453
454 foreach ($existing as $s)
455 {
456 // make sure the records to delete is not contained in the selected records
457 if (!in_array($s, $records))
458 {
459 $delete[] = $s;
460 }
461 }
462
463 if (count($delete))
464 {
465 $q = $dbo->getQuery(true)
466 ->delete($dbo->qn($this->getTableName()))
467 ->where(array(
468 $dbo->qn($this->_tbl_assoc_pk) . ' = ' . $id,
469 $dbo->qn($this->_tbl_assoc_fk) . ' IN (' . implode(',', $delete) . ')',
470 ));
471
472 $dbo->setQuery($q);
473 $dbo->execute();
474 }
475 }
476
477 /**
478 * Helper method used to store the user data within the session.
479 *
480 * @param mixed $data The array data to store.
481 *
482 * @return self This object to support chaining.
483 */
484 public function setUserStateData($data = null)
485 {
486 // extract table name from class
487 if (preg_match("/^([a-z]+)Table(.+?)$/i", get_class($this), $match) && $this->_setUserState)
488 {
489 $prefix = strtolower($match[1]);
490 $name = strtolower($match[2]);
491
492 // before registering the user state, make sure that
493 // the headers haven't been sent yet, in order to avoid
494 // post fatal errors
495 if (headers_sent() == false)
496 {
497 // set user state for later use
498 JFactory::getApplication()->setUserState($prefix . '.' . $name . '.data', $data);
499 }
500 }
501
502 return $this;
503 }
504
505 /**
506 * Helper method used to retrieve the user data saved in the session.
507 *
508 * @return array
509 */
510 public function getUserStateData()
511 {
512 // extract table name from class
513 if (preg_match("/^([a-z]+)Table(.+?)$/i", get_class($this), $match))
514 {
515 $prefix = strtolower($match[1]);
516 $name = strtolower($match[2]);
517
518 // before registering the user state, make sure that
519 // the headers haven't been sent yet, in order to avoid
520 // post fatal errors
521 if (headers_sent() == false)
522 {
523 // set user state for later use
524 return JFactory::getApplication()->getUserState($prefix . '.' . $name . '.data', array());
525 }
526 }
527
528 return array();
529 }
530
531 /**
532 * Recovers the table class name.
533 *
534 * @return string The class name.
535 */
536 protected function getName()
537 {
538 // extract table name from object class
539 if (preg_match("/Table([a-z0-9_]+)$/i", get_class($this), $match))
540 {
541 return strtolower(end($match));
542 }
543
544 return null;
545 }
546
547 /**
548 * Returns an associative array of object properties.
549 * Override parent method to excluded native vars.
550 *
551 * @param boolean $public If true, returns only the public properties.
552 *
553 * @return array
554 *
555 * @since 1.7.1
556 */
557 public function getProperties($public = true)
558 {
559 // invoke parent
560 $args = parent::getProperties($public);
561
562 // introduced in J4
563 unset($args['typeAlias']);
564
565 return $args;
566 }
567 }
568