<?PHP
#
#   FILE:  EditUserComplete.php
#
#   Part of the Collection Workflow Integration System (CWIS)
#   Copyright 2011-2013 Edward Almasy and Internet Scout Research Group
#   http://scout.wisc.edu/cwis/
#

# ----- LOCAL FUNCTIONS ------------------------------------------------------

/**
* Determine if the current user is editing his or her own account.
* @return Returns TRUE if the current user is editing his or her own account and
*      FALSE otherwise.
*/
function IsEditingOwnAccount()
{
    global $User;

    # user is logged in and is not SYSADMIN or USERADMIN
    if ((!$User->HasPriv(PRIV_SYSADMIN) &&
        !$User->HasPriv(PRIV_USERADMIN)) &&
        $User->IsLoggedIn() == TRUE)
        return TRUE;
    else
        return FALSE;
}

/**
* Update the saved searches for a user based on what was submitted in the form.
* @param User $User User whose saved searches should be updated.
*/
function UpdateSavedSearches(User $User)
{
    $SSFactory = new SavedSearchFactory();
    $Searches = $SSFactory->GetSearchesForUser($User->Id());

    # return if the user doesn't have any saved searches
    if (count($Searches) < 0)
    {
        return;
    }

    # get the possible mailing frequency values
    if ($User->HasPriv(PRIV_RESOURCEADMIN))
    {
        $FreqList = SavedSearch::GetSearchFrequencyList();
    }

    else
    {
        $FreqList = SavedSearch::GetSearchFrequencyList(SavedSearch::SEARCHFREQ_HOURLY);
    }

    # update each saved search
    foreach ($Searches as $Search)
    {
        $Frequency = GetArrayValue($_POST, "F_Frequency_".$Search->Id());

        # skip the frequency if it's invalid
        if (!array_key_exists($Frequency, $FreqList))
        {
            continue;
        }

        # update the frequency if given and it has changed
        if (!is_null($Frequency) && $Search->Frequency() != $Frequency)
        {
            $Search->Frequency($Frequency);
        }
    }
}

/**
* Save the given fields for the given user.
* @param CWUser $User The user for which to save fields.
* @param array $Fields The fields to save.
*/
function UpdateUser(CWUser $User, array $Fields)
{
    global $AF;

    # save new user settings
    $User->Set("RealName", GetArrayValue($_POST, "F_RealName"));
    $User->Set("EMail", GetArrayValue($_POST, "F_EMail"));
    $User->Set("WebSite", GetArrayValue($_POST, "F_WebSite"));
    $User->Set("AddressLineOne", GetArrayValue($_POST, "F_AddressLineOne"));
    $User->Set("AddressLineTwo", GetArrayValue($_POST, "F_AddressLineTwo"));
    $User->Set("City", GetArrayValue($_POST, "F_City"));
    $User->Set("State", GetArrayValue($_POST, "F_State"));
    $User->Set("ZipCode", GetArrayValue($_POST, "F_ZipCode"));
    $User->Set("Country", GetArrayValue($_POST, "F_Country"));

    # extract values from the form that need additional checks
    $F_NewPassword = GetArrayValue($_POST, "F_NewPassword");
    $F_Privileges = GetArrayValue($_POST, "F_Privileges", array());
    $F_UserDisabled = GetArrayValue($_POST, "F_UserDisabled", FALSE);
    $F_ActivateAccount = GetArrayValue($_POST, "F_ActivateAccount", FALSE);

    # change the user's password if necessary
    if (strlen($F_NewPassword))
    {
        $User->SetPassword($F_NewPassword);
    }

    # don't allow non sysadmin to turn off sysadmin, user for a sysadmin
    if ($User->HasPriv(PRIV_SYSADMIN)
        && $GLOBALS["User"]->HasPriv(PRIV_USERADMIN)
        && !$GLOBALS["User"]->HasPriv(PRIV_SYSADMIN))
    {
        $F_Privileges[] = PRIV_SYSADMIN;
        $F_Privileges[] = PRIV_USERADMIN;
    }

    # don't allow sysadmin turn off sysadmin, user for himself
    if ($User->Get("UserId") == $GLOBALS["User"]->Get("UserId")
        && $GLOBALS["User"]->HasPriv(PRIV_SYSADMIN))
    {
        $F_Privileges[] = PRIV_SYSADMIN;
        $F_Privileges[] = PRIV_USERADMIN;
    }

    # update the user's privileges
    $User->SetPrivList($F_Privileges);

    # disable the user account if necessary, but don't allow users to disable
    # their own accounts
    if ($F_UserDisabled && $User->Get("UserId") != $GLOBALS["User"]->Get("UserId"))
    {
        $User->GrantPriv(PRIV_USERDISABLED);
    }

    else
    {
        $User->RevokePriv(PRIV_USERDISABLED);
    }

    # activate the user account if necessary
    if ($F_ActivateAccount)
    {
        # enable account for login
        $User->RevokePriv(PRIV_USERDISABLED);
        $User->IsActivated(TRUE);

        # signal that user has been activated
        $GLOBALS["AF"]->SignalEvent(
            "EVENT_USER_VERIFIED", array("UserId" => $User->Id()));
    }

    # for each metadata field
    $Schema = new MetadataSchema(MetadataSchema::SCHEMAID_USER);
    $Resource = $User->GetResource();
    $EmptyFields = array();
    $RecordChanged = FALSE;
    foreach ($Fields as $Field)
    {
        # if user has permission to edit field
        if ($Resource->UserCanEditField($GLOBALS["G_User"], $Field))
        {
            $OldValue = $Resource->Get($Field);
            switch ($Field->Type())
            {
                case MetadataSchema::MDFTYPE_TEXT:
                case MetadataSchema::MDFTYPE_NUMBER:
                case MetadataSchema::MDFTYPE_DATE:
                case MetadataSchema::MDFTYPE_TIMESTAMP:
                case MetadataSchema::MDFTYPE_PARAGRAPH:
                case MetadataSchema::MDFTYPE_FLAG:
                case MetadataSchema::MDFTYPE_URL:
                    # if we have a value from the form for this field
                    # (check necessary because user may push Save button
                    #       before form has finished loading)
                    if (isset($_POST["F_".$Field->DBFieldName()]))
                    {
                        # run value through any hooked filters
                        $NewValue = trim($_POST["F_".$Field->DBFieldName()]);
                        $SignalResult = $AF->SignalEvent(
                                "EVENT_POST_FIELD_EDIT_FILTER", array(
                                       "Field" => $Field,
                                       "Resource" => $Resource,
                                       "Value" => $NewValue));
                        $NewValue = $SignalResult["Value"];

                        # filter date field values for validity
                        if ($Field->Type() & (MetadataSchema::MDFTYPE_DATE
                                |MetadataSchema::MDFTYPE_TIMESTAMP))
                        {
                            $TestDate = new Date($NewValue);
                            if ($TestDate->Precision() == 0) {  $NewValue = "";  }
                        }

                        # filter url values for URI
                        if ($Field->Type() == MetadataSchema::MDFTYPE_URL
                            && strlen($NewValue)
                            && !preg_match('/^[a-zA-Z]+:\/\//', $NewValue))
                        {
                            $NewValue = "http://".$NewValue;
                        }

                        # filter HTML tags out of text values if appropriate
                        if ($Field->Type() & (MetadataSchema::MDFTYPE_TEXT
                                |MetadataSchema::MDFTYPE_PARAGRAPH))
                        {
                            if (!$Field->AllowHTML())
                            {
                                $NewValue = strip_tags($NewValue);
                            }
                        }

                        # if value was supplied or field is not required
                        if (strlen($NewValue) || $Field->Optional())
                        {
                            # save field
                            $Resource->Set($Field, $NewValue);
                        }
                        else
                        {
                            # add field to error list
                            $EmptyFields[] = $Field;
                        }
                    }
                    break;

                case MetadataSchema::MDFTYPE_POINT:
                    # if there are no values set
                    if (!isset($_POST["F_".$Field->DBFieldName()."X"])
                        || !isset($_POST["F_".$Field->DBFieldName()."Y"]))
                    {
                        # if the field isn't optional, add it to the error list
                        if (!$Field->Optional())
                        {
                            $EmptyFields[] = $Field;
                        }

                        # go to the next field
                        continue;
                    }

                    # run value through any hooked filters
                    $NewValue = array(
                            "X" => $_POST["F_".$Field->DBFieldName()."X"],
                            "Y" => $_POST["F_".$Field->DBFIeldName()."Y"]);
                    $SignalResult = $AF->SignalEvent(
                            "EVENT_POST_FIELD_EDIT_FILTER", array(
                                   "Field" => $Field,
                                   "Resource" => $Resource,
                                   "Value" => $NewValue));
                    $NewValue = $SignalResult["Value"];

                    # if value looks valid
                    if (is_numeric($NewValue["X"])
                        && is_numeric($NewValue["Y"]))
                    {
                        # save new value
                        $Resource->Set($Field, $NewValue);
                    }

                    # the field is optional and the values are blank
                    else if ($Field->Optional()
                        && !strlen(trim($NewValue["X"]))
                        && !strlen(trim($NewValue["Y"])))
                    {
                        # save blank value
                        $Resource->Set($Field, array("X" => "", "Y" => ""));
                    }

                    # empty fields and field is required
                    else if (!$Field->Optional())
                    {
                        # flag field as empty
                        $EmptyFields[] = $Field;
                    }
                    break;

                case MetadataSchema::MDFTYPE_TREE:
                case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
                    # while there are form values for this field
                    $ValueCount = $Field->GetCountOfPossibleValues();
                    $InterfaceToggleThreshold = 250;
                    $Factory = $Field->GetFactory();
                    $ValuesToSet = array();
                    $InputValues = array();

                    # old way it was being set
                    $BaseFieldName = "D_".$Field->DBFieldName()."_";
                    $FieldIndex = 1;

                    # set values the old way
                    if (isset($_POST[$BaseFieldName.$FieldIndex]))
                    {
                        while (isset($_POST[$BaseFieldName.$FieldIndex]))
                        {
                            # retrieve value from form field
                            $InputValues[] = $_POST[$BaseFieldName.$FieldIndex];

                            # move to the next form value
                            $FieldIndex++;
                        }
                    }

                    # set values the new way
                    else if (isset($_POST["F_".$Field->DBFieldName()]))
                    {
                        $InputValues = $_POST["F_".$Field->DBFieldName()];
                    }

                    foreach ($InputValues as $Value)
                    {
                        # If we have a non-empty value
                        if (strlen($Value))
                        {
                            # Check to see if it was a name, and if so
                            # convert it to an index.  Otherwise,
                            # it was already an index and we should use it
                            # directly.
                            $Item = $Factory->GetItemByName($Value);
                            if ($Item)
                            {
                                $Value = $Item->Id();
                            }

                            # it looks like it was being wrongly assumed that
                            # this would always be a number, but when there's
                            # an error, it won't find an item and display SQL
                            # errors later on. So, if the value isn't numeric,
                            # refuse to work with it
                            else if (!is_numeric($Value))
                            {
                                $Value = -1;
                            }

                            else
                            {
                                $Value = intval($Value);
                            }
                        }
                        else
                        {
                            $Value = -1;
                        }

                        # if form value appears valid
                        if ($Value >= 0)
                        {
                            # add value to list of those to be set
                            # (set method expects IDs to appear as indexes)
                            $ValuesToSet[$Value] = 1;
                        }
                    }

                    # if value found to set or field is not required
                    if (count($ValuesToSet) || $Field->Optional())
                    {
                        $OldKeys = array_keys($OldValue);
                        $NewKeys = array_keys($ValuesToSet);

                        sort($OldKeys);
                        sort($NewKeys);

                        if ($OldKeys != $NewKeys)
                        {
                            # clear any existing values for this field
                            $Resource->ClearByField($Field);

                            # if values found to set
                            if (count($ValuesToSet))
                            {
                                # set values in resource
                                $Resource->Set($Field, $ValuesToSet);
                            }
                        }
                    }
                    else
                    {
                        # add field to error list
                        $EmptyFields[] = $Field;
                    }
                    break;

                case MetadataSchema::MDFTYPE_OPTION:
                    # if field allows multiple values
                    $ValuesToSet = array();
                    if ($Field->AllowMultiple())
                    {
                        # retrieve possible values for this field
                        $PossibleValues = $Field->GetPossibleValues();

                        # newer way to get the values
                        if (isset($_POST["F_".$Field->DBFieldName()]))
                        {
                            $GivenValues = $_POST["F_".$Field->DBFieldName()];

                            # for each possible value
                            foreach ($PossibleValues as $ValueId => $ValueName)
                            {
                                # if form field is set for value
                                if (in_array($ValueId, $GivenValues))
                                {
                                    # add value to list of those to be set
                                    $ValuesToSet[$ValueId] = 1;
                                }
                            }
                        }

                        # old way to get the values
                        else
                        {
                            # for each possible value
                            foreach ($PossibleValues as $ValueId => $ValueName)
                            {
                                # if form field is set for value
                                if (isset($_POST["D_".$Field->DBFieldName()."_".$ValueId]))
                                {
                                    # add value to list of those to be set
                                    $ValuesToSet[$ValueId] = 1;
                                }
                            }
                        }
                    }
                    else
                    {
                        # retrieve value for this field (if available)
                        if (isset($_POST["F_".$Field->DBFieldName()]))
                        {
                            $ValuesToSet[$_POST["F_".$Field->DBFieldName()]] = 1;
                        }
                    }

                    # if value found to set or field is not required
                    if (count($ValuesToSet) || $Field->Optional())
                    {
                        $OldKeys = array_keys($OldValue);
                        $NewKeys = array_keys($ValuesToSet);

                        sort($OldKeys);
                        sort($NewKeys);

                        if ($OldKeys != $NewKeys)
                        {
                            # clear any existing values for this field
                            $Resource->ClearByField($Field);

                            # if values found to set
                            if (count($ValuesToSet))
                            {
                                # set values in resource
                                $Resource->Set($Field, $ValuesToSet);
                            }
                        }
                    }
                    else
                    {
                        # add field to error list
                        $EmptyFields[] = $Field;
                    }
                    break;

                case MetadataSchema::MDFTYPE_USER:
                    $NewValue = trim(GetArrayValue(
                        $_POST,
                        "F_".$Field->DBFieldName()));

                    if (strlen($NewValue))
                    {
                        $SignalResult = $AF->SignalEvent(
                                "EVENT_POST_FIELD_EDIT_FILTER", array(
                                       "Field" => $Field,
                                       "Resource" => $Resource,
                                       "Value" => $NewValue));

                        $NewValue = $SignalResult["Value"];
                        $Resource->Set($Field, $NewValue);
                    }

                    # allow the field to be unset if it's optional
                    else if ($Field->Optional())
                    {
                        $SignalResult = $AF->SignalEvent(
                                "EVENT_POST_FIELD_EDIT_FILTER", array(
                                       "Field" => $Field,
                                       "Resource" => $Resource,
                                       "Value" => $NewValue));

                        $NewValue = $SignalResult["Value"];
                        $Resource->Set($Field, $NewValue);
                    }

                    break;

                case MetadataSchema::MDFTYPE_REFERENCE:
                    # get the new value from the submitted form data
                    $NewValue = GetArrayValue(
                        $_POST,
                        "F_".$Field->DBFieldName(),
                        array());

                    foreach ($NewValue as $Key => $ReferenceId)
                    {
                        # remove any blank values
                        if (strlen(trim($ReferenceId)) < 1)
                        {
                            unset($NewValue[$Key]);
                        }

                        # remove any values that don't look right
                        if (!is_numeric($ReferenceId))
                        {
                            unset($NewValue[$Key]);
                        }
                    }

                    # set the new value
                    $Resource->Set($Field, $NewValue);
                    break;

                case MetadataSchema::MDFTYPE_IMAGE:
                case MetadataSchema::MDFTYPE_FILE:
                    # (these types handled via special upload mechanisms)
                    break;

                default:
                    break;
            }
            # If anything changed, set the update flag.
            $RecordChanged |= ($OldValue
                               != $Resource->Get($Field));
        }
    }

    # If the record was changed, modify the appropriate timestamp fields
    if ($RecordChanged)
    {
        $TimestampFields = $Schema->GetFields(
            MetadataSchema::MDFTYPE_TIMESTAMP);
        foreach ($TimestampFields as $Field)
        {
            if ($Field->UpdateMethod() ==
                MetadataField::UPDATEMETHOD_ONRECORDCHANGE)
            {
                $Resource->Set($Field, "now");
            }
        }

        # update the last modified by ID field only if using the default schema
        if ($Schema->Id() == MetadataSchema::SCHEMAID_DEFAULT)
        {
            $Resource->Set("Last Modified By Id", $User);
        }

        # signal the modified event if the resource isn't a temp one
        if (!$Resource->IsTempResource())
        {
            $AF->SignalEvent("EVENT_RESOURCE_MODIFY", array("Resource" => $Resource));
        }
    }

    # return list of any empty required fields to caller
    return $EmptyFields;
}

/**
* Process image upload requests.
* @param CWUser $User The user to add images to.
*/
function UploadImages(CWUser $User)
{
    # for each metadata field that might have an uploaded image
    $Schema = new MetadataSchema(MetadataSchema::SCHEMAID_USER);
    $Resource = $User->GetResource();
    $Fields = $Schema->GetFields(MetadataSchema::MDFTYPE_IMAGE);
    foreach ($Fields as $Field)
    {
        $FormFieldName = "F_".$Field->DBFieldName();

        # if field is modifiable by specified user
        #       and we have an uploaded file for this field
        if ($Resource->UserCanEditField($User, $Field)
            && isset($_FILES[$FormFieldName]["tmp_name"])
            && is_uploaded_file($_FILES[$FormFieldName]["tmp_name"]))
        {
            # create temp copy of file with correct name
            $TempFile = "tmp/".$_FILES[$FormFieldName]['name'];
            copy($_FILES[$FormFieldName]["tmp_name"], $TempFile);

            # create new Image object from uploaded file
            $Image = new SPTImage(
                $TempFile,
                $Field->MaxWidth(), $Field->MaxHeight(),
                $Field->MaxPreviewWidth(), $Field->MaxPreviewHeight(),
                $Field->MaxThumbnailWidth(), $Field->MaxThumbnailHeight());

            # if file save failed
            if ($Image->Status() != AI_OKAY)
            {
                # set error message and error out
                switch ($Image->Status())
                {
                    case AI_UNKNOWNTYPE:
                    case AI_UNSUPPORTEDFORMAT:
                        $Error = ($Image->Status() == AI_UNSUPPORTEDFORMAT)
                            ? ERR_UNSUPPORTEDIMAGEFORMAT : ERR_UNKNOWNIMAGETYPE;
                        $ErrParamOne = $_FILES[$Field->DBFieldName()]['name'];
                        break;

                    default:
                        $Error = ERR_IMAGEUPLOADERROR;
                        $ErrParamOne = $_FILES[$FormFieldName]['name'];
                        $ErrParamTwo = $Image->Status();
                        break;
                }
            }
            else
            {
                # attach image to resource
                $Resource->Set($Field, $Image->Id());
            }
        }

        # delete images that have been selected for delete
        if (isset($_POST[$FormFieldName."_Delete"]))
        {
            $DeletionIds = $_POST[$FormFieldName."_Delete"];

            foreach ($DeletionIds as $DeletionId)
            {
                $Resource->Clear($Field, new SPTImage($DeletionId));
            }
        }
    }
}

/**
* Process file upload requests.
* @param CWUser $User The user to add files to.
*/
function UploadFiles(CWUser $User)
{
    # for each metadata field that might have an uploaded image
    $Schema = new MetadataSchema(MetadataSchema::SCHEMAID_USER);
    $Resource = $User->GetResource();
    $Fields = $Schema->GetFields(MetadataSchema::MDFTYPE_FILE);
    foreach ($Fields as $Field)
    {
        $FormFieldName = "F_".$Field->DBFieldName();

        # if field is modifiable by specified user
        #       and we have an uploaded file for this field
        if ($Resource->UserCanEditField($User, $Field)
            && isset($_FILES[$FormFieldName]["tmp_name"])
            && is_uploaded_file($_FILES[$FormFieldName]["tmp_name"]))
        {
            # save uploaded file
            $TmpFileName = $_FILES[$FormFieldName]["tmp_name"];
            $NewFile = new File($TmpFileName, $Resource->Id(), $Field->Id(),
                                $_FILES[$FormFieldName]["name"]);

            # if file save went fine
            if ($NewFile->Status() == File::FILESTAT_OK)
            {
                $GLOBALS["AF"]->SignalEvent(
                    "EVENT_RESOURCE_FILE_ADD",
                    array(
                        "Field" => $Field,
                        "Resource" => $Resource,
                        "File" => $NewFile));
            }

            else
            {
                # set error message and error out
                switch ($NewFile->Status())
                {
                    case File::FILESTAT_ZEROLENGTH:
                        $Error = ERR_ZEROLENGTH;
                        break;

                    default:
                        $Error = ERR_FILEUPLOADERROR;
                        break;
                }
                $ErrParamOne = $_FILES[$FormFieldName]['name'];
                $ErrParamTwo = $NewFile->Status();
            }

            # remove temp file
            unlink($TmpFileName);
        }

        # delete images that have been selected for delete
        if (isset($_POST[$FormFieldName."_Delete"]))
        {
            $DeletionIds = $_POST[$FormFieldName."_Delete"];

            foreach ($DeletionIds as $DeletionId)
            {
                $File = new File($DeletionId);

                $GLOBALS["AF"]->SignalEvent(
                    "EVENT_RESOURCE_FILE_DELETE",
                    array(
                        "Field" => $Field,
                        "Resource" => $Resource,
                        "File" => $File));

                $Resource->Clear($Field, $File->Id());
            }
        }
    }
}

# ----- MAIN -----------------------------------------------------------------

$F_DeleteSavedSearch = GetArrayValue($_POST, "F_DeleteSavedSearch");
$SubmitResult = GetArrayValue($_POST, "SubmitResult");

# make sure the user can view the page
if (!CheckAuthorization(PRIV_SYSADMIN, PRIV_USERADMIN)) {  return;  }

# pull up record of user being edited
$UserToEdit = new CWUser(intval($Session->Get("IdOfUserBeingEdited")));
$Session->UnregisterVariable("IdOfUserBeingEdited");

# if the user wasn't found
if ($UserToEdit->Status() !== U_OKAY)
{
    # put up error message
    ErrorOut("Could not find user record being edited");
    return;
}

# if submit button pushed
if ($SubmitResult == "Save Changes")
{
    # update the user information
    UpdateUser($UserToEdit, CWUser::GetCustomUserFields());
    UploadImages($UserToEdit);
    UploadFiles($UserToEdit);

    # update saved search mailing frequencies if mailings are enabled
    if ($GLOBALS["SysConfig"]->UserAgentsEnabled())
    {
        UpdateSavedSearches($UserToEdit);
    }

    # go back to the user list
    $GLOBALS["AF"]->SetJumpToPage("UserList");
}

# delete the user
else if ($SubmitResult == "Delete")
{
    # but first go to the confirmation page
    $Session->PassVariable("UserRemoveArray", array($UserToEdit->Id()));
    $GLOBALS["AF"]->SetJumpToPage("ConfirmRemoveUser");
}

# delete a saved search
else if (!is_null($F_DeleteSavedSearch))
{
    # update the user information
    UpdateUser($UserToEdit, CWUser::GetCustomUserFields());
    UploadImages($UserToEdit);
    UploadFiles($UserToEdit);

    # update saved search mailing frequencies if mailings are enabled
    if ($GLOBALS["SysConfig"]->UserAgentsEnabled())
    {
        UpdateSavedSearches($UserToEdit);
    }

    # delete the saved search
    $Search = new SavedSearch($F_DeleteSavedSearch);
    $Search->Delete();

    # return to the user editing page
    $AF->SetJumpToPage("EditUser&ID=".urlencode($UserToEdit->Id()));
}

else
{
    # return to front page
    $AF->SetJumpToPage("UserList");
}
