PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / trunk
VikAppointments Services Booking Calendar vtrunk
trunk 1.2.17 1.2.18 1.2.19
vikappointments / libraries / system / rssfeeds.php
vikappointments / libraries / system Last commit date
assets.php 1 month ago body.php 4 years ago builder.php 1 month ago cron.php 4 years ago feedback.php 4 years ago gutenberg.php 2 years ago install.php 1 month ago mce.php 4 years ago rssfeeds.php 5 months ago screen.php 4 years ago
rssfeeds.php
477 lines
1 <?php
2 /**
3 * @package VikAppointments - Libraries
4 * @subpackage system
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 * Helper class used to manage settings related to RSS feeds.
16 *
17 * @since 1.1.9
18 */
19 class VikAppointmentsRssFeeds
20 {
21 /**
22 * Hook used to manipulate the RSS channels.
23 *
24 * @param array $channels A list of RSS permalinks.
25 * @param boolean $published True to return only the published channels.
26 *
27 * @return array The channels to use for RSS subscription.
28 */
29 public static function getChannels(array $channels = array(), $published = true)
30 {
31 // subscribe reader to the following channels
32 $default = array(
33 'https://vikwp.com/rss/news/',
34 'https://vikwp.com/rss/promo/',
35 'https://vikwp.com/rss/tips/',
36 );
37
38 // allow channels manipulation only to PRO users
39 if (VikAppointmentsLicense::isPro() && $published)
40 {
41 $user = JFactory::getUser();
42
43 // get channels configuration
44 $config = get_user_meta($user->id, 'vikappointments_rss_urls', true);
45
46 // make sure we have a configuration
47 if (is_array($config))
48 {
49 // take only the active channels
50 $default = array_intersect($default, $config);
51 }
52 }
53
54 // apply filters only in case we need the final URI
55 if ($published)
56 {
57 // build query string for each channel
58 $default = array_map(function($url)
59 {
60 // create URI
61 $url = new JUri($url);
62
63 // append format and type
64 $url->setVar('format', 'feed');
65 $url->setVar('type', 'rss');
66
67 // apply tag filter (4: vikappointments, 10: vap-lite, 11: vap-pro)
68 $tags = array(4);
69
70 if (VikAppointmentsLicense::isPro())
71 {
72 // PRO tag
73 $tags[] = 11;
74 }
75 else
76 {
77 // LITE tag
78 $tags[] = 10;
79 }
80
81 $url->setVar('filter_tag', $tags);
82
83 // take language from WP locale
84 $langtag = JFactory::getLanguage()->getTag();
85
86 // look for language part
87 if (preg_match("/^([a-z]{2,})[\-_][a-z]{2,}$/i", $langtag, $match))
88 {
89 // Append language to URI.
90 // In case the language is not supported, the system
91 // will fallback to the default one.
92 $url->setVar('lang', end($match));
93 }
94
95 // take only the channel string
96 return (string) $url;
97 }, $default);
98 }
99
100 // merge default channels with existing ones
101 return array_merge($channels, $default);
102 }
103
104 /**
105 * Downloads the latest feed and display it, if any.
106 *
107 * @return void
108 */
109 public static function download()
110 {
111 $app = JFactory::getApplication();
112 $user = JFactory::getUser();
113
114 // make sure that all the following conditions are verified
115 $conditions = (
116 // we are not doing AJAX
117 !wp_doing_ajax()
118 // we are in the back-end
119 && $app->isClient('administrator')
120 // the user is an administrator
121 && $user->authorise('core.admin', 'com_vikappointments')
122 // the dashboard should display the RSS feeds
123 && static::isDashboard()
124 );
125
126 // validate conditions flag
127 if ($conditions)
128 {
129 // instantiate RSS reader
130 $rss = VikAppointmentsBuilder::setupRssReader();
131
132 try
133 {
134 // prepare download options
135 $options = array(
136 // take only one item
137 'limit' => 1,
138 // take only visible feeds
139 'new' => true,
140 // take oldest feed
141 'order' => 'asc',
142 );
143
144 // try to download the feed
145 $feed = $rss->download($options);
146
147 if ($feed)
148 {
149 // opt in missing, ask the user to agree our terms
150 echo JLayoutHelper::render('html.rss.feed', array('feed' => $feed));
151 }
152 }
153 catch (JRssOptInException $e)
154 {
155 // opt in missing, ask the user to agree our terms
156
157 $dbo = JFactory::getDbo();
158
159 // count number of appointments
160 $q = $dbo->getQuery(true)
161 ->select('COUNT(1)')
162 ->from($dbo->qn('#__vikappointments_reservation'));
163
164 $dbo->setQuery($q);
165 $dbo->execute();
166
167 /**
168 * Ask for opt-in only after a basic configuration
169 * of the plugin, in order to avoid spamming a popup
170 * at the beginning.
171 *
172 * The opt-in will be asked only after receiving at
173 * least 3 appointment requests.
174 *
175 * @since 1.1.10
176 */
177 if ((int) $dbo->loadResult() >= 3)
178 {
179 // display opt-in popup
180 echo JLayoutHelper::render('html.rss.optin');
181 }
182 }
183 catch (Throwable $e)
184 {
185 // service declined, go ahead silently
186 }
187 }
188 }
189
190 /**
191 * Displays the configuration fieldset to manage the opt-in.
192 *
193 * @param mixed $forms The HTML to display.
194 * @param mixed $view The current view instance.
195 *
196 * @return mixed The HTML to display.
197 *
198 * @return mixed The HTML to display.
199 */
200 public static function config($forms, $view)
201 {
202 $app = JFactory::getApplication();
203 $user = JFactory::getUser();
204
205 if (!is_array($forms))
206 {
207 $forms = array();
208 }
209
210 // make sure we are under VikAppointments
211 if ($app->input->get('option') != 'com_vikappointments')
212 {
213 // do not go ahead
214 return $forms;
215 }
216
217 // instantiate RSS reader
218 $rss = VikAppointmentsBuilder::setupRssReader();
219
220 // set up configuration array
221 $config = array();
222
223 try
224 {
225 $config['optin'] = $rss->optedIn();
226 }
227 catch (Exception $e)
228 {
229 $config['optin'] = false;
230 }
231
232 // get published channels
233 $config['channels'] = $rss->getChannels();
234
235 // take only the host and path because the query string might vary
236 $config['channels'] = array_map(function($url)
237 {
238 $url = new JUri($url);
239 $url->setQuery('');
240 return (string) $url;
241 }, $config['channels']);
242
243 // load all supported channels
244 $list = apply_filters('vikappointments_fetch_rss_channels', array(), false);
245
246 $channels = array();
247
248 // iterate channels to fetch a readable label
249 foreach ($list as $url)
250 {
251 $url = new JUri($url);
252
253 // get path without trailing slash
254 $key = trim($url->toString(array('host', 'path')), '/');
255 // explode paths
256 $chunks = explode('/', $key);
257 // take only the last
258 $key = array_pop($chunks);
259
260 // prepend path recursively in case of non-unique path
261 while (isset($channels[$key]) && $chunks)
262 {
263 $key = array_pop($chunks) . ' ' . $key;
264 }
265
266 // remove query from URL
267 $url->setQuery('');
268
269 // register channel
270 $channels[$key] = (string) $url;
271 }
272
273 // get display dashboard from user meta
274 $config['dashboard'] = static::isDashboard();
275
276 // prepare layout data
277 $data = array(
278 'rss' => $rss,
279 'config' => $config,
280 'channels' => $channels,
281 );
282
283 // include sub-fieldset to enable RSS configuration
284 $layout = new JLayoutFile('html.rss.config');
285 // render layout
286 $html = $layout->render($data);
287
288 // create an apposite fieldset for RSS
289 $forms['RSS'] = $html;
290
291 return $forms;
292 }
293
294 /**
295 * Saves the opt-in choice made by the user.
296 *
297 * @param mixed $save False to abort saving.
298 * @param array &$args The array to bind.
299 *
300 * @return boolean False to abort saving.
301 *
302 * @return void
303 */
304 public static function save($save, $args)
305 {
306 $app = JFactory::getApplication();
307 $user = JFactory::getUser();
308
309 // make sure we are under VikAppointments
310 if ($app->input->get('option') != 'com_vikappointments')
311 {
312 // do not go ahead
313 return;
314 }
315
316 // instantiate RSS reader
317 $rss = VikAppointmentsBuilder::setupRssReader();
318
319 try
320 {
321 $status = $rss->optedIn();
322 }
323 catch (Exception $e)
324 {
325 $status = false;
326 }
327
328 $input = JFactory::getApplication()->input;
329
330 // get user choice
331 $choice = $input->getBool('rss_optin_status', false);
332
333 // check whether the choice changed
334 if ($choice != $status)
335 {
336 // update choice
337 $rss->optIn($choice);
338 }
339
340 // recover display dashboard from request
341 $dashboard = $input->get('rss_display_dashboard', 0, 'uint');
342
343 // update dashboard visibility
344 update_user_meta($user->id, 'vikappointments_rss_display_dashboard', $dashboard);
345
346 // allow channels manipulation only to PRO users
347 if (VikAppointmentsLicense::isPro())
348 {
349 // recover specified channels from request
350 $channels = $input->get('rss_channel_url', array(), 'string');
351
352 // update channels configuration
353 update_user_meta($user->id, 'vikappointments_rss_urls', $channels);
354 }
355 }
356
357 /**
358 * Adjusts the RSS class to the plugin needs.
359 * Executes before using it.
360 *
361 * @param JRssReader $rss The RSS reader handler.
362 *
363 * @return void
364 */
365 public static function ready(&$rss)
366 {
367 /**
368 * Filters list of allowed CSS attributes.
369 *
370 * @since 2.8.1
371 *
372 * @param string[] $attr Array of allowed CSS attributes.
373 */
374 add_filter('safe_style_css', function($styles)
375 {
376 // add support to "display" CSS property
377 $styles[] = 'display';
378
379 return $styles;
380 });
381
382 /**
383 * Filters the HTML that is allowed for a given context.
384 *
385 * @since 3.5.0
386 *
387 * @param array $tags An associative array containing the supported tags
388 * and all the related attributes.
389 * @param string $context Context name.
390 */
391 add_filter('wp_kses_allowed_html', function($tags, $context)
392 {
393 // make sure we are filtering a POST context
394 if ($context == 'post')
395 {
396 // add support for input field
397 $tags['input'] = array(
398 'type' => true,
399 'name' => true,
400 'id' => true,
401 'class' => true,
402 'value' => true,
403 'style' => true,
404 'disabled' => true,
405 'readonly' => true,
406 );
407
408 // add support for textarea field
409 $tags['textarea'] = array(
410 'name' => true,
411 'id' => true,
412 'class' => true,
413 'style' => true,
414 'rows' => true,
415 'cols' => true,
416 'disabled' => true,
417 'readonly' => true,
418 );
419
420 // add support for button
421 if (isset($tags['button']))
422 {
423 // just include the use of onclick attribute
424 $tags['button']['onclick'] = true;
425 }
426 else
427 {
428 // define all supported attributes
429 $tags['button'] = array(
430 'type' => true,
431 'id' => true,
432 'class' => true,
433 'style' => true,
434 'onclick' => true,
435 'disabled' => true,
436 );
437 }
438
439 // add support for source under a <video> tag
440 $tags['source'] = [
441 'src' => true,
442 ];
443 }
444
445 return $tags;
446 }, 10, 2);
447 }
448
449 /**
450 * Returns true in case the RSS feeds should be displayed
451 * within the dashboard of VikAppointments.
452 *
453 * @return boolean
454 */
455 public static function isDashboard()
456 {
457 $user = JFactory::getUser();
458
459 // get display dashboard from user meta
460 $dashboard = get_user_meta($user->id, 'vikappointments_rss_display_dashboard', true);
461
462 // make sure we have a number
463 if (preg_match("/^[01]$/", (string) $dashboard))
464 {
465 // cast value to boolean
466 $dashboard = (bool) $dashboard;
467 }
468 else
469 {
470 // missing configuration, always show by default
471 $dashboard = true;
472 }
473
474 return $dashboard;
475 }
476 }
477