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

/**
* 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;

    # 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->UserCanAuthorField($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"];

                        # update release date if release flag was toggled to true
                        # or if the release flag was true and the Date of Record Release was NULL
                        $NewValue = StripXSSThreats($NewValue);
                        if ($Field->Name()=="Release Flag" &&
                            ((!$Resource->Get("Release Flag") && $NewValue == "1") ||
                             ($NewValue == "1" && is_null($Resource->Get("Date Of Record Release")))))
                        {
                            $Resource->Set("Date Of Record Release", date("Y-m-d H:i:s"));
                        }

                        # 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;
}

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

# set up expectations for incoming form

PageTitle("Processing New User Registration");

# Don't allow new user signups from spambots:
if ($GLOBALS["G_PluginManager"]->PluginEnabled("BotDetector") &&
    $GLOBALS["G_PluginManager"]->GetPlugin("BotDetector")->CheckForSpamBot())
{
    $AF->SetJumpToPage("UnauthorizedAccess");
    return;
}

$FormPage = "index.php?P=RequestAccount";
$FTool = new FormTool("RequestAccount");

# check incoming values for errors
$UserFactory = new CWUserFactory();
$UserErrorCodes = $UserFactory->TestNewUserValues($_POST["F_UserName"],
            $_POST["F_Password"], $_POST["F_PasswordAgain"],
            $_POST["F_EMail"], $_POST["F_EMailAgain"]);

$SignupStatus = $AF->SignalEvent("EVENT_USER_SIGNUP_VERIFY",
                                 array(
                                     "UserName" => $_POST["F_UserName"],
                                     "Password" => $_POST["F_Password"],
                                     "EMail"    => $_POST["F_EMail"],
                                     "Status" => U_OKAY ));

if ($SignupStatus["Status"] !== U_OKAY)
{
    $UserErrorCodes[]= $SignupStatus["Status"];
}

# if errors were found in incoming values
if ($FTool->IncomingFieldValuesHaveErrors() || count($UserErrorCodes))
{
    # make form fields based on error codes
    $CodeToFieldMap = array(
            U_DUPLICATEUSERNAME => "F_UserName",
            U_ILLEGALUSERNAME => "F_UserName",
            U_PASSWORDSDONTMATCH => array("F_Password", "F_PasswordAgain"),
            U_EMAILSDONTMATCH => array("F_EMail", "F_EMailAgain"),
            U_ILLEGALPASSWORD => "F_Password",
            U_ILLEGALPASSWORDAGAIN => "F_PasswordAgain",
            U_ILLEGALEMAIL => "F_EMail",
            U_ILLEGALEMAILAGAIN => "F_EMailAgain",
            U_EMPTYPASSWORD => "F_Password",
            U_EMPTYPASSWORDAGAIN => "F_PasswordAgain",
            U_EMPTYEMAIL => "F_EMail",
            U_EMPTYEMAILAGAIN => "F_EMailAgain",
            U_DUPLICATEEMAIL => array("F_EMail", "F_EMailAgain"),
            );

    foreach ($UserErrorCodes as $Code)
    {
        if (isset($CodeToFieldMap[$Code]))
        {
            if (is_array(isset($CodeToFieldMap[$Code])))
            {
                foreach ($CodeToFieldMap[$Code] as $FieldName)
                {
                    $FTool->SetAdditionalErrorFields($FieldName);
                }
            }
            else
            {
                $FTool->SetAdditionalErrorFields($CodeToFieldMap[$Code]);
            }
        }
    }

    $FTool->SetAdditionalErrorCodes($UserErrorCodes);
    $AF->SetJumpToPage($FTool->GetUrlWithValuesAndErrorCodes($FormPage));
    return;
}

# retrieve form values for account creation
$UserName = $_POST["F_UserName"];

# create new user
$NewUser = $UserFactory->CreateNewUser($_POST["F_UserName"],
            $_POST["F_Password"], $_POST["F_PasswordAgain"],
            $_POST["F_EMail"], $_POST["F_EMailAgain"]);

# if user creation failed
if (!is_object($NewUser) || ($NewUser->Status() != U_OKAY))
{
    # return to calling page with error code
    foreach ($NewUser as $ErrorCode)
    {
        $FTool->SetAdditionalErrorCodes($ErrorCode);
    }
    $AF->SetJumpToPage($FTool->GetUrlWithValuesAndErrorCodes("index.php?P=RequestAccount"));
    return;
}

$Protocol = isset($_SERVER["HTTPS"]) ? "https://" : "http://";

# send confirmation e-mail
$ActivationUrlParameters = "?UN=".urlencode($NewUser->Get("UserName"))
        ."&AC=".$NewUser->GetActivationCode();
$ActivationUrl = $Protocol.$_SERVER["SERVER_NAME"]
        .dirname($_SERVER["SCRIPT_NAME"])
        ."/index.php".$ActivationUrlParameters."&P=ActivateAccount";
$ManualActivationUrl = $Protocol.$_SERVER["SERVER_NAME"]
        .dirname($_SERVER["SCRIPT_NAME"])
        ."/index.php?P=ManuallyActivateAccount";
$OurSubstitutions = array(
        "X-PORTALNAME-X" => $SysConfig->PortalName(),
        "X-ACTIVATIONURL-X" => $ActivationUrl,
        "X-ACTIVATIONPARAMETERS-X" => $ActivationUrlParameters,
        "X-MANUALACTIVATIONURL-X" => $ManualActivationUrl,
        );
$MailTemplate = $SysConfig->PasswordMailSubject()."\n"
        .$SysConfig->PasswordMailBody();
$FromAddress = $SysConfig->PortalName()." <".$SysConfig->AdminEmail().">";
$NewUser->SendEMail($MailTemplate, $FromAddress, $OurSubstitutions);

# save other user information
$UserFields = array(
        "RealName",
        "WebSite",
        "AddressLineOne",
        "AddressLineTwo",
        "City",
        "State",
        "ZipCode",
        "Country",
        );
foreach ($UserFields as $VarName)
{
    $FormVarName = "F_".$VarName;
    if (isset($_POST[$FormVarName]) && strlen($_POST[$FormVarName]))
    {
        $NewUser->Set($VarName, $_POST[$FormVarName]);
    }
}

# set up initial UI setting
$NewUser->Set("ActiveUI", $SysConfig->DefaultActiveUI());

# set up initial privileges
foreach ($SysConfig->DefaultUserPrivs() as $Privilege)
{
    $NewUser->GivePriv($Privilege);
}

# always disable account until activated via confirmation e-mail
$NewUser->GivePriv(PRIV_USERDISABLED);

# set e-mail address and user name for use in page display
$G_EMailAddress = User::NormalizeEMailAddress($_POST["F_EMail"]);
$G_UserName = User::NormalizeUserName($_POST["F_UserName"]);

# update custom fields
UpdateUser($NewUser, CWUser::GetCustomUserFields());

# signal that user has been added
$AF->SignalEvent("EVENT_USER_ADDED", array(
        "UserId" => $NewUser->Id(), "Password" => $_POST["F_Password"]));
