PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / trunk
VikAppointments Services Booking Calendar vtrunk
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / admin / views / reservations / view.html.php
vikappointments / admin / views / reservations Last commit date
tmpl 3 months ago index.html 7 years ago view.html.php 1 year ago
view.html.php
435 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 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 * VikAppointments reservations view.
16 *
17 * @since 1.0
18 */
19 class VikAppointmentsViewreservations extends JViewVAP
20 {
21 /**
22 * VikAppointments view display method.
23 *
24 * @return void
25 */
26 function display($tpl = null)
27 {
28 $app = JFactory::getApplication();
29 $input = $app->input;
30 $dbo = JFactory::getDbo();
31
32 // set the toolbar
33 $this->addToolBar();
34
35 $filters = array();
36 $filters['keysearch'] = $app->getUserStateFromRequest($this->getPoolName() . '.keysearch', 'keysearch', '', 'string');
37 $filters['status'] = $app->getUserStateFromRequest($this->getPoolName() . '.status', 'status', '', 'string');
38 $filters['id_payment'] = $app->getUserStateFromRequest($this->getPoolName() . '.id_payment', 'id_payment', 0, 'uint');
39 $filters['datestart'] = $app->getUserStateFromRequest($this->getPoolName() . '.datestart', 'datestart', '', 'string');
40 $filters['dateend'] = $app->getUserStateFromRequest($this->getPoolName() . '.dateend', 'dateend', '', 'string');
41 $filters['id_location'] = $app->getUserStateFromRequest($this->getPoolName() . '.id_location', 'id_location', 0, 'uint');
42 $filters['res_id'] = $app->getUserStateFromRequest($this->getPoolName() . '.res_id', 'res_id', 0, 'uint');
43
44 $this->filters = $filters;
45
46 $this->ordering = $app->getUserStateFromRequest($this->getPoolName() . '.ordering', 'filter_order', 'r.createdon', 'string');
47 $this->orderDir = $app->getUserStateFromRequest($this->getPoolName() . '.orderdir', 'filter_order_Dir', 'DESC', 'string');
48
49 // db object
50 $lim = $app->getUserStateFromRequest('com_vikappointments.limit', 'limit', $app->get('list_limit'), 'uint');
51 $lim0 = $this->getListLimitStart($filters);
52 $navbut = "";
53
54 $rows = array();
55
56 // get reservations details
57
58 $search_has_id = false;
59
60 $q = $dbo->getQuery(true);
61
62 $q->select('SQL_CALC_FOUND_ROWS r.*')
63 ->select(array(
64 $dbo->qn('e.nickname', 'employee_name'),
65 $dbo->qn('e.timezone'),
66 $dbo->qn('s.name', 'service_name'),
67 $dbo->qn('p.name', 'payment_name'),
68 $dbo->qn('u.name', 'author'),
69 ))
70 ->from($dbo->qn('#__vikappointments_reservation', 'r'))
71 ->leftjoin($dbo->qn('#__vikappointments_service', 's') . ' ON ' . $dbo->qn('r.id_service') . ' = ' . $dbo->qn('s.id'))
72 ->leftjoin($dbo->qn('#__vikappointments_employee', 'e') . ' ON ' . $dbo->qn('r.id_employee') . ' = ' . $dbo->qn('e.id'))
73 ->leftjoin($dbo->qn('#__vikappointments_gpayments', 'p') . ' ON ' . $dbo->qn('r.id_payment') . ' = ' . $dbo->qn('p.id'))
74 ->leftjoin($dbo->qn('#__users', 'u') . ' ON ' . $dbo->qn('r.createdby') . ' = ' . $dbo->qn('u.id'))
75 ->where(1)
76 ->order($dbo->qn($this->ordering) . ' ' . $this->orderDir);
77
78 if ($this->ordering == 'r.createdon')
79 {
80 // then follow the ID ordering because 2 appointments might
81 // be created simultaneously
82 $q->order($dbo->qn('r.id') . ' ' . $this->orderDir);
83 }
84
85 if (strlen($filters['keysearch']))
86 {
87 $where = array(
88 $dbo->qn('r.purchaser_nominative') . ' LIKE ' . $dbo->q("%{$filters['keysearch']}%"),
89 $dbo->qn('r.purchaser_mail') . ' LIKE ' . $dbo->q("%{$filters['keysearch']}%"),
90 $dbo->qn('r.purchaser_phone') . ' LIKE ' . $dbo->q("%{$filters['keysearch']}%"),
91 );
92
93 /**
94 * Reverse the search key in order to try finding
95 * users by name even if it was wrote in the opposite way.
96 * If we searched by "John Smith", the system will search
97 * for "Smith John" too.
98 *
99 * @since 1.7
100 */
101 $reverse = preg_split("/\s+/", $filters['keysearch']);
102 $reverse = array_reverse($reverse);
103 $reverse = implode(' ', $reverse);
104
105 $where[] = $dbo->qn('r.purchaser_nominative') . ' LIKE ' . $dbo->q("%{$reverse}%");
106
107 // filter by service
108 $where[] = $dbo->qn('s.name') . ' LIKE ' . $dbo->q("%{$filters['keysearch']}%");
109
110 /**
111 * Reverse the search key in order to try finding employees
112 * by name even if it was wrote in the opposite way.
113 * If we searched by "John Smith", the system will search
114 * for "Smith John" too.
115 *
116 * @since 1.7
117 */
118 $where[] = $dbo->qn('e.nickname') . ' LIKE ' . $dbo->q("%{$filters['keysearch']}%");
119 $where[] = $dbo->qn('e.nickname') . ' LIKE ' . $dbo->q("%{$reverse}%");
120
121 // filter by coupon code (must start with the code)
122 $where[] = $dbo->qn('coupon_str') . ' LIKE ' . $dbo->q("{$filters['keysearch']}%");
123
124 /**
125 * It is now possible to search reservations by ID/SID and
126 * user ID through the main key search input.
127 *
128 * @since 1.7
129 */
130 if (preg_match("/^[A-Z0-9]{16,16}$/i", $filters['keysearch']))
131 {
132 // alphanumeric string of 16 characters, we are probably searching for "SID"
133 $where[] = $dbo->qn('r.sid') . ' = ' . $dbo->q($filters['keysearch']);
134 }
135 else if (preg_match("/^\d+\-[A-Z0-9]{16,16}$/i", $filters['keysearch']))
136 {
137 // we are probably searching for "ID" - "SID"
138 $where[] = sprintf('CONCAT_WS(\'-\', %s, %s) = %s', $dbo->qn('r.id'), $dbo->qn('r.sid'), $dbo->q($filters['keysearch']));
139 }
140 else if (preg_match("/^id:\s*(\d+)/i", $filters['keysearch'], $match))
141 {
142 // we are searching by ID
143 $where[] = $dbo->qn('r.id') . ' = ' . (int) $match[1];
144
145 $search_has_id = true;
146 }
147 else if (preg_match("/^id_user:\s*(\d+)/i", $filters['keysearch'], $match))
148 {
149 // we are searching by user ID
150 $where[] = $dbo->qn('r.id_user') . ' = ' . (int) $match[1];
151 }
152
153 $q->andWhere($where, 'OR');
154 }
155
156 if (!empty($filters['status']))
157 {
158 if ($filters['status'] != 'CLOSURE')
159 {
160 $q->where(array(
161 $dbo->qn('status') . ' = ' . $dbo->q($filters['status']),
162 $dbo->qn('r.closure') . ' = 0',
163 ));
164 }
165 else
166 {
167 $q->where($dbo->qn('r.closure') . ' = 1');
168 }
169 }
170
171 if ($filters['id_payment'])
172 {
173 $q->where($dbo->qn('id_payment') . ' = ' . $filters['id_payment']);
174 }
175
176 if (VAPDateHelper::isNull($filters['datestart']) === false)
177 {
178 /**
179 * Added support for end date filter.
180 *
181 * @since 1.7
182 */
183 if (VAPDateHelper::isNull($filters['dateend']))
184 {
185 // missing end-date, fetch appointments for the selected date
186 $filters['dateend'] = $filters['datestart'];
187 }
188
189 /**
190 * Get locale SQL strings to have dates adjusted to the current
191 * timezone. This way the dates will be refactored for being
192 * used in UTC, even if the locale is different.
193 *
194 * @since 1.7
195 */
196 $start = VAPDateHelper::getSqlDateLocale($filters['datestart'], 0, 0, 0);
197 $end = VAPDateHelper::getSqlDateLocale($filters['dateend'], 23, 59, 59);
198
199 $q->where($dbo->qn('r.checkin_ts') . ' BETWEEN ' . $dbo->q($start) . ' AND ' . $dbo->q($end));
200 }
201
202 /**
203 * Added support for location filter.
204 *
205 * @since 1.7
206 */
207 if ($filters['id_location'])
208 {
209 $q->leftjoin($dbo->qn('#__vikappointments_emp_worktime', 'w')
210 . ' ON ' . $dbo->qn('w.id_service') . ' = ' . $dbo->qn('r.id_service') . ' AND ' . $dbo->qn('w.id_employee') . ' = ' . $dbo->qn('r.id_employee'));
211
212 // filter working days by time
213 $q->where(sprintf(
214 'CAST(DATE_FORMAT(%1$s, \'%%H\') AS unsigned) * 60 + CAST(DATE_FORMAT(%1$s, \'%%i\') AS unsigned) BETWEEN %2$s AND %3$s',
215 $dbo->qn('r.checkin_ts'),
216 $dbo->qn('w.fromts'),
217 $dbo->qn('w.endts')
218 ));
219
220 // filter by date/day
221 $q->where(sprintf(
222 '((%1$s = CAST(DATE_FORMAT(%2$s, \'%%w\') AS unsigned) AND %3$s <= 0) OR %4$s = DATE_FORMAT(%2$s, \'%%Y-%%m-%%d\'))',
223 $dbo->qn('w.day'),
224 $dbo->qn('r.checkin_ts'),
225 $dbo->qn('w.ts'),
226 $dbo->qn('w.tsdate')
227 ));
228
229 // filter by location
230 $q->where($dbo->qn('w.id_location') . ' = ' . $filters['id_location']);
231
232 // avoid duplicates
233 $q->group($dbo->qn('r.id'));
234 }
235
236 // hide the parent orders if the reservations are not sorted by ID or
237 // if we are searching by check-in date/location
238 if (!in_array($this->ordering, array('r.id', 'r.createdon')) || $filters['datestart'] || $filters['id_location'])
239 {
240 $q->where($dbo->qn('r.id_parent') . ' <> -1');
241 }
242 // otherwise hide the children reservations (only if we are not filtering
243 // or searching by reservation ID.
244 else if ($filters['res_id'] == 0 && !$search_has_id)
245 {
246 $q->andWhere(array(
247 $dbo->qn('r.id_parent') . ' = -1',
248 $dbo->qn('r.id_parent') . ' = ' . $dbo->qn('r.id'),
249 ), 'OR');
250 }
251
252 if ($filters['res_id'])
253 {
254 // clear any other filter
255 $q->clear('where');
256 $q->where(1);
257
258 $q->andWhere(array(
259 $dbo->qn('r.id') . ' = ' . $filters['res_id'],
260 $dbo->qn('r.id_parent') . ' = ' . $filters['res_id'],
261 ), 'OR');
262 }
263
264 // hide closures in case the ordering is not supported
265 $closure_columns = array(
266 'r.id',
267 'r.createdon',
268 'r.checkin_ts',
269 'r.status',
270 'e.nickname',
271 );
272
273 if (!in_array($this->ordering, $closure_columns))
274 {
275 $q->where($dbo->qn('r.closure') . ' = 0');
276 }
277
278 /**
279 * Add support for manipulating query through the plugins.
280 *
281 * @see /site/helpers/libraries/mvc/view.php @ JViewVAP::onBeforeListQuery()
282 *
283 * @since 1.6.2
284 */
285 $this->onBeforeListQuery($q);
286
287 $dbo->setQuery($q, $lim0, $lim);
288 $dbo->execute();
289
290 // assert limit used for list query
291 $this->assertListQuery($lim0, $lim);
292
293 if ($dbo->getNumRows())
294 {
295 $rows = $dbo->loadAssocList();
296 $dbo->setQuery('SELECT FOUND_ROWS();');
297 jimport('joomla.html.pagination');
298 $pageNav = new JPagination($dbo->loadResult(), $lim0, $lim);
299 $navbut = JLayoutHelper::render('blocks.pagination', ['pageNav' => $pageNav]);
300 }
301
302 $invoiceModel = JModelVAP::getInstance('invoice');
303
304 foreach ($rows as $k => $row)
305 {
306 $id_order = $row['id'];
307
308 if ($row['id_parent'] != -1 && $row['id'] != $row['id_parent'])
309 {
310 $id_order = $row['id_parent'];
311 }
312
313 // load invoice details
314 $rows[$k]['invoice'] = $invoiceModel->getInvoice($id_order, 'appointments');
315
316 if (!$rows[$k]['invoice'])
317 {
318 // try to check whether we have an invoice file, since invoices generated
319 // before the 1.7 version are not stored within the database
320 $rows[$k]['invoice'] = $invoiceModel->getInvoiceBC($id_order, $row['sid'], 'appointments');
321 }
322 }
323
324 // import custom fields loader
325 VAPLoader::import('libraries.customfields.loader');
326
327 // get relevant custom fields only
328 $this->customFields = VAPCustomFieldsLoader::getInstance()
329 ->translate()
330 ->noRequiredCheckbox()
331 ->noSeparator()
332 ->noInputFile()
333 ->fetch();
334
335 $this->rows = $rows;
336 $this->navbut = $navbut;
337
338 // display the template (default.php)
339 parent::display($tpl);
340 }
341
342 /**
343 * Setting the toolbar.
344 *
345 * @return void
346 */
347 protected function addToolBar()
348 {
349 // add menu title and some buttons to the page
350 JToolBarHelper::title(JText::translate('VAPMAINTITLEVIEWRESERVATIONS'), 'vikappointments');
351
352 $user = JFactory::getUser();
353
354 if ($user->authorise('core.create', 'com_vikappointments'))
355 {
356 JToolBarHelper::addNew('findreservation');
357 }
358
359 if ($user->authorise('core.edit', 'com_vikappointments'))
360 {
361 JToolBarHelper::editList('reservation.edit');
362 }
363
364 if ($user->authorise('core.create', 'com_vikappointments') && VAPFactory::getConfig()->getBool('enablerecur'))
365 {
366 JToolBarHelper::custom('makerecurrence', 'loop', 'loop', JText::translate('VAPMAKERECURRENCE'), true);
367 }
368
369 JToolBarHelper::custom('exportres.add', 'download', 'download', JText::translate('VAPEXPORT'), false);
370 JToolBarHelper::custom('printorders', 'print', 'print', JText::translate('VAPPRINT'), true);
371
372 if ($this->isApiSmsConfigured())
373 {
374 JToolBarHelper::custom('reservation.sendsms', 'comment', 'comment', JText::translate('VAPSENDSMS'), true);
375 }
376
377 if ($user->authorise('core.create', 'com_vikappointments'))
378 {
379 JToolBarHelper::custom('invoice.generate', 'vcard', 'vcard', JText::translate('VAPINVOICE'), true);
380 JToolBarHelper::custom('closure.add', 'unpublish', 'unpublish', JText::translate('VAPBLOCK'), false);
381 }
382
383 if ($user->authorise('core.delete', 'com_vikappointments'))
384 {
385 JToolBarHelper::deleteList(VikAppointments::getConfirmSystemMessage(), 'reservation.delete');
386 }
387 }
388
389 /**
390 * Checks for advanced filters set in the request.
391 *
392 * @return boolean True if active, otherwise false.
393 */
394 protected function hasFilters()
395 {
396 if ($this->filters['res_id'])
397 {
398 return false;
399 }
400
401 return (!empty($this->filters['status'])
402 || $this->filters['id_payment']
403 || $this->filters['id_location']
404 || !VAPDateHelper::isNull($this->filters['datestart']));
405 }
406
407 /**
408 * Check if the SMS API is configured.
409 *
410 * @return boolean True on success, otherwise false.
411 */
412 protected function isApiSmsConfigured()
413 {
414 // first of all, check ACL
415 if (!JFactory::getUser()->authorise('core.edit.state', 'com_vikappointments'))
416 {
417 return false;
418 }
419
420 try
421 {
422 // try to instantiate the SMS API provider
423 $provider = VAPApplication::getInstance()->getSmsInstance();
424 }
425 catch (Exception $e)
426 {
427 // SMS provider not configured
428 return false;
429 }
430
431 // provider (probably) configured
432 return true;
433 }
434 }
435