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 / libraries / mvc / admin / models / shortcode.php
vikappointments / libraries / mvc / admin / models Last commit date
acl.php 2 days ago license.php 2 days ago override.php 2 days ago overrides.php 2 days ago shortcode.php 2 days ago shortcodes.php 2 days ago
shortcode.php
463 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 JLoader::import('adapter.mvc.models.form');
15
16 /**
17 * VikAppointments plugin Shortcode model.
18 *
19 * @since 1.0
20 * @see JModelForm
21 */
22 class VikAppointmentsModelShortcode extends JModelForm
23 {
24 /**
25 * @override
26 * This method should be used to pre-load an item considering
27 * the data set in the request.
28 *
29 * For example, if the request owns an ID, this method may try
30 * to retrieve the item from the database.
31 * Otherwise it may return an empty object.
32 *
33 * @return object The object found.
34 */
35 public function loadFormData()
36 {
37 $input = JFactory::getApplication()->input;
38
39 $id = $input->getUint('cid', array(0));
40
41 return $this->getItem(array_shift($id));
42 }
43
44 /**
45 * This method should be used to retrieve the posted data
46 * after the form submission.
47 *
48 * @return object The data object.
49 */
50 public function getFormData()
51 {
52 $input = JFactory::getApplication()->input;
53
54 // get data from request
55 $data = new stdClass;
56
57 $data->id = $input->getUint('id', 0);
58 $data->title = '';
59 $data->name = $input->getString('name');
60 $data->type = $input->getString('type');
61 $data->lang = $input->getString('lang');
62 $data->parent_id = $input->getUint('parent_id', 0);
63 $data->json = array();
64 $data->shortcode = '';
65
66 // only if we are creating the shortcode, set the creation date and user
67 if ($data->id <= 0)
68 {
69 $data->createdby = JFactory::getUser()->id;
70 $data->createdon = JFactory::getDate()->toSql();
71 }
72
73 // get layout path
74 $path = implode(DIRECTORY_SEPARATOR, array(VAPBASE, 'views', $data->type, 'tmpl', 'default.xml'));
75
76 // if the file doesn't exist, raise an exception
77 if (!is_file($path))
78 {
79 throw new Exception("Missing XML [{$data->type}] view type.", 404);
80 }
81
82 // load XML form
83 $form = JForm::getInstance($data->type, $path);
84
85 // obtain view title
86 $data->title = (string) $form->getXml()->layout->attributes()->title;
87
88 // get form data
89 $formData = $input->get('jform', array(), 'array');
90
91 // get input filter
92 $inputFilter = JInputFilter::getInstance();
93
94 // iterate the layout fields
95 foreach ($form->getFields() as $field)
96 {
97 $attrs = $field->attributes();
98
99 $name = (string) $attrs->name;
100 $filter = (string) $attrs->filter;
101 $req = (string) $attrs->required;
102
103 // use string if no filter
104 if (empty($filter))
105 {
106 $filter = 'string';
107 }
108
109 if (isset($formData[$name]))
110 {
111 // clean filter in request
112 $value = $inputFilter->clean($formData[$name], $filter);
113 }
114 else
115 {
116 // use NULL
117 $value = null;
118 }
119
120 // raise an exception if a mandatory field is empty
121 if (empty($value) && $req == 'true')
122 {
123 throw new Exception("Missing required [$name] field.", 400);
124 }
125
126 // save value only if not NULL
127 if ($value !== null)
128 {
129 $data->json[$name] = $value;
130 }
131 }
132
133 $viewData = array();
134 $viewData['view'] = $data->type;
135 $viewData['lang'] = $data->lang;
136
137 // merge VIEW name and LANG TAG with JSON params
138 $args = array_merge($data->json, $viewData);
139 // generate shortcode string
140 $data->shortcode = JFilterOutput::shortcode('vikappointments', $args);
141
142 // finally encode the params in JSON
143 $data->json = json_encode($data->json);
144
145 return $data;
146 }
147
148 /**
149 * @override
150 * Retrieves the specified item.
151 *
152 * @param mixed $pk The primary key value or a list of keys.
153 * @param boolean $create True to create an empty object.
154 *
155 * @return object The item found if exists, otherwise an empty object.
156 */
157 public function getItem($pk, $create = true)
158 {
159 $item = parent::getItem($pk, $create);
160
161 if ($item && !$item->id)
162 {
163 $item->name = '';
164 $item->type = '';
165 $item->json = '{}';
166 }
167
168 return $item;
169 }
170
171 /**
172 * @override
173 * Creates or updates the specified record.
174 *
175 * @param object &$data The record to insert.
176 *
177 * @return boolean True if the record has been inserted/updated, otherwise false.
178 */
179 public function save(&$data)
180 {
181 // get old item to get previous shortcode
182 $old = $this->getItem($data->id);
183
184 // save shortcode
185 $res = parent::save($data);
186
187 if ($res && $old)
188 {
189 // get saved item to access post ID property
190 $item = $this->getItem($data->id);
191
192 /**
193 * Get the post object.
194 * Obtain post only in case the shortcode is assigned
195 * to a real ID, otherwise get_post() could retrieve
196 * the last post used.
197 *
198 * @since 1.1
199 */
200 $post = $item->post_id ? get_post($item->post_id) : null;
201
202 // proceed only in case the post exists
203 if ($post)
204 {
205 /**
206 * Do not proceed in case the post already contains the shortcode.
207 * Otherwise we would fall in a loop as wp_update_post() triggers
208 * the action that invoked the current method.
209 *
210 * @see vikappointments.php @ action:save_post
211 */
212 // if (strpos($post->post_content, $item->shortcode) === false)
213 if ($this->postContainsShortcode($post, $item->shortcode) === false)
214 {
215 // replace old shortcode with the new one from the post contents
216 // $post->post_content = str_replace($old->shortcode, $item->shortcode, $post->post_content);
217 $this->replacePostShortcode($post, $old->shortcode, $item->shortcode);
218
219 // finalize the update
220 wp_update_post($post);
221 }
222 }
223 }
224
225 return $res;
226 }
227
228 /**
229 * Checks whether the specified shortcode is contained within the given post.
230 *
231 * @param object $post The WP post object.
232 * @param string $shortcode The shortcode to search.
233 *
234 * @return bool True if found, false otherwise.
235 *
236 * @since 1.2.19
237 */
238 public function postContainsShortcode(object $post, string $shortcode)
239 {
240 // construct all shortcode variants to search
241 $variants = [
242 $shortcode, // plain
243 html_entity_decode($shortcode), // HTML safe (classic editor)
244 str_replace('"', "\u0022", $shortcode), // JSON safe (DIVI: \u0022 = ")
245 ];
246
247 foreach ($variants as $variant)
248 {
249 if (strpos($post->post_content, $variant) !== false)
250 {
251 return true;
252 }
253 }
254
255 return false;
256 }
257
258 /**
259 * Replaces the shortcode currently set on a post with a new one.
260 *
261 * @param object $post The WP post object.
262 * @param string $old The previous shortcode to replace.
263 * @param string $new The replacement.
264 *
265 * @return void
266 *
267 * @since 1.2.19
268 */
269 public function replacePostShortcode(object $post, string $old, string $new)
270 {
271 // construct all shortcode variants to search
272 $variants = [
273 // plain
274 $old => $new,
275 // HTML safe (classic editor)
276 html_entity_decode($old) => html_entity_decode($new),
277 // JSON safe (DIVI: \u0022 = ")
278 str_replace('"', "\u0022", $old) => str_replace('"', "\u0022", $new),
279 ];
280
281 foreach ($variants as $variantOld => $variantNew)
282 {
283 $post->post_content = str_replace($variantOld, $variantNew, $post->post_content);
284 }
285 }
286
287 /**
288 * Obtains the JForm object related to the model view.
289 *
290 * @param mixed $item The data to bind.
291 *
292 * @return JForm The form object.
293 */
294 public function getTypeForm($item)
295 {
296 $item = (object) $item;
297
298 if (!$item->type)
299 {
300 return null;
301 }
302
303 // inject custom vars to access the layout
304 // file of a site view (change model name and client)
305 $this->_name = $item->type;
306 $this->_client = 'site';
307
308 // get the form
309 $form = parent::getForm();
310
311 $form->setFormControl('jform');
312
313 // reset the vars (it will be taken later if needed)
314 $this->_name = null;
315 $this->_client = null;
316
317 return $form;
318 }
319
320 /**
321 * Creates a page on WordPress with for each requested shortcode.
322 * This is useful to automatically link Shortcodes in pages with no manual actions.
323 *
324 * @param mixed $ids Either an array or a shortcode ID.
325 *
326 * @return boolean True on success, false otherwise.
327 *
328 * @since 1.2.3
329 */
330 public function addPage($ids)
331 {
332 $ids = (array) $ids;
333
334 $success = false;
335
336 foreach ($ids as $id)
337 {
338 // get shortcode record
339 $item = $this->getItem($id);
340
341 if (empty($item->id))
342 {
343 // missing shortcode
344 $this->setError(sprintf('Shortcode [%d] not found', $id));
345
346 continue;
347 }
348
349 // make sure the shortcode hasn't been assigned to any pages
350 if (empty($item->post_id))
351 {
352 $post_parent = 0;
353
354 if ($item->parent_id)
355 {
356 // get parent shortcode
357 $parent = $this->getItem($item->parent_id);
358
359 if ($parent && $parent->post_id)
360 {
361 // in case the parent shortcode is assigned to a post, use it as parent
362 $post_parent = $parent->post_id;
363 }
364 }
365
366 // Add a new page (we allow a WP_ERROR to be thrown in case of failure).
367 // This should automatically trigger the hook that we use to link the shortcode
368 // to the new page/post ID, and so there's no need to update the item.
369 $post_id = wp_insert_post(array(
370 'post_title' => (!empty($item->name) ? $item->name : JText::translate($item->title)),
371 'post_content' => $item->shortcode,
372 'post_status' => 'publish',
373 'post_type' => 'page',
374 'post_parent' => $post_parent,
375 ), $wp_error = false, $fire_after_hooks = true);
376
377 // check if we received an error
378 if ($post_id instanceof WP_Error)
379 {
380 // propagate error message
381 $this->setError($post_id);
382
383 continue;
384 }
385
386 // check whether the page has been created
387 $success = $success || (is_int($post_id) && $post_id > 0);
388 }
389 }
390
391 return $success;
392 }
393
394 /**
395 * Retrieves the IDs of the ancestors of a shortcode.
396 *
397 * @param mixed $shortcode Either a shortcode ID or an object.
398 *
399 * @return array Array of ancestor IDs or empty array if there are none.
400 *
401 * @since 1.2.3
402 */
403 public function getAncestors($shortcode)
404 {
405 if (is_array($shortcode))
406 {
407 // treat associative array as object
408 $shortcode = (object) $shortcode;
409 }
410 else if (!is_object($shortcode))
411 {
412 // fetch shortcode details from ID
413 $shortcode = $this->getItem($shortcode);
414 }
415
416 $ancestors = [];
417
418 if (!$shortcode || empty($shortcode->parent_id) || $shortcode->parent_id == $shortcode->id)
419 {
420 return $ancestors;
421 }
422
423 $id = $shortcode->parent_id;
424 $ancestors[] = $id;
425
426 while ($ancestor = $this->getItem($id))
427 {
428 // Loop detection: If the ancestor has been seen before, break
429 if (empty($ancestor->parent_id) || ($ancestor->parent_id == $shortcode->id ) || in_array($ancestor->parent_id, $ancestors, true))
430 {
431 break;
432 }
433
434 $id = $ancestor->parent_id;
435 $ancestors[] = $id;
436 }
437
438 return $ancestors;
439 }
440
441 /**
442 * Method to get a table object.
443 *
444 * @param string $name The table name.
445 * @param string $prefix The class prefix.
446 * @param array $options Configuration array for table.
447 *
448 * @return JTable A table object.
449 *
450 * @since 1.2.1
451 */
452 public function getTable($name = '', $prefix = 'JTable', $options = array())
453 {
454 if (!$name)
455 {
456 $name = 'shortcode';
457 $prefix = 'VAPTable';
458 }
459
460 return parent::getTable($name, $prefix, $options);
461 }
462 }
463