PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 All 36 releases
vikbooking / admin / helpers / src / notification / elements.php

elements.php in VikBooking Hotel Booking Engine & PMS trunk, at admin/helpers/src/notification/elements.php

454 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author Alessio Gaggii - E4J s.r.l.
6 * @copyright Copyright (C) 2024 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 * Notification elements registry to store a record for the Notification Center.
16 *
17 * @since 1.16.8 (J) - 1.6.8 (WP)
18 * @since 1.18.3 (J) - 1.8.3 (WP) refactoring makes the class no longer "final".
19 */
20 class VBONotificationElements extends JObject
21 {
22 /**
23 * Valid notification group enumerations.
24 *
25 * @var array
26 */
27 private $groupEnums = [
28 // notifications from Website
29 'website',
30 // notifications from OTAs
31 'otas',
32 // notifications from Channel Manager
33 'cm',
34 // notifications from Guests
35 'guests',
36 // notifications from Operators
37 'operators',
38 // notifications from (PMS) Reports
39 'reports',
40 // notifications from AI
41 'ai',
42 // notifications from Door Access Control
43 'dac',
44 // notifications from any Webhook event
45 'webhook',
46 ];
47
48 /**
49 * Default notification type.
50 *
51 * @var string
52 */
53 private $defaultType = 'info';
54
55 /**
56 * Constructs the proper notification elements object according to the notification type.
57 *
58 * @param array|object $notification The notification properties.
59 *
60 * @return VBONotificationElements
61 *
62 * @since 1.18.3 (J) - 1.8.3 (WP)
63 */
64 public static function getInstance($notification)
65 {
66 $notification = (array) $notification;
67
68 // access the notification type
69 $type = ucfirst(preg_replace('/[^a-z0-9]+/', '', strtolower((string) ($notification['type'] ?? 'null'))));
70
71 // build the expected notification class name
72 $ncn = __CLASS__ . $type;
73
74 if (class_exists($ncn)) {
75 return new $ncn($notification);
76 }
77
78 // construct a "generic" notification elements object
79 return new static($notification);
80 }
81
82 /**
83 * Determines and returns the group to which the notification belongs.
84 *
85 * @return string
86 */
87 public function getGroup()
88 {
89 // check if the group was already determined
90 $group = (string) $this->get('_group', '');
91
92 if (!$group) {
93 // access the notification sender property
94 $group = strtolower((string) $this->get('sender', $this->groupEnums[0]));
95
96 // validate the group property
97 $group = in_array($group, $this->groupEnums) ? $group : $this->groupEnums[0];
98
99 // cache determined group
100 $this->set('_group', $group);
101 }
102
103 return $group;
104 }
105
106 /**
107 * Returns the notification type.
108 *
109 * @return string
110 */
111 public function getType()
112 {
113 // access the notification type
114 $type = strtolower((string) $this->get('type', $this->defaultType));
115
116 // ensure the maximum length of 32 chars is respected
117 return $this->shortenString($type, 32, $this->defaultType);
118 }
119
120 /**
121 * Returns the notification title.
122 *
123 * @return string
124 */
125 public function getTitle()
126 {
127 // access the notification title
128 $title = strip_tags((string) $this->get('title', ''));
129
130 // try to guess the title
131 if (!$title) {
132 // check for virtual credit card balance
133 if (strpos($this->getType(), 'vcc_balance') === 0) {
134 $title = JText::translate('VBO_VCC_BALANCE');
135 if ($title == 'VBO_VCC_BALANCE') {
136 $title = ucwords(str_replace('_', ' ', $this->getType()));
137 }
138 }
139 }
140
141 // ensure the maximum length of 64 chars is respected
142 return $this->shortenString($title, 64);
143 }
144
145 /**
146 * Returns the notification summary.
147 *
148 * @return string
149 */
150 public function getSummary()
151 {
152 // access the notification summary
153 $summary = strip_tags((string) $this->get('summary', ''));
154
155 // normalize the summary, if needed
156 if ($summary && $this->getChannel() && !strcasecmp($this->getType(), 'lvf')) {
157 // listing verification framework (LVF)
158 $summary = JText::sprintf('VBO_VERIFY_LISTING_INFO', $summary);
159 }
160
161 // ensure the maximum length of 256 chars is respected
162 return $this->shortenString($summary, 256);
163 }
164
165 /**
166 * Returns the notification avatar URI.
167 *
168 * @return string
169 *
170 * @since 1.18.0 (J) - 1.8.0 (WP)
171 */
172 public function getAvatar()
173 {
174 // access the notification avatar
175 $avatar = (string) $this->get('avatar', '');
176
177 // turn full internal URIs into relative URIs
178 $avatar = str_replace(JUri::root(), '', $avatar);
179
180 // ensure the maximum length of 256 chars is respected
181 return $this->shortenString($avatar, 256);
182 }
183
184 /**
185 * Builds and returns the notification call-to-action data.
186 *
187 * @return null|string
188 */
189 public function getCallToActionData()
190 {
191 $cta_data = [];
192
193 // check if a widget identifier was provided
194 $widget = (string) $this->get('widget', '');
195 if ($widget) {
196 $cta_data['widget'] = $widget;
197 }
198
199 // check if some widget options were provided
200 $widget_options = (array) $this->get('widget_options', []);
201 if ($widget_options) {
202 $cta_data['widget_options'] = $widget_options;
203 }
204
205 // check if a notification URL was provided
206 $cta_url = (string) $this->get('cta_url', $this->get('url'));
207 if ($cta_url) {
208 $cta_data['url'] = $cta_url;
209 }
210
211 // check if a custom label was provided
212 if ($cta_data && is_string($this->get('label'))) {
213 $cta_data['label'] = $this->get('label');
214 }
215
216 if (!$cta_data) {
217 // attempt to determine the CTA payload to set
218 if (strpos($this->getType(), 'vcc_balance') === 0 && ($this->getReservationID() || $this->getOTAReservationID())) {
219 // set call-to-action for Virtual Terminal admin-widget for VCC balance
220 $cta_data = [
221 'label' => JText::translate('VBO_CC_DOCHARGE'),
222 'widget' => 'virtual_terminal',
223 'widget_options' => [
224 'bid' => $this->getReservationID() ?: $this->getOTAReservationID(),
225 ],
226 ];
227 } elseif (!strcasecmp($this->getType(), 'guest_message') && ($this->getReservationID() || $this->getOTAReservationID())) {
228 // set call-to-action for Guest Messages admin-widget to reply to the guest message
229 $cta_data = [
230 'label' => JText::translate('VBO_REPLY'),
231 'widget' => 'guest_messages',
232 'widget_options' => [
233 'bid' => $this->getReservationID() ?: $this->getOTAReservationID(),
234 ],
235 ];
236 } elseif (!strcasecmp($this->getType(), 'ob') && $this->getReservationID()) {
237 // set call-to-action for Bookings Calendar admin-widget in case of overbooking
238 $cta_data = [
239 'widget' => 'bookings_calendar',
240 'widget_options' => [
241 'bid' => $this->getReservationID(),
242 'overbooking' => 1,
243 ],
244 ];
245 }
246 }
247
248 return $cta_data ? json_encode($cta_data) : null;
249 }
250
251 /**
252 * Returns the VikBooking reservation ID.
253 *
254 * @return null|int
255 */
256 public function getReservationID()
257 {
258 $res_id = $this->get('idorder', null);
259
260 return $res_id ? (int) $res_id : null;
261 }
262
263 /**
264 * Returns the OTA reservation ID.
265 *
266 * @return null|string
267 */
268 public function getOTAReservationID()
269 {
270 $ota_res_id = $this->get('idorderota', null);
271
272 return $ota_res_id ? (string) $ota_res_id : null;
273 }
274
275 /**
276 * Returns the channel name.
277 *
278 * @return null|string
279 */
280 public function getChannel()
281 {
282 $channel = $this->get('channel', null);
283
284 return $channel ? (string) $channel : null;
285 }
286
287 /**
288 * Returns the notification date and time.
289 *
290 * @return string
291 */
292 public function getDate()
293 {
294 try {
295 $date = JFactory::getDate($this->get('date') ?: 'now');
296 } catch(Exception $e) {
297 $date = JFactory::getDate();
298 }
299
300 return $date->toSql();
301 }
302
303 /**
304 * Returns the notification signature.
305 *
306 * @return string
307 */
308 public function getSignature()
309 {
310 $signature = (string) $this->get('_signature', '');
311
312 if (!$signature) {
313 $signature = $this->buildSignature();
314 }
315
316 return $signature;
317 }
318
319 /**
320 * Sets the notification signature.
321 *
322 * @return self
323 */
324 public function setSignature(string $signature = '')
325 {
326 $this->set('_signature', $signature);
327
328 return $this;
329 }
330
331 /**
332 * Builds, sets and returns the notification signature.
333 *
334 * @return string
335 *
336 * @since 1.18.3 (J) - 1.8.3 (WP) signature also contains "idorderota".
337 */
338 public function buildSignature()
339 {
340 // build notification signature elements
341 $elements = [
342 'id' => $this->get('id', $this->get('notification_id', 0)),
343 'idorder' => $this->get('idorder', 0),
344 'idorderota' => $this->get('idorderota', 0),
345 'sender' => $this->get('sender', ''),
346 'type' => $this->get('type', ''),
347 'title' => $this->get('title', ''),
348 'summary' => $this->get('summary', ''),
349 ];
350
351 // build notification signature string
352 $signature = md5(serialize($elements));
353
354 // set signature string
355 $this->setSignature($signature);
356
357 return $signature;
358 }
359
360 /**
361 * Method invoked after storing a notification for eventually executing actions.
362 * Specific notification (class) types should implement their own actions.
363 *
364 * @return void
365 *
366 * @since 1.18.3 (J) - 1.8.3 (WP)
367 */
368 public function postflight()
369 {
370 return;
371 }
372
373 /**
374 * Returns the notification error code.
375 *
376 * @return int
377 */
378 public function getErrorCode()
379 {
380 return (int) $this->get('_errorCode', 500);
381 }
382
383 /**
384 * Sets the notification error code.
385 *
386 * @return self
387 */
388 public function setErrorCode($code = 500)
389 {
390 $this->set('_errorCode', $code);
391
392 return $this;
393 }
394
395 /**
396 * Ensures a string reflects the given length, or it will be eventually shortened or replaced.
397 *
398 * @param string $value the string to check.
399 * @param int $length the length to reflect.
400 * @param string $fallback optional string to replace as fallback.
401 *
402 * @return string
403 */
404 private function shortenString(string $value, int $length, string $fallback = '')
405 {
406 if (strlen($value) <= $length || $length <= 0) {
407 // length is safe
408 return $value;
409 }
410
411 if ($fallback) {
412 // replace string with provided fallback
413 return $fallback;
414 }
415
416 // shorten the string to the desired length
417 if (!function_exists('mb_strlen')) {
418 // use a regular sub-string without multi-byte support
419 return rtrim(substr($value, 0, $length - 3), '.,?!;:#\'"([{ ') . '...';
420 }
421
422 // calculate string length
423 $size = strlen($value);
424 $mb_size = mb_strlen($value);
425 $ch_diff = $size - $mb_size;
426
427 if ($ch_diff <= 0) {
428 // no multi-byte chars found
429 return rtrim(substr($value, 0, $length - 3), '.,?!;:#\'"([{ ') . '...';
430 }
431
432 // safely construct the string with one multibyte char per time
433 $mb_value = '';
434 $mb_char_start = 0;
435 while (strlen($mb_value) < $length - 3) {
436 // get a one-char multi-byte portion
437 $mb_portion = mb_substr($value, $mb_char_start, 1, 'UTF-8');
438
439 if (strlen($mb_value . $mb_portion) > $length) {
440 // abort to not exceed the length
441 return $mb_value;
442 }
443
444 // add portion to string value
445 $mb_value .= $mb_portion;
446
447 // increase chart start counter
448 $mb_char_start++;
449 }
450
451 return rtrim($mb_value, '.,?!;:#\'"([{ ') . '...';
452 }
453 }
454