Openemr fix 4723 module install script uses OpenEMR sql upgrade service (#4724)
[openemr.git] / portal / index.php
blob1f61ff5a2cf6193ca9f699c25a5c8d388d8c56c0
1 <?php
3 /**
4 * import_template.php
6 * @package OpenEMR
7 * @link https://www.open-emr.org
8 * @author Cassian LUP <cassi.lup@gmail.com>
9 * @author Jerry Padgett <sjpadgett@gmail.com>
10 * @author Brady Miller <brady.g.miller@gmail.com>
11 * @author Tyler Wrenn <tyler@tylerwrenn.com>
12 * @copyright Copyright (c) 2011 Cassian LUP <cassi.lup@gmail.com>
13 * @copyright Copyright (c) 2016-2019 Jerry Padgett <sjpadgett@gmail.com>
14 * @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com>
15 * @copyright Copyright (c) 2020 Tyler Wrenn <tyler@tylerwrenn.com>
16 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
19 //setting the session & other config options
21 // Will start the (patient) portal OpenEMR session/cookie.
22 require_once dirname(__FILE__) . "/../src/Common/Session/SessionUtil.php";
23 OpenEMR\Common\Session\SessionUtil::portalSessionStart();
25 //don't require standard openemr authorization in globals.php
26 $ignoreAuth_onsite_portal = true;
28 //includes
29 require_once '../interface/globals.php';
30 require_once dirname(__FILE__) . "/lib/appsql.class.php";
31 $logit = new ApplicationTable();
33 use OpenEMR\Common\Crypto\CryptoGen;
34 use OpenEMR\Core\Header;
36 //For redirect if the site on session does not match
37 $landingpage = "index.php?site=" . urlencode($_SESSION['site_id']);
39 //exit if portal is turned off
40 if (!(isset($GLOBALS['portal_onsite_two_enable'])) || !($GLOBALS['portal_onsite_two_enable'])) {
41 echo xlt('Patient Portal is turned off');
42 exit;
44 $auth['portal_pwd'] = '';
45 if (isset($_GET['woops'])) {
46 unset($_GET['woops']);
47 unset($_SESSION['password_update']);
49 if (isset($_GET['forward'])) {
50 $auth = false;
51 if (strlen($_GET['forward']) >= 64) {
52 $crypto = new CryptoGen();
53 if ($crypto->cryptCheckStandard($_GET['forward'])) {
54 $one_time = $crypto->decryptStandard($_GET['forward'], null, 'drive', 6);
55 if (!empty($one_time)) {
56 $auth = sqlQueryNoLog("Select * From patient_access_onsite Where portal_onetime Like BINARY ?", array($one_time . '%'));
60 if ($auth === false) {
61 error_log("PORTAL ERROR: " . errorLogEscape('One time reset:' . $_GET['forward']), 0);
62 $logit->portalLog('login attempt', '', ($_GET['forward'] . ':invalid one time'), '', '0');
63 OpenEMR\Common\Session\SessionUtil::portalSessionCookieDestroy();
64 header('Location: ' . $landingpage . '&w&u');
65 exit();
67 $parse = str_replace($one_time, '', $auth['portal_onetime']);
68 $validate = hex2bin(substr($parse, 6));
69 if ($validate <= time()) {
70 error_log("PORTAL ERROR: " . errorLogEscape('One time reset link expired. Dying.'), 0);
71 $logit->portalLog('password reset attempt', '', ($_POST['uname'] . ':link expired'), '', '0');
72 OpenEMR\Common\Session\SessionUtil::portalSessionCookieDestroy();
73 die(xlt("Your one time credential reset link has expired. Reset and try again.") . "time:$validate time:" . time());
75 $_SESSION['pin'] = substr($parse, 0, 6);
76 $_SESSION['forward'] = $auth['portal_onetime'];
77 $_SESSION['portal_username'] = $auth['portal_username'];
78 $_SESSION['portal_login_username'] = $auth['portal_login_username'];
79 $_SESSION['password_update'] = 2;
80 $_SESSION['onetime'] = $auth['portal_pwd'];
81 unset($auth);
83 // security measure -- will check on next page.
84 $_SESSION['itsme'] = 1;
88 // Deal with language selection
90 // collect default language id (skip this if this is a password update)
91 if (!(isset($_SESSION['password_update']) || isset($_GET['requestNew']))) {
92 $res2 = sqlStatement("select * from lang_languages where lang_description = ?", array($GLOBALS['language_default']));
93 for ($iter = 0; $row = sqlFetchArray($res2); $iter++) {
94 $result2[$iter] = $row;
97 if (count($result2) == 1) {
98 $defaultLangID = $result2[0]["lang_id"];
99 $defaultLangName = $result2[0]["lang_description"];
100 } else {
101 //default to english if any problems
102 $defaultLangID = 1;
103 $defaultLangName = "English";
106 // set session variable to default so login information appears in default language
107 $_SESSION['language_choice'] = $defaultLangID;
108 // collect languages if showing language menu
109 if ($GLOBALS['language_menu_login']) {
110 // sorting order of language titles depends on language translation options.
111 $mainLangID = empty($_SESSION['language_choice']) ? '1' : $_SESSION['language_choice'];
112 // Use and sort by the translated language name.
113 $sql = "SELECT ll.lang_id, " .
114 "IF(LENGTH(ld.definition),ld.definition,ll.lang_description) AS trans_lang_description, " .
115 "ll.lang_description " .
116 "FROM lang_languages AS ll " .
117 "LEFT JOIN lang_constants AS lc ON lc.constant_name = ll.lang_description " .
118 "LEFT JOIN lang_definitions AS ld ON ld.cons_id = lc.cons_id AND " .
119 "ld.lang_id = ? " .
120 "ORDER BY IF(LENGTH(ld.definition),ld.definition,ll.lang_description), ll.lang_id";
121 $res3 = SqlStatement($sql, array($mainLangID));
122 for ($iter = 0; $row = sqlFetchArray($res3); $iter++) {
123 $result3[$iter] = $row;
125 if (count($result3) == 1) {
126 //default to english if only return one language
127 $hiddenLanguageField = "<input type='hidden' name='languageChoice' value='1' />\n";
129 } else {
130 $hiddenLanguageField = "<input type='hidden' name='languageChoice' value='" . attr($defaultLangID) . "' />\n";
134 <!DOCTYPE html>
135 <html>
136 <head>
137 <title><?php echo xlt('Patient Portal Login'); ?></title>
138 <?php
139 Header::setupHeader(['no_main-theme', 'datetime-picker', 'jquery-gritter', 'patientportal-style', 'patientportal-base', 'patientportal-register']);
141 <script>
142 function checkUserName() {
143 let vacct = document.getElementById('uname').value;
144 let vsuname = document.getElementById('login_uname').value;
145 if (vsuname.length < 12) {
146 alert(<?php echo xlj('User Name must be at least 12 characters!'); ?>);
147 return false;
149 let data = {
150 'action': 'userIsUnique',
151 'account': vacct,
152 'loginUname': vsuname
154 $.ajax({
155 type: 'GET',
156 url: './account/account.php',
157 data: data
158 }).done(function (rtn) {
159 if (rtn === '1') {
160 return true;
162 alert(<?php echo xlj('Log In Name is unavailable. Try again!'); ?>);
163 return false;
167 function process() {
168 if (!(validate())) {
169 alert(<?php echo xlj('Field(s) are missing!'); ?>);
170 return false;
172 if(!checkUserName()) {
173 alert(<?php echo xlj('Entered User Name is unavalable!'); ?>);
174 return false;
176 return true;
179 function validate() {
180 let pass = true;
182 if (document.getElementById('uname').value == "") {
183 $('#uname').addClass('is-invalid');
184 pass = false;
186 if (document.getElementById('pass').value == "") {
187 $('#pass').addClass('is-invalid');
188 pass = false;
190 return pass;
193 function process_new_pass() {
194 if (!(validate_new_pass())) {
195 alert(<?php echo xlj('Field(s) are missing!'); ?>);
196 return false;
198 if (document.getElementById('pass_new').value != document.getElementById('pass_new_confirm').value) {
199 alert(<?php echo xlj('The new password fields are not the same.'); ?>);
200 return false;
202 if (document.getElementById('pass').value == document.getElementById('pass_new').value) {
203 alert(<?php echo xlj('The new password can not be the same as the current password.'); ?>);
204 return false;
208 function validate_new_pass() {
209 var pass = true;
210 if (document.getElementById('uname').value == "") {
211 $('#uname').addClass('is-invalid');
212 pass = false;
214 if (document.getElementById('pass').value == "") {
215 $('#pass').addClass('is-invalid');
216 pass = false;
218 if (document.getElementById('pass_new').value == "") {
219 $('#pass_new').addClass('is-invalid');
220 pass = false;
222 if (document.getElementById('pass_new_confirm').value == "") {
223 $('#pass_new_confirm').addClass('is-invalid');
224 pass = false;
226 return pass;
228 </script>
229 </head>
230 <body class="login container mt-2">
231 <div id="wrapper" class="container-fluid text-center">
232 <?php if (isset($_SESSION['password_update']) || isset($_GET['password_update'])) {
233 $_SESSION['password_update'] = 1;
235 <h2 class="title"><?php echo xlt('Please Enter New Credentials'); ?></h2>
236 <form class="form pb-5" action="get_patient_info.php" method="POST" onsubmit="return process_new_pass()">
237 <input style="display: none" type="text" name="dummyuname" />
238 <input style="display: none" type="password" name="dummypass" />
239 <div class="form-row my-3">
240 <label class="col-md-2 col-form-label" for="uname"><?php echo xlt('Account Name'); ?></label>
241 <div class="col-md">
242 <input class="form-control" name="uname" id="uname" type="text" readonly autocomplete="none" value="<?php echo attr($_SESSION['portal_username']); ?>" />
243 </div>
244 </div>
245 <div class="form-row my-3">
246 <label class="col-md-2 col-form-label" for="login_uname"><?php echo xlt('Use Username'); ?></label>
247 <div class="col-md">
248 <input class="form-control" name="login_uname" id="login_uname" type="text" autofocus autocomplete="none" title="<?php echo xla('Please enter a username of 12 to 80 characters. Recommended to include symbols and numbers but not required.'); ?>" placeholder="<?php echo xla('Must be 12 to 80 characters'); ?>" pattern=".{12,80}" value="<?php echo attr($_SESSION['portal_login_username']); ?>" onblur="checkUserName()" />
249 </div>
250 </div>
251 <div class="form-row my-3">
252 <label class="col-md-2 col-form-label" for="pass"><?php echo !$_SESSION['onetime'] ? xlt('Current Password') : ''; ?></label>
253 <div class="col-md">
254 <input class="form-control" name="pass" id="pass" <?php echo $_SESSION['onetime'] ? 'type="hidden" ' : 'type="password" '; ?> autocomplete="none" value="<?php echo attr($_SESSION['onetime']);
255 $_SESSION['password_update'] = $_SESSION['onetime'] ? 2 : 1;
256 unset($_SESSION['onetime']); ?>" required />
257 </div>
258 </div>
259 <?php if ($_SESSION['pin']) { ?>
260 <div class="form-row my-3">
261 <label class="col-md-2 col-form-label" for="token_pin"><?php echo xlt('One Time PIN'); ?></label>
262 <div class="col-md">
263 <input class="form-control" name="token_pin" id="token_pin" type="password" autocomplete="none" value="" required pattern=".{6,20}" />
264 </div>
265 </div>
266 <?php } ?>
267 <div class="form-row my-3">
268 <label class="col-md-2 col-form-label" for="pass_new"><?php echo xlt('New Password'); ?></label>
269 <div class="col-md">
270 <input class="form-control" name="pass_new" id="pass_new" type="password" required placeholder="<?php echo xla('Min length is 8 with upper,lowercase,numbers mix'); ?>" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" />
271 </div>
272 </div>
273 <div class="form-row my-3">
274 <label class="col-md-2 col-form-label" for="pass_new_confirm"><?php echo xlt('Confirm New Password'); ?></label>
275 <div class="col-md">
276 <input class="form-control" name="pass_new_confirm" id="pass_new_confirm" type="password" required pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" />
277 </div>
278 </div>
279 <?php if ($GLOBALS['enforce_signin_email']) { ?>
280 <div class="form-row my-3">
281 <label class="col-md-2 col-form-label" for="passaddon"><?php echo xlt('Confirm Email Address'); ?></label>
282 <div class="col-md">
283 <input class="form-control" name="passaddon" id="passaddon" required placeholder="<?php echo xla('Current on record trusted email'); ?>" type="email" autocomplete="none" value="" />
284 </div>
285 </div>
286 <?php } ?>
287 <input class="btn btn-secondary float-left" type="button" onclick="document.location.replace('./index.php?woops=1');" value="<?php echo xla('Cancel'); ?>" />
288 <input class="btn btn-primary float-right" type="submit" value="<?php echo xla('Log In'); ?>" />
289 </form>
290 <?php } elseif (isset($_GET['requestNew'])) { ?>
291 <form id="resetPass" action="#" method="post">
292 <div class="text-center">
293 <fieldset>
294 <legend class='bg-primary text-white pt-2 py-1'><h3><?php echo xlt('Patient Credentials Reset') ?></h3></legend>
295 <div class="jumbotron jumbotron-fluid px-5 py-3">
296 <div class="form-row my-3">
297 <label class="col-md-2 col-form-label" for="fname"><?php echo xlt('First Name') ?></label>
298 <div class="col-md">
299 <input type="text" class="form-control" id="fname" required placeholder="<?php echo xla('First Name'); ?>" />
300 </div>
301 </div>
302 <div class="form-row my-3">
303 <label class="col-md-2 col-form-label" for="lname"><?php echo xlt('Last Name') ?></label>
304 <div class="col-md">
305 <input type="text" class="form-control" id="lname" required placeholder="<?php echo xla('Last Name'); ?>" />
306 </div>
307 </div>
308 <div class="form-row my-3">
309 <label class="col-md-2 col-form-label" for="dob"><?php echo xlt('Birth Date') ?></label>
310 <div class="col-md">
311 <input id="dob" type="text" required class="form-control datepicker" placeholder="<?php echo xla('YYYY-MM-DD'); ?>" />
312 </div>
313 </div>
314 <div class="form-row my-3">
315 <label class="col-md-2 col-form-label" for="emailInput"><?php echo xlt('Enter E-Mail Address') ?></label>
316 <div class="col-md">
317 <input id="emailInput" type="email" class="form-control" required placeholder="<?php echo xla('Current trusted email address on record.'); ?>" maxlength="100" />
318 </div>
319 </div>
320 </div>
321 <input class="btn btn-secondary float-left" type="button" onclick="document.location.replace('./index.php?woops=1');" value="<?php echo xla('Cancel'); ?>" />
322 <button id="submitRequest" class="btn btn-primary nextBtn float-right" type="submit"><?php echo xlt('Verify') ?></button>
323 </fieldset>
324 </div>
325 </form>
326 <?php } else {
327 ?> <!-- Main logon -->
328 <img class="img-responsive center-block login-image" src='<?php echo $GLOBALS['images_static_relative']; ?>/login-logo.png' />
329 <form class="text-center" action="get_patient_info.php" method="POST" onsubmit="return process()">
330 <fieldset>
331 <legend class="bg-primary text-white pt-2 py-1"><h3><?php echo xlt('Patient Portal Login'); ?></h3></legend>
332 <div class="jumbotron jumbotron-fluid px-5 py-3">
333 <div class="form-row my-3">
334 <label class="col-md-2 col-form-label" for="uname"><?php echo xlt('Username') ?></label>
335 <div class="col-md">
336 <input type="text" class="form-control" name="uname" id="uname" type="text" autocomplete="none" required />
337 </div>
338 </div>
339 <div class="form-row mt-3">
340 <label class="col-md-2 col-form-label" for="pass"><?php echo xlt('Password') ?></label>
341 <div class="col-md">
342 <input class="form-control" name="pass" id="pass" type="password" required autocomplete="none" />
343 </div>
344 </div>
345 <?php if ($GLOBALS['enforce_signin_email']) { ?>
346 <div class="form-row mt-3">
347 <label class="col-md-2 col-form-label" for="passaddon"><?php echo xlt('E-Mail Address') ?></label>
348 <div class="col-md">
349 <input class="form-control" name="passaddon" id="passaddon" type="email" autocomplete="none" />
350 </div>
351 </div>
352 <?php } ?>
353 <?php if ($GLOBALS['language_menu_login']) { ?>
354 <?php if (count($result3) != 1) { ?>
355 <div class="form-group mt-1">
356 <label class="col-form-label-sm" for="selLanguage"><?php echo xlt('Language'); ?></label>
357 <select class="form-control form-control-sm" id="selLanguage" name="languageChoice">
358 <?php
359 echo "<option selected='selected' value='" . attr($defaultLangID) . "'>" .
360 text(xl('Default') . " - " . xl($defaultLangName)) . "</option>\n";
361 foreach ($result3 as $iter) {
362 if ($GLOBALS['language_menu_showall']) {
363 if (!$GLOBALS['allow_debug_language'] && $iter['lang_description'] == 'dummy') {
364 continue; // skip the dummy language
366 echo "<option value='" . attr($iter['lang_id']) . "'>" .
367 text($iter['trans_lang_description']) . "</option>\n";
368 } else {
369 if (in_array($iter['lang_description'], $GLOBALS['language_menu_show'])) {
370 if (!$GLOBALS['allow_debug_language'] && $iter['lang_description'] == 'dummy') {
371 continue; // skip the dummy language
373 echo "<option value='" . attr($iter['lang_id']) . "'>" .
374 text($iter['trans_lang_description']) . "</option>\n";
379 </select>
380 </div>
381 <?php }
382 } ?>
383 </div>
384 <div class="row">
385 <div class="col-12">
386 <?php if ($GLOBALS['portal_onsite_two_register']) { ?>
387 <button class="btn btn-light float-left" onclick="location.replace('./account/register.php')"><?php echo xlt('Register'); ?></button>
388 <?php } ?>
389 <?php if ($GLOBALS['portal_two_pass_reset'] && isset($_GET['w']) && (isset($_GET['u']) || isset($_GET['p']))) { ?>
390 <button class="btn btn-danger" onclick="location.replace('./index.php?requestNew=1')" style="margin-left:10px"><?php echo xlt('Reset Credentials'); ?></button>
391 <?php } ?>
392 <button class="btn btn-success float-right" type="submit"><?php echo xlt('Log In'); ?></button>
393 </div>
394 </div>
395 </fieldset>
396 <?php if (!(empty($hiddenLanguageField))) {
397 echo $hiddenLanguageField;
398 } ?>
399 </form>
401 </div><!-- div wrapper -->
402 <?php } ?> <!-- logon wrapper -->
404 <script>
405 var tab_mode = true;
406 var webroot_url = <?php echo js_escape($GLOBALS['web_root']) ?>;
407 function restoreSession(){
408 //dummy functions so the dlgopen function will work in the patient portal
409 return true;
411 var isPortal = 1;
413 $(function () {
414 <?php // if something went wrong
415 if (isset($_GET['requestNew'])) {
416 $_SESSION['register'] = true;
417 $_SESSION['authUser'] = 'portal-user';
418 $_SESSION['pid'] = true;
420 $('.datepicker').datetimepicker({
421 <?php $datetimepicker_timepicker = false; ?>
422 <?php $datetimepicker_showseconds = false; ?>
423 <?php $datetimepicker_formatInput = false; ?>
424 <?php require $GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'; ?>
426 $(document.body).on('hidden.bs.modal', function () {
427 callServer('cleanup');
429 $("#resetPass").on('submit', function (e) {
430 e.preventDefault();
431 callServer('is_new', '');
432 return false;
434 <?php } ?>
435 <?php if (isset($_GET['w'])) { ?>
436 var unique_id = $.gritter.add({
437 title: '<span class="red">' + <?php echo xlj('Oops!');?> +'</span>',
438 text: <?php echo xlj('Something went wrong. Please try again.'); ?>,
439 sticky: false,
440 time: '5000',
441 class_name: 'my-nonsticky-class'
443 <?php } ?>
444 <?php // if successfully logged out
445 if (isset($_GET['logout'])) { ?>
446 var unique_id = $.gritter.add({
447 title: '<span class="green">' + <?php echo xlj('Success');?> +'</span>',
448 text: <?php echo xlj('You have been successfully logged out.');?>,
449 sticky: false,
450 time: '5000',
451 class_name: 'my-nonsticky-class'
453 <?php } ?>
455 return false;
458 function callServer(action, value, value2, last, first) {
459 var data = {
460 'action': action,
461 'value': value,
462 'dob': $("#dob").val(),
463 'last': $("#lname").val(),
464 'first': $("#fname").val(),
465 'email': $("#emailInput").val()
467 if (action === 'do_signup') {
468 data = {
469 'action': action,
470 'pid': value
472 } else if (action === 'notify_admin') {
473 data = {
474 'action': action,
475 'pid': value,
476 'provider': value2
478 } else if (action === 'cleanup') {
479 data = {
480 'action': action
483 $.ajax({
484 type: 'GET',
485 url: './account/account.php',
486 data: data
487 }).done(function (rtn) {
488 if (action === "cleanup") {
489 window.location.href = "./index.php" // Goto landing page.
490 } else if (action === "userIsUnique") {
491 return rtn === '1' ? true : false;
492 } else if (action === "is_new") {
493 if (parseInt(rtn) !== 0) {
494 var yes = confirm(<?php echo xlj("Account is validated. Send new credentials?") ?>);
495 if (!yes)
496 callServer('cleanup');
497 else
498 callServer('do_signup', parseInt(rtn));
499 } else {
500 // After error alert app exit to landing page.
501 var message = <?php echo xlj('Unable to find your records. Be sure to use your correct Dob, First and Last name and Email of record.') ?>;
502 message += "<br />" + <?php echo xlj('All search inputs are case sensitive and must match entries in your profile.'); ?>;
503 dialog.alert(message, <?php echo xlj("Alert") ?>)
504 .then(function(result) {
505 console.error('Reset failed to vaidate');
507 return false;
509 } else if (action === 'do_signup') {
510 if (rtn.indexOf('ERROR') !== -1) {
511 var message = <?php echo xlj('Unable to either create credentials or send email.'); ?>;
512 message += "<br /><br />" + <?php echo xlj('Here is what we do know.'); ?> +": " + rtn + "<br />";
513 dialog.alert(message);
514 return false;
516 //alert(rtn); // sync alert.. rtn holds username and password for testing.
517 var message = <?php echo xlj("Your new credentials have been sent. Check your email inbox and also possibly your spam folder. Once you log into your patient portal feel free to make an appointment or send us a secure message. We look forward to seeing you soon."); ?>;
518 dialog.alert(message); // This is an async call. The modal close event exits us to portal landing page after cleanup.
519 return false;
521 }).fail(function (err) {
522 var message = <?php echo xlj('Something went wrong.') ?>;
523 alert(message);
526 </script>
527 </body>
528 </html>