fix: whitelist session pid setting pt from billing manager (#7980)
[openemr.git] / portal / home.php
blob11a1c92ca756e20691784231cc327019851bfe3b
1 <?php
3 /**
4 * Patient Portal Home
6 * @package OpenEMR
7 * @link http://www.open-emr.org
8 * @author Jerry Padgett <sjpadgett@gmail.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @author Shiqiang Tao <StrongTSQ@gmail.com>
11 * @author Ben Marte <benmarte@gmail.com>
12 * @copyright Copyright (c) 2016-2024 Jerry Padgett <sjpadgett@gmail.com>
13 * @copyright Copyright (c) 2019-2021 Brady Miller <brady.g.miller@gmail.com>
14 * @copyright Copyright (c) 2020 Shiqiang Tao <StrongTSQ@gmail.com>
15 * @copyright Copyright (c) 2021 Ben Marte <benmarte@gmail.com>
16 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
19 require_once('verify_session.php');
20 require_once("$srcdir/patient.inc.php");
21 require_once("$srcdir/options.inc.php");
22 require_once('lib/portal_mail.inc.php');
23 require_once(__DIR__ . '/../library/appointments.inc.php');
25 use OpenEMR\Common\Csrf\CsrfUtils;
26 use OpenEMR\Common\Twig\TwigContainer;
27 use OpenEMR\Common\Logging\SystemLogger;
28 use OpenEMR\Events\PatientPortal\AppointmentFilterEvent;
29 use OpenEMR\Events\PatientReport\PatientReportFilterEvent;
30 use OpenEMR\Events\PatientPortal\RenderEvent;
31 use OpenEMR\Services\LogoService;
32 use OpenEMR\Services\Utils\TranslationService;
33 use Twig\Error\LoaderError;
34 use Twig\Error\RuntimeError;
35 use Twig\Error\SyntaxError;
37 if (isset($_SESSION['register']) && $_SESSION['register'] === true) {
38 require_once(__DIR__ . '/../src/Common/Session/SessionUtil.php');
39 OpenEMR\Common\Session\SessionUtil::portalSessionCookieDestroy();
40 header('Location: ' . $landingpage . '&w');
41 exit();
44 if (!isset($_SESSION['portal_init'])) {
45 $_SESSION['portal_init'] = true;
48 $logoService = new LogoService();
51 // Get language definitions for js
52 $language_defs = TranslationService::getLanguageDefinitionsForSession();
53 $whereto = $_SESSION['whereto'] ?? null;
55 $user = $_SESSION['sessionUser'] ?? 'portal user';
56 $result = getPatientData($pid);
58 $msgs = getPortalPatientNotes($_SESSION['portal_username']);
59 $msgcnt = count($msgs);
60 $newcnt = 0;
61 foreach ($msgs as $i) {
62 if ($i['message_status'] == 'New') {
63 $newcnt += 1;
67 // force to message page if new messages.
68 /*if ($newcnt > 0 && $_SESSION['portal_init']) {
69 $whereto = $_SESSION['whereto'] = '#secure-msgs-card';
70 }*/
71 $messagesURL = $GLOBALS['web_root'] . '/portal/messaging/messages.php';
73 $isEasyPro = $GLOBALS['easipro_enable'] && !empty($GLOBALS['easipro_server']) && !empty($GLOBALS['easipro_name']);
75 $current_date2 = date('Y-m-d');
76 $apptLimit = 10;
77 $appts = fetchNextXAppts($current_date2, $pid, $apptLimit);
78 $past_appts = fetchXPastAppts($pid, 10);
80 $appointments = $past_appointments = array();
81 if ($appts) {
82 $stringCM = '(' . xl('Comments field entry present') . ')';
83 $stringR = '(' . xl('Recurring appointment') . ')';
84 $count = 0;
85 foreach ($appts as $row) {
86 $status_title = getListItemTitle('apptstat', $row['pc_apptstatus']);
87 $count++;
88 $dayname = xl(date('l', strtotime($row['pc_eventDate'])));
89 $dispampm = 'am';
90 $disphour = (int)substr($row['pc_startTime'], 0, 2);
91 $dispmin = substr($row['pc_startTime'], 3, 2);
92 if ($disphour >= 12) {
93 $dispampm = 'pm';
94 if ($disphour > 12) {
95 $disphour -= 12;
99 if ($row['pc_hometext'] != '') {
100 $etitle = xl('Comments') . ': ' . $row['pc_hometext'] . "\r\n";
101 } else {
102 $etitle = '';
105 $formattedRecord = [
106 'appointmentDate' => $dayname . ', ' . oeFormatShortDate($row['pc_eventDate']) . ' ' . $disphour . ':' . $dispmin . ' ' . $dispampm,
107 'appointmentType' => xl('Type') . ': ' . $row['pc_catname'],
108 'provider' => xl('Provider') . ': ' . $row['ufname'] . ' ' . $row['ulname'],
109 'status' => xl('Status') . ': ' . $status_title,
110 'mode' => (int)$row['pc_recurrtype'] > 0 ? 'recurring' : $row['pc_recurrtype'],
111 'icon_type' => (int)$row['pc_recurrtype'] > 0,
112 'etitle' => $etitle,
113 'pc_eid' => $row['pc_eid'],
115 $filteredEvent = $GLOBALS['kernel']->getEventDispatcher()->dispatch(new AppointmentFilterEvent($row, $formattedRecord), AppointmentFilterEvent::EVENT_NAME);
116 $appointments[] = $filteredEvent->getAppointment() ?? $formattedRecord;
119 if ($past_appts) {
120 $stringCM = '(' . xl('Comments field entry present') . ')';
121 $stringR = '(' . xl('Recurring appointment') . ')';
122 $pastCount = 0;
123 foreach ($past_appts as $row) {
124 $status_title = getListItemTitle('apptstat', $row['pc_apptstatus']);
125 $pastCount++;
126 $dayname = xl(date('l', strtotime($row['pc_eventDate'])));
127 $dispampm = 'am';
128 $disphour = (int)substr($row['pc_startTime'], 0, 2);
129 $dispmin = substr($row['pc_startTime'], 3, 2);
130 if ($disphour >= 12) {
131 $dispampm = 'pm';
132 if ($disphour > 12) {
133 $disphour -= 12;
137 if ($row['pc_hometext'] != '') {
138 $etitle = xl('Comments') . ': ' . $row['pc_hometext'] . "\r\n";
139 } else {
140 $etitle = '';
143 $formattedRecord = [
144 'appointmentDate' => $dayname . ', ' . oeFormatShortDate($row['pc_eventDate']) . ' ' . $disphour . ':' . $dispmin . ' ' . $dispampm,
145 'appointmentType' => xl('Type') . ': ' . $row['pc_catname'],
146 'provider' => xl('Provider') . ': ' . $row['ufname'] . ' ' . $row['ulname'],
147 'status' => xl('Status') . ': ' . $status_title,
148 'mode' => (int)$row['pc_recurrtype'] > 0 ? 'recurring' : $row['pc_recurrtype'],
149 'icon_type' => (int)$row['pc_recurrtype'] > 0,
150 'etitle' => $etitle,
151 'pc_eid' => $row['pc_eid'],
153 $filteredEvent = $GLOBALS['kernel']->getEventDispatcher()->dispatch(new AppointmentFilterEvent($row, $formattedRecord), AppointmentFilterEvent::EVENT_NAME);
154 $past_appointments[] = $filteredEvent->getAppointment() ?? $formattedRecord;
157 $current_theme = sqlQuery("SELECT `setting_value` FROM `patient_settings` WHERE setting_patient = ? AND `setting_label` = ?", array($pid, 'portal_theme'))['setting_value'] ?? '';
158 function collectStyles(): array
160 global $webserver_root;
161 $theme_dir = "$webserver_root/public/themes";
162 $dh = opendir($theme_dir);
163 $styleArray = array();
164 while (false !== ($tfname = readdir($dh))) {
165 if (
166 $tfname == 'style_blue.css' ||
167 $tfname == 'style_pdf.css' ||
168 !preg_match("/^" . 'style_' . ".*\.css$/", $tfname)
170 continue;
172 $styleDisplayName = str_replace("_", " ", substr($tfname, 6));
173 $styleDisplayName = ucfirst(str_replace(".css", "", $styleDisplayName));
174 $styleArray[$tfname] = $styleDisplayName;
176 asort($styleArray);
177 closedir($dh);
178 return $styleArray;
181 function buildNav($newcnt, $pid, $result): array
183 $hideLedger = false;
184 $hidePayment = false;
185 if (empty($GLOBALS['portal_two_ledger'])) {
186 $hideLedger = true;
189 if (empty($GLOBALS['portal_two_payments'])) {
190 $hidePayment = true;
192 $navItems = [
194 'url' => '#',
195 'label' => xl('Menu'),
196 'icon' => 'fa-user',
197 'dropdownID' => 'account',
198 'messageCount' => $newcnt ?? 0,
199 'children' => [
201 'url' => '#quickstart-card',
202 'id' => 'quickstart_id',
203 'label' => xl('Dashboard'),
204 'icon' => 'fa-tasks',
205 'dataToggle' => 'collapse',
208 'url' => '#secure-msgs-card',
209 'label' => xl('Secure Messaging'),
210 'icon' => 'fa-envelope',
211 'dataToggle' => 'collapse',
212 'messageCount' => $newcnt ?? 0,
215 'url' => $GLOBALS['web_root'] . '/portal/patient/onsitedocuments?pid=' . urlencode($pid),
216 'label' => xl('Forms and Documents'),
217 'icon' => 'fa-file',
220 'url' => '#profilecard',
221 'label' => xl('Profile'),
222 'icon' => 'fa-user',
223 'dataToggle' => 'collapse',
226 'url' => '#lists',
227 'label' => xl('Health Snapshot'),
228 'icon' => 'fa-list',
229 'dataToggle' => 'collapse'
232 'url' => '#ledgercard',
233 'label' => xl('Billing Summary'),
234 'icon' => 'fa-folder-open',
235 'dataToggle' => 'collapse',
236 'hide' => $hideLedger
239 'url' => '#paymentcard',
240 'label' => xl('Make Payment'),
241 'icon' => 'fa-credit-card',
242 'dataToggle' => 'collapse',
243 'hide' => $hidePayment
244 ],*/
249 // Build sub nav items
251 for ($i = 0, $iMax = count($navItems); $i < $iMax; $i++) {
252 if ($GLOBALS['allow_portal_appointments'] && $navItems[$i]['label'] === xl('Menu')) {
253 $navItems[$i]['children'][] = [
254 'url' => '#appointmentcard',
255 'label' => xl('Appointments'),
256 'icon' => 'fa-calendar-check',
257 'dataToggle' => 'collapse'
261 if ($navItems[$i]['label'] === xl('Menu')) {
262 array_push(
263 $navItems[$i]['children'],
265 'url' => 'javascript:changeCredentials(event)',
266 'label' => xl('Manage Login Credentials'),
267 'icon' => 'fa-cog fa-fw',
270 'url' => '#openSignModal',
271 'label' => xl('Manage Signature'),
272 'icon' => 'fa-file-signature',
273 'dataToggle' => 'modal',
274 'dataType' => 'patient-signature'
277 'url' => 'logout.php',
278 'label' => xl('Logout'),
279 'icon' => 'fa-ban fa-fw',
284 return $navItems;
287 // Build our navigation
288 $navMenu = buildNav($newcnt, $pid, $result);
290 // Fetch immunization records
291 $query = "SELECT im.*, cd.code_text, DATE(administered_date) AS administered_date,
292 DATE_FORMAT(administered_date,'%m/%d/%Y') AS administered_formatted, lo.title as route_of_administration,
293 u.title, u.fname, u.mname, u.lname, u.npi, u.street, u.streetb, u.city, u.state, u.zip, u.phonew1,
294 f.name, f.phone, lo.notes as route_code
295 FROM immunizations AS im
296 LEFT JOIN codes AS cd ON cd.code = im.cvx_code
297 JOIN code_types AS ctype ON ctype.ct_key = 'CVX' AND ctype.ct_id=cd.code_type
298 LEFT JOIN list_options AS lo ON lo.list_id = 'drug_route' AND lo.option_id = im.route
299 LEFT JOIN users AS u ON u.id = im.administered_by_id
300 LEFT JOIN facility AS f ON f.id = u.facility_id
301 WHERE im.patient_id=?";
302 $result = sqlStatement($query, array($pid));
303 $immunRecords = array();
304 while ($row = sqlFetchArray($result)) {
305 $immunRecords[] = $row;
308 // CCDA Alt Service
309 $ccdaOk = ($GLOBALS['ccda_alt_service_enable'] == 2 || $GLOBALS['ccda_alt_service_enable'] == 3);
311 // Available Themes
312 $styleArray = collectStyles();
314 // Render Home Page
315 $twig = (new TwigContainer('', $GLOBALS['kernel']))->getTwig();
316 try {
317 $healthSnapshot = [
318 'immunizationRecords' => $immunRecords,
319 'patientID' => $pid
321 $patientReportEvent = new PatientReportFilterEvent();
322 $patientReportEvent->setDataElement('healthSnapshot', $healthSnapshot);
323 $filteredEvent = $GLOBALS['kernel']->getEventDispatcher()->dispatch($patientReportEvent, PatientReportFilterEvent::FILTER_PORTAL_HEALTHSNAPSHOT_TWIG_DATA);
324 $data = [
325 'user' => $user,
326 'whereto' => $_SESSION['whereto'] ?? null ?: ($whereto ?? '#quickstart-card'),
327 'result' => $result,
328 'msgs' => $msgs,
329 'msgcnt' => $msgcnt,
330 'newcnt' => $newcnt,
331 'menuLogo' => $logoService->getLogo('portal/menu/primary'),
332 'allow_portal_appointments' => $GLOBALS['allow_portal_appointments'],
333 'web_root' => $GLOBALS['web_root'],
334 'payment_gateway' => $GLOBALS['payment_gateway'],
335 'gateway_mode_production' => $GLOBALS['gateway_mode_production'],
336 'portal_two_payments' => $GLOBALS['portal_two_payments'],
337 'allow_portal_chat' => $GLOBALS['allow_portal_chat'],
338 'portal_onsite_document_download' => $GLOBALS['portal_onsite_document_download'],
339 'portal_two_ledger' => $GLOBALS['portal_two_ledger'],
340 'images_static_relative' => $GLOBALS['images_static_relative'],
341 'youHave' => xl('You have'),
342 'navMenu' => $navMenu,
343 'primaryMenuLogoHeight' => $GLOBALS['portal_primary_menu_logo_height'] ?? '30',
344 'pagetitle' => $GLOBALS['openemr_name'] . ' ' . xl('Portal'),
345 'messagesURL' => $messagesURL,
346 'patientID' => $pid,
347 'patientName' => $_SESSION['ptName'] ?? null,
348 'csrfUtils' => CsrfUtils::collectCsrfToken(),
349 'isEasyPro' => $isEasyPro,
350 'appointments' => $appointments,
351 'pastAppointments' => $past_appointments,
352 'appts' => $appts,
353 'appointmentLimit' => $apptLimit,
354 'appointmentCount' => $count ?? null,
355 'pastAppointmentCount' => $pastCount ?? null,
356 'displayLimitLabel' => xl('Display limit reached'),
357 'site_id' => $_SESSION['site_id'] ?? ($_GET['site'] ?? 'default'), // one way or another, we will have a site_id.
358 'portal_timeout' => $GLOBALS['portal_timeout'] ?? 1800, // timeout is in seconds
359 'language_defs' => $language_defs,
360 'current_theme' => $current_theme,
361 'styleArray' => $styleArray,
362 'ccdaOk' => $ccdaOk,
363 'allow_custom_report' => $GLOBALS['allow_custom_report'] ?? '0',
364 'healthSnapshot' => $filteredEvent->getDataElement('healthSnapshot'),
365 'languageDirection' => $_SESSION['language_direction'] ?? 'ltr',
366 'dateDisplayFormat' => $GLOBALS['date_display_format'],
367 'timezone' => $GLOBALS['gbl_time_zone'] ?? '',
368 'assetVersion' => $GLOBALS['v_js_includes'],
369 'eventNames' => [
370 'sectionRenderPost' => RenderEvent::EVENT_SECTION_RENDER_POST,
371 'scriptsRenderPre' => RenderEvent::EVENT_SCRIPTS_RENDER_PRE,
372 'dashboardInjectCard' => RenderEvent::EVENT_DASHBOARD_INJECT_CARD,
373 'dashboardRenderScripts' => RenderEvent::EVENT_DASHBOARD_RENDER_SCRIPTS
377 echo $twig->render('portal/home.html.twig', $data);
378 } catch (LoaderError | RuntimeError | SyntaxError $e) {
379 OpenEMR\Common\Session\SessionUtil::portalSessionCookieDestroy();
380 if ($e instanceof SyntaxError) {
381 (new SystemLogger())->error($e->getMessage(), ['file' => $e->getFile(), 'trace' => $e->getTraceAsString()]);
383 die(text($e->getMessage()));