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 |