Merge pull request #940 for adding access controls for encounter categories
[openemr.git] / portal / find_appt_popup_user.php
bloba74a1d1b0e43f3da7b3a31a77950d17b18617c86
1 <?php
2 /**
4 * Modified from main codebase for the patient portal.
6 * Copyright (C) 2005-2006, 2013 Rod Roark <rod@sunsetsystems.com>
7 * Copyright (C) 2016-2017 Jerry Padgett <sjpadgett@gmail.com>
9 * LICENSE: This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 3
12 * of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
20 * @package OpenEMR
21 * @author Rod Roark <rod@sunsetsystems.com>
22 * @author Jerry Padgett <sjpadgett@gmail.com>
23 * @link http://www.open-emr.org
26 // Note from Rod 2013-01-22:
27 // This module needs to be refactored to share the same code that is in
28 // interface/main/calendar/find_appt_popup.php. It contains an old version
29 // of that logic and does not support exception dates for repeating events.
31 //continue session
32 session_start();
35 //landing page definition -- where to go if something goes wrong
36 $landingpage = "index.php?site=".$_SESSION['site_id'];
39 // kick out if patient not authenticated
40 if ( isset($_SESSION['pid']) && isset($_SESSION['patient_portal_onsite_two']) ) {
41 $pid = $_SESSION['pid'];
43 else {
44 session_destroy();
45 header('Location: '.$landingpage.'&w');
46 exit;
50 $ignoreAuth = 1;
52 include_once("../interface/globals.php");
53 include_once("$srcdir/patient.inc");
55 // Exit if the modify calendar for portal flag is not set patched out v5
56 /* if (!($GLOBALS['portal_onsite_appt_modify'])) {
57 echo htmlspecialchars( xl('You are not authorized to schedule appointments.'),ENT_NOQUOTES);
58 exit;
59 } */
61 $input_catid = $_REQUEST['catid'];
63 // Record an event into the slots array for a specified day.
64 function doOneDay($catid, $udate, $starttime, $duration, $prefcatid)
66 global $slots, $slotsecs, $slotstime, $slotbase, $slotcount, $input_catid;
67 $udate = strtotime($starttime, $udate);
68 if ($udate < $slotstime) return;
69 $i = (int) ($udate / $slotsecs) - $slotbase;
70 $iend = (int) (($duration + $slotsecs - 1) / $slotsecs) + $i;
71 if ($iend > $slotcount) $iend = $slotcount;
72 if ($iend <= $i) $iend = $i + 1;
73 for (; $i < $iend; ++$i) {
74 if ($catid == 2) { // in office
75 // If a category ID was specified when this popup was invoked, then select
76 // only IN events with a matching preferred category or with no preferred
77 // category; other IN events are to be treated as OUT events.
78 if ($input_catid) {
79 if ($prefcatid == $input_catid || !$prefcatid)
80 $slots[$i] |= 1;
81 else
82 $slots[$i] |= 2;
83 } else {
84 $slots[$i] |= 1;
86 break; // ignore any positive duration for IN
87 } else if ($catid == 3) { // out of office
88 $slots[$i] |= 2;
89 break; // ignore any positive duration for OUT
90 } else { // all other events reserve time
91 $slots[$i] |= 4;
96 // seconds per time slot
97 $slotsecs = $GLOBALS['calendar_interval'] * 60;
99 $catslots = 1;
100 if ($input_catid) {
101 $srow = sqlQuery("SELECT pc_duration FROM openemr_postcalendar_categories WHERE pc_catid = '$input_catid'");
102 if ($srow['pc_duration']) $catslots = ceil($srow['pc_duration'] / $slotsecs);
105 $info_msg = "";
107 $searchdays = 7; // default to a 1-week lookahead
108 if ($_REQUEST['searchdays']) $searchdays = $_REQUEST['searchdays'];
110 // Get a start date.
111 if ($_REQUEST['startdate'] && preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/",
112 $_REQUEST['startdate'], $matches))
114 $sdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
115 } else {
116 $sdate = date("Y-m-d");
119 // Get an end date - actually the date after the end date.
120 preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $sdate, $matches);
121 $edate = date("Y-m-d",
122 mktime(0, 0, 0, $matches[2], $matches[3] + $searchdays, $matches[1]));
124 // compute starting time slot number and number of slots.
125 $slotstime = strtotime("$sdate 00:00:00");
126 $slotetime = strtotime("$edate 00:00:00");
127 $slotbase = (int) ($slotstime / $slotsecs);
128 $slotcount = (int) ($slotetime / $slotsecs) - $slotbase;
130 if ($slotcount <= 0 || $slotcount > 100000) die("Invalid date range.");
132 $slotsperday = (int) (60 * 60 * 24 / $slotsecs);
134 // If we have a provider, search.
136 if ($_REQUEST['providerid']) {
137 $providerid = $_REQUEST['providerid'];
139 // Create and initialize the slot array. Values are bit-mapped:
140 // bit 0 = in-office occurs here
141 // bit 1 = out-of-office occurs here
142 // bit 2 = reserved
143 // So, values may range from 0 to 7.
145 $slots = array_pad(array(), $slotcount, 0);
147 // Note there is no need to sort the query results.
148 // echo $sdate." -- ".$edate;
149 $query = "SELECT pc_eventDate, pc_endDate, pc_startTime, pc_duration, " .
150 "pc_recurrtype, pc_recurrspec, pc_alldayevent, pc_catid, pc_prefcatid, pc_title " .
151 "FROM openemr_postcalendar_events " .
152 "WHERE pc_aid = '$providerid' AND " .
153 "((pc_endDate >= '$sdate' AND pc_eventDate < '$edate') OR " .
154 "(pc_endDate = '0000-00-00' AND pc_eventDate >= '$sdate' AND pc_eventDate < '$edate'))";
155 $res = sqlStatement($query);
156 // print_r($res);
158 while ($row = sqlFetchArray($res)) {
159 $thistime = strtotime($row['pc_eventDate'] . " 00:00:00");
160 if ($row['pc_recurrtype']) {
162 preg_match('/"event_repeat_freq_type";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
163 $repeattype = $matches[1];
165 preg_match('/"event_repeat_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
166 $repeatfreq = $matches[1];
167 if ($row['pc_recurrtype'] == 2) {
168 // Repeat type is 2 so frequency comes from event_repeat_on_freq.
169 preg_match('/"event_repeat_on_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
170 $repeatfreq = $matches[1];
172 if (! $repeatfreq) $repeatfreq = 1;
174 preg_match('/"event_repeat_on_num";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
175 $my_repeat_on_num = $matches[1];
177 preg_match('/"event_repeat_on_day";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
178 $my_repeat_on_day = $matches[1];
180 $endtime = strtotime($row['pc_endDate'] . " 00:00:00") + (24 * 60 * 60);
181 if ($endtime > $slotetime) $endtime = $slotetime;
183 $repeatix = 0;
184 while ($thistime < $endtime) {
186 // Skip the event if a repeat frequency > 1 was specified and this is
187 // not the desired occurrence.
188 if (! $repeatix) {
189 doOneDay($row['pc_catid'], $thistime, $row['pc_startTime'],
190 $row['pc_duration'], $row['pc_prefcatid']);
192 if (++$repeatix >= $repeatfreq) $repeatix = 0;
194 $adate = getdate($thistime);
196 if ($row['pc_recurrtype'] == 2) {
197 // Need to skip to nth or last weekday of the next month.
198 $adate['mon'] += 1;
199 if ($adate['mon'] > 12) {
200 $adate['year'] += 1;
201 $adate['mon'] -= 12;
203 if ($my_repeat_on_num < 5) { // not last
204 $adate['mday'] = 1;
205 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN, $adate['mon'], $adate['mday'], $adate['year']));
206 if ($dow > $my_repeat_on_day) $dow -= 7;
207 $adate['mday'] += ($my_repeat_on_num - 1) * 7 + $my_repeat_on_day - $dow;
209 else { // last weekday of month
210 $adate['mday'] = cal_days_in_month(CAL_GREGORIAN, $adate['mon'], $adate['year']);
211 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN, $adate['mon'], $adate['mday'], $adate['year']));
212 if ($dow < $my_repeat_on_day) $dow += 7;
213 $adate['mday'] += $my_repeat_on_day - $dow;
215 } // end recurrtype 2
217 else { // recurrtype 1
219 if ($repeattype == 0) { // daily
220 $adate['mday'] += 1;
221 } else if ($repeattype == 1) { // weekly
222 $adate['mday'] += 7;
223 } else if ($repeattype == 2) { // monthly
224 $adate['mon'] += 1;
225 } else if ($repeattype == 3) { // yearly
226 $adate['year'] += 1;
227 } else if ($repeattype == 4) { // work days
228 if ($adate['wday'] == 5) // if friday, skip to monday
229 $adate['mday'] += 3;
230 else if ($adate['wday'] == 6) // saturday should not happen
231 $adate['mday'] += 2;
232 else
233 $adate['mday'] += 1;
234 } else if ($repeattype == 5) { // monday
235 $adate['mday'] += 7;
236 } else if ($repeattype == 6) { // tuesday
237 $adate['mday'] += 7;
238 } else if ($repeattype == 7) { // wednesday
239 $adate['mday'] += 7;
240 } else if ($repeattype == 8) { // thursday
241 $adate['mday'] += 7;
242 } else if ($repeattype == 9) { // friday
243 $adate['mday'] += 7;
244 } else {
245 die("Invalid repeat type '$repeattype'");
248 } // end recurrtype 1
250 $thistime = mktime(0, 0, 0, $adate['mon'], $adate['mday'], $adate['year']);
252 } else {
253 doOneDay($row['pc_catid'], $thistime, $row['pc_startTime'],
254 $row['pc_duration'], $row['pc_prefcatid']);
258 // Mark all slots reserved where the provider is not in-office.
259 // Actually we could do this in the display loop instead.
260 $inoffice = false;
261 for ($i = 0; $i < $slotcount; ++$i) {
262 if (($i % $slotsperday) == 0) $inoffice = false;
263 if ($slots[$i] & 1) $inoffice = true;
264 if ($slots[$i] & 2) $inoffice = false;
265 if (! $inoffice) $slots[$i] |= 4;
269 <html>
270 <head>
271 <?php html_header_show(); ?>
272 <title><?php xl('Find Available Appointments','e'); ?></title>
273 <link rel="stylesheet" href='<?php echo $css_header ?>' type='text/css'>
274 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-3-3-4/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
275 <?php if ($_SESSION['language_direction'] == 'rtl') { ?>
276 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-rtl-3-3-4/dist/css/bootstrap-rtl.min.css" rel="stylesheet" type="text/css" />
277 <?php } ?>
278 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.min.css">
281 <!-- for the pop up calendar -->
282 <script src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-min-1-11-3/index.js" type="text/javascript"></script>
283 <script src="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-3-3-4/dist/js/bootstrap.min.js" type="text/javascript"></script>
284 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.full.min.js"></script>
286 <script>
288 function setappt(year,mon,mday,hours,minutes) {
289 if (opener.closed || ! opener.setappt)
290 alert('<?php xl('The destination form was closed; I cannot act on your selection.','e'); ?>');
291 else
292 opener.setappt(year,mon,mday,hours,minutes);
293 window.close();
294 return false;
297 </script>
300 <style>
301 form {
302 /* this eliminates the padding normally around a FORM tag */
303 padding: 0px;
304 margin: 0px;
306 #searchCriteria {
307 text-align: center;
308 width: 100%;
309 /* font-size: 0.8em; */
310 background-color: #bfe6ff;
311 font-weight: bold;
312 padding: 3px;
314 #searchResultsHeader {
315 width: 100%;
316 background-color: lightgrey;
318 #searchResultsHeader table {
319 width: 96%; /* not 100% because the 'searchResults' table has a scrollbar */
320 border-collapse: collapse;
322 #searchResultsHeader th {
323 /* font-size: 0.7em; */
325 #searchResults {
326 width: 100%;
327 height: 100%;
328 overflow: auto;
331 .srDate { width: 20%; }
332 .srTimes { width: 80%; }
334 #searchResults table {
335 width: 100%;
336 border-collapse: collapse;
337 background-color: white;
339 #searchResults td {
340 /* font-size: 0.7em; */
341 border-bottom: 1px solid gray;
342 padding: 1px 5px 1px 5px;
344 .highlight { background-color: #ff9; }
345 .blue_highlight { background-color: #BBCCDD; color: white; }
346 #am {
347 border-bottom: 1px solid lightgrey;
348 color: #00c;
350 #pm { color: #c00; }
351 #pm a { color: #c00; }
352 </style>
354 </head>
356 <body class="body_top">
358 <div id="searchCriteria">
359 <form method='post' name='theform' action='./find_appt_popup_user.php?providerid=<?php echo $providerid ?>&catid=<?php echo $input_catid ?>'>
360 <input type="hidden" name='bypatient' />
362 <?php xl('Start date:','e'); ?>
365 <input type='text' class='datepicker' name='startdate' id='startdate' size='10' value='<?php echo $sdate ?>'
366 title='yyyy-mm-dd starting date for search'/>
368 <?php xl('for','e'); ?>
369 <input type='text' name='searchdays' size='3' value='<?php echo $searchdays ?>'
370 title='Number of days to search from the start date' />
371 <?php xl('days','e'); ?>&nbsp;
372 <input type='submit' value='<?php xl('Search','e'); ?>'>
373 </div>
375 <?php if (!empty($slots)) : ?>
377 <div id="searchResultsHeader">
378 <table class='table table-bordered'>
379 <tr>
380 <th class="srDate"><?php xl ('Day','e'); ?></th>
381 <th class="srTimes"><?php xl ('Available Times','e'); ?></th>
382 </tr>
383 </table>
384 </div>
386 <div id="searchResults">
387 <table class='table table-condensed table-inversed table-bordered'>
388 <?php
389 $lastdate = "";
390 $ampmFlag = "am"; // establish an AM-PM line break flag
391 for ($i = 0; $i < $slotcount; ++$i) {
393 $available = true;
394 for ($j = $i; $j < $i + $catslots; ++$j) {
395 if ($slots[$j] >= 4) $available = false;
397 if (!$available) continue; // skip reserved slots
399 $utime = ($slotbase + $i) * $slotsecs;
400 $thisdate = date("Y-m-d", $utime);
401 if ($thisdate != $lastdate) {
402 // if a new day, start a new row
403 if ($lastdate) {
404 echo "</div>";
405 echo "</td>\n";
406 echo " </tr>\n";
408 $lastdate = $thisdate;
409 echo " <tr class='oneresult'>\n";
410 echo " <td class='srDate'>" . date("l", $utime)."<br>".date("Y-m-d", $utime) . "</td>\n";
411 echo " <td class='srTimes'>";
412 echo "<div id='am'>AM ";
413 $ampmFlag = "am"; // reset the AMPM flag
416 $ampm = date('a', $utime);
417 if ($ampmFlag != $ampm) { echo "</div><div id='pm'>PM "; }
418 $ampmFlag = $ampm;
420 $atitle = "Choose ".date("h:i a", $utime);
421 $adate = getdate($utime);
422 $anchor = "<a href='' onclick='return setappt(" .
423 $adate['year'] . "," .
424 $adate['mon'] . "," .
425 $adate['mday'] . "," .
426 $adate['hours'] . "," .
427 $adate['minutes'] . ")'".
428 " title='$atitle' alt='$atitle'".
429 ">";
430 echo (strlen(date('g',$utime)) < 2 ? "<span style='visibility:hidden'>0</span>" : "") .
431 $anchor . date("g:i", $utime) . "</a> ";
433 // If category duration is more than 1 slot, increment $i appropriately.
434 // This is to avoid reporting available times on undesirable boundaries.
435 $i += $catslots - 1;
437 if ($lastdate) {
438 echo "</td>\n";
439 echo " </tr>\n";
440 } else {
441 echo " <tr><td colspan='2'> " . xl('No openings were found for this period.','e') . "</td></tr>\n";
444 </table>
445 </div>
446 </div>
447 <?php endif; ?>
449 </form>
450 </body>
452 <script language='JavaScript'>
454 // jQuery stuff to make the page a little easier to use
455 $(document).ready(function(){
456 $(".oneresult").mouseover(function() { $(this).toggleClass("highlight"); });
457 $(".oneresult").mouseout(function() { $(this).toggleClass("highlight"); });
458 $(".oneresult a").mouseover(function () { $(this).toggleClass("blue_highlight"); $(this).children().toggleClass("blue_highlight"); });
459 $(".oneresult a").mouseout(function() { $(this).toggleClass("blue_highlight"); $(this).children().toggleClass("blue_highlight"); });
461 $('.datepicker').datetimepicker({
462 <?php $datetimepicker_timepicker = false; ?>
463 <?php $datetimepicker_formatInput = false; ?>
464 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
465 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
470 </script>
472 </html>