"
+ );
+ }
+
+ // If the user is editing his/her own account, disable all permission
+ // checkboxes. Note that they will not be submitted when the form is
+ // saved
+ $attribs = array("class" => "perm_$lastRole");
+ if ($this->_isEditingOwnAccount()) {
+ $attribs['disabled'] = true;
+ }
+ $group[] = $this->createCheckbox(
+ 'permID['.$row['permID'].']',
+ htmlspecialchars($row['description']) . "
",
+ $attribs
+ );
+ }
+ $this->addGroup($group, 'PermID_Group', 'Permissions', "", false);
+ unset($group);
+
+ //getting users name and emails to create checkboxes
+ // to email supervisors on permissions changes
+ $DB_factory =& NDB_Factory::singleton();
+ $DB = $DB_factory->database();
+
+ $query = "SELECT u.Real_Name, u.email FROM permissions p
+ JOIN user_perm_rel up ON (p.permID = up.PermID)
+ JOIN users u ON (u.ID = up.userID)
+ WHERE p.code = 'send_to_dcc'";
+
+ $results = $DB->pselect($query, array());
+
+ $group[] = $this->form->createElement(
+ 'static',
+ null,
+ null,
+ '
'
+ . " "
+ . ""
+ );
+
+ $attribs = $this->_isEditingOwnAccount() ? array('disabled' => true) : null;
+ foreach ($results as $row) {
+ $group[] = $this->createCheckbox(
+ 'supervisorEmail[' . $row['email'] .']',
+ htmlspecialchars($row['Real_Name']) . "
",
+ $attribs
+ );
+ }
+
+ $this->addGroup($group, 'Supervisors_Group', 'Supervisors', "", false);
+ unset($group);
+
+ if (!$this->isCreatingNewUser()) {
+ $user =& User::factory($this->identifier);
+
+ // add hidden permissions if editor has less permissions than user
+ // being edited
+ $perms = array_diff(
+ $user->getPermissionIDs(),
+ $editor->getPermissionIDs()
+ );
+ foreach ($perms as $value) {
+ $this->addHidden("permID[$value]", 1);
+ }
+ }
+
+ //------------------------------------------------------------
+
+ // unique key and password rules
+ $this->form->addFormRule(array(&$this, '_validateEditUser'));
+ }
+
+ /**
+ * Controls the output/behaviour of the form is used to edit
+ * the user's preferences.
+ *
+ * @return void
+ */
+ // @codingStandardsIgnoreStart
+ function my_preferences()
+ {
+ // @codingStandardsIgnoreEnd
+ $this->identifier = $_SESSION['State']->getUsername();
+
+ ///get the value for additional_user_info flag
+ $config =& NDB_Config::singleton();
+ $additional_user_info = $config->getSetting('additional_user_info');
+
+ //------------------------------------------------------------
+
+ // user name
+ $this->addScoreColumn('UserID', 'User name');
+
+ // full name
+ // The supplied pattern is:
+ // - must have at least one non-whitespace characters (i.e. required)
+ // - once leading and trailing spaces are stripped, the field should
+ // not exceed 120 chars
+ $fistNameInvalidMsg = "First name is required and "
+ . "should not exceed 120 characters";
+ $this->addBasicText(
+ 'First_name',
+ 'First name',
+ array(
+ 'oninvalid' => "this.setCustomValidity('$firstNameInvalidMsg')",
+ 'onchange' => "this.setCustomValidity('')",
+ 'pattern' => '^\s*\S.{0,119}\s*$',
+ 'required' => true,
+ )
+ );
+ // The supplied pattern is:
+ // - must have at least one non-whitespace characters (i.e. required)
+ // - once leading and trailing spaces are stripped, the field should
+ // not exceed 120 chars
+ $lastNameInvalidMsg = "Last name is required and "
+ . "should not exceed 120 characters";
+ $this->addBasicText(
+ 'Last_name',
+ 'Last name',
+ array(
+ 'oninvalid' => "this.setCustomValidity('$lastNameInvalidMsg')",
+ 'onchange' => "this.setCustomValidity('')",
+ 'pattern' => '^\s*\S.{0,119}\s*$',
+ 'required' => true,
+ )
+ );
+
+ // extra info
+ ////if the option is not set or if it's and it's true then display it
+ if ($additional_user_info) {
+ $this->addBasicText('Degree', 'Degree');
+ $this->addBasicText('Position_title', 'Academic Position');
+ $this->addBasicText('Institution', 'Institution');
+ $this->addBasicText('Department', 'Department');
+ $this->addBasicText('Address', 'Street Address');
+ $this->addBasicText('City', 'City');
+ $this->addBasicText('State', 'State/Province');
+ $this->addBasicText('Zip_code', 'Zip/Postal Code');
+ $this->addBasicText('Country', 'Country');
+ $this->addBasicText('Fax', 'FAX');
+ }
+
+ // email address
+ $this->addBasicText(
+ 'Email',
+ 'Email address',
+ array(
+ 'oninvalid' => "this.setCustomValidity('Email address is required')",
+ 'onchange' => "this.setCustomValidity('')",
+ )
+ );
+
+ // email address rules
+ $this->addRule('Email', 'Email address is required', 'required');
+ $this->addRule('Email', 'Your email address must be valid', 'email');
+ $this->addRule(
+ 'Email',
+ 'Your email address must be less than 255 characters long',
+ 'maxlength',
+ 255
+ );
+
+ // password
+ $this->form->addElement('password', 'Password_md5', 'New Password');
+ $this->form->addElement('password', '__Confirm', 'Confirm Password');
+
+ // document repository notifications
+ $editor =& User::singleton();
+ if ($editor->hasPermission('document_repository_view')
+ || $editor->hasPermission('document_repository_delete')
+ ) {
+ $doc_Repo_Notifications_Options
+ = array(
+ 'N' => 'No',
+ 'Y' => 'Yes',
+ );
+ $this->addSelect(
+ 'Doc_Repo_Notifications',
+ 'Receive Document Repository email notifications',
+ $doc_Repo_Notifications_Options
+ );
+ }
+
+ //------------------------------------------------------------
+
+ // unique key and password rules
+ $this->form->addFormRule(array(&$this, '_validateMyPreferences'));
+ }
+
+
+ /**
+ * Validates the data entered in the edit user form.
+ *
+ * @param array $values what the user entered on the form.
+ *
+ * @return array $errors all the errors found.
+ */
+ function _validateEditUser($values)
+ {
+ // create DB object
+ $DB =& Database::singleton();
+ $errors = array();
+
+ //============================================
+ // Validate UserID and NA_UserID
+ //============================================
+ if ($this->isCreatingNewUser()) {
+ // Clicked on "UID == email" and specified a UID
+ if (!empty($values['UserID']) && $values['NA_UserID'] == 'on') {
+ $errors['UserID_Group']
+ = 'You cannot enter a user name '
+ . 'if you want it to match the email address';
+ } elseif (empty($values['UserID']) && $values['NA_UserID'] != 'on') {
+ // Not clicked on "UID == email" and not specified a UID
+ $errors['UserID_Group']
+ = 'You must enter a user name '
+ . 'or choose to make it match the email address';
+ } elseif (!empty($values['UserID'])
+ || ($values['NA_UserID'] == 'on' && $values['Email'])
+ ) {
+ // Either specified a UID or clicked on "UID = email"
+ // with a non-empty email
+ $effectiveUID = empty($values['UserID'])
+ ? $values['Email'] : $values['UserID'];
+
+ $effectiveUID = trim($effectiveUID);
+
+ // check username's uniqueness
+ $result = $DB->pselectOne(
+ "SELECT COUNT(*) FROM users WHERE UserID = :UID",
+ array('UID' => $effectiveUID)
+ );
+
+ if ($result > 0) {
+ $errors['UserID_Group'] = 'The user name already exists';
+ }
+
+ if (strlen($effectiveUID) > 255) {
+ $errors['UserID_Group']
+ = 'The user name must not exsceed 255 characters';
+ }
+ }
+ }
+
+ //==================================
+ // Password validation
+ //==================================
+ if (!is_null($this->identifier)) {
+ $pass = $DB->pselectOne(
+ "SELECT COALESCE(Password_hash, Password_md5) "
+ . "as Current_password FROM users WHERE UserID = :UID",
+ array('UID' => $this->identifier)
+ );
+
+ //case of new user the password column will be null
+ //so either password should be set or
+ // password should be generated
+ if (is_null($pass)
+ && empty($values['Password_md5'])
+ && $values['NA_Password'] != 'on'
+ ) {
+ $errors['Password_Group']
+ = 'Please specify password or click Generate new password';
+ }
+ }
+
+ // if password is user-defined, and user wants to change password
+ if (empty($values['NA_Password'])
+ && (!empty($values['Password_md5']) || !empty($values['__Confirm']))
+ ) {
+ $isPasswordStrong = User::isPasswordStrong(
+ $values['Password_md5'],
+ array(
+ $values['__Confirm'],
+ isset($values['UserID']) ? $values['UserID'] : $this->identifier,
+ $values['Email'],
+ ),
+ array(
+ '==',
+ '!=',
+ '!=',
+ )
+ );
+
+ // check password strength
+ if (!$isPasswordStrong) {
+ $errors['Password_Group']
+ = 'The password is weak, or the passwords do not match';
+ } else {
+ // New password must be different than current one
+ if (!$this->_passwordChanged($DB, $values['Password_md5'])) {
+ $errors['Password_Group'] = 'New and old passwords '
+ . 'are identical: please choose another one';
+ }
+ }
+ }
+
+ // if password is generated then the email user button should be clicked
+ if ($values['NA_Password'] == "on" && $values['SendEmail'] != "on") {
+ $errors['Email_Group']
+ = 'When generating a new password, '
+ . 'please notify the user by checking Send email to user box';
+ }
+
+ if ($values['NA_Password'] == 'on' && $values['Password_md5'] != '') {
+ $errors['Password_Group'] = 'You must leave the password field empty '
+ . 'if you want the system to generate one for you';
+ }
+
+ if (is_null($this->identifier)
+ && ($values['NA_Password'] != 'on')
+ && empty($values['Password_md5'])
+ ) {
+ $errors['Password_Group'] = 'Password is required';
+ }
+
+ //======================================
+ // Validate Email
+ //======================================
+
+ // If an email was entered
+ if (!empty($values['Email'])) {
+ $emailError = $this->_getEmailError($DB, $values['Email']);
+ if (!is_null($emailError)) {
+ $errors['Email_Group'] = $emailError;
+ } elseif ($this->isCreatingNewUser()) {
+ if ($values['Email'] != $values['__ConfirmEmail']) {
+ $errors['__ConfirmEmail'] = 'Email and confirmed email '
+ . ' do not match';
+ }
+ }
+ } else {
+ // No email entered: error
+ $errors['Email_Group'] = 'You must enter an email address';
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Determines if the new password that the user entered is different
+ * than its current password (the one stored in the database).
+ *
+ * @param Database $db database object.
+ * @param string $newPassword the new password the user entered.
+ *
+ * @return bool true if the password changed, false otherwise.
+ */
+ function _passwordChanged($db, $newPassword)
+ {
+ //--- Get current password stored in database
+ $passwordQuery
+ = "SELECT Password_hash, Password_md5
+ FROM users
+ WHERE UserID = :UID";
+
+ $passwords = $db->pselectRow(
+ $passwordQuery,
+ array('UID' => $this->identifier)
+ );
+
+ // If we are using PHP5.5+ and entry Password_has in the DB is not null
+ // use method password_verify to check if the password changed
+ if (function_exists('password_verify')
+ && !is_null($passwords['Password_hash'])
+ ) {
+ return !password_verify($newPassword, $passwords['Password_hash']);
+ } elseif (!function_exists('password_verify')
+ && !is_null($passwords['Password_md5'])
+ ) {
+ // If PHP version < 5.5, use old MD5Unsalt method to check for
+ // password change
+ return !User::MD5Unsalt($newPassword, $passwords['Password_md5']);
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the data entered in the form when editing one's preferences.
+ *
+ * @param array $values values the user entered in the form.
+ *
+ * @return array $errors all the errors found.
+ */
+ function _validateMyPreferences($values)
+ {
+ // create DB object
+ $DB =& Database::singleton();
+ $errors = array();
+
+ // if password is user-defined, and user wants to change password
+ if (!empty($values['Password_md5'])) {
+ // check password strength
+ $isPasswordStrong = User::isPasswordStrong(
+ $values['Password_md5'],
+ array(
+ $values['__Confirm'],
+ $this->identifier,
+ $values['Email'],
+ ),
+ array(
+ '==',
+ '!=',
+ '!=',
+ )
+ );
+ if (!$isPasswordStrong) {
+ $errors['Password_md5']
+ = 'The password is weak, or the passwords do not match';
+ } else {
+ // New password must be different than current one
+ if (!$this->_passwordChanged($DB, $values['Password_md5'])) {
+ $errors['Password_Group'] = 'New and old passwords '
+ . 'are identical: please choose another one';
+ }
+ }
+ }
+
+ // Validate email
+ $emailError = $this->_getEmailError($DB, $values['Email']);
+ if (!is_null($emailError)) {
+ $errors['Email'] = $emailError;
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Validates that en email address entered for a given user
+ * (either new or existing) is valid and unique.
+ *
+ * @param Database $DB database object.
+ * @param string $email user's email.
+ *
+ * @return string error message if email is invalid, null otherwise.
+ */
+ private function _getEmailError($DB, $email)
+ {
+ // remove illegal characters
+ $email = filter_var($email, FILTER_SANITIZE_EMAIL);
+
+ // check email address' uniqueness
+ $query = "SELECT COUNT(*) FROM users WHERE Email = :VEmail ";
+ $params = array('VEmail' => $email);
+ if (!is_null($this->identifier)) {
+ $query .= " AND userID <> :UID";
+ $params['UID'] = $this->identifier;
+ }
+ $result = $DB->pselectOne($query, $params);
+
+ // Email already exists in database
+ if ($result > 0) {
+ return 'The email address already exists';
+ } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ // If email not syntactically valid
+ return "Invalid email address";
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the complete description for a permission.
+ *
+ * @param int $permID permission ID.
+ *
+ * @return string the description.
+ */
+ function getDescriptionUsingPermID($permID)
+ {
+ $db_factory =& NDB_Factory::singleton();
+ $db = $db_factory->database();
+
+ $permission = $db->pselectOne(
+ "SELECT Description FROM permissions WHERE permID =:pID",
+ array('pID' => $permID)
+ );
+ if (is_array($permission) && count($permission)) {
+ list(,$description) = each($permission[0]);
+ }
+ return $permission;
+ }
+}
+?>
diff --git a/modules/user_accounts/php/NDB_Menu_Filter_user_accounts.class.inc b/modules/user_accounts/php/NDB_Menu_Filter_user_accounts.class.inc
new file mode 100644
index 0000000..4b3f322
--- /dev/null
+++ b/modules/user_accounts/php/NDB_Menu_Filter_user_accounts.class.inc
@@ -0,0 +1,72 @@
+hasPermission('user_accounts');
+ }
+
+ function _setupVariables()
+ {
+ $user =& User::singleton();
+
+ // the base query
+ $query = " FROM users LEFT JOIN psc ON (psc.CenterID = users.CenterID) WHERE 1=1 ";
+ if (!$user->hasPermission('user_accounts_multisite')) {
+ $query .= " AND users.CenterID = '" . $user->getData('CenterID') . "' ";
+ }
+
+ // set the class variables
+ $this->columns = array("COALESCE(psc.Name,'Not Assigned') AS PSC", 'UserID AS Username', 'Real_name AS Full_name', 'Email', 'Active', 'Pending_approval');
+ $this->query = $query;
+ $this->order_by = 'Username';
+ $this->validFilters = array('users.CenterID', 'users.UserID', 'users.Real_name', 'users.Email', 'users.Active', 'users.Examiner', 'users.Pending_approval');
+
+ $this->formToFilter = array(
+ 'centerID' => 'users.CenterID',
+ 'active' => 'users.Active',
+ 'userID' => 'users.UserID',
+ 'real_name' => 'users.Real_name',
+ 'email' => 'users.Email',
+ 'pending' => 'users.Pending_approval'
+ );
+ return true;
+ }
+
+
+ function _setFilterForm()
+ {
+ // create user object
+ $user =& User::singleton();
+
+ // PSC
+ if ($user->hasPermission('user_accounts_multisite')) {
+ // get the list of study sites - to be replaced by the Site object
+ $list_of_sites = Utility::getSiteList(false);
+ if(is_array($list_of_sites)) $list_of_sites = array('' => 'All') + $list_of_sites;
+ }
+ else {
+ // allow only to view own site data
+ $site =& Site::singleton($user->getData('CenterID'));
+ $list_of_sites = array($user->getData('CenterID') => $user->getData('Site'));
+ }
+
+ // add form elements
+ $this->addSelect('centerID', 'Site:', $list_of_sites);
+ $this->addSelect('active', 'Active:', array('' => 'All', 'Y' => 'Y', 'N' => 'N'));
+ $this->addBasicText('userID', 'Username:');
+ $this->addBasicText('real_name', 'Full name:');
+ $this->addBasicText('email', 'Email:');
+ $this->addSelect('pending', 'Pending Approval:', array(''=>'All', 'N'=>'N', 'Y'=>'Y'));
+
+ return true;
+ }
+}
+?>
diff --git a/modules/user_accounts/templates/#form_edit_user.tpl# b/modules/user_accounts/templates/#form_edit_user.tpl#
new file mode 100644
index 0000000..d28baa3
--- /dev/null
+++ b/modules/user_accounts/templates/#form_edit_user.tpl#
@@ -0,0 +1,330 @@
+
+{literal}
+
+
+{/literal}
+