<?PHP
#
#   FILE:  MLPHPList.php
#
#   Part of the Collection Workflow Integration System (CWIS)
#   Copyright 2014 Internet Scout Project
#   http://scout.wisc.edu/cwis
#

class MLPHPList extends Plugin
{
    /**
    * Register information about this plugin.
    */
    public function Register()
    {
        $this->Name = "PHPList";
        $this->Version = "1.0.0";
        $this->Description = "MailingList backend plugin to support PHPList.";
        $this->Author = "Internet Scout";
        $this->Url = "http://scout.wisc.edu/cwis/";
        $this->Email = "scout@scout.wisc.edu";
        $this->Requires = array(
            "CWISCore" => "2.2.3",
            "MailingList" => "1.0.0",
            );
        $this->EnabledByDefault = FALSE;

        $this->InitializeBefore = array("MailingList");

        $this->CfgSetup["RESTUrl"] = array(
            "Type" => "URL",
            "Label" => "PHPList REST API URL",
            "MaxLength" => 255,
            "Help" => "The URL to use for sending commands to PHPList." );

        $this->CfgSetup["PHPListUser"] = array(
            "Type" => "Text",
            "Label" => "PHPList Username",
            "Help" => "PHPList administrative user to use" );

        $this->CfgSetup["PHPListPass"] = array(
            "Type" => "Text",
            "Label" => "PHPList Password",
            "Help" => "Password for the PHPList admin user" );
    }

    /**
    * Make sure the configuration values are valid
    * @return null|string NULL if there are no errors or an error message
    */
    public function Initialize()
    {
        # verify that all our required parameters have been configured
        $RESTUrl = $this->ConfigSetting("RESTUrl");
        $PHPListUser = $this->ConfigSetting("PHPListUser");
        $PHPListPass = $this->ConfigSetting("PHPListPass");

        $Errors = array();

        # make sure that login creds were specified
        if (strlen($RESTUrl)==0)
            { $Errors[]= "No REST Url was specified" ; }

        if (filter_var($RESTUrl, FILTER_VALIDATE_URL)===FALSE)
            { $Errors[]= "REST Url setting is invalid"; }

        if (strlen($PHPListUser)==0)
            { $Errors[]= "No PHPList Username was specified" ; }

        if (strlen($PHPListPass)==0)
            { $Errors[]= "No PHPList Password was specified"; }

        if (count($Errors)>0)
        {
            return $Errors;
        }

        # if login creds have changed, verify that we can use them
        $ConfigHash = md5($RESTUrl.$PHPListUser.$PHPListPass);
        if ($this->ConfigSetting("PreviousConfigHash") != $ConfigHash)
        {
            if ($this->GetAllLists() === NULL)
            {
                return "Could not connect to PHPList with the provided credentials.";
            }
            else
            {
                $this->ConfigSetting("PreviousConfigHash", $ConfigHash);
            }

        }

        # register ourselves
        MailingList::RegisterBackend($this->Name, "PHPList Support", $this);
    }

    /**
    * Update the user's mailing list subscriptions when his or her e-mail
    * address has changed.
    * @param string $OldEmail Previous e-mail address for the user.
    * @param string $NewEmail New e-mail address for the user.
    */
    public function UpdateUserEmail($OldEmail, $NewEmail)
    {
        # fetch corresponding phplist user
        $UserInfo = $this->RestCommand(
            array("cmd" => "userGetByEmail", "email" => $OldEmail));

        # if no user was found, then there's no user to update
        if (!is_array($UserInfo) || count($UserInfo)==0)
        {
            return;
        }

        # otherwise, update user with new email address
        $this->RestCommand(array(
            "cmd" => "userUpdate",
            "id" => $UserInfo["id"],
            "email" => $NewEmail,
            "confirmed" => $UserInfo["confirmed"],
            "htmlemail" => $UserInfo["htmlemail"],
            "rssfrequency" => $UserInfo["rssfrequency"],
            "password" => $UserInfo["password"],
            "disabled" => $UserInfo["disabled"]));
    }

    /**
    * Get all available mailman mailing lists.  Lists are fetched once
    * per object.
    * @return array all available mailing lists, keyed by phplist's listid.
    */
    public function GetAllLists()
    {
        if (!isset($this->Lists))
        {
            $this->Lists = array();

            $Result = $this->RestCommand(
                array("cmd" => "listsGet"));

            if ($Result===NULL)
            {
                return $Result;
            }

            foreach ($Result as $Item)
            {
                $this->Lists[$Item["id"]] = $Item["name"];
            }
        }

        return $this->Lists;
    }

    /**
    * Get the mailing lists to which the given user is subscribed.
    * @param string $EMail Address for the user.
    * @return array Names of mailing lists to which the user is subscribed.
    */
    public function GetUserSubscriptions($EMail)
    {
        $Subscriptions = array();

        # grab the users's phplist information
        $UserInfo = $this->RestCommand(
            array("cmd" => "userGetByEmail",
                  "email" => $EMail));

        # no corresponding phplist user -> no subscriptions to get
        if (!is_array($UserInfo) || count($UserInfo)==0)
        {
            return $Subscriptions;
        }

        # otherwise, fetch the user's lists
        $UserLists = $this->RestCommand(
            array("cmd"=>"listsUser", "user_id" => $UserInfo["id"]) );
        foreach ($UserLists as $UserList)
        {
            $Subscriptions[]= $UserList["name"];
        }

        return $Subscriptions;
    }

    /**
    * Subscribe the given user to the given mailing list.
    * @param string $Email Address for user to subscribe.
    * @param string $List Mailing list.
    * @return bool TRUE on success.
    */
    public function Subscribe($Email, $List)
    {
        $ListId = array_search($List, $this->GetAllLists() );

        # check if phplist has a user corresponding to this CWIS user
        $UserInfo = $this->RestCommand(
           array("cmd"=>"userGetByEmail", "email"=>$Email));

        # if not, create one
        if (!is_array($UserInfo) || count($UserInfo)==0)
        {
            $UserInfo = $this->RestCommand(array(
                "cmd" => "userAdd",
                "email" => $Email,
                "confirmed" => 1,
                "htmlemail" => 1,
                "rssfrequency" => 0,
                "password" => md5( base64_encode( openssl_random_pseudo_bytes(15) )),
                "disabled" => 0 ));
        }

        # add the user to the specified list
        #  (result is an array of lists to which the user is subscribed)
        $Result = $this->RestCommand(array(
            "cmd" => "listUserAdd",
            "list_id" => $ListId,
            "user_id" => $UserInfo["id"] ));

        # iterate over the user's subscriptions, returning TRUE if
        #  the requested list is among them
        foreach ($Result as $ThisList)
        {
            if ($ThisList["name"] == $List)
            {
                return TRUE;
            }
        }

        return FALSE;
    }

    /**
    * Unsubscribe the given user from the given mailing list.
    * @param string $Email Address for user to unsubscribe.
    * @param string $List Mailing list.
    * @return bool TRUE on success.
    */
    public function Unsubscribe($Email, $List)
    {
        $ListId = array_search($List, $this->GetAllLists() );

        # pull up the corresponding phplist user
        $UserInfo = $this->RestCommand(
            array("cmd"=>"userGetByEmail", "email"=> $Email));

        # if no user was found, then we have nothing to do as they
        # couldn't have been subscribed in the first place
        if (!is_array($UserInfo) || count($UserInfo)==0)
        {
            return FALSE;
        }

        # delete the user from this list
        $Result = $this->RestCommand(array(
            "cmd" => "listUserDelete",
            "list_id" => $ListId,
            "user_id" => $UserInfo["id"]));

        # check that the command was successful
        if ($Result == "User ".$UserInfo["id"]." is unassigned from list ".$ListId)
        {
            return TRUE;
        }
        else
        {
            return FALSE;
        }
    }

    /**
    * Get the list of subscribers to the given list.
    * @param string $List Mailing list name.
    * @return array Email addresses subscribed to the given list.
    */
    public function GetSubscribers($List)
    {
        $Subscribers = array();

        # extract all known users
        $AllUsers = $this->RestCommand(
            array("cmd"=>"usersGet", "limit"=>PHP_INT_MAX));

        # if list of users obtained
        if (is_array($AllUsers))
        {
            # iterate over them, fetching list info for each
            foreach ($AllUsers as $UserInfo)
            {
                $UserLists = $this->RestCommand(
                    array("cmd"=>"listsUser", "user_id"=>$UserInfo["id"]) );

                # iterate over their lists, checking if they are on this one
                foreach ($UserLists as $UserList)
                {
                    if ($UserList["name"] == $List)
                    {
                        $Subscribers[]= $UserInfo["email"];
                        continue;
                    }
                }
            }
        }

        return $Subscribers;
    }

    /**
    * Run a REST command against a PHPList instance.
    * Logs us in to phplist on the first function call.
    * @param array $Params POST parameters.
    * @return response from PHPList (format depends on the command
    *   issued) or NULL on command failure.
    */
    private function RestCommand($Params)
    {
        static $Context;

        if (!isset($Context))
        {
            $Context = curl_init();

            # enable cookie handling
            curl_setopt($Context, CURLOPT_COOKIEFILE, '');

            # perform login
            $LoginParams = array(
                "cmd" => "login",
                "login" => $this->ConfigSetting("PHPListUser"),
                "password" => $this->ConfigSetting("PHPListPass"));
            $Reply = $this->DoCurlRequest($Context, $LoginParams);

            if ($Reply["status"] != "success")
            {
                return NULL;
            }
        }

        # perform the requested REST call
        $Reply = $this->DoCurlRequest($Context, $Params);
        if ($Reply === NULL)
        {
            return NULL;
        }

        return $Reply["data"];
    }

    /**
    * Send an HTTP POST request to a specified URL with Curl.
    * @param resource $Context Created by curl_init().
    * @param array $Params POST parameters to send.
    * @return array Response data (precise contents depend on the
    * request sent).
    */
    private function DoCurlRequest($Context, $Params)
    {
        # use our configured endpoint
        curl_setopt($Context, CURLOPT_URL,
                    $this->ConfigSetting("RESTUrl"));
        # get results back as a string
        curl_setopt($Context, CURLOPT_RETURNTRANSFER, TRUE);
        # send data in a POST
        curl_setopt($Context, CURLOPT_POST, TRUE);
        # load the POST data
        curl_setopt($Context, CURLOPT_POSTFIELDS,
                     http_build_query($Params));

        $CurlResponse = curl_exec($Context);
        if ($CurlResponse === FALSE)
        {
            $errno = curl_errno($Context);
            $GLOBALS["AF"]->LogMessage(
                ApplicationFramework::LOGLVL_ERROR,
                "MLPHPList: Unable to make CURL request. "
                ."CURL errno: ".$errno);
            return NULL;
        }
        else
        {
            # fetch and decode the data
            $Result = json_decode($CurlResponse, TRUE);
            return $Result;
        }
    }

    /**
    * @var array $Lists mailing list cache
    */
    private $Lists;
}
