New CCDA Templates (#4427)
[openemr.git] / portal / account / register.php
blob4ab9cd64405b82f75d65071f2afb4980c00d995c
1 <?php
3 /**
4 * Portal Registration Wizard
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 * @copyright Copyright (c) 2017-2019 Jerry Padgett <sjpadgett@gmail.com>
11 * @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com>
12 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
15 use OpenEMR\Core\Header;
17 // Will start the (patient) portal OpenEMR session/cookie.
18 require_once(dirname(__FILE__) . "/../../src/Common/Session/SessionUtil.php");
19 OpenEMR\Common\Session\SessionUtil::portalSessionStart();
20 session_regenerate_id(true);
22 unset($_SESSION['itsme']);
23 $_SESSION['authUser'] = 'portal-user';
24 $_SESSION['pid'] = true;
25 $_SESSION['register'] = true;
27 $_SESSION['site_id'] = isset($_SESSION['site_id']) ? $_SESSION['site_id'] : 'default';
28 $landingpage = "index.php?site=" . urlencode($_SESSION['site_id']);
30 $ignoreAuth_onsite_portal = true;
32 require_once("../../interface/globals.php");
33 if (!$GLOBALS['portal_onsite_two_register']) {
34 OpenEMR\Common\Session\SessionUtil::portalSessionCookieDestroy();
35 echo xlt("Not Authorized");
36 @header('HTTP/1.1 401 Unauthorized');
37 die();
40 $res2 = sqlStatement("select * from lang_languages where lang_description = ?", array(
41 $GLOBALS['language_default']
42 ));
43 for ($iter = 0; $row = sqlFetchArray($res2); $iter++) {
44 $result2[$iter] = $row;
46 if (count($result2) == 1) {
47 $defaultLangID = $result2[0]["lang_id"];
48 $defaultLangName = $result2[0]["lang_description"];
49 } else {
50 // default to english if any problems
51 $defaultLangID = 1;
52 $defaultLangName = "English";
55 if (!isset($_SESSION['language_choice'])) {
56 $_SESSION['language_choice'] = $defaultLangID;
58 // collect languages if showing language menu
59 if ($GLOBALS['language_menu_login']) {
60 // sorting order of language titles depends on language translation options.
61 $mainLangID = empty($_SESSION['language_choice']) ? '1' : $_SESSION['language_choice'];
62 // Use and sort by the translated language name.
63 $sql = "SELECT ll.lang_id, " . "IF(LENGTH(ld.definition),ld.definition,ll.lang_description) AS trans_lang_description, " . "ll.lang_description " .
64 "FROM lang_languages AS ll " . "LEFT JOIN lang_constants AS lc ON lc.constant_name = ll.lang_description " .
65 "LEFT JOIN lang_definitions AS ld ON ld.cons_id = lc.cons_id AND " . "ld.lang_id = ? " .
66 "ORDER BY IF(LENGTH(ld.definition),ld.definition,ll.lang_description), ll.lang_id";
67 $res3 = SqlStatement($sql, array(
68 $mainLangID
69 ));
71 for ($iter = 0; $row = sqlFetchArray($res3); $iter++) {
72 $result3[$iter] = $row;
75 if (count($result3) == 1) {
76 // default to english if only return one language
77 $hiddenLanguageField = "<input type='hidden' name='languageChoice' value='1' />\n";
79 } else {
80 $hiddenLanguageField = "<input type='hidden' name='languageChoice' value='" . attr($defaultLangID) . "' />\n";
84 <!DOCTYPE html>
85 <html>
86 <head>
87 <title><?php echo xlt('New Patient'); ?> | <?php echo xlt('Register'); ?></title>
88 <meta name="description" content="Developed By sjpadgett@gmail.com" />
90 <?php Header::setupHeader(['no_main-theme', 'datetime-picker', 'patientportal-style', 'patientportal-register']); ?>
92 <script>
93 var webRoot = <?php echo js_escape($GLOBALS['web_root']); ?>;
94 top.webroot_url = webRoot;
95 var newPid = 0;
96 var curPid = 0;
97 var provider = 0;
99 function restoreSession() {
100 //dummy functions so the dlgopen function will work in the patient portal
101 return true;
103 $(function () {
104 var navListItems = $('div.setup-panel div a'),
105 allWells = $('.setup-content'),
106 allNextBtn = $('.nextBtn'),
107 allPrevBtn = $('.prevBtn');
109 allWells.hide();
111 navListItems.click(function (e) {
112 e.preventDefault();
113 if (!$(this).hasClass('disabled')) {
114 navListItems.removeClass('btn-primary').addClass('btn-light');
115 $(this).addClass('btn-primary').removeClass('btn-light');
116 allWells.hide();
117 $($(this).attr('href')).show();
118 $($(this).attr('href')).find('input:eq(0)').focus();
122 allPrevBtn.click(function () {
123 var curStep = $(this).closest(".setup-content"),
124 curStepBtn = curStep.attr("id"),
125 prevstepwiz = $('div.setup-panel div a[href="#' + curStepBtn + '"]').parent().prev().children("a");
126 prevstepwiz.removeClass('disabled').trigger('click');
129 allNextBtn.click(function () {
130 var profile = $("#profileFrame").contents();
132 // Fix for iFrame height
133 window.addEventListener('message', function(e) {
134 var scroll_height = e.data;
135 document.getElementById('profileFrame').style.height = scroll_height + 'px';
136 }, false);
138 var curStep = $(this).closest(".setup-content"),
139 curStepBtn = curStep.attr("id"),
140 nextstepwiz = $('div.setup-panel div a[href="#' + curStepBtn + '"]').parent().next().children("a"),
141 curInputs = curStep.find("input[type='text'],input[type='email'],select"),
142 isValid = true;
144 $(".form-group").removeClass("has-error");
145 for (var i = 0; i < curInputs.length; i++) {
146 if (!curInputs[i].validity.valid) {
147 isValid = false;
148 $(curInputs[i]).closest(".form-group").addClass("has-error");
151 if (isValid) {
152 if (curStepBtn == 'step-1') { // leaving step 1 setup profile frame. Prob not nec but in case
153 let fn = $("#fname").val().replace(/^./, $("#fname").val()[0].toUpperCase());
154 let ln = $("#lname").val().replace(/^./, $("#lname").val()[0].toUpperCase());
155 profile.find('input#fname').val(fn);
156 profile.find('input#mname').val($("#mname").val());
157 profile.find('input#lname').val(ln);
158 profile.find('input#dob').val($("#dob").val());
159 profile.find('input#email').val($("#emailInput").val());
160 profile.find('input#emailDirect').val($("#emailInput").val());
161 // disable to prevent already validated field changes.
162 profile.find('input#fname').prop("disabled", true);
163 profile.find('input#mname').prop("disabled", true);
164 profile.find('input#lname').prop("disabled", true);
165 profile.find('input#dob').prop("disabled", true);
166 profile.find('input#email').prop("disabled", true);
167 profile.find('input#emailDirect').prop("disabled", true);
169 profile.find('input[name=allowPatientPortal]').val(['YES']);
170 profile.find('input[name=hipaaAllowemail]').val(['YES']);
171 // need these for validation.
172 profile.find('select#providerid option:contains("Unassigned")').val('');
173 // must have a provider for many reasons. w/o save won't work.
174 //profile.find('select#providerid').attr('required', true);
175 profile.find('select#sex option:contains("Unassigned")').val('');
176 profile.find('select#sex').attr('required', true);
178 var pid = profile.find('input#pid').val();
179 if (pid < 1) { // form pid set in promise
180 callServer('get_newpid', '',
181 encodeURIComponent($("#dob").val()),
182 encodeURIComponent($("#lname").val()),
183 encodeURIComponent($("#fname").val()),
184 encodeURIComponent($("#emailInput").val()));
187 nextstepwiz.removeClass('disabled').trigger('click');
191 $("#profileNext").click(function () {
192 var profile = $("#profileFrame").contents();
193 var curStep = $(this).closest(".setup-content"),
194 curStepBtn = curStep.attr("id"),
195 nextstepwiz = $('div.setup-panel div a[href="#' + curStepBtn + '"]').parent().next().children("a"),
196 curInputs = $("#profileFrame").contents().find("input[type='text'],input[type='email'],select"),
197 isValid = true;
198 $(".form-group").removeClass("has-error");
199 var flg = 0;
200 for (var i = 0; i < curInputs.length; i++) {
201 if (!curInputs[i].validity.valid) {
202 isValid = false;
203 if (!flg) {
204 curInputs[i].scrollIntoView();
205 curInputs[i].focus();
206 flg = 1;
208 $(curInputs[i]).closest(".form-group").addClass("has-error");
211 // test for new once again
212 // this time using the profile data that will be saved as new patient.
213 // callserver will intercept on fail or silence to continue.
214 let stillNew = callServer('get_newpid', '',
215 encodeURIComponent(profile.find('input#dob').val()),
216 encodeURIComponent(profile.find('input#lname').val()),
217 encodeURIComponent(profile.find('input#fname').val()),
218 encodeURIComponent(profile.find('input#email').val()));
219 if (isValid) {
220 provider = profile.find('select#providerid').val();
221 nextstepwiz.removeClass('disabled').trigger('click');
225 $("#submitPatient").click(function () {
226 var profile = $("#profileFrame").contents();
227 var pid = profile.find('input#pid').val();
229 if (pid < 1) {
230 callServer('get_newpid', '');
233 var isOk = checkRegistration(newPid);
234 if (isOk) {
235 // Use portals rest api. flag 1 is write to chart. flag 0 writes an audit record for review in dashboard.
236 // rest update will determine if new or existing pid for save. In register step-1 we catch existing pid but,
237 // we can still use update here if we want to allow changing passwords.
239 // save the new patient.
240 document.getElementById('profileFrame').contentWindow.postMessage({submitForm: true}, window.location.origin);
241 $("#insuranceForm").submit();
242 // cleanup is in callServer done promise. This starts end session.
246 $('div.setup-panel div a.btn-primary').trigger('click');
248 $('.datepicker').datetimepicker({
249 <?php $datetimepicker_timepicker = false; ?>
250 <?php $datetimepicker_showseconds = false; ?>
251 <?php $datetimepicker_formatInput = false; ?>
252 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
255 $("#insuranceForm").submit(function (e) {
256 e.preventDefault();
257 var url = "account.php?action=new_insurance&pid=" + encodeURIComponent(newPid);
258 $.ajax({
259 url: url,
260 type: 'post',
261 data: $("#insuranceForm").serialize(),
262 success: function (serverResponse) {
263 doCredentials(newPid) // this is the end for session.
264 return false;
269 $('#selLanguage').on('change', function () {
270 callServer("set_lang", this.value);
273 $(document.body).on('hidden.bs.modal', function () { //@TODO maybe make a promise for wiz exit
274 callServer('cleanup');
277 $('#inscompany').on('change', function () {
278 if ($('#inscompany').val().toUpperCase() === 'SELF') {
279 $("#insuranceForm input").removeAttr("required");
280 let message = <?php echo xlj('You have chosen to be self insured or currently do not have insurance. Click next to continue registration.'); ?>;
281 alert(message);
285 $("#dob").on('blur', function () {
286 let bday = $(this).val() ?? '';
287 let age = Math.round(Math.abs((new Date().getTime() - new Date(bday).getTime())));
288 age = Math.round(age / 1000 / 60 / 60 / 24);
289 // need to be at least 30 days old otherwise likely an error.
290 if (age < 30) {
291 let msg = <?php echo (xlj("Invalid Date format or value! Type date as YYYY-MM-DD or use the calendar.") ); ?> ;
292 $(this).val('');
293 $(this).prop('placeholder', 'Invalid Date');
294 alert(msg);
295 return false;
299 }); // ready end
301 function doCredentials(pid) {
302 callServer('do_signup', pid);
305 function checkRegistration(pid) {
306 var profile = $("#profileFrame").contents();
307 var curStep = $("#step-2"),
308 curStepBtn = curStep.attr("id"),
309 nextstepwiz = $('div.setup-panel div a[href="#' + curStepBtn + '"]').parent().next().children("a"),
310 curInputs = $("#profileFrame").contents().find("input[type='text'],input[type='email'],select"),
311 isValid = true;
312 $(".form-group").removeClass("has-error");
313 var flg = 0;
314 for (var i = 0; i < curInputs.length; i++) {
315 if (!curInputs[i].validity.valid) {
316 isValid = false;
317 if (!flg) {
318 curInputs[i].scrollIntoView();
319 curInputs[i].focus();
320 flg = 1;
322 $(curInputs[i]).closest(".form-group").addClass("has-error");
326 if (!isValid) {
327 return false;
330 return true;
333 function callServer(action, value, value2, last, first, email = null) {
334 let message = '';
335 let data = {
336 'action': action,
337 'value': value,
338 'dob': value2,
339 'last': last,
340 'first': first,
341 'email': email
343 if (action == 'do_signup') {
344 data = {
345 'action': action,
346 'pid': value
348 } else if (action == 'notify_admin') {
349 data = {
350 'action': action,
351 'pid': value,
352 'provider': value2
354 } else if (action == 'cleanup') {
355 data = {
356 'action': action
359 // The magic that is jquery ajax.
360 $.ajax({
361 type: 'GET',
362 url: 'account.php',
363 data: data
364 }).done(function (rtn) {
365 if (action == "cleanup") {
366 window.location.href = "./../index.php" // Goto landing page.
367 } else if (action == "set_lang") {
368 window.location.href = window.location.href;
369 } else if (action == "get_newpid") {
370 if (parseInt(rtn) > 0) {
371 newPid = rtn;
372 $("#profileFrame").contents().find('input#pubpid').val(newPid);
373 $("#profileFrame").contents().find('input#pid').val(newPid);
374 } else {
375 // After error alert app exit to landing page.
376 // Existing user error. Error message is translated in account.lib.php.
377 dialog.alert(rtn);
379 } else if (action == 'do_signup') {
380 if (rtn.indexOf('ERROR') !== -1) {
381 message = <?php echo xlj('Unable to either create credentials or send email.'); ?>;
382 message += "<br /><br />" + <?php echo xlj('Here is what we do know.'); ?> +": " + rtn + "<br />";
383 dialog.alert(message);
384 return false;
386 // For production. Here we're finished so do signup closing alert and then cleanup.
387 callServer('notify_admin', newPid, provider); // pnote notify to selected provider
388 // alert below for ease of testing.
389 //alert(rtn); // sync alert.. rtn holds username and password for testing.
391 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."); ?>;
392 dialog.alert(message); // This is an async call. The modal close event exits us to portal landing page after cleanup.
393 return false;
395 }).fail(function (err) {
396 message = <?php echo xlj('Something went wrong.') ?>;
397 alert(message);
400 </script>
401 </head>
402 <body class="mt-4 skin-blue">
403 <div class="container">
404 <h1 class="text-center"><?php echo xlt('Account Registration'); ?></h1>
405 <div class="stepwiz">
406 <div class="stepwiz-row setup-panel">
407 <div class="stepwiz-step">
408 <a href="#step-1" type="button" class="btn btn-primary btn-circle">1</a>
409 <p><?php echo xlt('Get Started') ?></p>
410 </div>
411 <div class="stepwiz-step">
412 <a href="#step-2" type="button" class="btn btn-light btn-circle disabled">2</a>
413 <p><?php echo xlt('Profile') ?></p>
414 </div>
415 <div class="stepwiz-step">
416 <a href="#step-3" type="button" class="btn btn-light btn-circle disabled">3</a>
417 <p><?php echo xlt('Insurance') ?></p>
418 </div>
419 <div class="stepwiz-step">
420 <a href="#step-4" type="button" class="btn btn-light btn-circle disabled">4</a>
421 <p><?php echo xlt('Register') ?></p>
422 </div>
423 </div>
424 </div>
425 <!-- // Start Forms // -->
426 <form id="startForm" role="form" action="" method="post" onsubmit="">
427 <div class="text-center setup-content" id="step-1">
428 <legend class="bg-primary text-white"><?php echo xlt('Contact Information') ?></legend>
429 <div class="jumbotron">
430 <?php if ($GLOBALS['language_menu_login']) { ?>
431 <?php if (count($result3) != 1) { ?>
432 <div class="form-group">
433 <label class="col-form-label" for="selLanguage"><?php echo xlt('Language'); ?></label>
434 <select class="form-control" id="selLanguage" name="languageChoice">
435 <?php
436 echo "<option selected='selected' value='" . attr($defaultLangID) . "'>" .
437 text(xl('Default') . " - " . xl($defaultLangName)) . "</option>\n";
438 foreach ($result3 as $iter) {
439 if ($GLOBALS['language_menu_showall']) {
440 if (!$GLOBALS['allow_debug_language'] && $iter['lang_description'] == 'dummy') {
441 continue; // skip the dummy language
443 echo "<option value='" . attr($iter['lang_id']) . "'>" .
444 text($iter['trans_lang_description']) . "</option>\n";
445 } else {
446 if (in_array($iter['lang_description'], $GLOBALS['language_menu_show'])) {
447 if (!$GLOBALS['allow_debug_language'] && $iter['lang_description'] == 'dummy') {
448 continue; // skip the dummy language
450 echo "<option value='" . attr($iter['lang_id']) . "'>" .
451 text($iter['trans_lang_description']) . "</option>\n";
456 </select>
457 </div>
458 <?php }
459 } ?>
460 <div class="form-row">
461 <div class="col form-group">
462 <label for="fname"><?php echo xlt('First Name') ?></label>
463 <input type="text" class="form-control" id="fname" required placeholder="<?php echo xla('First Name'); ?>" />
464 </div>
465 <div class="col form-group">
466 <label for="mname"><?php echo xlt('Middle Name') ?></label>
467 <input type="text" class="form-control" id="mname" placeholder="<?php echo xla('Full or Initial'); ?>" />
468 </div>
469 <div class="col form-group">
470 <label for="lname"><?php echo xlt('Last Name') ?></label>
471 <input type="text" class="form-control" id="lname" required placeholder="<?php echo xla('Enter Last'); ?>" />
472 </div>
473 <div class="col form-group">
474 <label for="dob"><?php echo xlt('Birth Date') ?></label>
475 <input id="dob" type="text" required class="form-control datepicker" placeholder="<?php echo xla('YYYY-MM-DD'); ?>" />
476 </div>
477 </div>
478 <div class="form-group">
479 <label class="col-form-label" for="emailInput"><?php echo xlt('Enter E-Mail Address') ?></label>
480 <input id="emailInput" type="email" class="reg-email form-control" required placeholder="<?php echo xla('Enter email address to receive registration.'); ?>" maxlength="100" />
481 </div>
482 </div>
484 <button class="btn btn-primary nextBtn pull-right" type="button"><?php echo xlt('Next') ?></button>
485 </div>
486 </form>
487 <!-- Profile Form -->
488 <form id="profileForm" role="form" action="account.php" method="post">
489 <div class="text-center setup-content" id="step-2" style="display: none">
490 <legend class="bg-primary text-white"><?php echo xlt('Profile') ?></legend>
491 <div class="jumbotron">
492 <iframe class="embedded-content" src="../patient/patientdata?pid=0&register=true" id="profileFrame" name="Profile Info"></iframe>
493 </div>
494 <button class="btn btn-primary prevBtn pull-left" type="button"><?php echo xlt('Previous') ?></button>
495 <button class="btn btn-primary pull-right" type="button" id="profileNext"><?php echo xlt('Next') ?></button>
496 </div>
497 </form>
498 <!-- Insurance Form -->
499 <form id="insuranceForm" role="form" action="" method="post">
500 <div class="text-center setup-content" id="step-3" style="display: none">
501 <legend class='bg-primary text-white'><?php echo xlt('Insurance') ?></legend>
502 <div class="jumbotron">
503 <div class="form-row">
504 <div class="col form-group">
505 <label for="provider"><?php echo xlt('Insurance Company') ?></label>
506 <div class="controls inline-inputs">
507 <input type="text" class="form-control" name="provider" id="inscompany" required placeholder="<?php echo xla('Enter Self if None'); ?>">
508 </div>
509 </div>
510 <div class="col form-group">
511 <label for="plan_name"><?php echo xlt('Plan Name') ?></label>
512 <div class="controls inline-inputs">
513 <input type="text" class="form-control" name="plan_name" required placeholder="<?php echo xla('required'); ?>">
514 </div>
515 </div>
516 <div class="col form-group">
517 <label for="policy_number"><?php echo xlt('Policy Number') ?></label>
518 <div class="controls inline-inputs">
519 <input type="text" class="form-control" name="policy_number" required placeholder="<?php echo xla('required'); ?>">
520 </div>
521 </div>
522 </div>
523 <div class="form-row">
524 <div class="col form-group">
525 <label for="group_number"><?php echo xlt('Group Number') ?></label>
526 <div class="controls inline-inputs">
527 <input type="text" class="form-control" name="group_number" required placeholder="<?php echo xla('required'); ?>">
528 </div>
529 </div>
530 <div class="col form-group">
531 <label for="date"><?php echo xlt('Policy Begin Date') ?></label>
532 <div class="controls inline-inputs">
533 <input type="text" class="form-control datepicker" name="date" placeholder="<?php echo xla('Policy effective date'); ?>">
534 </div>
535 </div>
536 <div class="col form-group">
537 <label for="copay"><?php echo xlt('Co-Payment') ?></label>
538 <div class="controls inline-inputs">
539 <input type="number" class="form-control" name="copay" placeholder="<?php echo xla('Plan copay if known'); ?>">
540 </div>
541 </div>
542 </div>
543 </div>
544 <button class="btn btn-primary prevBtn btn-sm pull-left" type="button"><?php echo xlt('Previous') ?></button>
545 <button class="btn btn-primary nextBtn btn-sm pull-right" type="button"><?php echo xlt('Next') ?></button>
546 </div>
547 </form>
548 <!-- End Insurance. Next what we've been striving towards..the end-->
549 <div class="text-center setup-content" id="step-4" style="display: none">
550 <legend class='bg-success text-white'><?php echo xlt('Register') ?></legend>
551 <div class="jumbotron">
552 <h4 class='bg-success'><?php echo xlt("All set. Click Send Request below to finish registration.") ?></h4>
553 <hr />
555 <?php echo xlt("An e-mail with your new account credentials will be sent to the e-mail address supplied earlier. You may still review or edit any part of your information by using the top step buttons to go to the appropriate panels. Note to be sure you have given your correct e-mail address. If after receiving credentials and you have trouble with access to the portal, please contact administration.") ?>
556 </p>
557 </div>
558 <hr />
559 <button class="btn btn-primary prevBtn float-left" type="button"><?php echo xlt('Previous') ?></button>
560 <button class="btn btn-success float-right" type="button" id="submitPatient"><?php echo xlt('Send Request') ?></button>
561 </div>
562 </div>
563 </body>
564 </html>