/**
  * Helper method that takes a list of user identifiers such as email addresses and returns the appropriate user ID
  * from the warehouse, which can then be used in subsequent calls to save the data. Takes the 
  * following parameters in the $request (which is a merge of $_GET or $_POST data) in addition to a nonce and auth_token for a write operation:<ul>
  * <li><strong>identifiers</strong/><br/>
  * Required. A JSON encoded array of identifiers known for the user. Each array entry is an object 
  * with a type property (e.g. twitter, openid) and identifier property (e.g. twitter account). An identifier of type
  * email must be provided in case a new user account has to be created on the warehouse.</li>
  * <li><strong>surname</strong/><br/>
  * Required. Surname of the user, enabling a new user account to be created on the warehouse.</li>
  * <li><strong>first_name</strong/><br/>
  * Optional. First name of the user, enabling a new user account to be created on the warehouse.</li>
  * <li><strong>cms_user_id</strong/><br/>
  * Optional. User ID from the client website's login system. Allows existing records to be linked to the created account when migrating from a 
  * CMS user ID based authentication to Easy Login based authentication.</li>
  * <li><strong>warehouse_user_id</strong/><br/>
  * Optional. Where a user ID is already known but a new identifier is being provided (e.g. an email switch), provide the warehouse user ID.</li>
  * <li><strong>force</strong/><br/>
  * Optional. Only relevant after a request has returned an array of several possible matches. Set to 
  * merge or split to define the action.</li>
  * <li><strong>users_to_merge</strong/><br/>
  * If force=merge, then this parameter can be optionally used to limit the list of users in the merge operation.
  * Pass a JSON encoded array of user IDs.</li>
  * <li><strong>attribute_values</strong>
  * Optional list of custom attribute values for the person which have been modified on the client website
  * and should be synchronised into the warehouse person record. The custom attributes must already exist
  * on the warehouse and have a matching caption, as well as being marked as synchronisable or the attribute
  * values will be ignored. Provide this as a JSON object with the properties being the caption of the 
  * attribute and the values being the values to change.
  * </li>
  * <li><strong>shares_to_prevent</strong>
  * If the user has opted out of allowing their records to be shared with other 
  * websites, the sharing tasks which they have opted out of should be passed as a comma separated list
  * here. Valid sharing tasks are: reporting, peer_review, verification, data_flow, moderation. They 
  * will then be stored against the user account. </li>
  * </ul>
  * @return JSON JSON object containing the following properties:
  *   userId - If a single user account has been identified then returns the Indicia user ID for the existing 
  *     or newly created account. Otherwise not returned.
  *   attrs - If a single user account has been identifed then returns a list of captions and values for the 
  *     attributes to update on the client account.
  *   possibleMatches - If a list of possible users has been identified then this property includes a list of people that 
  *     match from the warehouse - each with the user ID, website ID and website title they are
  *     members of. If this happens then the client must ask the user to confirm that they 
  *     are the same person as the users of this website and if so, the response is sent back with a force=merge
  *     parameter to force the merge of the people. If they are the same person as only some of the other users,
  *     then use users_to_merge to supply an array of the user IDs that should be merged. Alternatively, if 
  *     force=split is passed through then the best fit user ID is returned and no merge operation occurs.
  *   error - Error string if an error occurred.
  */
 public static function get_user_id($request, $websiteId)
 {
     if (!array_key_exists('identifiers', $request)) {
         throw new exception('Error: missing identifiers parameter');
     }
     $identifiers = json_decode($request['identifiers']);
     if (!is_array($identifiers)) {
         throw new Exception('Error: identifiers parameter not of correct format');
     }
     if (empty($request['surname'])) {
         throw new exception('Call to get_user_id requires a surname in the GET or POST data.');
     }
     $userPersonObj = new stdClass();
     $userPersonObj->db = new Database();
     if (!empty($request['warehouse_user_id'])) {
         $userId = $request['warehouse_user_id'];
         $qry = $userPersonObj->db->select('person_id')->from('users')->where(array('id' => $userId))->get()->result_array(false);
         if (!isset($qry[0])) {
             throw new exception("Error: unknown warehouse_user_id ({$userId})");
         }
         $userPersonObj->person_id = $qry[0]['person_id'];
     } else {
         $existingUsers = array();
         // work through the list of identifiers and find the users for the ones we already know about,
         // plus find the list of identifiers we've not seen before.
         // email is a special identifier used to create person.
         $email = null;
         foreach ($identifiers as $identifier) {
             // store the email address, since this is always required to create a person
             if ($identifier->type === 'email') {
                 $email = $identifier->identifier;
                 // The query to find an existing user is slightly different for emails, since the
                 // email can be in the user identifier list or the person record
                 $joinType = 'LEFT';
             } else {
                 $joinType = 'INNER';
             }
             $userPersonObj->db->select('DISTINCT u.id as user_id, u.person_id')->from('users as u')->join('people as p', 'p.id', 'u.person_id')->join('user_identifiers as um', 'um.user_id', 'u.id', $joinType)->join('termlists_terms as tlt1', 'tlt1.id', 'um.type_id', $joinType)->join('termlists_terms as tlt2', 'tlt2.meaning_id', 'tlt1.meaning_id', $joinType)->join('terms as t', 't.id', 'tlt2.term_id', $joinType)->where(array('u.deleted' => 'f', 'p.deleted' => 'f'));
             $ident = pg_escape_string($identifier->identifier);
             $type = pg_escape_string($identifier->type);
             if ($identifier->type === 'email') {
                 // Filter to find either the user identifier or the email in the person record
                 $userPersonObj->db->where("(um.identifier='{$ident}' OR p.email_address='{$ident}')");
                 $userPersonObj->db->where("(t.term='{$type}' OR p.email_address='{$ident}')");
             } else {
                 $userPersonObj->db->where("um.identifier='{$ident}'");
                 $userPersonObj->db->where("t.term='{$type}'");
             }
             if (isset($request['users_to_merge'])) {
                 $usersToMerge = json_decode($request['users_to_merge']);
                 $userPersonObj->db->in('user_id', $usersToMerge);
             }
             $r = $userPersonObj->db->get()->result_array(true);
             foreach ($r as $existingUser) {
                 // create a placeholder for the known user we just found
                 if (!isset($existingUsers[$existingUser->user_id])) {
                     $existingUsers[$existingUser->user_id] = array();
                 }
                 // add the identifier detail to this known user
                 $existingUsers[$existingUser->user_id][] = array('identifier' => $identifier->identifier, 'type' => $identifier->type, 'person_id' => $existingUser->person_id);
             }
         }
         if ($email === null) {
             throw new exception('Call to get_user_id requires an email address in the list of provided identifiers.');
         }
         // Now we have a list of the existing users that match this identifier. If there are none, we
         // can create a new user and attach to the current website. If there is one, then we can
         // just return it. If more than one, then we have a resolution task since it probably
         // means 2 user records refer to the same physical person, or someone is sharing their
         // identifiers!
         if (count($existingUsers) === 0) {
             $userId = self::createUser($email, $userPersonObj);
         } elseif (count($existingUsers) === 1) {
             // single, known user associated with these identifiers
             $keys = array_keys($existingUsers);
             $userId = array_pop($keys);
             $userPersonObj->person_id = $existingUsers[$userId][0]['person_id'];
         }
         if (!isset($userId)) {
             $resolution = self::resolveMultipleUsers($identifiers, $existingUsers, $userPersonObj);
             // response could be a list of possible users to match against, or a single user ID.
             if (isset($resolution['possibleMatches'])) {
                 return $resolution;
             } else {
                 $userId = $resolution['userId'];
                 $userPersonObj->person_id = $existingUsers[$userId][0]['person_id'];
             }
         }
     }
     self::storeIdentifiers($userId, $identifiers, $userPersonObj, $websiteId);
     self::associateWebsite($userId, $userPersonObj, $websiteId);
     self::storeSharingPreferences($userId, $userPersonObj);
     $attrs = self::getAttributes($userPersonObj, $websiteId);
     self::storeCustomAttributes($userId, $attrs, $userPersonObj);
     // Convert the attributes to update in the client website account into an array
     // of captions & values
     $attrsToReturn = array();
     foreach ($attrs as $attr) {
         $attrsToReturn[$attr['caption']] = $attr['value'];
     }
     // If allocating a new user ID, then update the created_by_id for all records that were created by this cms_user_id. This
     // takes ownership of the records.
     if (empty($request['warehouse_user_id']) && !empty($request['cms_user_id'])) {
         postgreSQL::setOccurrenceCreatorByCmsUser($websiteId, $userId, $request['cms_user_id'], $userPersonObj->db);
     }
     return array('userId' => $userId, 'attrs' => $attrsToReturn);
 }