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 / adapter / rss / reader.php
vikappointments / libraries / adapter / rss Last commit date
feed.php 3 days ago optinexception.php 3 days ago reader.php 3 days ago
reader.php
304 lines
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.rss
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2023 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.rss.feed');
15 JLoader::import('adapter.rss.optinexception');
16
17 /**
18 * Helper class used to read the RSS feed provided by VikWP.
19 *
20 * @since 10.1.31
21 */
22 class JRssReader
23 {
24 /**
25 * A list of instances.
26 *
27 * @var array
28 */
29 protected static $instances = array();
30
31 /**
32 * A list of feed channels (URL).
33 *
34 * @var array
35 */
36 protected $channels;
37
38 /**
39 * The name of the callee;
40 *
41 * @var string
42 */
43 protected $plugin;
44
45 /**
46 * Returns the singleton of this class.
47 *
48 * @param mixed $url Either the feed URL or an array.
49 * @param string $plugin The plugin that called this class. If not
50 * specified, it will be taken from the request.
51 *
52 * @return self The instance of this object.
53 */
54 public static function getInstance($url, $plugin = null)
55 {
56 if (!$plugin)
57 {
58 // try to recover plugin name from request if not specified
59 $plugin = JFactory::getApplication()->input->get('option', 'vikplugin');
60 }
61
62 // serialize arguments
63 $sign = serialize(func_get_args());
64
65 // check whether the instance already exists
66 if (!isset(static::$instances[$sign]))
67 {
68 // instantiate only once
69 static::$instances[$sign] = new static($url, $plugin);
70 }
71
72 return static::$instances[$sign];
73 }
74
75 /**
76 * Class constructor.
77 *
78 * @param mixed $url Either the feed URL or an array.
79 * @param string $plugin The plugin that called this class.
80 */
81 protected function __construct($url, $plugin)
82 {
83 $this->channels = (array) $url;
84 $this->plugin = (string) $plugin;
85 }
86
87 /**
88 * Choose whether to opt in to the RSS feed services.
89 *
90 * @param boolean $status True to opt in, false to the decline.
91 *
92 * @return self This object to support chaining.
93 */
94 public function optIn($status = true)
95 {
96 $user = JFactory::getUser();
97
98 if ($user->guest)
99 {
100 // throw exception in case of guest user
101 throw new Exception('Guest users cannot opt-in to the RSS service', 403);
102 }
103
104 if ($status)
105 {
106 // register opt-in date
107 $status = JFactory::getDate()->toSql();
108 }
109 else
110 {
111 // service declined
112 $status = 0;
113 }
114
115 // register user choice
116 update_user_meta($user->id, $this->plugin . '_rss_optin', $status);
117
118 return $this;
119 }
120
121 /**
122 * Checks whether the user opted in the RSS feed services.
123 *
124 * @param string $date True to return the opt-in date.
125 *
126 * @return mixed $status True if opted in, false if declined.
127 * A JDate instance in case $date is true.
128 *
129 * @throws Exception In case the user didn't decide yet.
130 */
131 public function optedIn($date = false)
132 {
133 $user = JFactory::getUser();
134
135 if ($user->guest)
136 {
137 // ignore choice of guest users
138 return false;
139 }
140
141 // retrieve user choice
142 $choice = get_user_meta($user->id, $this->plugin . '_rss_optin', true);
143
144 // make sure a choice was made
145 if ($choice === false || $choice === '')
146 {
147 // missing choice, throw exception
148 throw new JRssOptInException();
149 }
150
151 if ($date && $choice)
152 {
153 // return opt-in date
154 return JFactory::getDate($choice);
155 }
156
157 return (bool) $choice;
158 }
159
160 /**
161 * Returns a list of RSS permalinks.
162 *
163 * @return array
164 */
165 public function getChannels()
166 {
167 return $this->channels;
168 }
169
170 /**
171 * Download a list of feeds from the registered channels.
172 *
173 * @param array $options A configuration array.
174 * - start the starting index to take feeds;
175 * - limit the number of feeds to take;
176 * - new true to retrieve only feeds never seen;
177 * - order the sorting mode (asc or desc).
178 *
179 * @return JRssFeed[] An array of feeds.
180 */
181 public function download(array $options = array())
182 {
183 // get opt-in date
184 $optinDate = $this->optedIn(true);
185
186 // make sure the user opted in the RSS service
187 if (!$optinDate)
188 {
189 // authorization denied, do not go ahead
190 throw new Exception('Missing RSS authorization', 403);
191 }
192
193 $feeds = [];
194
195 /**
196 * Since WP 6.9 and SimplePie 1.9, multiple channels are no longer accepted.
197 * We should rather instantiate a single object per feed and then merge them
198 * together through SimplePie::merge_items().
199 *
200 * @since 10.1.67
201 */
202 foreach ($this->channels as $channel)
203 {
204 // read feed channels
205 $feed = fetch_feed($channel);
206
207 if ($feed instanceof WP_Error)
208 {
209 // get SimplePie error
210 $error = $feed->get_error_message();
211
212 // extract first element as long as error is an array
213 while (is_array($error))
214 {
215 $error = array_shift($error);
216 }
217
218 // something went wrong, propagate error
219 throw new Exception($error, (int) $feed->get_error_code());
220 }
221
222 $feeds[] = $feed;
223 }
224
225 $list = array();
226
227 $start = isset($options['start']) ? abs($options['start']) : 0;
228 $limit = isset($options['limit']) ? abs($options['limit']) : 0;
229
230 // check if we should retrive only the visible feeds
231 $strict = isset($options['new']) ? (bool) $options['new'] : false;
232
233 // check if we should retrieve the feeds in ascending order
234 $ordering = isset($options['order']) ? strtolower($options['order']) : 'desc';
235
236 if ($strict || $ordering == 'asc')
237 {
238 // take all the items without limits because
239 // we need to check whether the feeds are visible
240 // $items = $feed->get_items();
241 $items = \SimplePie\SimplePie::merge_items($feeds);
242
243 if ($ordering == 'asc')
244 {
245 // load items in reverse order from oldest to newest
246 $items = array_reverse($items);
247 }
248 }
249 else
250 {
251 // take the feeds according to the specified limits
252 // $items = $feed->get_items($start, $limit);
253 $items = \SimplePie\SimplePie::merge_items($feeds, $start, $limit);
254 }
255
256 // iterate multi-feed property to scan all the supported feeds
257 foreach ($items as $data)
258 {
259 try
260 {
261 // encapsulate feed data
262 $feed = new JRssFeed($data, $this->plugin);
263
264 // check if we should take only visible feeds
265 if (!$strict || ($feed->isVisible() && $optinDate < $feed->date))
266 {
267 // take feed
268 $list[] = $feed;
269 }
270 }
271 catch (Throwable $error)
272 {
273 // an error has occurred while trying to initialize the feed
274 continue;
275 }
276 }
277
278 if ($strict)
279 {
280 // prepare base arguments for array_splice
281 $args = array(&$list, $start);
282
283 if ($limit)
284 {
285 // consider limit only if specified, because
286 // array_splice take care of the number of
287 // arguments instead of on their values
288 $args[] = $limit;
289 }
290
291 // take only the feeds inside the specified range
292 $list = call_user_func_array('array_splice', $args);
293 }
294
295 if ($limit == 1)
296 {
297 // take directly the first element
298 return array_shift($list);
299 }
300
301 return $list;
302 }
303 }
304