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

/**
* CWIS-specific user class.
*/
class CWUser extends User {

    # ---- PUBLIC INTERFACE --------------------------------------------------

    /**
    * Load user data from the given user info or from the session if available.
    * @param mixed $UserInfo A user ID or user name. (OPTIONAL)
    */
    public function __construct($UserInfo=NULL)
    {
        static $EmailWrapperSet = FALSE;
        if (!$EmailWrapperSet)
        {
            User::SetEmailFunction(array("CWUser", "EmailWrapper"));
            $EmailWrapperSet = TRUE;
        }

        # call the parent constructor
        parent::User($UserInfo);

        # try to fetch the associated resource if the user was found
        if ($this->Result === U_OKAY)
        {
            $Resource = $this->FetchAssociatedResource($this->UserId);

            # the associated resource was successfully found
            if ($Resource instanceof Resource)
            {
                $this->Resource = $Resource;
            }

            # there was a problem finding the resource
            else
            {
                $this->Result = $Resource;
            }
        }
    }

    /**
    * Get/set user privileges as a set.
    * @param PrivilegeSet $NewValue New set of privileges.  (OPTIONAL)
    * @return PrivilegeSet Current set of privileges.
    */
    public function Privileges(PrivilegeSet $NewValue = NULL)
    {
        # if new set of privileges supplied
        if ($NewValue !== NULL)
        {
            # retrieve privilege list from set
            $PrivList = $NewValue->GetPrivilegeList();

            # set new privilege list for user
            $this->SetPrivList($PrivList);
        }

        # retrieve privilege list for user
        $Privs = $this->GetPrivList();

        # convert privilege list to privilege set
        $Set = new PrivilegeSet();
        foreach ($Privs as $Priv) {  $Set->AddPrivilege($Priv);  }

        # set user associated with privilege set
        $Set->AssociatedUserId($this->Id());

        # return privilege set to caller
        return $Set;
    }

    /**
    * Get the ID of the user resource associated with the user.
    * @return Returns the ID of the associated user resource or NULL if it's not
    *      available, e.g., the user isn't logged in.
    */
    public function ResourceId()
    {
        return $this->IsResourceObjectSet() ? $this->Resource->Id() : NULL;
    }

    /**
    * Get the associated user resource for this user.
    * @return Returns the associated user resource or NULL if it's not
    *      available, e.g., the user isn't logged in.
    */
    public function GetResource()
    {
        return $this->IsResourceObjectSet() ? $this->Resource : NULL;
    }

    /**
    * Adapter method to bridge between AxisPHP User class and ScoutLib Email class.
    * @param string $To To line for message.
    * @param string $Subject Subject line for message.
    * @param string $Message Body of message.
    * @param string $AdditionalHeaders Other message header lines, concatenated
    *       together into a string.
    * @return bool TRUE if message successfully accepted for delivery,
    *       otherwise FALSE.
    */
    public static function EmailWrapper($To, $Subject, $Message, $AdditionalHeaders)
    {
        # extract "From" address from supplied headers if available
        if (strlen($AdditionalHeaders))
        {
            $HeaderLines = explode("\n", $AdditionalHeaders);
            $Headers = array();
            foreach ($HeaderLines as $Line)
            {
                $HeaderLine = trim($Line);
                if (preg_match("/^from:/i", $Line))
                {
                    $From = preg_replace("/^from:/i", "", $Line);
                }
                else
                {
                    $Headers[] = $HeaderLine;
                }
            }
        }

        # send message
        $Msg = new Email();
        if (isset($From)) {  $Msg->From($From);  }
        $Msg->To($To);
        $Msg->Subject($Subject);
        $Msg->AddHeaders($Headers);
        $Msg->Body($Message);
        $Result = $Msg->Send();

        # report success to caller
        return $Result;
    }

    /**
    * Get all custom user fields.
    * @return Returns an array of the custom user fields.
    */
    public static function GetCustomUserFields()
    {
        static $CustomFields;

        if (!isset($CustomFields))
        {
            $CustomFields = array();
            $Schema = new MetadataSchema(MetadataSchema::SCHEMAID_USER);

            foreach ($Schema->GetFields() as $Field)
            {
                # they're custom if not owned by CWIS
                if ($Field->Owner() != "CWISCore")
                {
                    $CustomFields[$Field->Id()] = $Field;
                }
            }
        }

        return $CustomFields;
    }

    /**
    * Get the default user fields.
    * @return Returns an array of the default user fields.
    */
    public static function GetDefaultUserFields()
    {
        static $DefaultFields;

        if (!isset($DefaultFields))
        {
            $DefaultFields = array();
            $Schema = new MetadataSchema(MetadataSchema::SCHEMAID_USER);

            foreach ($Schema->GetFields() as $Field)
            {
                # they're default if owned by CWIS
                if ($Field->Owner() != "CWISCore")
                {
                    $DefaultFields[$Field->Id()] = $Field;
                }
            }
        }

        return $DefaultFields;
    }

    # ---- OVERRIDDEN METHODS ------------------------------------------------

    /**
    * Delete the user and its associated user resource. Methods should not be
    * called on the object after calling this method.
    * @return Returns the status of the deletion attempt.
    */
    public function Delete()
    {
        # delete the associated user resource if set
        if (isset($this->Resource))
        {
            $this->Resource->Delete();
            $this->Result = U_OKAY;
        }

        return parent::Delete();
    }

    /**
    * Get a value from the specified field.
    * @param string $FieldName The name of the field to get.
    * @return Returns the field value or NULL if user data isn't available,
    *      e.g., the user isn't logged in.
    */
    public function Get($FieldName)
    {
        # provide backwards-compatibility for data migrated to users fields as
        # of CWIS 3.0.0
        if (in_array($FieldName, self::$MigratedUserFields))
        {
            # return NULL if the resource object isn't set
            if (!$this->IsResourceObjectSet())
            {
                return NULL;
            }

            # return the value from the associated resource
            return $this->Resource->Get($FieldName);
        }

        # otherwise, get it from the APUsers table
        return parent::Get($FieldName);
    }

    /**
    * Set a value for the specified field.
    * @param string $FieldName The name of the field to set.
    * @param mixed $NewValue The value to which to set the field.
    * @return Returns the status of the operation.
    */
    public function Set($FieldName, $NewValue)
    {
        # provide backwards-compatibility for data migrated to users fields as
        # of CWIS 3.0.0
        if (in_array($FieldName, self::$MigratedUserFields))
        {
            # set the value only if the resource object is set
            if ($this->IsResourceObjectSet())
            {
                $this->Resource->Set($FieldName, $NewValue);
                $this->Result = U_OKAY;
            }
        }

        # transform boolean values to 1 or 0 because that's what the User
        # class expects
        if (is_bool($NewValue))
        {
            $NewValue = $NewValue ? 1 : 0;
        }

        # update the APUsers table
        return parent::Set($FieldName, $NewValue);
    }

    # ---- PRIVATE INTERFACE -------------------------------------------------

    /**
    * The user resource associated with the user or NULL if the user isn't
    * logged in.
    */
    protected $Resource;

    /**
    * Fields that were previously part of the APUsers table that have been
    * migrated to the Resources table as of CWIS 3.0.0.
    */
    protected static $MigratedUserFields = array(
        "RealName", "WebSite", "AddressLineOne", "AddressLineTwo", "City",
        "State", "ZipCode", "Country");

    /**
    * Fetch the associated user resource based off of a user ID.
    * @param int $UserId The user ID for the user associated with the esource.
    * @return Returns the associated user resource or an error status if an
    *      error occurs.
    */
    protected function FetchAssociatedResource($UserId)
    {
        try
        {
            $Factory = new ResourceFactory(MetadataSchema::SCHEMAID_USER);
            $Schema = new MetadataSchema(MetadataSchema::SCHEMAID_USER);
        }

        # couldn't get the factory or schema, which probably means CWIS hasn't
        # been installed yet
        catch (Exception $Exception)
        {
            return U_ERROR;
        }

        # the UserId field doesn't exist, which probably means CWIS hasn't been
        # installed yet
        if (!$Schema->FieldExists("UserId"))
        {
            return U_ERROR;
        }

        # get matching resources, which should only be one
        $Field = $Schema->GetFieldByName("UserId");
        $ResourceIds = $Factory->GetItemIds("`".$Field->DBFieldName()
                ."` = '".intval($UserId)."'");
        $ResourceIdCount = count($ResourceIds);

        # no resource found
        if ($ResourceIdCount < 1)
        {
            return U_NOSUCHUSER;
        }

        # too many resources found
        if ($ResourceIdCount > 1)
        {
            return U_ERROR;
        }

        # construct the associated resource and return it
        return new Resource(array_shift($ResourceIds));
    }

    /**
    * Determine if the resource object for this object is set.
    * @return Returns TRUE if the resource object for this objects is set.
    */
    protected function IsResourceObjectSet()
    {
        # there must be a user ID, which is what the User class assumes, and the
        # resource must be set
        return isset($this->UserId) && isset($this->Resource);
    }

}
