PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.8
VikBooking Hotel Booking Engine & PMS v1.8.8
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.8, at admin/helpers/src/chat/mediator.php

484 lines 13.8 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,svg,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 {
99 // fetch details of the logged in operator
100 $operator = VikBooking::getOperatorInstance()->getOperatorAccount();
101
102 if ($operator) {
103 // authenticate as operator
104 $user = new VBOChatUserOperator($operator);
105 } else {
106 // authenticate as guest user
107 $user = new VBOChatUserGuest;
108 }
109 }
110 }
111
112 $this->user = $user;
113
114 return $this;
115 }
116
117 /**
118 * Returns the currently logged in user.
119 * In case of missing authentication, the system will attempt to
120 * perform an auto-login.
121 *
122 * @return VBOChatUser
123 */
124 public function getUser()
125 {
126 if (!$this->user) {
127 // no authenticated user, auto-login now
128 $this->authenticate();
129 }
130
131 return $this->user;
132 }
133
134 /**
135 * Returns the messages matching the specified search query.
136 *
137 * @param VBOChatSearch $search
138 *
139 * @return VBOChatMessage[]
140 */
141 public function getMessages(VBOChatSearch $search)
142 {
143 $messages = [];
144
145 if (!$search->hasReader()) {
146 // forces the current user as the reader
147 $search->reader($this->getUser()->getID());
148 }
149
150 // pull the messages from the storage
151 $rows = $this->storage->getMessages($search);
152
153 foreach ($rows as $raw) {
154 // wrap raw record within a message object
155 $messages[] = $this->createMessage($raw);
156 }
157
158 return $messages;
159 }
160
161 /**
162 * Sends a new message to all the recipients of the context.
163 *
164 * @param VBOChatMessage $message
165 *
166 * @return void
167 */
168 public function send(VBOChatMessage $message)
169 {
170 $user = $this->getUser();
171
172 // force the sender name and ID according to the details of the logged in user
173 $message->setSender($user->getName(), $user->getID());
174
175 // attempt to save the message
176 $this->saveMessage($message);
177
178 // iterate all the users that should receive a notification
179 foreach ($message->getContext()->getRecipients() as $recipient) {
180 if ($message->getSenderID() == $recipient->getID()) {
181 // do not notify myself
182 continue;
183 }
184
185 if ($recipient instanceof VBOChatNotifiable) {
186 // schedule message notification
187 $recipient->scheduleNotification($message, $user);
188 }
189 }
190 }
191
192 /**
193 * Saves the provided message.
194 *
195 * @param VBOChatMessage $message The message to save.
196 *
197 * @return void
198 *
199 * @throws Exception
200 *
201 * @since 1.8.8
202 */
203 public function saveMessage(VBOChatMessage $message)
204 {
205 // save the message
206 $this->storage->saveMessage($message);
207 }
208
209 /**
210 * Removes the provided message.
211 *
212 * @param VBOChatMessage $message The message to delete.
213 *
214 * @return void
215 *
216 * @throws Exception
217 *
218 * @since 1.8.8
219 */
220 public function deleteMessage(VBOChatMessage $message)
221 {
222 // delete the message
223 $this->storage->deleteMessage($message);
224 }
225
226 /**
227 * Moves the uploaded temporary file onto the server and creates a new attachment.
228 *
229 * @param array $file The temporary file under $_FILES.
230 *
231 * @return VBOChatAttachment
232 *
233 * @throws Exception
234 */
235 public function uploadAttachment(array $file)
236 {
237 // assert attachments folder first
238 if (!JFolder::exists($this->attachmentsPath) && !JFolder::create($this->attachmentsPath)) {
239 throw new \RuntimeException('Unable to create the attachments folder: ' . $this->attachmentsPath, 403);
240 }
241
242 // create upload attachment
243 $attachment = new VBOChatAttachmentUpload($file, $this->attachmentsPath);
244
245 // make sure the file extension is supported
246 if (!VikBooking::isFileTypeCompatible($attachment->getExtension(), $this->supportedFiles)) {
247 throw new \RuntimeException('File type not supported: ' . $attachment->getExtension(), 400);
248 }
249
250 if (!$attachment->upload()) {
251 throw new \RuntimeException('Impossible to upload the file: ' . $attachment->getName(), 403);
252 }
253
254 return $attachment;
255 }
256
257 /**
258 * Removes the specified attachment from the server.
259 *
260 * @param VBOChatAttachment $attachment
261 *
262 * @return bool
263 */
264 public function removeAttachment(VBOChatAttachment $attachment)
265 {
266 if (!$attachment->exists()) {
267 return false;
268 }
269
270 return JFile::delete($attachment->getPath());
271 }
272
273 /**
274 * Reads all the messages under the specified context for the currently logged in user.
275 *
276 * @param VBOChatContext $context The chat context.
277 * @param string|null $date When specified, only the messages with creation date equal or
278 * lower than this value will be read.
279 *
280 * @return int[] A list of read message IDs.
281 */
282 public function readMessages(VBOChatContext $context, ?string $date = null)
283 {
284 $search = (new VBOChatSearch)
285 // take the latest 50 unread messages
286 ->start(0)->limit(50)->unread()
287 // under the specified context
288 ->withContext($context)
289 // created before the specified threshold date
290 ->date($date ?: JFactory::getDate('now')->toSql(), '<=')
291 // unread by the currently logged in user
292 ->reader($this->getUser()->getID());
293
294 $read = [];
295
296 // iterate all unread messages
297 foreach ($this->storage->getMessages($search) as $message) {
298 try {
299 // read the message
300 $this->storage->readMessage($message->id, $this->getUser()->getID());
301
302 // mark message as read
303 $read[] = $message->id;
304 } catch (Exception $error) {
305 // go ahead silently
306 }
307 }
308
309 return $read;
310 }
311
312 /**
313 * Read the provided message.
314 *
315 * @param VBOChatMessage $message The message to read.
316 *
317 * @return void
318 *
319 * @throws Exception
320 *
321 * @since 1.8.8
322 */
323 public function readMessage(VBOChatMessage $message)
324 {
325 // read the message
326 $this->storage->readMessage($message->getID(), $this->getUser()->getID());
327 }
328
329 /**
330 * Creates a new message object.
331 *
332 * @param object|array $message The raw message record.
333 *
334 * @return VBOChatMessage
335 */
336 public function createMessage($message)
337 {
338 if (!is_array($message) && !is_object($message)) {
339 throw new \InvalidArgumentException('Cannot bind chat message! Array or object expected, ' . gettype($message) . ' given.', 400);
340 }
341
342 $message = (object) $message;
343
344 // hold raw data into a message object
345 return new VBOChatMessage(
346 // create proper context handler
347 $this->createContext($message->context ?? '', $message->id_context ?? 0),
348 // bind raw information
349 $message
350 );
351 }
352
353 /**
354 * Creates a new context object.
355 *
356 * @param string $alias The context alias identifier.
357 * @param int $id The context foreign key.
358 *
359 * @return VBOChatContext
360 */
361 public function createContext(string $alias, int $id)
362 {
363 if ($alias === '') {
364 throw new InvalidArgumentException('The context alias cannot be empty.', 400);
365 }
366
367 if ($id <= 0) {
368 throw new InvalidArgumentException('Invalid context foreign key provided.', 400);
369 }
370
371 // build context class name
372 $classname = 'VBOChatContext' . ucfirst(strtolower($alias));
373
374 if (!class_exists($classname)) {
375 throw new RuntimeException('The class [' . $classname . '] does not exist.', 404);
376 }
377
378 // instantiate class by inject the provided ID
379 return new $classname($id);
380 }
381
382 /**
383 * Forces the pre-loading of the resources to make the chat scripts work.
384 *
385 * @return self
386 */
387 public function useAssets()
388 {
389 static $loaded = false;
390
391 if ($loaded) {
392 // do not load assets again
393 return $this;
394 }
395
396 $loaded = true;
397
398 // load dependencies first
399 JHtml::fetch('jquery.framework');
400 VikBooking::getVboApplication()->loadContextMenuAssets();
401
402 // make translations available also for JS scripts
403 JText::script('VBO_CHAT_YOU');
404 JText::script('VBTODAY');
405 JText::script('VBOYESTERDAY');
406 JText::script('VBO_CHAT_SENDING_ERR');
407 JText::script('VBO_CHAT_TEXTAREA_PLACEHOLDER');
408 JText::script('VBO_ATTACH');
409
410 $document = JFactory::getDocument();
411 $document->addScript(VBO_SITE_URI . 'resources/chat.js');
412 $document->addStyleSheet(VBO_SITE_URI . 'resources/chat.css');
413
414 /** @var VBOChatUser */
415 $user = $this->getUser();
416
417 // load assets for each supported context
418 (new VBOChatContextTask(0))->useAssets($user);
419 (new VBOChatContextSession(0))->useAssets($user);
420
421 return $this;
422 }
423
424 /**
425 * Renders the chat interface.
426 *
427 * @param VBOChatContext $context The conversation context.
428 * @param array $options A configuration array.
429 *
430 * List of supported configuration options.
431 * @var bool assets Whether the resources should be loaded (true by default).
432 *
433 * @return string The chat interface output.
434 */
435 public function render(VBOChatContext $context, array $options = [])
436 {
437 if ($options['assets'] ?? true) {
438 $this->useAssets();
439 }
440
441 /** @var VBOChatUser */
442 $user = $this->getUser();
443
444 // load the latest 20 messages of the specified context
445 $messages = $this->getMessages(
446 (new VBOChatSearch)->limit($options['limit'] ?? 20)->withContext($context)
447 );
448
449 $users = [];
450
451 // get all involved users
452 foreach ($context->getRecipients() as $recipient) {
453 $users[$recipient->getID()] = $recipient;
454 }
455
456 // detect AJAX base URI environment depending on the platform
457 $ajaxUri = VBOFactory::getPlatform()->getUri()->ajax('index.php?option=com_vikbooking');
458
459 // generate a random suffix in case it has been specified
460 $options['suffix'] = $options['suffix'] ?? uniqid();
461
462 // create layout file
463 $layout = new JLayoutFile('chat.chat', null, [
464 'component' => 'com_vikbooking',
465 'client' => 'admin',
466 ]);
467
468 // render template
469 return $layout->render([
470 'uri' => $ajaxUri,
471 'messages' => $messages,
472 'users' => $users,
473 'user' => $user,
474 'options' => $options,
475 'context' => [
476 'id' => $context->getID(),
477 'alias' => $context->getAlias(),
478 'actions' => $context->getActions($user),
479 'metadata' => $context->getMetadata(true),
480 ],
481 ]);
482 }
483 }
484