Fix Only Year validation
[openemr.git] / portal / find_appt_popup_user.php
blobb8300ae2c2c5d76729e48ff95430b9adb3797ee6
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'];
42 } else {
43 session_destroy();
44 header('Location: '.$landingpage.'&w');
45 exit();
50 $ignoreAuth = 1;
52 include_once("../interface/globals.php");
53 include_once("$srcdir/patient.inc");
55 $input_catid = $_REQUEST['catid'];
57 // Record an event into the slots array for a specified day.
58 function doOneDay($catid, $udate, $starttime, $duration, $prefcatid)
60 global $slots, $slotsecs, $slotstime, $slotbase, $slotcount, $input_catid;
61 $udate = strtotime($starttime, $udate);
62 if ($udate < $slotstime) {
63 return;
66 $i = (int) ($udate / $slotsecs) - $slotbase;
67 $iend = (int) (($duration + $slotsecs - 1) / $slotsecs) + $i;
68 if ($iend > $slotcount) {
69 $iend = $slotcount;
72 if ($iend <= $i) {
73 $iend = $i + 1;
76 for (; $i < $iend; ++$i) {
77 if ($catid == 2) { // in office
78 // If a category ID was specified when this popup was invoked, then select
79 // only IN events with a matching preferred category or with no preferred
80 // category; other IN events are to be treated as OUT events.
81 if ($input_catid) {
82 if ($prefcatid == $input_catid || !$prefcatid) {
83 $slots[$i] |= 1;
84 } else {
85 $slots[$i] |= 2;
87 } else {
88 $slots[$i] |= 1;
91 break; // ignore any positive duration for IN
92 } else if ($catid == 3) { // out of office
93 $slots[$i] |= 2;
94 break; // ignore any positive duration for OUT
95 } else { // all other events reserve time
96 $slots[$i] |= 4;
101 // seconds per time slot
102 $slotsecs = $GLOBALS['calendar_interval'] * 60;
104 $catslots = 1;
105 if ($input_catid) {
106 $srow = sqlQuery("SELECT pc_duration FROM openemr_postcalendar_categories WHERE pc_catid = '$input_catid'");
107 if ($srow['pc_duration']) {
108 $catslots = ceil($srow['pc_duration'] / $slotsecs);
112 $info_msg = "";
114 $searchdays = 7; // default to a 1-week lookahead
115 if ($_REQUEST['searchdays']) {
116 $searchdays = $_REQUEST['searchdays'];
119 // Get a start date.
120 if ($_REQUEST['startdate'] && preg_match(
121 "/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/",
122 $_REQUEST['startdate'],
123 $matches
124 )) {
125 $sdate = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
126 } else {
127 $sdate = date("Y-m-d");
130 // Get an end date - actually the date after the end date.
131 preg_match("/(\d\d\d\d)\D*(\d\d)\D*(\d\d)/", $sdate, $matches);
132 $edate = date(
133 "Y-m-d",
134 mktime(0, 0, 0, $matches[2], $matches[3] + $searchdays, $matches[1])
137 // compute starting time slot number and number of slots.
138 $slotstime = strtotime("$sdate 00:00:00");
139 $slotetime = strtotime("$edate 00:00:00");
140 $slotbase = (int) ($slotstime / $slotsecs);
141 $slotcount = (int) ($slotetime / $slotsecs) - $slotbase;
143 if ($slotcount <= 0 || $slotcount > 100000) {
144 die("Invalid date range.");
147 $slotsperday = (int) (60 * 60 * 24 / $slotsecs);
149 // If we have a provider, search.
151 if ($_REQUEST['providerid']) {
152 $providerid = $_REQUEST['providerid'];
154 // Create and initialize the slot array. Values are bit-mapped:
155 // bit 0 = in-office occurs here
156 // bit 1 = out-of-office occurs here
157 // bit 2 = reserved
158 // So, values may range from 0 to 7.
160 $slots = array_pad(array(), $slotcount, 0);
162 // Note there is no need to sort the query results.
163 // echo $sdate." -- ".$edate;
164 $query = "SELECT pc_eventDate, pc_endDate, pc_startTime, pc_duration, " .
165 "pc_recurrtype, pc_recurrspec, pc_alldayevent, pc_catid, pc_prefcatid, pc_title " .
166 "FROM openemr_postcalendar_events " .
167 "WHERE pc_aid = '$providerid' AND " .
168 "((pc_endDate >= '$sdate' AND pc_eventDate < '$edate') OR " .
169 "(pc_endDate = '0000-00-00' AND pc_eventDate >= '$sdate' AND pc_eventDate < '$edate'))";
170 $res = sqlStatement($query);
171 // print_r($res);
173 while ($row = sqlFetchArray($res)) {
174 $thistime = strtotime($row['pc_eventDate'] . " 00:00:00");
175 if ($row['pc_recurrtype']) {
176 preg_match('/"event_repeat_freq_type";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
177 $repeattype = $matches[1];
179 preg_match('/"event_repeat_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
180 $repeatfreq = $matches[1];
181 if ($row['pc_recurrtype'] == 2) {
182 // Repeat type is 2 so frequency comes from event_repeat_on_freq.
183 preg_match('/"event_repeat_on_freq";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
184 $repeatfreq = $matches[1];
187 if (! $repeatfreq) {
188 $repeatfreq = 1;
191 preg_match('/"event_repeat_on_num";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
192 $my_repeat_on_num = $matches[1];
194 preg_match('/"event_repeat_on_day";s:1:"(\d)"/', $row['pc_recurrspec'], $matches);
195 $my_repeat_on_day = $matches[1];
197 $endtime = strtotime($row['pc_endDate'] . " 00:00:00") + (24 * 60 * 60);
198 if ($endtime > $slotetime) {
199 $endtime = $slotetime;
202 $repeatix = 0;
203 while ($thistime < $endtime) {
204 // Skip the event if a repeat frequency > 1 was specified and this is
205 // not the desired occurrence.
206 if (! $repeatix) {
207 doOneDay(
208 $row['pc_catid'],
209 $thistime,
210 $row['pc_startTime'],
211 $row['pc_duration'],
212 $row['pc_prefcatid']
216 if (++$repeatix >= $repeatfreq) {
217 $repeatix = 0;
220 $adate = getdate($thistime);
222 if ($row['pc_recurrtype'] == 2) {
223 // Need to skip to nth or last weekday of the next month.
224 $adate['mon'] += 1;
225 if ($adate['mon'] > 12) {
226 $adate['year'] += 1;
227 $adate['mon'] -= 12;
230 if ($my_repeat_on_num < 5) { // not last
231 $adate['mday'] = 1;
232 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN, $adate['mon'], $adate['mday'], $adate['year']));
233 if ($dow > $my_repeat_on_day) {
234 $dow -= 7;
237 $adate['mday'] += ($my_repeat_on_num - 1) * 7 + $my_repeat_on_day - $dow;
238 } else { // last weekday of month
239 $adate['mday'] = cal_days_in_month(CAL_GREGORIAN, $adate['mon'], $adate['year']);
240 $dow = jddayofweek(cal_to_jd(CAL_GREGORIAN, $adate['mon'], $adate['mday'], $adate['year']));
241 if ($dow < $my_repeat_on_day) {
242 $dow += 7;
245 $adate['mday'] += $my_repeat_on_day - $dow;
247 } // end recurrtype 2
249 else { // recurrtype 1
251 if ($repeattype == 0) { // daily
252 $adate['mday'] += 1;
253 } else if ($repeattype == 1) { // weekly
254 $adate['mday'] += 7;
255 } else if ($repeattype == 2) { // monthly
256 $adate['mon'] += 1;
257 } else if ($repeattype == 3) { // yearly
258 $adate['year'] += 1;
259 } else if ($repeattype == 4) { // work days
260 if ($adate['wday'] == 5) { // if friday, skip to monday
261 $adate['mday'] += 3;
262 } else if ($adate['wday'] == 6) { // saturday should not happen
263 $adate['mday'] += 2;
264 } else {
265 $adate['mday'] += 1;
267 } else if ($repeattype == 5) { // monday
268 $adate['mday'] += 7;
269 } else if ($repeattype == 6) { // tuesday
270 $adate['mday'] += 7;
271 } else if ($repeattype == 7) { // wednesday
272 $adate['mday'] += 7;
273 } else if ($repeattype == 8) { // thursday
274 $adate['mday'] += 7;
275 } else if ($repeattype == 9) { // friday
276 $adate['mday'] += 7;
277 } else {
278 die("Invalid repeat type '$repeattype'");
280 } // end recurrtype 1
282 $thistime = mktime(0, 0, 0, $adate['mon'], $adate['mday'], $adate['year']);
284 } else {
285 doOneDay(
286 $row['pc_catid'],
287 $thistime,
288 $row['pc_startTime'],
289 $row['pc_duration'],
290 $row['pc_prefcatid']
295 // Mark all slots reserved where the provider is not in-office.
296 // Actually we could do this in the display loop instead.
297 $inoffice = false;
298 for ($i = 0; $i < $slotcount; ++$i) {
299 if (($i % $slotsperday) == 0) {
300 $inoffice = false;
303 if ($slots[$i] & 1) {
304 $inoffice = true;
307 if ($slots[$i] & 2) {
308 $inoffice = false;
311 if (! $inoffice) {
312 $slots[$i] |= 4;
317 <!DOCTYPE html>
318 <html>
319 <head>
320 <script type="text/javascript" src="<?php echo $webroot ?>/interface/main/tabs/js/include_opener.js"></script>
321 <title><?php xl('Find Available Appointments', 'e'); ?></title>
322 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-3-3-4/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
323 <?php if ($_SESSION['language_direction'] == 'rtl') { ?>
324 <link href="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-rtl-3-3-4/dist/css/bootstrap-rtl.min.css" rel="stylesheet" type="text/css" />
325 <?php } ?>
326 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.min.css">
327 <!-- for the pop up calendar -->
328 <script src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-min-3-1-1/index.js" type="text/javascript"></script>
329 <script src="<?php echo $GLOBALS['assets_static_relative']; ?>/bootstrap-3-3-4/dist/js/bootstrap.min.js" type="text/javascript"></script>
330 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker-2-5-4/build/jquery.datetimepicker.full.min.js"></script>
332 <script>
334 function setappt(year,mon,mday,hours,minutes) {
335 opener.setappt(year,mon,mday,hours,minutes);
336 dlgclose();
337 return false;
340 </script>
342 <style>
343 form {
344 /* this eliminates the padding normally around a FORM tag */
345 padding: 0px;
346 margin: 0px;
349 #searchCriteria {
350 text-align: center;
351 width: 100%;
352 background-color: #bfe6ff4d;
353 font-weight: bold;
354 padding: 3px;
357 #searchResults {
358 width: 100%;
359 height: 100%;
360 overflow: auto;
363 .srDate {
364 background-color: #bfe6ff4d;
367 #searchResults table {
368 width: 100%;
369 border-collapse: collapse;
370 background-color: white;
373 #searchResults td {
374 border-bottom: 1px solid gray;
375 padding: 1px 5px 1px 5px;
378 .highlight {
379 background-color: #ffff994d;
382 .blue_highlight {
383 background-color: #BBCCDD;
384 color: white;
387 #am {
388 border-bottom: 1px solid lightgrey;
389 color: #00c;
392 #pm {
393 color: #c00;
396 #pm a {
397 color: #c00;
399 </style>
401 </head>
403 <body class="body_top">
405 <div id="searchCriteria">
406 <form method='post' name='theform' action='./find_appt_popup_user.php?providerid=<?php echo $providerid ?>&catid=<?php echo $input_catid ?>'>
407 <input type="hidden" name='bypatient' />
409 <?php xl('Start date:', 'e'); ?>
411 <input type='text' class='datepicker' name='startdate' id='startdate' size='10' value='<?php echo $sdate ?>'
412 title='yyyy-mm-dd starting date for search'/>
414 <?php xl('for', 'e'); ?>
415 <input type='text' name='searchdays' size='3' value='<?php echo $searchdays ?>'
416 title='Number of days to search from the start date' />
417 <?php xl('days', 'e'); ?>&nbsp;
418 <input type='submit' value='<?php xl('Search', 'e'); ?>'>
419 </div>
421 <?php if (!empty($slots)) : ?>
423 <div id="searchResultsHeader">
424 <table class='table table-bordered'>
426 </table>
427 </div>
429 <div id="searchResults" class="container">
430 <table class='table table-inversed table-bordered'>
431 <thead id="searchResultsHeader">
432 <tr>
433 <th class="srDate"><?php xl('Day', 'e'); ?></th>
434 <th class="srTimes"><?php xl('Available Times', 'e'); ?></th>
435 </tr>
436 </thead>
437 <?php
438 $lastdate = "";
439 $ampmFlag = "am"; // establish an AM-PM line break flag
440 for ($i = 0; $i < $slotcount; ++$i) {
441 $available = true;
442 for ($j = $i; $j < $i + $catslots; ++$j) {
443 if ($slots[$j] >= 4) {
444 $available = false;
448 if (!$available) {
449 continue; // skip reserved slots
452 $utime = ($slotbase + $i) * $slotsecs;
453 $thisdate = date("Y-m-d", $utime);
454 if ($thisdate != $lastdate) {
455 // if a new day, start a new row
456 if ($lastdate) {
457 echo "</div>";
458 echo "</td>\n";
459 echo " </tr>\n";
462 $lastdate = $thisdate;
463 echo " <tr class='oneresult'>\n";
464 echo " <td class='srDate'>" . date("l", $utime)."<br>".date("Y-m-d", $utime) . "</td>\n";
465 echo " <td class='srTimes'>";
466 echo "<div id='am'>AM ";
467 $ampmFlag = "am"; // reset the AMPM flag
470 $ampm = date('a', $utime);
471 if ($ampmFlag != $ampm) {
472 echo "</div><div id='pm'>PM ";
475 $ampmFlag = $ampm;
477 $atitle = "Choose ".date("h:i a", $utime);
478 $adate = getdate($utime);
479 $anchor = "<a href='' onclick='return setappt(" .
480 $adate['year'] . "," .
481 $adate['mon'] . "," .
482 $adate['mday'] . "," .
483 $adate['hours'] . "," .
484 $adate['minutes'] . ")'".
485 " title='$atitle' alt='$atitle'".
486 ">";
487 echo (strlen(date('g', $utime)) < 2 ? "<span style='visibility:hidden'>0</span>" : "") .
488 $anchor . date("g:i", $utime) . "</a> ";
490 // If category duration is more than 1 slot, increment $i appropriately.
491 // This is to avoid reporting available times on undesirable boundaries.
492 $i += $catslots - 1;
495 if ($lastdate) {
496 echo "</td>\n";
497 echo " </tr>\n";
498 } else {
499 echo " <tr><td colspan='2'> " . xl('No openings were found for this period.', 'e') . "</td></tr>\n";
502 </table>
503 </div>
504 </div>
505 <?php endif; ?>
507 </form>
508 </body>
510 <script language='JavaScript'>
512 // jQuery stuff to make the page a little easier to use
513 $(document).ready(function(){
514 $(".oneresult").mouseover(function() { $(this).toggleClass("highlight"); });
515 $(".oneresult").mouseout(function() { $(this).toggleClass("highlight"); });
516 $(".oneresult a").mouseover(function () { $(this).toggleClass("blue_highlight"); $(this).children().toggleClass("blue_highlight"); });
517 $(".oneresult a").mouseout(function() { $(this).toggleClass("blue_highlight"); $(this).children().toggleClass("blue_highlight"); });
519 $('.datepicker').datetimepicker({
520 <?php $datetimepicker_timepicker = false; ?>
521 <?php $datetimepicker_formatInput = false; ?>
522 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
523 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
528 </script>
530 </html>