PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
1.8.16 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 All 37 releases
vikbooking / admin / helpers / src / chat / mediator.php

mediator.php in VikBooking Hotel Booking Engine & PMS 1.8.16, at admin/helpers/src/chat/mediator.php

518 lines 15.0 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 E4J s.r.l.
6 * @copyright Copyright (C) 2026 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 * Chat mediator class.
16 *
17 * @since 1.8
18 */
19 class VBOChatMediator
20 {
21 /**
22 * The storage engine used for input/output purposes.
23 *
24 * @var VBOChatStorage
25 */
26 protected $storage;
27
28 /**
29 * The currently authenticated user.
30 *
31 * @var VBOChatUser
32 */
33 private $user;
34
35 /**
36 * The path where the attachments are internally stored.
37 *
38 * @var string
39 * @since 1.8.8 Changed visibility to public.
40 */
41 public $attachmentsPath;
42
43 /**
44 * A string holding all the supported file extensions, separated by a comma.
45 *
46 * @var string
47 * @since 1.8.8 Changed visibility to public.
48 */
49 public $supportedFiles;
50
51 /**
52 * Class constructor.
53 *
54 * @param VBOChatStorage $storage
55 */
56 public function __construct(VBOChatStorage $storage)
57 {
58 $this->storage = $storage;
59
60 // create default attachments folder
61 $this->attachmentsPath = (defined('VBO_MEDIA_PATH') ? VBO_MEDIA_PATH : '') . DIRECTORY_SEPARATOR . 'attachments';
62
63 // create default attachments extension filters
64 $this->supportedFiles = implode(',', [
65 // images
66 'png,apng,bmp,gif,ico,jpg,jpeg,heic,webp',
67 // videos
68 'mp4,mov,ogm,webm,3gp,asf,avi,divx,flv,mkv,mpg,mpeg,wmv,xvid',
69 // audios
70 'aac,m4a,mp3,ogg,opus,wav,wave,ac3,aiff,flac,mid,midi,wma',
71 // archives
72 'zip,tar,rar,gz,bzip2',
73 // documents
74 'pdf,doc,docx,rtf,odt,pages,txt,md,markdown',
75 // spreedsheets
76 'xls,xlsx,csv,ods,numbers',
77 // presentations
78 'pps,ppsx,odp,keynote',
79 ]);
80 }
81
82 /**
83 * Authenticates as the provided user.
84 * When no user is passed, the system will attempt to auto-login according
85 * to the client and session data.
86 *
87 * @param VBOChatUser|null $user
88 *
89 * @return self
90 */
91 public function authenticate(?VBOChatUser $user = null)
92 {
93 if ($user === null) {
94 // auto-bind the sender only if not provided
95 if (JFactory::getApplication()->isClient('administrator')) {
96 // authenticate as administrator
97 $user = new VBOChatUserAdmin;
98 } else if (class_exists('VCMAppFramework') && ($appUser = VCMAppFramework::getUser())) {
99 /**
100 * The user authenticated via APP.
101 * Create a chat account accordingly.
102 *
103 * @since 1.18.16 (J) - 1.8.16 (WP)
104 */
105 if ($appUser['admin']) {
106 // authenticate as administrator
107 $user = new VBOChatUserAdmin(new JUser($appUser['id']));
108 } else {
109 // authenticate as operator
110 $user = new VBOChatUserOperator($appUser['id']);
111 }
112 } else {
113 // fetch details of the logged in operator
114 $operator = VikBooking::getOperatorInstance()->getOperatorAccount();
115
116 if ($operator) {
117 // authenticate as operator
118 $user = new VBOChatUserOperator($operator);
119 } else {
120 // authenticate as guest user
121 $user = new VBOChatUserGuest;
122 }
123 }
124 }
125
126 $this->user = $user;
127
128 return $this;
129 }
130
131 /**
132 * Returns the currently logged in user.
133 * In case of missing authentication, the system will attempt to
134 * perform an auto-login.
135 *
136 * @return VBOChatUser
137 */
138 public function getUser()
139 {
140 if (!$this->user) {
141 // no authenticated user, auto-login now
142 $this->authenticate();
143 }
144
145 return $this->user;
146 }
147
148 /**
149 * Returns the messages matching the specified search query.
150 *
151 * @param VBOChatSearch $search
152 *
153 * @return VBOChatMessage[]
154 */
155 public function getMessages(VBOChatSearch $search)
156 {
157 $messages = [];
158
159 if (!$search->hasReader()) {
160 // forces the current user as the reader
161 $search->reader($this->getUser()->getID());
162 }
163
164 // pull the messages from the storage
165 $rows = $this->storage->getMessages($search);
166
167 foreach ($rows as $raw) {
168 // wrap raw record within a message object
169 $messages[] = $this->createMessage($raw);
170 }
171
172 return $messages;
173 }
174
175 /**
176 * Sends a new message to all the recipients of the context.
177 *
178 * @param VBOChatMessage $message
179 *
180 * @return void
181 */
182 public function send(VBOChatMessage $message)
183 {
184 $user = $this->getUser();
185
186 // force the sender name and ID according to the details of the logged in user
187 $message->setSender($user->getName(), $user->getID());
188
189 // attempt to save the message
190 $this->saveMessage($message);
191
192 // iterate all the users that should receive a notification
193 foreach ($message->getContext()->getRecipients() as $recipient) {
194 if ($message->getSenderID() == $recipient->getID()) {
195 // do not notify myself
196 continue;
197 }
198
199 if ($recipient instanceof VBOChatNotifiable) {
200 // schedule message notification
201 $recipient->scheduleNotification($message, $user);
202 }
203 }
204 }
205
206 /**
207 * Saves the provided message.
208 *
209 * @param VBOChatMessage $message The message to save.
210 *
211 * @return void
212 *
213 * @throws Exception
214 *
215 * @since 1.8.8
216 */
217 public function saveMessage(VBOChatMessage $message)
218 {
219 // save the message
220 $this->storage->saveMessage($message);
221 }
222
223 /**
224 * Removes the provided message.
225 *
226 * @param VBOChatMessage $message The message to delete.
227 *
228 * @return void
229 *
230 * @throws Exception
231 *
232 * @since 1.8.8
233 */
234 public function deleteMessage(VBOChatMessage $message)
235 {
236 // delete the message
237 $this->storage->deleteMessage($message);
238 }
239
240 /**
241 * Moves the uploaded temporary file onto the server and creates a new attachment.
242 *
243 * @param array $file The temporary file under $_FILES.
244 *
245 * @return VBOChatAttachment
246 *
247 * @throws Exception
248 */
249 public function uploadAttachment(array $file)
250 {
251 // assert attachments folder first
252 if (!JFolder::exists($this->attachmentsPath) && !JFolder::create($this->attachmentsPath)) {
253 throw new \RuntimeException('Unable to create the attachments folder: ' . $this->attachmentsPath, 403);
254 }
255
256 // create upload attachment
257 $attachment = new VBOChatAttachmentUpload($file, $this->attachmentsPath);
258
259 // make sure the file extension is supported
260 if (!VikBooking::isFileTypeCompatible($attachment->getExtension(), $this->supportedFiles)) {
261 throw new \RuntimeException('File type not supported: ' . $attachment->getExtension(), 400);
262 }
263
264 if (!$attachment->upload()) {
265 throw new \RuntimeException('Impossible to upload the file: ' . $attachment->getName(), 403);
266 }
267
268 return $attachment;
269 }
270
271 /**
272 * Removes the specified attachment from the server.
273 *
274 * @param VBOChatAttachment $attachment
275 *
276 * @return bool
277 */
278 public function removeAttachment(VBOChatAttachment $attachment)
279 {
280 if (!$attachment->exists()) {
281 return false;
282 }
283
284 $filePath = $attachment->getPath();
285
286 /**
287 * Resolve traversal segments before comparing the received path
288 * against the allowed folder.
289 *
290 * @since 1.18.11 (J) - 1.8.11 (WP)
291 */
292 $filePath = realpath($filePath);
293
294 /**
295 * Make sure the file is located under the registered attachments path
296 * to prevent arbitrary file deletion.
297 *
298 * @since 1.18.10 (J) - 1.8.10 (WP)
299 */
300 if (!$filePath || strpos($filePath, $this->attachmentsPath) !== 0) {
301 return false;
302 }
303
304 return JFile::delete($filePath);
305 }
306
307 /**
308 * Reads all the messages under the specified context for the currently logged in user.
309 *
310 * @param VBOChatContext $context The chat context.
311 * @param string|null $date When specified, only the messages with creation date equal or
312 * lower than this value will be read.
313 *
314 * @return int[] A list of read message IDs.
315 */
316 public function readMessages(VBOChatContext $context, ?string $date = null)
317 {
318 $search = (new VBOChatSearch)
319 // take the latest 50 unread messages
320 ->start(0)->limit(50)->unread()
321 // under the specified context
322 ->withContext($context)
323 // created before the specified threshold date
324 ->date($date ?: JFactory::getDate('now')->toSql(), '<=')
325 // unread by the currently logged in user
326 ->reader($this->getUser()->getID());
327
328 $read = [];
329
330 // iterate all unread messages
331 foreach ($this->storage->getMessages($search) as $message) {
332 try {
333 // read the message
334 $this->storage->readMessage($message->id, $this->getUser()->getID());
335
336 // mark message as read
337 $read[] = $message->id;
338 } catch (Exception $error) {
339 // go ahead silently
340 }
341 }
342
343 return $read;
344 }
345
346 /**
347 * Read the provided message.
348 *
349 * @param VBOChatMessage $message The message to read.
350 *
351 * @return void
352 *
353 * @throws Exception
354 *
355 * @since 1.8.8
356 */
357 public function readMessage(VBOChatMessage $message)
358 {
359 // read the message
360 $this->storage->readMessage($message->getID(), $this->getUser()->getID());
361 }
362
363 /**
364 * Creates a new message object.
365 *
366 * @param object|array $message The raw message record.
367 *
368 * @return VBOChatMessage
369 */
370 public function createMessage($message)
371 {
372 if (!is_array($message) && !is_object($message)) {
373 throw new \InvalidArgumentException('Cannot bind chat message! Array or object expected, ' . gettype($message) . ' given.', 400);
374 }
375
376 $message = (object) $message;
377
378 // hold raw data into a message object
379 return new VBOChatMessage(
380 // create proper context handler
381 $this->createContext($message->context ?? '', $message->id_context ?? 0),
382 // bind raw information
383 $message
384 );
385 }
386
387 /**
388 * Creates a new context object.
389 *
390 * @param string $alias The context alias identifier.
391 * @param int $id The context foreign key.
392 *
393 * @return VBOChatContext
394 */
395 public function createContext(string $alias, int $id)
396 {
397 if ($alias === '') {
398 throw new InvalidArgumentException('The context alias cannot be empty.', 400);
399 }
400
401 if ($id <= 0) {
402 throw new InvalidArgumentException('Invalid context foreign key provided.', 400);
403 }
404
405 // build context class name
406 $classname = 'VBOChatContext' . ucfirst(strtolower($alias));
407
408 if (!class_exists($classname)) {
409 throw new RuntimeException('The class [' . $classname . '] does not exist.', 404);
410 }
411
412 // instantiate class by injecting the provided ID
413 return new $classname($id);
414 }
415
416 /**
417 * Forces the pre-loading of the resources to make the chat scripts work.
418 *
419 * @return self
420 */
421 public function useAssets()
422 {
423 static $loaded = false;
424
425 if ($loaded) {
426 // do not load assets again
427 return $this;
428 }
429
430 $loaded = true;
431
432 // load dependencies first
433 JHtml::fetch('jquery.framework');
434 VikBooking::getVboApplication()->loadContextMenuAssets();
435
436 // make translations available also for JS scripts
437 JText::script('VBO_CHAT_YOU');
438 JText::script('VBTODAY');
439 JText::script('VBOYESTERDAY');
440 JText::script('VBO_CHAT_SENDING_ERR');
441 JText::script('VBO_CHAT_TEXTAREA_PLACEHOLDER');
442 JText::script('VBO_ATTACH');
443
444 $document = JFactory::getDocument();
445 $document->addScript(VBO_SITE_URI . 'resources/chat.js');
446 $document->addStyleSheet(VBO_SITE_URI . 'resources/chat.css');
447
448 /** @var VBOChatUser */
449 $user = $this->getUser();
450
451 // load assets for each supported context
452 (new VBOChatContextTask(0))->useAssets($user);
453 (new VBOChatContextSession(0))->useAssets($user);
454
455 return $this;
456 }
457
458 /**
459 * Renders the chat interface.
460 *
461 * @param VBOChatContext $context The conversation context.
462 * @param array $options A configuration array.
463 *
464 * List of supported configuration options.
465 * @var bool assets Whether the resources should be loaded (true by default).
466 *
467 * @return string The chat interface output.
468 */
469 public function render(VBOChatContext $context, array $options = [])
470 {
471 if ($options['assets'] ?? true) {
472 $this->useAssets();
473 }
474
475 /** @var VBOChatUser */
476 $user = $this->getUser();
477
478 // load the latest 20 messages of the specified context
479 $messages = $this->getMessages(
480 (new VBOChatSearch)->limit($options['limit'] ?? 20)->withContext($context)
481 );
482
483 $users = [];
484
485 // get all involved users
486 foreach ($context->getRecipients() as $recipient) {
487 $users[$recipient->getID()] = $recipient;
488 }
489
490 // detect AJAX base URI environment depending on the platform
491 $ajaxUri = VBOFactory::getPlatform()->getUri()->ajax('index.php?option=com_vikbooking');
492
493 // generate a random suffix in case it has been specified
494 $options['suffix'] = $options['suffix'] ?? uniqid();
495
496 // create layout file
497 $layout = new JLayoutFile('chat.chat', null, [
498 'component' => 'com_vikbooking',
499 'client' => 'admin',
500 ]);
501
502 // render template
503 return $layout->render([
504 'uri' => $ajaxUri,
505 'messages' => $messages,
506 'users' => $users,
507 'user' => $user,
508 'options' => $options,
509 'context' => [
510 'id' => $context->getID(),
511 'alias' => $context->getAlias(),
512 'actions' => $context->getActions($user),
513 'metadata' => $context->getMetadata(true),
514 ],
515 ]);
516 }
517 }
518