Fix for issue reported in 'Scheduling Failures' on forum
[openemr.git] / patients / find_appt_popup_user.php
bloba543d1aff0b6bf3653c7ee3eb0b9a326ad08cedc
1 <?php
2 /**
3 * find appt popup
5 * @package OpenEMR
6 * @link http://www.open-emr.org
7 * @author Rod Roark <rod@sunsetsystems.com>
8 * @author Brady Miller <brady.g.miller@gmail.com>
9 * @copyright Copyright (c) 2005-2006 Rod Roark <rod@sunsetsystems.com>
10 * @copyright Copyright (c) 2017 Brady Miller <brady.g.miller@gmail.com>
11 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
14 // Note from Rod 2013-01-22:
15 // This module needs to be refactored to share the same code that is in
16 // interface/main/calendar/find_appt_popup.php. It contains an old version
17 // of that logic and does not support exception dates for repeating events.
19 //continue session
20 session_start();
23 //landing page definition -- where to go if something goes wrong
24 $landingpage = "index.php?site=".$_SESSION['site_id'];
27 // kick out if patient not authenticated
28 if (isset($_SESSION['pid']) && isset($_SESSION['patient_portal_onsite'])) {
29 $pid = $_SESSION['pid'];
30 } else {
31 session_destroy();
32 header('Location: '.$landingpage.'&w');
33 exit;
38 $ignoreAuth = 1;
40 require_once("../interface/globals.php");
41 require_once("$srcdir/patient.inc");
43 // Exit if the modify calendar for portal flag is not set
44 if (!($GLOBALS['portal_onsite_appt_modify'])) {
45 echo htmlspecialchars(xl('You are not authorized to schedule appointments.'), ENT_NOQUOTES);
46 exit;
49 $input_catid = $_REQUEST['catid'];
51 // Record an event into the slots array for a specified day.
52 function doOneDay($catid, $udate, $starttime, $duration, $prefcatid)
54 global $slots, $slotsecs, $slotstime, $slotbase, $slotcount, $input_catid;
55 $udate = strtotime($starttime, $udate);
56 if ($udate < $slotstime) {
57 return;
60 $i = (int) ($udate / $slotsecs) - $slotbase;
61 $iend = (int) (($duration + $slotsecs - 1) / $slotsecs) + $i;
62 if ($iend > $slotcount) {
63 $iend = $slotcount;
66 if ($iend <= $i) {
67 $iend = $i + 1;
70 for (; $i < $iend; ++$i) {
71 if ($catid == 2) { // in office
72 // If a category ID was specified when this popup was invoked, then select
73 // only IN events with a matching preferred category or with no preferred
74 // category; other IN events are to be treated as OUT events.
75 if ($input_catid) {
76 if ($prefcatid == $input_catid || !$prefcatid) {
77 $slots[$i] |= 1;
78 } else {
79 $slots[$i] |= 2;
81 } else {
82 $slots[$i] |= 1;
85 break; // ignore any positive duration for IN
86 } else if ($catid == 3) { // out of office
87 $slots[$i] |= 2;
88 break; // ignore any positive duration for OUT
89 } else { // all other events reserve time
90 $slots[$i] |= 4;
95 // seconds per time slot
96 $slotsecs = $GLOBALS['calendar_interval'] * 60;
98 $catslots = 1;
99 if ($input_catid) {
100 $srow = sqlQuery("SELECT pc_duration FROM openemr_postcalendar_categories WHERE pc_catid = '$input_catid'");
101 if ($srow['pc_duration']) {
102 $catslots = ceil($srow['pc_duration'] / $slotsecs);
106 $info_msg = "";
108 $searchdays = 7; // default to a 1-week lookahead
109 if ($_REQUEST['searchdays']) {
110 $searchdays = $_REQUEST['searchdays'];
113 // Get a start date.
114 if ($_REQUEST['startdate'] && preg_match(
115 "/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/",
116 $_REQUEST['startdate'],
117 $matches
118 )) {
119 $sdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
120 } else {
121 $sdate = date("Y-m-d");
124 // Get an end date - actually the date after the end date.
125 preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $sdate, $matches);
126 $edate = date(
127 "Y-m-d",
128 mktime(0, 0, 0, $matches[2], $matches[3] + $searchdays, $matches[1])
131 // compute starting time slot number and number of slots.
132 $slotstime = strtotime("$sdate 00:00:00");
133 $slotetime = strtotime("$edate 00:00:00");
134 $slotbase = (int) ($slotstime / $slotsecs);
135 $slotcount = (int) ($slotetime / $slotsecs) - $slotbase;
137 if ($slotcount <= 0 || $slotcount > 100000) {
138 die("Invalid date range");
141 $slotsperday = (int) (60 * 60 * 24 / $slotsecs);
143 // If we have a provider, search.
145 if ($_REQUEST['providerid']) {
146 $providerid = $_REQUEST['providerid'];
148 // Create and initialize the slot array. Values are bit-mapped:
149 // bit 0 = in-office occurs here
150 // bit 1 = out-of-office occurs here
151 // bit 2 = reserved
152 // So, values may range from 0 to 7.
154 $slots = array_pad(array(), $slotcount, 0);
156 // Note there is no need to sort the query results.
157 // echo $sdate." -- ".$edate;
158 $query = "SELECT pc_eventDate, pc_endDate, pc_startTime, pc_duration, " .
159 "pc_recurrtype, pc_recurrspec, pc_alldayevent, pc_catid, pc_prefcatid, pc_title " .
160 "FROM openemr_postcalendar_events " .
161 "WHERE pc_aid = '$providerid' AND " .
162 "((pc_endDate >= '$sdate' AND pc_eventDate < '$edate') OR " .
163 "(pc_endDate = '0000-00-00' AND pc_eventDate >= '$sdate' AND pc_eventDate < '$edate'))";
164 $res = sqlStatement($query);
165 // print_r($res);
167 while ($row = sqlFetchArray($res)) {
168 $thistime = strtotime($row['pc_eventDate'] . " 00:00:00");
169 if ($row['pc_recurrtype']) {
170 preg_match('/"event_repeat_freq_type";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
171 $repeattype = $matches[1];
173 preg_match('/"event_repeat_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
174 $repeatfreq = $matches[1];
175 if ($row['pc_recurrtype'] == 2) {
176 // Repeat type is 2 so frequency comes from event_repeat_on_freq.
177 preg_match('/"event_repeat_on_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
178 $repeatfreq = $matches[1];
181 if (! $repeatfreq) {
182 $repeatfreq = 1;
185 preg_match('/"event_repeat_on_num";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
186 $my_repeat_on_num = $matches[1];
188 preg_match('/"event_repeat_on_day";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
189 $my_repeat_on_day = $matches[1];
191 $endtime = strtotime($row['pc_endDate'] . " 00:00:00") + (24 * 60 * 60);
192 if ($endtime > $slotetime) {
193 $endtime = $slotetime;
196 $repeatix = 0;
197 while ($thistime < $endtime) {
198 // Skip the event if a repeat frequency > 1 was specified and this is
199 // not the desired occurrence.
200 if (! $repeatix) {
201 doOneDay(
202 $row['pc_catid'],
203 $thistime,
204 $row['pc_startTime'],
205 $row['pc_duration'],
206 $row['pc_prefcatid']
210 if (++$repeatix >= $repeatfreq) {
211 $repeatix = 0;
214 $adate = getdate($thistime);
216 if ($row['pc_recurrtype'] == 2) {
217 // Need to skip to nth or last weekday of the next month.
218 $adate['mon'] += 1;
219 if ($adate['mon'] > 12) {
220 $adate['year'] += 1;
221 $adate['mon'] -= 12;
224 if ($my_repeat_on_num < 5) { // not last
225 $adate['mday'] = 1;
226 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN, $adate['mon'], $adate['mday'], $adate['year']));
227 if ($dow > $my_repeat_on_day) {
228 $dow -= 7;
231 $adate['mday'] += ($my_repeat_on_num - 1) * 7 + $my_repeat_on_day - $dow;
232 } else { // last weekday of month
233 $adate['mday'] = cal_days_in_month(CAL_GREGORIAN, $adate['mon'], $adate['year']);
234 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN, $adate['mon'], $adate['mday'], $adate['year']));
235 if ($dow < $my_repeat_on_day) {
236 $dow += 7;
239 $adate['mday'] += $my_repeat_on_day - $dow;
241 } // end recurrtype 2
243 else { // recurrtype 1
245 if ($repeattype == 0) { // daily
246 $adate['mday'] += 1;
247 } else if ($repeattype == 1) { // weekly
248 $adate['mday'] += 7;
249 } else if ($repeattype == 2) { // monthly
250 $adate['mon'] += 1;
251 } else if ($repeattype == 3) { // yearly
252 $adate['year'] += 1;
253 } else if ($repeattype == 4) { // work days
254 if ($adate['wday'] == 5) { // if friday, skip to monday
255 $adate['mday'] += 3;
256 } else if ($adate['wday'] == 6) { // saturday should not happen
257 $adate['mday'] += 2;
258 } else {
259 $adate['mday'] += 1;
261 } else if ($repeattype == 5) { // monday
262 $adate['mday'] += 7;
263 } else if ($repeattype == 6) { // tuesday
264 $adate['mday'] += 7;
265 } else if ($repeattype == 7) { // wednesday
266 $adate['mday'] += 7;
267 } else if ($repeattype == 8) { // thursday
268 $adate['mday'] += 7;
269 } else if ($repeattype == 9) { // friday
270 $adate['mday'] += 7;
271 } else {
272 die("Invalid repeat type '$repeattype'");
274 } // end recurrtype 1
276 $thistime = mktime(0, 0, 0, $adate['mon'], $adate['mday'], $adate['year']);
278 } else {
279 doOneDay(
280 $row['pc_catid'],
281 $thistime,
282 $row['pc_startTime'],
283 $row['pc_duration'],
284 $row['pc_prefcatid']
289 // Mark all slots reserved where the provider is not in-office.
290 // Actually we could do this in the display loop instead.
291 $inoffice = false;
292 for ($i = 0; $i < $slotcount; ++$i) {
293 if (($i % $slotsperday) == 0) {
294 $inoffice = false;
297 if ($slots[$i] & 1) {
298 $inoffice = true;
301 if ($slots[$i] & 2) {
302 $inoffice = false;
305 if (! $inoffice) {
306 $slots[$i] |= 4;
311 <html>
312 <head>
313 <?php html_header_show(); ?>
314 <title><?php xl('Find Available Appointments', 'e'); ?></title>
315 <link rel="stylesheet" href='<?php echo $css_header ?>' type='text/css'>
316 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.min.css">
318 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-min-1-7-2/index.js"></script>
319 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.full.min.js"></script>
321 <script language="JavaScript">
323 function setappt(year,mon,mday,hours,minutes) {
324 if (opener.closed || ! opener.setappt)
325 alert('<?php xl('The destination form was closed; I cannot act on your selection.', 'e'); ?>');
326 else
327 opener.setappt(year,mon,mday,hours,minutes);
328 window.close();
329 return false;
332 </script>
335 <style>
336 form {
337 /* this eliminates the padding normally around a FORM tag */
338 padding: 0px;
339 margin: 0px;
341 #searchCriteria {
342 text-align: center;
343 width: 100%;
344 font-size: 0.8em;
345 background-color: #ddddff;
346 font-weight: bold;
347 padding: 3px;
349 #searchResultsHeader {
350 width: 100%;
351 background-color: lightgrey;
353 #searchResultsHeader table {
354 width: 96%; /* not 100% because the 'searchResults' table has a scrollbar */
355 border-collapse: collapse;
357 #searchResultsHeader th {
358 font-size: 0.7em;
360 #searchResults {
361 width: 100%;
362 height: 350px;
363 overflow: auto;
366 .srDate { width: 20%; }
367 .srTimes { width: 80%; }
369 #searchResults table {
370 width: 100%;
371 border-collapse: collapse;
372 background-color: white;
374 #searchResults td {
375 font-size: 0.7em;
376 border-bottom: 1px solid gray;
377 padding: 1px 5px 1px 5px;
379 .highlight { background-color: #ff9; }
380 .blue_highlight { background-color: #336699; color: white; }
381 #am {
382 border-bottom: 1px solid lightgrey;
383 color: #00c;
385 #pm { color: #c00; }
386 #pm a { color: #c00; }
387 </style>
389 </head>
391 <body class="body_top">
393 <div id="searchCriteria">
394 <form method='post' name='theform' action='find_appt_popup.php?providerid=<?php echo $providerid ?>&catid=<?php echo $input_catid ?>'>
395 <input type="hidden" name='bypatient' />
397 <?php xl('Start date:', 'e'); ?>
400 <input type='text' class='datepicker' name='startdate' id='startdate' size='10' value='<?php echo $sdate ?>'
401 title='yyyy-mm-dd starting date for search'/>
403 <?php xl('for', 'e'); ?>
404 <input type='text' name='searchdays' size='3' value='<?php echo $searchdays ?>'
405 title='Number of days to search from the start date' />
406 <?php xl('days', 'e'); ?>&nbsp;
407 <input type='submit' value='<?php xl('Search', 'e'); ?>'>
408 </div>
410 <?php if (!empty($slots)) : ?>
412 <div id="searchResultsHeader">
413 <table>
414 <tr>
415 <th class="srDate"><?php xl('Day', 'e'); ?></th>
416 <th class="srTimes"><?php xl('Available Times', 'e'); ?></th>
417 </tr>
418 </table>
419 </div>
421 <div id="searchResults">
422 <table>
423 <?php
424 $lastdate = "";
425 $ampmFlag = "am"; // establish an AM-PM line break flag
426 for ($i = 0; $i < $slotcount; ++$i) {
427 $available = true;
428 for ($j = $i; $j < $i + $catslots; ++$j) {
429 if ($slots[$j] >= 4) {
430 $available = false;
434 if (!$available) {
435 continue; // skip reserved slots
438 $utime = ($slotbase + $i) * $slotsecs;
439 $thisdate = date("Y-m-d", $utime);
440 if ($thisdate != $lastdate) {
441 // if a new day, start a new row
442 if ($lastdate) {
443 echo "</div>";
444 echo "</td>\n";
445 echo " </tr>\n";
448 $lastdate = $thisdate;
449 echo " <tr class='oneresult'>\n";
450 echo " <td class='srDate'>" . date("l", $utime)."<br>".date("Y-m-d", $utime) . "</td>\n";
451 echo " <td class='srTimes'>";
452 echo "<div id='am'>AM ";
453 $ampmFlag = "am"; // reset the AMPM flag
456 $ampm = date('a', $utime);
457 if ($ampmFlag != $ampm) {
458 echo "</div><div id='pm'>PM ";
461 $ampmFlag = $ampm;
463 $atitle = "Choose ".date("h:i a", $utime);
464 $adate = getdate($utime);
465 $anchor = "<a href='' onclick='return setappt(" .
466 $adate['year'] . "," .
467 $adate['mon'] . "," .
468 $adate['mday'] . "," .
469 $adate['hours'] . "," .
470 $adate['minutes'] . ")'".
471 " title='$atitle' alt='$atitle'".
472 ">";
473 echo (strlen(date('g', $utime)) < 2 ? "<span style='visibility:hidden'>0</span>" : "") .
474 $anchor . date("g:i", $utime) . "</a> ";
476 // If category duration is more than 1 slot, increment $i appropriately.
477 // This is to avoid reporting available times on undesirable boundaries.
478 $i += $catslots - 1;
481 if ($lastdate) {
482 echo "</td>\n";
483 echo " </tr>\n";
484 } else {
485 echo " <tr><td colspan='2'> " . xl('No openings were found for this period.', 'e') . "</td></tr>\n";
488 </table>
489 </div>
490 </div>
491 <?php endif; ?>
493 </form>
494 </body>
496 <script language='JavaScript'>
497 // jQuery stuff to make the page a little easier to use
499 $(document).ready(function(){
500 $(".oneresult").mouseover(function() { $(this).toggleClass("highlight"); });
501 $(".oneresult").mouseout(function() { $(this).toggleClass("highlight"); });
502 $(".oneresult a").mouseover(function () { $(this).toggleClass("blue_highlight"); $(this).children().toggleClass("blue_highlight"); });
503 $(".oneresult a").mouseout(function() { $(this).toggleClass("blue_highlight"); $(this).children().toggleClass("blue_highlight"); });
504 //$(".event").dblclick(function() { EditEvent(this); });
506 $('.datepicker').datetimepicker({
507 <?php $datetimepicker_timepicker = false; ?>
508 <?php $datetimepicker_showseconds = false; ?>
509 <?php $datetimepicker_formatInput = false; ?>
510 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
511 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
515 </script>
517 </html>