5 * Modified from main codebase for the patient portal.
8 * @link http://www.open-emr.org
9 * @author Rod Roark <rod@sunsetsystems.com>
10 * @author Jerry Padgett <sjpadgett@gmail.com>
11 * @author Brady Miller <brady.g.miller@gmail.com>
12 * @author Ian Jardine ( github.com/epsdky )
13 * @copyright Copyright (C) 2005-2013 Rod Roark <rod@sunsetsystems.com>
14 * @copyright Copyright (C) 2016-2017 Jerry Padgett <sjpadgett@gmail.com>
15 * @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com>
16 * @copyright Copyright (C) 2019 Ian Jardine ( github.com/epsdky )
17 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
20 // Note from Rod 2013-01-22:
21 // This module needs to be refactored to share the same code that is in
22 // interface/main/calendar/find_appt_popup.php. It contains an old version
23 // of that logic and does not support exception dates for repeating events.
25 // Rod mentioned in the previous comment that the code "does not support exception dates for repeating events".
26 // This issue no longer exists - epsdky 2019
29 // Will start the (patient) portal OpenEMR session/cookie.
30 require_once(dirname(__FILE__
) . "/../src/Common/Session/SessionUtil.php");
31 OpenEMR\Common\Session\SessionUtil
::portalSessionStart();
34 //landing page definition -- where to go if something goes wrong
35 $landingpage = "index.php?site=" . urlencode($_SESSION['site_id']);
38 // kick out if patient not authenticated
39 if (isset($_SESSION['pid']) && isset($_SESSION['patient_portal_onsite_two'])) {
40 $pid = $_SESSION['pid'];
42 OpenEMR\Common\Session\SessionUtil
::portalSessionCookieDestroy();
43 header('Location: ' . $landingpage . '&w');
49 $ignoreAuth_onsite_portal = true;
51 require_once("../interface/globals.php");
52 require_once("$srcdir/patient.inc.php");
53 require_once(dirname(__FILE__
) . "/../library/appointments.inc.php");
55 use OpenEMR\Core\Header
;
57 $input_catid = $_REQUEST['catid'];
59 // Record an event into the slots array for a specified day.
60 function doOneDay($catid, $udate, $starttime, $duration, $prefcatid)
62 global $slots, $slotsecs, $slotstime, $slotbase, $slotcount, $input_catid;
63 $udate = strtotime($starttime, $udate);
64 if ($udate < $slotstime) {
68 $i = (int)($udate / $slotsecs) - $slotbase;
69 $iend = (int)(($duration +
$slotsecs - 1) / $slotsecs) +
$i;
70 if ($iend > $slotcount) {
78 for (; $i < $iend; ++
$i) {
79 if ($catid == 2) { // in office
80 // If a category ID was specified when this popup was invoked, then select
81 // only IN events with a matching preferred category or with no preferred
82 // category; other IN events are to be treated as OUT events.
84 if (!empty($slots[$i])) {
85 if ($prefcatid == $input_catid ||
!$prefcatid) {
97 break; // ignore any positive duration for IN
98 } elseif ($catid == 3) { // out of office
100 break; // ignore any positive duration for OUT
101 } else { // all other events reserve time
107 // seconds per time slot
108 $slotsecs = $GLOBALS['calendar_interval'] * 60;
112 $srow = sqlQuery("SELECT pc_duration FROM openemr_postcalendar_categories WHERE pc_catid = ?", array($input_catid));
113 if ($srow['pc_duration']) {
114 $catslots = ceil($srow['pc_duration'] / $slotsecs);
120 $searchdays = 7; // default to a 1-week lookahead
121 if ($_REQUEST['searchdays'] ??
null) {
122 $searchdays = $_REQUEST['searchdays'];
127 $_REQUEST['startdate'] && preg_match(
128 "/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/",
129 $_REQUEST['startdate'],
133 $sdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
135 $sdate = date("Y-m-d");
138 // Get an end date - actually the date after the end date.
139 preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $sdate, $matches);
142 mktime(0, 0, 0, $matches[2], $matches[3] +
$searchdays, $matches[1])
145 // compute starting time slot number and number of slots.
146 $slotstime = strtotime("$sdate 00:00:00");
147 $slotetime = strtotime("$edate 00:00:00");
148 $slotbase = (int)($slotstime / $slotsecs);
149 $slotcount = (int)($slotetime / $slotsecs) - $slotbase;
151 if ($slotcount <= 0 ||
$slotcount > 100000) {
152 die("Invalid date range.");
155 $slotsperday = (int)(60 * 60 * 24 / $slotsecs);
157 // If we have a provider, search.
159 if ($_REQUEST['providerid']) {
160 $providerid = $_REQUEST['providerid'];
162 // Create and initialize the slot array. Values are bit-mapped:
163 // bit 0 = in-office occurs here
164 // bit 1 = out-of-office occurs here
166 // So, values may range from 0 to 7.
168 $slots = array_pad(array(), $slotcount, 0);
170 // Note there is no need to sort the query results.
171 // echo $sdate." -- ".$edate;
172 $query = "SELECT pc_eventDate, pc_endDate, pc_startTime, pc_duration, " .
173 "pc_recurrtype, pc_recurrspec, pc_alldayevent, pc_catid, pc_prefcatid, pc_title " .
174 "FROM openemr_postcalendar_events " .
175 "WHERE pc_aid = ? AND " .
176 "((pc_endDate >= ? AND pc_eventDate < ?) OR " .
177 "(pc_endDate = '0000-00-00' AND pc_eventDate >= ? AND pc_eventDate < ?))";
179 $sqlBindArray = array();
180 array_push($sqlBindArray, $providerid, $sdate, $edate, $sdate, $edate);
182 $events2 = fetchEvents($sdate, $edate, null, null, false, 0, $sqlBindArray, $query);
183 foreach ($events2 as $row) {
184 $thistime = strtotime($row['pc_eventDate'] . " 00:00:00");
188 $row['pc_startTime'],
195 // Mark all slots reserved where the provider is not in-office.
196 // Actually we could do this in the display loop instead.
198 for ($i = 0; $i < $slotcount; ++
$i) {
199 if (($i %
$slotsperday) == 0) {
203 if ($slots[$i] & 1) {
207 if ($slots[$i] & 2) {
220 <title
><?php
echo xlt('Find Available Appointments'); ?
></title
>
221 <?php Header
::setupHeader(['no_main-theme', 'portal-theme', 'datetime-picker', 'opener']); ?
>
224 function setappt(year
, mon
, mday
, hours
, minutes
) {
225 opener
.setappt(year
, mon
, mday
, hours
, minutes
);
234 /* this eliminates the padding normally around a FORM tag */
252 #searchResults table {
254 border
-collapse
: collapse
;
255 background
-color
: var(--white
);
259 border
-bottom
: 1px solid
var(--gray600
);
264 background
-color
: #BBCCDD;
270 text
-decoration
: none
;
274 color
: var(--danger
);
276 text
-decoration
: none
;
282 <body
class="body_top">
284 <div
class="table-primary" id
="searchCriteria">
285 <form method
='post' name
='theform' action
='./find_appt_popup_user.php?providerid=<?php echo attr_url($providerid); ?>&catid=<?php echo attr_url($input_catid); ?>'>
286 <input type
="hidden" name
='bypatient' />
287 <div
class="form-row mx-0 align-items-center">
288 <label
for="startdate" class="col-1 mx-2 col-form-label"><?php
echo xlt('Start date:'); ?
></label
>
289 <div
class="col-auto">
290 <input type
='text' class='datepicker form-control' name
='startdate' id
='startdate' size
='10' value
='<?php echo attr($sdate); ?>' title
='yyyy-mm-dd starting date for search' />
292 <label
for="searchdays" class="col-auto col-form-label"><?php
echo xlt('for'); ?
></label
>
293 <div
class="col-auto">
294 <input type
='text' class="form-control" name
='searchdays' id
='searchdays' size
='3' value
='<?php echo attr($searchdays); ?>' title
='Number of days to search from the start date' />
296 <label
for="searchdays" class="col-auto col-form-label"><?php
echo xlt('days'); ?
></label
>
297 <div
class="col-auto">
298 <input type
='submit' class="btn btn-primary btn-sm btn-block" value
='<?php echo xla('Search
'); ?>' />
303 <?php
if (!empty($slots)) : ?
>
304 <div id
="searchResultsHeader">
305 <table
class='table table-bordered'>
310 <div id
="searchResults" class="container-fluid">
311 <div
class="table-responsive">
312 <table
class='table table-sm table-striped table-bordered'>
313 <thead id
="searchResultsHeader">
315 <th
class="table-dark text-light"><?php
echo xlt('Day'); ?
></th
>
316 <th
class="srTimes"><?php
echo xlt('Available Times'); ?
></th
>
321 $ampmFlag = "am"; // establish an AM-PM line break flag
322 for ($i = 0; $i < $slotcount; ++
$i) {
324 for ($j = $i; $j < $i +
$catslots; ++
$j) {
325 if ($slots[$j] >= 4) {
331 continue; // skip reserved slots
334 $utime = ($slotbase +
$i) * $slotsecs;
335 $thisdate = date("Y-m-d", $utime);
336 if ($thisdate != $lastdate) {
337 // if a new day, start a new row
344 $lastdate = $thisdate;
345 echo " <tr class='oneresult'>\n";
346 echo " <td class='table-dark text-light'>" . date("l", $utime) . "<br />" . date("Y-m-d", $utime) . "</td>\n";
347 echo " <td class='srTimes'>";
348 echo "<div id='am'>AM<hr class='m-0 p-0 mb-n3'/><br/>";
349 $ampmFlag = "am"; // reset the AMPM flag
352 $ampm = date('a', $utime);
353 if ($ampmFlag != $ampm) {
354 echo "</div><div id='pm'><hr class='m-0 p-0' />PM<hr class='m-0 p-0 mb-n3' /><br/>";
359 $atitle = "Choose " . date("h:i a", $utime);
360 $adate = getdate($utime);
362 $anchor = "<a href='' onclick='return setappt(" .
363 attr_js(date("Y", $utime)) . "," .
364 attr_js($adate['mon']) . "," .
365 attr_js($adate['mday']) . "," .
366 attr_js(date("G", $utime)) . "," .
367 attr_js(date("i", $utime)) . "," .
368 attr_js(date('a', $utime)) . ")'" .
369 " title='" . attr($atitle) . "' alt='" . attr($atitle) . "'" .
371 echo (strlen(date('g', $utime)) < 2 ?
"<span style='visibility:hidden'>0</span>" : "") .
372 $anchor . date("g:i", $utime) . "</a> ";
374 // If category duration is more than 1 slot, increment $i appropriately.
375 // This is to avoid reporting available times on undesirable boundaries.
383 echo " <tr><td colspan='2'> " . xlt('No openings were found for this period.') . "</td></tr>\n";
396 // jQuery stuff to make the page a little easier to use
398 $
(".oneresult").hover(function () {
399 $
(this
).toggleClass("highlight");
401 $
(this
).toggleClass("highlight");
403 $
(".oneresult a").hover(function () {
404 $
(this
).toggleClass("blue_highlight");
405 $
(this
).children().toggleClass("blue_highlight");
407 $
(this
).toggleClass("blue_highlight");
408 $
(this
).children().toggleClass("blue_highlight");
411 $
('.datepicker').datetimepicker({
412 <?php
$datetimepicker_timepicker = false; ?
>
413 <?php
$datetimepicker_formatInput = false; ?
>
414 <?php
require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?
>
415 <?php
// can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>