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

class Mailer extends Plugin {

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

    function Register()
    {
        $this->Name = "Mailer";
        $this->Version = "1.0.0";
        $this->Description = "Generates and emails messages to users based"
                ." on templates.";
        $this->Author = "Internet Scout";
        $this->Url = "http://scout.wisc.edu/cwis/";
        $this->Email = "scout@scout.wisc.edu";
        $this->Requires = array(
                "CWISCore" => "2.2.4");
        $this->EnabledByDefault = TRUE;

        $this->CfgSetup["BaseUrl"] = array(
                "Type" => "Text",
                "Label" => "Base URL",
                "Help" => "This value overrides any automatically-determined"
                        ." value for the X-BASEURL-X template keyword.",
                );
    }

    function Install()
    {
        $this->ConfigSetting("Templates", array());
    }

    function HookEvents()
    {
        return array(
                "EVENT_COLLECTION_ADMINISTRATION_MENU" => "AddCollectionAdminMenuItems",
                );
    }

    function AddCollectionAdminMenuItems()
    {
        return array(
                "EditMessageTemplates" => "Edit Email Templates",
                );
    }

    /**
    * Retrieve list of currently available templates, with template IDs for
    * the indexes and template names for the values.
    * @return Array containing template names.
    */
    function GetTemplateList()
    {
        $Templates = $this->ConfigSetting("Templates");
        $TemplateList = array();
        foreach ($Templates as $Id => $Template)
        {
            $TemplateList[$Id] = $Template["Name"];
        }
        return $TemplateList;
    }

    /**
    * Send email to specified recipients using specified template.
    * @param TemplateId ID of template to use in generating emails.
    * @param Users User object or ID or array of User objects or IDs
    *       as recipients.
    * @param Resources Resource or Resource ID or array of Resources or
    *       array of Resource IDs to be referred to within email messages.
    *       (OPTIONAL, defaults to NULL)
    * @param ExtraValues Array of additional values to swap into template,
    *       with value keywords (without the "X-" and "-X") for the index
    *       and values for the values.  This parameter can be used to
    *       override the builtin keywords.  (OPTIONAL)
    * @return Number of email messages sent.
    */
    function SendEmail($TemplateId, $Users, $Resources = NULL, $ExtraValues = NULL)
    {
        # initialize count of emails sent
        $MessagesSent = 0;

        # convert incoming parameters to arrays if necessary
        if (!is_array($Users)) {  $Users = array($Users);  }
        if ($Resources && !is_array($Resources))
                {  $Resources = array($Resources);  }

        # load resource objects if necessary
        if (count($Resources) && !is_object(reset($Resources)))
        {
            foreach ($Resources as $Id)
            {
                $NewResources[$Id] = new Resource($Id);
            }
            $Resources = $NewResources;
        }

        # retrieve appropriate template
        $Templates = $this->ConfigSetting("Templates");
        $Template = $Templates[$TemplateId];

        # set up parameters for keyword replacement callback
        $this->KRItemBody = $Template["ItemBody"];
        $this->KRResources = $Resources;
        $this->KRExtraValues = $ExtraValues;

        # for each user
        foreach ($Users as $User)
        {
            # set up per-user parameters for keyword replacement callback
            if (!is_object($User))
            {
                if (!isset($DB)) {  $DB = new Database();  }
                $User = new User($DB, $User);
                if ($User->Status() != U_OKAY) {  continue;  }
            }
            $this->KRUser = $User;

            # set up headers for message
            $HeaderLines = array();
            $HeaderLines[] = "From: ".$this->ReplaceKeywords($Template["From"]);
            $HeaderLines[] = "Auto-Submitted: auto-generated";
            $HeaderLines[] = "Precedence: list";
            $HeaderLines[] = "MIME-Version: 1.0";
            $HeaderLines[] = "Content-Type: text/html; charset="
                    .$GLOBALS["AF"]->HtmlCharset();
            if (strlen($Template["Headers"]))
            if (strlen($Template["Headers"]))
            {
                $ExtraHeaderLines = explode("\n", $ExtraHeaderLines);
                foreach ($ExtraHeaderLines as $Line)
                {
                    if (strlen(trim($Line)))
                    {
                        $HeaderLines[] = $this->ReplaceKeywords($Line);
                    }
                }
            }
            $Headers = implode("\r\n", $HeaderLines);

            # set up subject for message
            $Subject = trim($this->ReplaceKeywords($Template["Subject"]));

            # start with empty message body
            $Body = "";

            # for each line of template
            $TemplateLines = explode("\n", $Template["Body"]);
            foreach ($TemplateLines as $Line)
            {
                # replace any keywords in line and add line to message body
                $Body .= $this->ReplaceKeywords($Line);
            }

            # strip any insecurities
            $Body = StripXSSThreats($Body);

            # wrap the message in boilerplate HTML
            $Body = '<!DOCTYPE html>
                     <html lang="en">
                      <head><meta charset="'.$GLOBALS["AF"]->HtmlCharset().'" /></head>
                      <body>'.$Body.'</body>
                     </html>';

            # if we have all the needed pieces to send a message
            $Address = trim($User->Get("EMail"));
            if (strlen($Address) && strlen($Subject) && strlen($Subject))
            {
                # send email to user
                mail($Address, $Subject, $Body, $Headers);
                $MessagesSent++;
            }
        }

        # report number of emails sent back to caller
        return $MessagesSent;
    }


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

    # values for use by KeywordReplacmentCallback()
    private $KRUser;
    private $KRItemBody;
    private $KRResources;
    private $KRExtraValues;

    private function ReplaceKeywords($Line)
    {
        return preg_replace_callback("/X-([A-Z0-9:]+)-X/",
                array($this, "KeywordReplacementCallback"), $Line);
    }

    private function KeywordReplacementCallback($Matches)
    {
        static $InResourceList = FALSE;
        static $CurrentResource;
        static $ResourceNumber;
        static $FieldNameMappings;

        # if extra value was supplied with keyword that matches the match string
        if (count($this->KRExtraValues)
                && isset($this->KRExtraValues[$Matches[1]]))
        {
            # return extra value to caller
            return $this->KRExtraValues[$Matches[1]];
        }

        # if current resource is not yet set then set default value if available
        if (!isset($CurrentResource) && count($this->KRResources))
        {
            $CurrentResource = reset($this->KRResources);
        }

        # start out with assumption that no replacement text will be found
        $Replacement = $Matches[0];

        # switch on match string
        switch ($Matches[1])
        {
            case "PORTALNAME":
                $Replacement = $GLOBALS["G_SysConfig"]->PortalName();
                break;

            case "BASEURL":
                $Replacement = strlen(trim($this->ConfigSetting("BaseUrl")))
                        ? trim($this->ConfigSetting("BaseUrl"))
                        : OurBaseUrl()."index.php";
                break;

            case "ADMINEMAIL":
                $Replacement = $GLOBALS["G_SysConfig"]->AdminEmail();
                break;

            case "LEGALNOTICE":
                $Replacement = $GLOBALS["G_SysConfig"]->LegalNotice();
                break;

            case "USERLOGIN":
            case "USERNAME":
                $Replacement = $this->KRUser->Get("UserName");
                break;

            case "USERREALNAME":
                $Replacement = $this->KRUser->Get("RealName");
                if (!strlen(trim($Replacement))) {
                        $Replacement = $this->KRUser->Get("UserName");  }
                break;

            case "USEREMAIL":
                $Replacement = $this->KRUser->Get("EMail");
                break;

            case "RESOURCELIST":
                $Replacement = "";
                if ($InResourceList == FALSE)
                {
                    $InResourceList = TRUE;
                    $ResourceNumber = 1;
                    foreach ($this->KRResources as $Resource)
                    {
                        $CurrentResource = $Resource;
                        $TemplateLines = explode("\n", $this->KRItemBody);
                        foreach ($TemplateLines as $Line)
                        {
                            $Replacement .= preg_replace_callback(
                                    "/X-([A-Z0-9:]+)-X/",
                                    array($this, "KeywordReplacementCallback"),
                                    $Line);
                        }
                        $ResourceNumber++;
                    }
                    $InResourceList = FALSE;
                }
                break;

            case "RESOURCENUMBER":
                $Replacement = $ResourceNumber;
                break;

            case "RESOURCECOUNT":
                $Replacement = count($this->KRResources);
                break;

            case "RESOURCEID":
                $Replacement = $CurrentResource->Id();
                break;

            default:
                # map to date/time value if appropriate
                $DateFormats = array(
                        "DATE"            => "M j Y",
                        "TIME"            => "g:ia T",
                        "YEAR"            => "Y",
                        "YEARABBREV"      => "y",
                        "MONTH"           => "n",
                        "MONTHNAME"       => "F",
                        "MONTHABBREV"     => "M",
                        "MONTHZERO"       => "m",
                        "DAY"             => "j",
                        "DAYZERO"         => "d",
                        "DAYWITHSUFFIX"   => "jS",
                        "WEEKDAYNAME"     => "l",
                        "WEEKDAYABBREV"   => "D",
                        "HOUR"            => "g",
                        "HOURZERO"        => "h",
                        "MINUTE"          => "i",
                        "TIMEZONE"        => "T",
                        "AMPMLOWER"       => "a",
                        "AMPMUPPER"       => "A",
                        );
                if (isset($DateFormats[$Matches[1]]))
                {
                    $Replacement = date($DateFormats[$Matches[1]]);
                }
                else
                {
                    # load field name mappings (if not already loaded)
                    if (!isset($FieldNameMappings))
                    {
                        $Schema = new MetadataSchema();
                        $Fields = $Schema->GetFields();
                        foreach ($Fields as $Field)
                        {
                            $NormalizedName = strtoupper(
                                    preg_replace("/[^A-Za-z0-9]/",
                                    "", $Field->Name()));
                            $FieldNameMappings[$NormalizedName] = $Field;
                        }
                    }

                    # if keyword refers to known field and we have a current resource
                    $KeywordIsField = preg_match(
                            "/FIELD:([A-Z0-9]+)/", $Matches[1], $SubMatches);
                    if ($KeywordIsField && isset($FieldNameMappings[$SubMatches[1]])
                            && isset($CurrentResource))
                    {
                        # replacement is value from current resource
                        $Field = $FieldNameMappings[$SubMatches[1]];
                        $Replacement = $CurrentResource->Get($Field);
                        if (is_array($Replacement))
                        {
                            foreach ($Replacement as $ReplacementEntry)
                            {
                                if (!isset($RebuiltReplacement))
                                {
                                    $RebuiltReplacement = $ReplacementEntry;
                                }
                                else
                                {
                                    $RebuiltReplacement .= ", ".$ReplacementEntry;
                                }
                            }
                            $Replacement = isset($RebuiltReplacement)
                                    ? $RebuiltReplacement : "";
                        }
                        if (!$Field->AllowHTML())
                        {
                            $Replacement = htmlspecialchars($Replacement);
                        }
                        $Replacement = wordwrap($Replacement, 78);
                    }
                }
                break;
        }

        # return replacement string to caller
        return $Replacement;
    }
}


