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

class XmlSitemap extends Plugin
{
    /**
    * Register information about this plugin.
    */
    public function Register()
    {
        $this->Name = "XML Sitemap";
        $this->Version = "1.0.0";
        $this->Description =
            "Provide search engine crawlers with an XML sitemap "
            ."listing all available Resources.";
        $this->Author = "Internet Scout";
        $this->Url = "http://scout.wisc.edu/cwis/";
        $this->Email = "scout@scout.wisc.edu";
        $this->Requires = array(
            "CWISCore" => "3.2.0",
            );
        $this->EnabledByDefault = TRUE;

        $this->CfgSetup["MemLimitForUpdate"] = array(
            "Type" => "Number",
            "Label" => "Memory Limit for Updates",
            "Help" => "Listing all the resources in your collection may require more "
                     ."memory than normal operation, as permissions may need to be "
                     ."evaluated for every resource in the collection. If you are "
                     ."running out of memory, this can raise the PHP memory limit.",
            "Units" => "MB",
            "Default" => 256);
    }

    /**
    * Initialize this plugin, setting up a CleanURL for the xml sitemap.
    */
    public function Initialize()
    {
        $Result = $this->CheckCacheDirectory();
        if ($Result !== NULL)
        {
            return $Result;
        }

        $GLOBALS["AF"]->AddCleanUrl("%^sitemap.xml$%", "P_XmlSitemap_Sitemap");
    }


    /**
    * Set up configuration for the plugin.
    */
    public function SetUpConfigOptions()
    {
        # get the metadata schema options
        $SchemaOptions = array();
        foreach (MetadataSchema::GetAllSchemas() as $Schema)
        {
            # we don't want to provide entries for Users
            if ($Schema->Id() != MetadataSchema::SCHEMAID_USER)
            {
                $SchemaOptions[$Schema->Id()] = $Schema->Name() . " Schema";
            }
        }

        $this->CfgSetup["Schemas"] = array(
            "Type" => "Option",
            "Label" => "Schemas to include in sitemap",
            "Help" => "Publicly viewable resources from all selected schemas "
                    ."will be included in the generated XML Sitemap",
            "AllowMultiple" => TRUE,
            "Rows" => count($SchemaOptions),
            "Options" => $SchemaOptions);
    }

    /**
    * Hook the events into the application framework.
    * @return Returns an array of events to be hooked into the application
    *      framework.
    */
    public function HookEvents()
    {
        return array(
            "EVENT_DAILY" => "DailyMaintenance",
            "EVENT_RESOURCE_ADD" => "UpdateResourceTimestamp",
            "EVENT_RESOURCE_MODIFY" => "UpdateResourceTimestamp",
            "EVENT_RESOURCE_DELETE" => "UpdateResourceTimestamp",
            );
    }

    /**
    * Update the timestamp storing the last change to any resource.
    */
    public function UpdateResourceTimestamp()
    {
        $this->ConfigSetting("ResourcesLastModified", time());
    }

    /**
    * Daily maintenance to regenerate the XML sitemap if it has gotten
    * stale.
    */
    public function DailyMaintenance()
    {
        $CacheFile = $this->GetCachePath()."/sitemap.xml";

        # if the cache has gone stale, regenerate it
        if (!file_exists($CacheFile) || (filemtime($CacheFile) <
                $this->ConfigSetting("ResourceLastModified")))
        {
            file_put_contents($CacheFile, $this->GenerateSitemap());
        }
    }

    /**
    * Fetch the XML sitemap.
    * @return string xml sitemap content
    */
    public function GetSitemap()
    {
        $CacheFile = $this->GetCachePath()."/sitemap.xml";

        # if we have a cached sitemap file, just return that
        #  otherwise, generate one in the forgeround
        if (!file_exists($CacheFile))
        {
            $Xml = $this->GenerateSitemap();
            file_put_contents($CacheFile, $Xml);
        }
        else
        {
            $Xml = file_get_contents($CacheFile);
        }

        return $Xml;
    }

    /**
    * Get the path of the cache directory.
    * @return Returns the path of the cache directory.
    */
    private function GetCachePath()
    {
        return getcwd() . "/local/data/caches/XmlSitemap";
    }

    /**
    * Make sure the cache directories exist and are usable, creating them if
    * necessary.
    * @return Returns a string if there's an error and NULL otherwise.
    */
    private function CheckCacheDirectory()
    {
        $CachePath = $this->GetCachePath();

        # the cache directory doesn't exist, try to create it
        if (!file_exists($CachePath))
        {
            $Result = @mkdir($CachePath, 0777, TRUE);

            if (FALSE === $Result)
            {
                return $CachePath." could not be created.";
            }
        }

        # exists, but is not a directory
        if (!is_dir($CachePath))
        {
            return $CachePath." is not a directory.";
        }

        # exists and is a directory, but is not writeable
        if (!is_writeable($CachePath))
        {
            return $CachePath." is not writeable.";
        }

        return NULL;
    }

    /**
    * Genreate XML for the sitemap.
    * @return string xml sitemap.
    */
    private function GenerateSitemap()
    {
        # increase memory limit
        ini_set("memory_limit",
                $this->ConfigSetting("MemLimitForUpdate")."M");

        global $AF;

        $MemLimit = $AF->GetPhpMemoryLimit();

        # if there was an APUserId, remove it so that creating a new user
        #  doesn't automagically pull that in
        if (isset($_SESSION["APUserId"]))
        {
            $OldUserId = $_SESSION["APUserId"];
            unset ($_SESSION["APUserId"]);
        }

        # set up an anon user
        $AnonUser = new CWUser();

        # compute our URL prefix
        $UrlPrefix = $AF->RootUrl().$AF->BasePath();

        # generate start tags for the sitemap
        $Xml = '<?xml version="1.0" encoding="UTF-8"?>'."\n"
            .'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'."\n";

        # iterate over all the scheams that we're including
        foreach ($this->ConfigSetting("Schemas") as $SCId)
        {
            $Schema = new MetadataSchema($SCId);

            # grab a Resource Factory for this schema
            $RFactory = new ResourceFactory($SCId);

            # and ask it for a list of viewable resources
            $ViewableIds = $RFactory->FilterNonViewableResources(
                $RFactory->GetItemIds(), $AnonUser);

            # iterate over viewable resources
            foreach ($ViewableIds as $Id)
            {
                # compute the view page path
                $PagePath = str_replace('$ID', $Id, $Schema->ViewPage() );
                if ($PagePath[0] == "?")
                {
                    $PagePath = "index.php".$PagePath;
                }

                # append this element to the sitemap
                $Xml.="<url><loc>".defaulthtmlentities(
                        $UrlPrefix
                        .$AF->GetCleanUrlForPath($PagePath))
                        ."</loc></url>\n";
            }
        }

        # end the sitemap
        $Xml .= "</urlset>";

        # if there was a user stuffed in the _SESSION, restore it
        if (isset($OldUserId))
        {
            $_SESSION["APUserId"] = $OldUserId;
        }

        return $Xml;
    }
}
