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>;.
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.
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'];
45 header('Location: '.$landingpage.'&w');
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);
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) {
65 global $slots, $slotsecs, $slotstime, $slotbase, $slotcount, $input_catid;
66 $udate = strtotime($starttime, $udate);
67 if ($udate < $slotstime) return;
68 $i = (int) ($udate / $slotsecs) - $slotbase;
69 $iend = (int) (($duration +
$slotsecs - 1) / $slotsecs) +
$i;
70 if ($iend > $slotcount) $iend = $slotcount;
71 if ($iend <= $i) $iend = $i +
1;
72 for (; $i < $iend; ++
$i) {
73 if ($catid == 2) { // in office
74 // If a category ID was specified when this popup was invoked, then select
75 // only IN events with a matching preferred category or with no preferred
76 // category; other IN events are to be treated as OUT events.
78 if ($prefcatid == $input_catid ||
!$prefcatid)
85 break; // ignore any positive duration for IN
86 } else if ($catid == 3) { // out of office
88 break; // ignore any positive duration for OUT
89 } else { // all other events reserve time
95 // seconds per time slot
96 $slotsecs = $GLOBALS['calendar_interval'] * 60;
100 $srow = sqlQuery("SELECT pc_duration FROM openemr_postcalendar_categories WHERE pc_catid = '$input_catid'");
101 if ($srow['pc_duration']) $catslots = ceil($srow['pc_duration'] / $slotsecs);
106 $searchdays = 7; // default to a 1-week lookahead
107 if ($_REQUEST['searchdays']) $searchdays = $_REQUEST['searchdays'];
110 if ($_REQUEST['startdate'] && preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/",
111 $_REQUEST['startdate'], $matches))
113 $sdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
115 $sdate = date("Y-m-d");
118 // Get an end date - actually the date after the end date.
119 preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $sdate, $matches);
120 $edate = date("Y-m-d",
121 mktime(0, 0, 0, $matches[2], $matches[3] +
$searchdays, $matches[1]));
123 // compute starting time slot number and number of slots.
124 $slotstime = strtotime("$sdate 00:00:00");
125 $slotetime = strtotime("$edate 00:00:00");
126 $slotbase = (int) ($slotstime / $slotsecs);
127 $slotcount = (int) ($slotetime / $slotsecs) - $slotbase;
129 if ($slotcount <= 0 ||
$slotcount > 100000) die("Invalid date range.");
131 $slotsperday = (int) (60 * 60 * 24 / $slotsecs);
133 // If we have a provider, search.
135 if ($_REQUEST['providerid']) {
136 $providerid = $_REQUEST['providerid'];
138 // Create and initialize the slot array. Values are bit-mapped:
139 // bit 0 = in-office occurs here
140 // bit 1 = out-of-office occurs here
142 // So, values may range from 0 to 7.
144 $slots = array_pad(array(), $slotcount, 0);
146 // Note there is no need to sort the query results.
147 // echo $sdate." -- ".$edate;
148 $query = "SELECT pc_eventDate, pc_endDate, pc_startTime, pc_duration, " .
149 "pc_recurrtype, pc_recurrspec, pc_alldayevent, pc_catid, pc_prefcatid, pc_title " .
150 "FROM openemr_postcalendar_events " .
151 "WHERE pc_aid = '$providerid' AND " .
152 "((pc_endDate >= '$sdate' AND pc_eventDate < '$edate') OR " .
153 "(pc_endDate = '0000-00-00' AND pc_eventDate >= '$sdate' AND pc_eventDate < '$edate'))";
154 $res = sqlStatement($query);
157 while ($row = sqlFetchArray($res)) {
158 $thistime = strtotime($row['pc_eventDate'] . " 00:00:00");
159 if ($row['pc_recurrtype']) {
161 preg_match('/"event_repeat_freq_type";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
162 $repeattype = $matches[1];
164 preg_match('/"event_repeat_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
165 $repeatfreq = $matches[1];
166 if ($row['pc_recurrtype'] == 2) {
167 // Repeat type is 2 so frequency comes from event_repeat_on_freq.
168 preg_match('/"event_repeat_on_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
169 $repeatfreq = $matches[1];
171 if (! $repeatfreq) $repeatfreq = 1;
173 preg_match('/"event_repeat_on_num";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
174 $my_repeat_on_num = $matches[1];
176 preg_match('/"event_repeat_on_day";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
177 $my_repeat_on_day = $matches[1];
179 $endtime = strtotime($row['pc_endDate'] . " 00:00:00") +
(24 * 60 * 60);
180 if ($endtime > $slotetime) $endtime = $slotetime;
183 while ($thistime < $endtime) {
185 // Skip the event if a repeat frequency > 1 was specified and this is
186 // not the desired occurrence.
188 doOneDay($row['pc_catid'], $thistime, $row['pc_startTime'],
189 $row['pc_duration'], $row['pc_prefcatid']);
191 if (++
$repeatix >= $repeatfreq) $repeatix = 0;
193 $adate = getdate($thistime);
195 if ($row['pc_recurrtype'] == 2) {
196 // Need to skip to nth or last weekday of the next month.
198 if ($adate['mon'] > 12) {
202 if ($my_repeat_on_num < 5) { // not last
204 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN
, $adate['mon'], $adate['mday'], $adate['year']));
205 if ($dow > $my_repeat_on_day) $dow -= 7;
206 $adate['mday'] +
= ($my_repeat_on_num - 1) * 7 +
$my_repeat_on_day - $dow;
208 else { // last weekday of month
209 $adate['mday'] = cal_days_in_month(CAL_GREGORIAN
, $adate['mon'], $adate['year']);
210 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN
, $adate['mon'], $adate['mday'], $adate['year']));
211 if ($dow < $my_repeat_on_day) $dow +
= 7;
212 $adate['mday'] +
= $my_repeat_on_day - $dow;
214 } // end recurrtype 2
216 else { // recurrtype 1
218 if ($repeattype == 0) { // daily
220 } else if ($repeattype == 1) { // weekly
222 } else if ($repeattype == 2) { // monthly
224 } else if ($repeattype == 3) { // yearly
226 } else if ($repeattype == 4) { // work days
227 if ($adate['wday'] == 5) // if friday, skip to monday
229 else if ($adate['wday'] == 6) // saturday should not happen
233 } else if ($repeattype == 5) { // monday
235 } else if ($repeattype == 6) { // tuesday
237 } else if ($repeattype == 7) { // wednesday
239 } else if ($repeattype == 8) { // thursday
241 } else if ($repeattype == 9) { // friday
244 die("Invalid repeat type '$repeattype'");
247 } // end recurrtype 1
249 $thistime = mktime(0, 0, 0, $adate['mon'], $adate['mday'], $adate['year']);
252 doOneDay($row['pc_catid'], $thistime, $row['pc_startTime'],
253 $row['pc_duration'], $row['pc_prefcatid']);
257 // Mark all slots reserved where the provider is not in-office.
258 // Actually we could do this in the display loop instead.
260 for ($i = 0; $i < $slotcount; ++
$i) {
261 if (($i %
$slotsperday) == 0) $inoffice = false;
262 if ($slots[$i] & 1) $inoffice = true;
263 if ($slots[$i] & 2) $inoffice = false;
264 if (! $inoffice) $slots[$i] |
= 4;
270 <?php
html_header_show(); ?
>
271 <title
><?php
xl('Find Available Appointments','e'); ?
></title
>
272 <link rel
="stylesheet" href
='<?php echo $css_header ?>' type
='text/css'>
273 <link href
="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-3-3-4/dist/css/bootstrap.min.css" rel
="stylesheet" type
="text/css" />
274 <?php
if ($_SESSION['language_direction'] == 'rtl') { ?
>
275 <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 <link rel
="stylesheet" href
="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.min.css">
280 <!-- for the pop up calendar
-->
281 <script src
="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-min-1-11-3/index.js" type
="text/javascript"></script
>
282 <script src
="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-3-3-4/dist/js/bootstrap.min.js" type
="text/javascript"></script
>
283 <script type
="text/javascript" src
="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.full.min.js"></script
>
287 function setappt(year
,mon
,mday
,hours
,minutes
) {
288 if (opener
.closed ||
! opener
.setappt
)
289 alert('<?php xl('The destination form was closed
; I cannot act on your selection
.','e
'); ?>');
291 opener
.setappt(year
,mon
,mday
,hours
,minutes
);
301 /* this eliminates the padding normally around a FORM tag */
308 /* font-size: 0.8em; */
309 background
-color
: #bfe6ff;
313 #searchResultsHeader {
315 background
-color
: lightgrey
;
317 #searchResultsHeader table {
318 width
: 96%
; /* not 100% because the 'searchResults' table has a scrollbar */
319 border
-collapse
: collapse
;
321 #searchResultsHeader th {
322 /* font-size: 0.7em; */
330 .srDate
{ width
: 20%
; }
331 .srTimes
{ width
: 80%
; }
333 #searchResults table {
335 border
-collapse
: collapse
;
336 background
-color
: white
;
339 /* font-size: 0.7em; */
340 border
-bottom
: 1px solid gray
;
341 padding
: 1px
5px
1px
5px
;
343 .highlight
{ background
-color
: #ff9; }
344 .blue_highlight
{ background
-color
: #BBCCDD; color: white; }
346 border
-bottom
: 1px solid lightgrey
;
350 #pm a { color: #c00; }
355 <body
class="body_top">
357 <div id
="searchCriteria">
358 <form method
='post' name
='theform' action
='./find_appt_popup_user.php?providerid=<?php echo $providerid ?>&catid=<?php echo $input_catid ?>'>
359 <input type
="hidden" name
='bypatient' />
361 <?php
xl('Start date:','e'); ?
>
364 <input type
='text' class='datepicker' name
='startdate' id
='startdate' size
='10' value
='<?php echo $sdate ?>'
365 title
='yyyy-mm-dd starting date for search'/>
367 <?php
xl('for','e'); ?
>
368 <input type
='text' name
='searchdays' size
='3' value
='<?php echo $searchdays ?>'
369 title
='Number of days to search from the start date' />
370 <?php
xl('days','e'); ?
> 
;
371 <input type
='submit' value
='<?php xl('Search
','e
'); ?>'>
374 <?php
if (!empty($slots)) : ?
>
376 <div id
="searchResultsHeader">
377 <table
class='table table-bordered'>
379 <th
class="srDate"><?php
xl ('Day','e'); ?
></th
>
380 <th
class="srTimes"><?php
xl ('Available Times','e'); ?
></th
>
385 <div id
="searchResults">
386 <table
class='table table-condensed table-inversed table-bordered'>
389 $ampmFlag = "am"; // establish an AM-PM line break flag
390 for ($i = 0; $i < $slotcount; ++
$i) {
393 for ($j = $i; $j < $i +
$catslots; ++
$j) {
394 if ($slots[$j] >= 4) $available = false;
396 if (!$available) continue; // skip reserved slots
398 $utime = ($slotbase +
$i) * $slotsecs;
399 $thisdate = date("Y-m-d", $utime);
400 if ($thisdate != $lastdate) {
401 // if a new day, start a new row
407 $lastdate = $thisdate;
408 echo " <tr class='oneresult'>\n";
409 echo " <td class='srDate'>" . date("l", $utime)."<br>".date("Y-m-d", $utime) . "</td>\n";
410 echo " <td class='srTimes'>";
411 echo "<div id='am'>AM ";
412 $ampmFlag = "am"; // reset the AMPM flag
415 $ampm = date('a', $utime);
416 if ($ampmFlag != $ampm) { echo "</div><div id='pm'>PM "; }
419 $atitle = "Choose ".date("h:i a", $utime);
420 $adate = getdate($utime);
421 $anchor = "<a href='' onclick='return setappt(" .
422 $adate['year'] . "," .
423 $adate['mon'] . "," .
424 $adate['mday'] . "," .
425 $adate['hours'] . "," .
426 $adate['minutes'] . ")'".
427 " title='$atitle' alt='$atitle'".
429 echo (strlen(date('g',$utime)) < 2 ?
"<span style='visibility:hidden'>0</span>" : "") .
430 $anchor . date("g:i", $utime) . "</a> ";
432 // If category duration is more than 1 slot, increment $i appropriately.
433 // This is to avoid reporting available times on undesirable boundaries.
440 echo " <tr><td colspan='2'> " . xl('No openings were found for this period.','e') . "</td></tr>\n";
451 <script language
='JavaScript'>
453 // jQuery stuff to make the page a little easier to use
454 $
(document
).ready(function(){
455 $
(".oneresult").mouseover(function() { $
(this
).toggleClass("highlight"); });
456 $
(".oneresult").mouseout(function() { $
(this
).toggleClass("highlight"); });
457 $
(".oneresult a").mouseover(function () { $
(this
).toggleClass("blue_highlight"); $
(this
).children().toggleClass("blue_highlight"); });
458 $
(".oneresult a").mouseout(function() { $
(this
).toggleClass("blue_highlight"); $
(this
).children().toggleClass("blue_highlight"); });
460 $
('.datepicker').datetimepicker({
461 <?php
$datetimepicker_timepicker = false; ?
>
462 <?php
$datetimepicker_formatInput = false; ?
>
463 <?php
require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?
>
464 <?php
// can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>