4 * Portal Registration Wizard
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');
40 $res2 = sqlStatement("select * from lang_languages where lang_description = ?", array(
41 $GLOBALS['language_default']
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"];
50 // default to english if any problems
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(
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";
80 $hiddenLanguageField = "<input type='hidden' name='languageChoice' value='" . attr($defaultLangID) . "' />\n";
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']); ?
>
93 var webRoot
= <?php
echo js_escape($GLOBALS['web_root']); ?
>;
94 top
.webroot_url
= webRoot
;
99 function restoreSession() {
100 //dummy functions so the dlgopen function will work in the patient portal
104 var navListItems
= $
('div.setup-panel div a'),
105 allWells
= $
('.setup-content'),
106 allNextBtn
= $
('.nextBtn'),
107 allPrevBtn
= $
('.prevBtn');
111 navListItems
.click(function (e
) {
113 if (!$
(this
).hasClass('disabled')) {
114 navListItems
.removeClass('btn-primary').addClass('btn-light');
115 $
(this
).addClass('btn-primary').removeClass('btn-light');
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';
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"),
144 $
(".form-group").removeClass("has-error");
145 for (var i
= 0; i
< curInputs
.length
; i++
) {
146 if (!curInputs
[i
].validity
.valid
) {
148 $
(curInputs
[i
]).closest(".form-group").addClass("has-error");
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"),
198 $
(".form-group").removeClass("has-error");
200 for (var i
= 0; i
< curInputs
.length
; i++
) {
201 if (!curInputs
[i
].validity
.valid
) {
204 curInputs
[i
].scrollIntoView();
205 curInputs
[i
].focus();
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()));
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();
230 callServer('get_newpid', '');
233 var isOk
= checkRegistration(newPid
);
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
) {
257 var url
= "account.php?action=new_insurance&pid=" +
encodeURIComponent(newPid
);
261 data
: $
("#insuranceForm").serialize(),
262 success
: function (serverResponse
) {
263 doCredentials(newPid
) // this is the end for session.
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.'); ?
>;
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.
291 let msg
= <?php
echo (xlj("Invalid Date format or value! Type date as YYYY-MM-DD or use the calendar.") ); ?
> ;
293 $
(this
).prop('placeholder', 'Invalid Date');
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"),
312 $
(".form-group").removeClass("has-error");
314 for (var i
= 0; i
< curInputs
.length
; i++
) {
315 if (!curInputs
[i
].validity
.valid
) {
318 curInputs
[i
].scrollIntoView();
319 curInputs
[i
].focus();
322 $
(curInputs
[i
]).closest(".form-group").addClass("has-error");
333 function callServer(action
, value
, value2
, last
, first
, email
= null) {
343 if (action
== 'do_signup') {
348 } else if (action
== 'notify_admin') {
354 } else if (action
== 'cleanup') {
359 // The magic that is jquery ajax.
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) {
372 $
("#profileFrame").contents().find('input#pubpid').val(newPid
);
373 $
("#profileFrame").contents().find('input#pid').val(newPid
);
375 // After error alert app exit to landing page.
376 // Existing user error. Error message is translated in account.lib.php.
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
);
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.
395 }).fail(function (err
) {
396 message
= <?php
echo xlj('Something went wrong.') ?
>;
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
>
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
>
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
>
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
>
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">
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";
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";
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'); ?>" />
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'); ?>" />
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'); ?>" />
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'); ?>" />
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" />
484 <button
class="btn btn-primary nextBtn pull-right" type
="button"><?php
echo xlt('Next') ?
></button
>
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®ister=true" id
="profileFrame" name
="Profile Info"></iframe
>
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
>
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'); ?>">
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'); ?>">
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'); ?>">
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'); ?>">
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'); ?>">
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'); ?>">
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
>
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>
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.") ?>
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>