PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.14
Booktics – Appointment Booking Calendar for Service Businesses v1.0.14
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / vendor / arraytics / plugin-notice-sdk / src / Stories.php

Stories.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.14, at vendor/arraytics/plugin-notice-sdk/src/Stories.php

420 lines 10.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Arraytics\PluginNotice;
3
4 class Stories
5 {
6 /**
7 * Plugin version (set from main plugin).
8 *
9 * @var string
10 */
11 protected $version = '1.0.0'; // default fallback
12
13 /**
14 * Holds the stories data object.
15 *
16 * @var object|null
17 */
18 protected $data = null;
19
20 /**
21 * Title for the stories widget.
22 *
23 * @var string|null
24 */
25 protected $title = null;
26
27 /**
28 * Plugin links to be added via filter.
29 *
30 * @var array
31 */
32 protected $plugin_link = [];
33
34 /**
35 * Timestamp of the last check for stories.
36 *
37 * @var int
38 */
39 protected $last_check = 0;
40
41 /**
42 * Interval (in seconds) between API fetches.
43 *
44 * @var int
45 */
46 protected $check_interval = 3600 * 6; // 6 hours
47
48 /**
49 * Allowed plugin admin screens for displaying stories.
50 *
51 * @var array|null
52 */
53 protected $plugin_screens = [];
54
55 /**
56 * Text domain for localization and option keys.
57 *
58 * @var string|null
59 */
60 protected $text_domain = null;
61
62 /**
63 * Comma-separated filter string for blacklisting stories.
64 *
65 * @var string|null
66 */
67 protected $filter_string = null;
68
69 /**
70 * Base URL of the API for fetching stories.
71 *
72 * @var string|null
73 */
74 protected $api_url = null;
75
76 /**
77 * Internal storage for filtered stories.
78 *
79 * @var array
80 */
81 private $stories = [];
82
83
84 /**
85 * Singleton instance
86 *
87 * @var self|null
88 */
89 private static $instance = null;
90
91 /**
92 * Get singleton instance
93 *
94 * @param string $text_domain
95 * @return self
96 */
97 public static function instance($text_domain = '')
98 {
99 if (self::$instance === null) {
100 self::$instance = new self();
101 }
102 if ($text_domain !== '') {
103 self::$instance->set_text_domain($text_domain);
104 }
105 return self::$instance;
106 }
107
108 /**
109 * Set the plugin version dynamically.
110 *
111 * @param string $version
112 * @return $this
113 */
114 public function set_version($version)
115 {
116 $this->version = $version;
117 return $this;
118 }
119
120 /**
121 * Get plugin version.
122 *
123 * @return string
124 */
125 public function get_version()
126 {
127 return $this->version;
128 }
129
130 /**
131 * Get current file path of this class
132 *
133 * @return string
134 */
135 public function get_script_location()
136 {
137 return __FILE__;
138 }
139
140 /**
141 * Set plugin link for filter hooks
142 *
143 * @param string $link_title
144 * @param string $weblink
145 * @return $this
146 */
147 public function set_plugin($link_title, $weblink = '')
148 {
149 $plugin = [$link_title, $weblink];
150 add_filter('eventin/stories/plugin_links', function ($plugin_links) use ($plugin) {
151 $plugin_links[] = $plugin;
152 return $plugin_links;
153 });
154 return $this;
155 }
156
157 /**
158 * Register the dashboard widget callback
159 */
160 public function call()
161 {
162 add_action('wp_dashboard_setup', [$this, 'show_story_widget'], 111);
163 }
164
165 /**
166 * Set the dashboard widget title
167 *
168 * @param string $title
169 * @return $this
170 */
171 public function set_title($title)
172 {
173 $this->title = $title;
174 return $this;
175 }
176
177 /**
178 * Enable test mode (shorten interval)
179 *
180 * @param bool $is_test
181 * @return $this
182 */
183 public function is_test($is_test = false)
184 {
185 if ($is_test) {
186 $this->check_interval = 1;
187 }
188 return $this;
189 }
190
191 /**
192 * Set the text domain for options and filtering
193 *
194 * @param string $text_domain
195 * @return $this
196 */
197 public function set_text_domain($text_domain)
198 {
199 $this->text_domain = $text_domain;
200 return $this;
201 }
202
203 /**
204 * Set the filter string for blacklist/whitelist filtering
205 *
206 * @param string $filter_string
207 * @return $this
208 */
209 public function set_filter($filter_string)
210 {
211 $this->filter_string = $filter_string;
212 return $this;
213 }
214
215 /**
216 * Set API URL for fetching stories
217 *
218 * @param string $url
219 * @return $this
220 */
221 public function set_api_url($url)
222 {
223 $this->api_url = rtrim($url, '/') . '/';
224 return $this;
225 }
226
227 /**
228 * Add allowed plugin screens where the widget should appear
229 *
230 * @param string $screen
231 * @return $this
232 */
233 public function set_plugin_screens($screen)
234 {
235 $this->plugin_screens[] = $screen;
236 return $this;
237 }
238
239 /**
240 * Show the dashboard widget with stories
241 */
242 public function show_story_widget()
243 {
244 $this->get_stories();
245
246 if (empty($this->data) || !empty($this->data->error)) {
247 return;
248 }
249
250 $filterList = [];
251 if (!empty($this->filter_string)) {
252 $filterList = array_filter(array_map('trim', explode(',', $this->filter_string)));
253 }
254
255 foreach ($this->data as $story) {
256 if (!empty($filterList) && $this->in_blacklist($story, $filterList)) {
257 continue;
258 }
259 $this->set_stories($story);
260 }
261
262 if (empty($this->stories)) {
263 return;
264 }
265
266 // error_log(print_r('Story loading 5', true));
267 $widget_title = $this->title ? "{$this->title} Stories" : 'Stories';
268
269 wp_add_dashboard_widget(
270 'wpmet-stories',
271 $widget_title,
272 [$this, 'show']
273 );
274
275 // Move the widget to the top of the normal-high priority widgets
276 global $wp_meta_boxes;
277 $dashboard = isset($wp_meta_boxes['dashboard']['normal']['high']) ? $wp_meta_boxes['dashboard']['normal']['high'] : [];
278 if (isset($dashboard['wpmet-stories'])) {
279 $wp_meta_boxes['dashboard']['normal']['high'] =
280 array_merge(['wpmet-stories' => $dashboard['wpmet-stories']], $dashboard);
281 }
282 }
283
284 /**
285 * Render the stories widget output
286 */
287 public function show()
288 {
289 usort($this->stories, function ($a, $b) {
290 if ($a['priority'] === $b['priority']) {
291 return 0;
292 }
293 return ($a['priority'] < $b['priority']) ? -1 : 1;
294 });
295 include_once 'views/utility-story-template.php';
296 }
297
298 /**
299 * Check if current admin screen is allowed to show stories widget
300 *
301 * @param string $b_screen
302 * @param string $screen_id
303 * @return bool
304 */
305 public function is_correct_screen_to_show($b_screen, $screen_id)
306 {
307 if (in_array($b_screen, [$screen_id, 'all_page'], true)) {
308 return true;
309 }
310 if ($b_screen === 'plugin_page') {
311 return in_array($screen_id, $this->plugin_screens ?? [], true);
312 }
313 return false;
314 }
315
316 /**
317 * Check if a given configuration is whitelisted for the provided list
318 *
319 * @param object $conf
320 * @param array $list
321 * @return bool
322 */
323 private function in_whitelist($conf, $list)
324 {
325 $match = isset($conf->data->whitelist) ? $conf->data->whitelist : '';
326 if (empty($match)) {
327 return true;
328 }
329 $match_arr = array_map('trim', explode(',', $match));
330 return count(array_intersect($list, $match_arr)) > 0;
331 }
332
333 /**
334 * Check if a given configuration is blacklisted for the provided list
335 *
336 * @param object $conf
337 * @param array $list
338 * @return bool
339 */
340 private function in_blacklist($conf, $list)
341 {
342 $match = isset($conf->data->blacklist) ? $conf->data->blacklist : '';
343 if (empty($match)) {
344 return false;
345 }
346 $match_arr = array_map('trim', explode(',', $match));
347 return count(array_intersect($list, $match_arr)) > 0;
348 }
349
350 /**
351 * Add a story to the internal collection after checks
352 *
353 * @param object $story
354 */
355 private function set_stories($story)
356 {
357 if (isset($this->stories[$story->id])) {
358 return; // already added
359 }
360
361 // Check time window (start and end timestamps)
362 if (!empty($story->start) && !empty($story->end)) {
363 $now = time();
364 if (intval($story->start) > $now || intval($story->end) < $now) {
365 return;
366 }
367 }
368
369 // Prepare filter list for active plugins + current text domain
370 $filter = [$this->text_domain];
371 foreach ((array) get_option('active_plugins', []) as $plugin) {
372 $plugin_name = pathinfo($plugin, PATHINFO_FILENAME);
373 if (!empty($plugin_name)) {
374 $filter[] = $plugin_name;
375 }
376 }
377
378 if (empty(array_intersect($filter, (array) $story->plugins))) {
379 return;
380 }
381
382 $this->stories[$story->id] = [
383 'id' => $story->id,
384 'title' => $story->title,
385 'description' => $story->description,
386 'type' => $story->type,
387 'priority' => $story->priority,
388 'story_link' => isset($story->data->story_link) ? $story->data->story_link : '',
389 'story_image' => isset($story->data->story_image) ? $story->data->story_image : '',
390 ];
391 }
392
393 /**
394 * Fetch stories from API or cache and update local storage
395 */
396 private function get_stories()
397 {
398 $this->data = get_option($this->text_domain . '__stories_data');
399 $this->data = empty($this->data) ? [] : $this->data;
400
401 $this->last_check = (int) get_option($this->text_domain . '__stories_last_check', 0);
402
403 if (($this->last_check + $this->check_interval) < time()) {
404 $response = wp_remote_get($this->api_url . 'cache/stories.json?nocache=' . time(), [
405 'timeout' => 10,
406 'httpversion' => '1.1',
407 ]);
408
409 if (!is_wp_error($response) && !empty($response['body'])) {
410 $fetched_data = json_decode($response['body']);
411 if (!empty($fetched_data)) {
412 $this->data = $fetched_data;
413 update_option($this->text_domain . '__stories_last_check', time());
414 update_option($this->text_domain . '__stories_data', $this->data);
415 }
416 }
417 }
418 }
419 }
420