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

/**
* Free-form page plugin.
*/
class Pages extends Plugin
{

    # ---- STANDARD PLUGIN METHODS -------------------------------------------

    /**
    * Set the plugin attributes.  At minimum this method MUST set $this->Name
    * and $this->Version.  This is called when the plugin is initially loaded.
    */
    public function Register()
    {
        $this->Name = "Pages";
        $this->Version = "2.0.2";
        $this->Description = "Allows the creation and editing of additional"
                ." web pages with free-form HTML content";
        $this->Author = "Internet Scout";
        $this->Url = "http://scout.wisc.edu/cwis/";
        $this->Email = "scout@scout.wisc.edu";
        $this->Requires = array(
                "CWISCore" => "3.1.0");
        $this->EnabledByDefault = TRUE;

        $this->CfgSetup["MaxImageWidth"] = array(
                "Type" => "Number",
                "Label" => "Maximum Image Width",
                "Help" => "Maximum width of images inserted into pages"
                        ." via the editing interface.  Images wider than"
                        ." this will be resized down to this width"
                        ." (maintaining the aspect ratio).",
                "Default" => 400,
                "MinVal" => 50,
                "MaxVal" => 2000,
                );
        $this->CfgSetup["MaxImageHeight"] = array(
                "Type" => "Number",
                "Label" => "Maximum Image Height",
                "Help" => "Maximum height of images inserted into pages"
                        ." via the editing interface.  Images taller than"
                        ." this will be resized down to this height"
                        ." (maintaining the aspect ratio).",
                "Default" => 400,
                "MinVal" => 50,
                "MaxVal" => 2000,
                );
        $this->CfgSetup["SummaryLength"] = array(
                "Type" => "Number",
                "Label" => "Summary Length",
                "Help" => "Target length (in characters) for page summaries,"
                        ." displayed in search results.",
                "Default" => 400,
                "MinVal" => 10,
                "MaxVal" => 2000,
                );
    }

    /**
    * Initialize the plugin.  This is called after all plugins have been loaded
    * but before any methods for this plugin (other than Register() or Initialize())
    * have been called.
    * @return mixed NULL if initialization was successful, otherwise a string
    *       containing an error message indicating why initialization failed.
    */
    public function Initialize()
    {
        $this->DB = new Database();

        # give page factory our metadata schema ID
        Pages_PageFactory::$SchemaId = $this->ConfigSetting("MetadataSchemaId");

        # set up clean URL mappings for current pages
        $PFactory = new Pages_PageFactory();
        $CleanUrls = $PFactory->GetCleanUrls();
        foreach ($CleanUrls as $PageId => $Url)
        {
            if (strlen($Url))
            {
                $GLOBALS["AF"]->AddCleanUrl("%^".$Url."$%",
                        "P_Pages_DisplayPage",
                        array("ID" => $PageId),
                        $Url);
            }
        }

        # set up clean URL mappings for tabs within current pages
        foreach ($CleanUrls as $PageId => $Url)
        {
            if (strlen($Url))
            {
                $GLOBALS["AF"]->AddCleanUrl("%^".$Url."/([a-zA-Z0-9]+)$%",
                        "P_Pages_DisplayPage",
                        array("ID" => $PageId, "AT" => "\$1"),
                        $Url."/\$AT");
            }
        }

        # set up standard metadata field mappings for our fields
        $Schema = new MetadataSchema($this->ConfigSetting("MetadataSchemaId"));
        $Schema->StdNameToFieldMapping("Title",
                $Schema->GetFieldIdByName("Title"));
        $Schema->StdNameToFieldMapping("Description",
                $Schema->GetFieldIdByName("Summary"));
        $Schema->StdNameToFieldMapping("File",
                $Schema->GetFieldIdByName("Files"));

        # report successful initialization
        return NULL;
    }

    /**
    * Perform any work needed when the plugin is first installed (for example,
    * creating database tables).
    * @return NULL if installation succeeded, otherwise a string containing
    *       an error message indicating why installation failed.
    */
    public function Install()
    {
        # set up metadata schema
        $Result = $this->SetUpSchema();
        if ($Result !== NULL) {  return $Result;  }

        # set up database tables
        $DB = new Database();
        $Result = $DB->Query(
                "CREATE TABLE IF NOT EXISTS Pages_Privileges (
                    PageId              INT NOT NULL,
                    ViewingPrivileges   BLOB,
                    INDEX       (PageId))");
        if ($Result === FALSE) {  return "Database setup failed.";  }

        # set the AbbreviatedName for Pages schema
        $Schema = new MetadataSchema($this->ConfigSetting("MetadataSchemaId"));
        $Schema->AbbreviatedName("W");

        # report success to caller
        return NULL;
    }

    /**
    * Upgrade this plugin from a previous version.
    * @param string $PreviousVersion String contining previous version number.
    * @return null|string NULL if successful or error message string if not.
    */
    public function Upgrade($PreviousVersion)
    {
        # ugprade from versions < 1.0.1 to 1.0.1
        if (version_compare($PreviousVersion, "1.0.1", "<"))
        {
            $PrivilegeToAuthor = $this->ConfigSetting("PrivilegeToAuthor");
            $PrivilegeToEdit = $this->ConfigSetting("PrivilegeToEdit");

            # if authoring privilege is an array because it hasn't been edited
            # since the plugin was installed
            if (is_array($PrivilegeToAuthor))
            {
                $this->ConfigSetting("PrivilegeToAuthor",
                        array_shift($PrivilegeToAuthor));
            }

            # if editing privilege is an array because it hasn't been edited
            # since the plugin was installed
            if (is_array($PrivilegeToEdit))
            {
                $this->ConfigSetting("PrivilegeToEdit", array_shift($PrivilegeToEdit));
            }
        }

        # ugprade from versions < 1.0.2 to 1.0.2
        if (version_compare($PreviousVersion, "1.0.2", "<"))
        {
            # add clean URL column to database
            $Result = $this->DB->Query("ALTER TABLE Pages_Pages"
                    ." ADD COLUMN CleanUrl TEXT");
            if ($Result === FALSE) {  return "Upgrade failed adding"
                    ." \"CleanUrl\" column to database.";  }
        }

        # upgrade from versions < 1.0.5 to 1.0.5
        if (version_compare($PreviousVersion, "1.0.5", "<"))
        {
            # convert content column in database to larger type
            $Result = $this->DB->Query("ALTER TABLE Pages_Pages"
                    ." MODIFY COLUMN PageContent MEDIUMTEXT");
            if ($Result === FALSE) {  return "Upgrade failed converting"
                    ." \"PageContent\" column to MEDIUMTEXT.";  }
        }

        # upgrade from versions < 2.0.0 to 2.0.0
        if (version_compare($PreviousVersion, "2.0.0", "<"))
        {
            # switch to metadata-schema-based storage of pages
            $Result = $this->UpgradeToMetadataPageStorage();
            if ($Result !== NULL) {  return $Result;  }
        }

        # upgrade from versions < 2.0.1 to 2.0.1
        if (version_compare($PreviousVersion, "2.0.1", "<"))
        {
            # set the AbbreviatedName for Pages schema
            $Schema = new MetadataSchema($this->ConfigSetting("MetadataSchemaId"));
            $Schema->AbbreviatedName("W");
        }

        # upgrade from versions < 2.0.2 to 2.0.2
        if (version_compare($PreviousVersion, "2.0.2", "<"))
        {
            # add Summary metadata field
            $Schema = new MetadataSchema($this->ConfigSetting("MetadataSchemaId"));
            if ($Schema->AddFieldsFromXmlFile(
                    "plugins/".__CLASS__."/install/MetadataSchema--"
                            .__CLASS__.".xml", $this->Name) == FALSE)
            {
                return "Error Loading Metadata Fields from XML: ".implode(
                        " ", $Schema->ErrorMessages("AddFieldsFromXmlFile"));
            }

            # populate Summary field for all pages
            Pages_PageFactory::$SchemaId = $this->ConfigSetting("MetadataSchemaId");
            $PFactory = new Pages_PageFactory();
            $Ids = $PFactory->GetItemIds();
            foreach ($Ids as $Id)
            {
                $Page = new Pages_Page($Id);
                $Page->Set("Summary", $Page->GetSummary());
            }
        }

        # report success to caller
        return NULL;
    }

    /**
    * Perform any work needed when the plugin is uninstalled.
    * @return NULL if uninstall succeeded, otherwise a string containing
    *       an error message indicating why uninstall failed.
    */
    public function Uninstall()
    {
        # delete all pages
        $PFactory = new Pages_PageFactory();
        $Ids = $PFactory->GetItemIds();
        foreach ($Ids as $Id)
        {
            $Page = new Pages_Page($Id);
            $Page->Delete();
        }

        # delete our metadata schema
        $Schema = new MetadataSchema($this->ConfigSetting("MetadataSchemaId"));
        $Schema->Delete();

        # remove privileges table from database
        $this->DB->Query("DROP TABLE Pages_Privileges");
        return NULL;
    }

    /**
    * Hook the events into the application framework.
    * @return Returns an array of events to be hooked into the application
    *      framework.
    */
    public function HookEvents()
    {
        $Events = array(
                "EVENT_MODIFY_SECONDARY_NAV" => "AddMenuOptions",
                "EVENT_PLUGIN_CONFIG_CHANGE" => "PluginConfigChange",
                );
        if ($this->ConfigSetting("PrivilegeToEdit") == PRIV_SYSADMIN)
        {
            $Events["EVENT_SYSTEM_ADMINISTRATION_MENU"] = "AddAdminMenuItems";
        }
        elseif ($this->ConfigSetting("PrivilegeToEdit") == PRIV_USERADMIN)
        {
            $Events["EVENT_USER_ADMINISTRATION_MENU"] = "AddAdminMenuItems";
        }
        elseif ($this->ConfigSetting("PrivilegeToEdit") == PRIV_COLLECTIONADMIN)
        {
            $Events["EVENT_COLLECTION_ADMINISTRATION_MENU"] = "AddAdminMenuItems";
        }
        return $Events;
    }


    # ---- HOOKED METHODS ----------------------------------------------------

    /**
    * Add options to administration menu.
    * @return array Array of menu items to add.
    */
    public function AddAdminMenuItems()
    {
        return array(
                "index.php?P=P_Pages_EditPage&amp;ID=NEW" => "Add New Page",
                "index.php?P=P_Pages_ListPages" => "List Pages",
                );
    }

    /**
    * Add options to secondary navigation menu.
    * @param array $NavItems Existing array of menu items.
    * @return array Modified array of menu items.
    */
    public function AddMenuOptions($NavItems)
    {
        # build up list of nav items to add
        $Schema = new MetadataSchema($this->ConfigSetting("MetadataSchemaId"));
        if ($Schema->UserCanAuthor($GLOBALS["G_User"])
                || $Schema->UserCanEdit($GLOBALS["G_User"]))
        {
            if ($Schema->UserCanAuthor($GLOBALS["G_User"]))
            {
                $MyNavItems["Add New Page"] =
                        "index.php?P=P_Pages_EditPage&amp;ID=NEW";
            }
            $MyNavItems["List Pages"] = "index.php?P=P_Pages_ListPages";
        }

        # if there are nav items to add
        if (isset($MyNavItems))
        {
            # step through options to add new entries at most desirable spot
            $NewNavItems = array();
            $NeedToAddMyOptions = TRUE;
            foreach ($NavItems as $Label => $Link)
            {
                if ($NeedToAddMyOptions &&
                        (($Label == "Administration") || ($Label == "Log Out")))
                {
                    $NewNavItems = $NewNavItems + $MyNavItems;
                    $NeedToAddMyOptions = FALSE;
                }
                $NewNavItems[$Label] = $Link;
            }
            if ($NeedToAddMyOptions) {  $NewNavItems = $NewNavItems + $MyNavItems;  }
        }
        else
        {
            # return nav item list unchanged
            $NewNavItems = $NavItems;
        }

        # return new list of nav options to caller
        return array("NavItems" => $NewNavItems);
    }

    /**
    * Handle plugin configuration changes.
    * @param string $PluginName Name of the plugin that has changed.
    * @param string $ConfigSetting Name of the setting that has change.
    * @param mixed $OldValue The old value of the setting.
    * @param mixed $NewValue The new value of the setting.
    */
    public function PluginConfigChange($PluginName, $ConfigSetting, $OldValue, $NewValue)
    {
        # only worried about changes to our settings
        if ($PluginName != __CLASS__) {  return;  }

        # update image field settings if they've changed
        $SchemaId = $this->ConfigSetting("MetadataSchemaId");
        $Schema = new MetadataSchema($SchemaId);
        $ImageField = $Schema->GetFieldByName("Images");
        if ($ConfigSetting == "MaxImageWidth")
        {
            $ImageField->MaxPreviewWidth($NewValue);
        }
        if ($ConfigSetting == "MaxImageHeight")
        {
            $ImageField->MaxPreviewHeight($NewValue);
        }

        # regenerate all page summaries if summary length has changed
        if ($ConfigSetting == "SummaryLength")
        {
            Pages_PageFactory::$SchemaId = $this->ConfigSetting("MetadataSchemaId");
            $PFactory = new Pages_PageFactory();
            $Ids = $PFactory->GetItemIds();
            foreach ($Ids as $Id)
            {
                $Page = new Pages_Page($Id);
                $Page->Set("Summary", $Page->GetSummary($NewValue));
            }
        }
    }


    # ---- CALLABLE METHODS --------------------------------------------------

    # (no externally-callable methods)


    # ---- PRIVATE METHODS ---------------------------------------------------

    /**
    * Set up our metadata schema.
    * @return NULL upon success, or error string upon failure.
    */
    private function SetUpSchema()
    {
        # setup the default privileges for authoring and editing
        $AuthorPrivs = new PrivilegeSet();
        $AuthorPrivs->AddPrivilege(PRIV_SYSADMIN);
        $EditPrivs = new PrivilegeSet();
        $EditPrivs->AddPrivilege(PRIV_SYSADMIN);

        # create a new metadata schema and save its ID
        $Schema = MetadataSchema::Create(
            "Pages",
            $AuthorPrivs,
            $EditPrivs,
            NULL,
            "index.php?P=P_Pages_DisplayPage&ID=\$ID");
        $this->ConfigSetting("MetadataSchemaId", $Schema->Id());
        Pages_PageFactory::$SchemaId = $Schema->Id();

        # load fields into schema
        if ($Schema->AddFieldsFromXmlFile(
                "plugins/".__CLASS__."/install/MetadataSchema--"
                        .__CLASS__.".xml", $this->Name) == FALSE)
        {
            return "Error Loading Metadata Fields from XML: ".implode(
                    " ", $Schema->ErrorMessages("AddFieldsFromXmlFile"));
        }

        # report success
        return NULL;
    }

    /**
    * Migrate data from database-based format to metadata-schema-based format.
    * @return null|string NULL upon success, or error string upon failure.
    */
    private function UpgradeToMetadataPageStorage()
    {
        # bail out if conversion has already been done or is under way
        if (!$this->DB->TableExists("Pages_Pages")
                || !$this->DB->Query("RENAME TABLE Pages_Pages"
                        ." TO Pages_Pages_OLD"))
        {
            return NULL;
        }

        # set up metadata schema
        $Result = $this->SetUpSchema();
        if ($Result !== NULL) {  return $Result;  }

        # load old privilege information
        $this->DB->Query("SELECT * FROM Pages_Privileges");
        $OldPrivs = array();
        while ($Row = $this->DB->FetchRow())
        {
            $OldPrivs[$Row["PageId"]][] = $Row["Privilege"];
        }

        # create new privileges table
        $this->DB->Query("DROP TABLE Pages_Privileges");
        $this->DB->Query(
                "CREATE TABLE IF NOT EXISTS Pages_Privileges (
                    PageId              INT NOT NULL,
                    ViewingPrivileges   BLOB,
                    INDEX       (PageId))");

        # for each page in database
        $this->DB->Query("SELECT * FROM Pages_Pages_OLD");
        while ($Row = $this->DB->FetchRow())
        {
            # add new record for page
            $Page = Pages_Page::Create();

            # transfer page values
            $Page->Set("Title", $Row["PageTitle"]);
            $Page->Set("Content", $Row["PageContent"]);
            $Page->Set("Clean URL", $Row["CleanUrl"]);
            $Page->Set("Creation Date", $Row["CreatedOn"]);
            $Page->Set("Added By Id", $Row["AuthorId"]);
            $Page->Set("Date Last Modified", $Row["UpdatedOn"]);
            $Page->Set("Last Modified By Id", $Row["EditorId"]);

            # set viewing privileges
            $PrivSet = new PrivilegeSet();
            if (isset($OldPrivs[$Row["PageId"]]))
            {
                $PrivSet->AddPrivilege($OldPrivs[$Row["PageId"]]);
            }
            $Page->ViewingPrivileges($PrivSet);

            # make page permanent
            $Page->IsTempResource(FALSE);
        }

        # drop content table from database
        $this->DB->Query("DROP TABLE IF EXISTS Pages_Pages_OLD");

        # report success to caller
        return NULL;
    }

    private $DB;
}
