<?PHP

/**
 * CWIS plugin that provides foldering functionality for resources.
 */
class Folders extends Plugin
{

    /**
     * Register information about this plugin.
     * @return void
     */
    public function Register()
    {
        $this->Name = "Folders";
        $this->Version = "1.0.4";
        $this->Description = "Functionality for adding resources into folders.";
        $this->Author = "Internet Scout";
        $this->Url = "http://scout.wisc.edu/cwis/";
        $this->Email = "scout@scout.wisc.edu";
        $this->Requires = array("CWISCore" => "2.3.1");
        $this->EnabledByDefault = FALSE;

        $this->CfgSetup["NumDisplayedResources"] = array(
            "Type" => "Number",
            "Label" => "Resource to Display in Sidebar",
            "Help" => "The number of resources to display in the sidebar for "
                     ."the selected folder",
            "MaxVal" => 20);
    }

    /**
    * Startup initialization for plugin.
    * @return NULL if initialization was successful, otherwise a string containing
    *       an error message indicating why initialization failed.
    */
    public function Initialize()
    {
        # set up clean URL mapping for folders (folders/folder_id/normalized_folder_name)
        $GLOBALS["AF"]->AddCleanUrl(
            "%^folders/0*([1-9][0-9]*)%",
            "P_Folders_ViewFolder",
            array("FolderId" => "\$1"),
            array($this, "CleanUrlTemplate"));

        # report success
        return NULL;
    }

    /**
     * Create the database tables necessary to use this plugin.
     * @return NULL on success or an error message otherwise
     */
    public function Install()
    {
        $Database = new Database();

        # selected folders table
        if (FALSE === $Database->Query("
            CREATE TABLE IF NOT EXISTS Folders_SelectedFolders (
                OwnerId      INT,
                FolderId     INT,
                PRIMARY KEY  (OwnerId)
            );"))
        { return "Could not create the selected folders table"; }

        $this->ConfigSetting("NumDisplayedResources", 5);
    }

    /**
     * Uninstall the plugin.
     * @return NULL|string NULL if successful or an error message otherwise
     */
    public function Uninstall()
    {
        $Database = new Database();

        # selected folders table
        if (FALSE === $Database->Query("DROP TABLE Folders_SelectedFolders;"))
        { return "Could not remove the selected folders table"; }
    }

    /**
     * Declare the events this plugin provides to the application framework.
     * @return array the events this plugin provides
     */
    public function DeclareEvents()
    {
        return array(
            "FOLDERS_GET_SELECTED_FOLDER" => ApplicationFramework::EVENTTYPE_FIRST,
            "FOLDERS_RESOURCE_IN_SELECTED_FOLDER" => ApplicationFramework::EVENTTYPE_FIRST);
    }

    /**
     * Hook the events into the application framework.
     * @return array the events to be hooked into the application framework
     */
    public function HookEvents()
    {
        $Events = array(
            "EVENT_IN_HTML_HEADER" => "InHtmlHeader",
            "EVENT_REQUEST_SIDEBAR_CONTENT" => "RequestSidebarContent",
            "FOLDERS_GET_SELECTED_FOLDER" => "GetSelectedFolder",
            "FOLDERS_RESOURCE_IN_SELECTED_FOLDER" => "ResourceInSelectedFolder");

        # add hooks for the Database Clean Up plugin if it's enabled
        if ($GLOBALS["G_PluginManager"]->PluginEnabled("DBCleanUp"))
        {
            $Events["DBCleanUp_EXTERNAL_CLEAN"] = "DatabaseCleanUp";
        }

        # add hooks for the Account Pruner plugin if it's enabled
        if ($GLOBALS["G_PluginManager"]->PluginEnabled("AccountPruner"))
        {
            $Events["AccountPruner_EVENT_DO_NOT_PRUNE_USER"] = "PreventAccountPruning";
        }

        return $Events;
    }

    /**
     * Print HTML intended for the document head element.
     * @return void
     */
    public function InHtmlHeader()
    {
        $GLOBALS["AF"]->RequireUIFile("CW-Popup.js");
        $GLOBALS["AF"]->RequireUIFile("jquery-ui.js");
?>
        <link rel="stylesheet" type="text/css" href="plugins/Folders/interface/default/include/folders.css" />
        <script type="text/javascript" src="plugins/Folders/interface/default/include/Folders.js"></script>
        <script type="text/javascript" src="plugins/Folders/interface/default/include/main.js"></script>
<?PHP
    }

    /**
     * Return a HTML list of resources in the currently selected folder.
     * @return string HTML content
     */
    public function RequestSidebarContent()
    {
        global $User;

        # do not return content if the user is not logged in
        if (!$User->IsLoggedIn())
        {
            return NULL;
        }

        $SelectedFolder = $this->GetSelectedFolder($User);
        $Name = $SelectedFolder->Name();
        $ItemIds = $SelectedFolder->GetItemIds();
        $ItemCount = count($ItemIds);
        $HasResources = $ItemCount > 0;
        $NumDisplayedResources = $this->ConfigSetting("NumDisplayedResources");
        $HasMoreResources = $ItemCount > $NumDisplayedResources;
        $ResourceInfo = array();

        foreach ($ItemIds as $ResourceId) {
            $Resource = new Resource($ResourceId);
            $Title = Folders_Common::GetSafeResourceTitle($Resource);

            $ResourceInfo[$ResourceId] = array(
                "ResourceId" => defaulthtmlentities($Resource->Id()),
                "ResourceTitle" => NeatlyTruncateString($Title, 24));

            # only display the first $NumDisplayedResources
            if (count($ResourceInfo) >= $NumDisplayedResources)
            {
                break;
            }
        }

        $SafeFolderId = defaulthtmlentities($SelectedFolder->Id());
        $SafeName = defaulthtmlentities(NeatlyTruncateString($Name, 18));

        ob_start();
?>
        <!-- BEGIN FOLDERS DISPLAY -->
        <div class="cw-section cw-section-simple cw-html5-section folders-sidebar">
          <div class="cw-section-header cw-html5-header">
            <a href="index.php?P=P_Folders_ViewFolder&amp;FolderId=<?PHP print $SafeFolderId; ?>">
              <?PHP if ($Name) { ?>
                <b><?PHP print $SafeName; ?></b>
              <?PHP } else { ?>
                <b>Current Folder</b>
              <?PHP } ?>
            </a>
            <a class="cw-button cw-button-constrained cw-button-elegant" href="index.php?P=P_Folders_ManageFolders">Manage</a>
          </div>
          <div class="cw-section-body">
            <?PHP if ($HasResources) { ?>
              <ul class="cw-list cw-list-noindent cw-list-dotlist">
                <?PHP foreach ($ResourceInfo as $Info) { extract($Info); ?>
                  <li>
                    <a href="index.php?P=FullRecord&amp;ID=<?PHP print $ResourceId; ?>">
                      <?PHP print $ResourceTitle; ?>
                    </a>
                  </li>
                <?PHP } ?>
              </ul>
              <?PHP if ($HasMoreResources) { ?>
                <a class="folders-seeall" href="index.php?P=P_Folders_ViewFolder&amp;FolderId=<?PHP print $SafeFolderId; ?>">See all &rarr;</a>
              <?PHP } ?>
            <?PHP } else { ?>
              <p class="folders-noitems">There are no resources in this folder.</p>
            <?PHP } ?>
          </div>
        </div>
        <!-- END FOLDERS DISPLAY -->
<?PHP
        $Content = ob_get_contents();
        ob_end_clean();

        return $Content;
    }

    /**
    * Perform database cleanup operations when signaled by the DBCleanUp plugin.
    */
    public function DatabaseCleanUp()
    {
        $Database = new Database();

        # remove folder items in folders that no longer exist
        $Database->Query("
            DELETE FII FROM FolderItemInts FII
            LEFT JOIN Folders F ON FII.FolderId = F.FolderId
            WHERE F.FolderId IS NULL");

        # remove selected folders for folders that no longer exist
        $Database->Query("
            DELETE FSF FROM Folders_SelectedFolders FSF
            LEFT JOIN Folders F ON FSF.FolderId = F.FolderId
            WHERE F.FolderId IS NULL");

        # find folder items for resources that no longer exist
        $Database->Query("
            SELECT FII.FolderId, FII.ItemId AS ResourceId FROM FolderItemInts FII
            LEFT JOIN Folders F ON FII.FolderId = F.FolderId
            LEFT JOIN Resources R ON FII.ItemId = R.ResourceId
            WHERE F.ContentType = '".intval(Folder::GetItemTypeId("Resource"))."'
            AND R.ResourceId IS NULL");

        # remove the resources from the folders they belong to
        while (FALSE !== ($Row = $Database->FetchRow()))
        {
            $Folder = new Folder($Row["FolderId"]);
            $ResourceId = $Row["ResourceId"];

            # mixed item type folder
            if ($Folder->ContainsItem($ResourceId, "Resource"))
            {
                $Folder->RemoveItem($ResourceId, "Resource");
            }

            # single item type folder
            else
            {
                $Folder->RemoveItem($ResourceId);
            }
        }
    }

    /**
     * Get the selected folder for the given owner.
     * @param mixed User object, user ID, or NULL to use the global user object
     * @return Folder|null selected folder or NULL if it can't be retrieved
     */
    public function GetSelectedFolder($Owner=NULL)
    {
        global $User;

        # look for passed in user object, user ID, and then the global user
        # object
        $IsLoggedIn = $User->IsLoggedIn();
        $Owner = $Owner instanceof User ? $Owner->Id() : $Owner;
        $Owner = !$Owner && $IsLoggedIn ? $User->Id() : $Owner;

        $FolderFactory = new Folders_FolderFactory($Owner);

        try
        {
            $SelectedFolder = $FolderFactory->GetSelectedFolder();
        }

        catch (Exception $Exception)
        {
            return NULL;
        }

        return $SelectedFolder;
    }

    /**
     * Get the resource folder for the given owner.
     * @param mixed User object, user ID, or NULL to use the global user object
     * @return Folder|null resource folder that contains folders of resources or
     *   NULL if it can't be retrieved
     */
    public function GetResourceFolder($Owner=NULL)
    {
        global $User;

        # look for passed in user object, user ID, and then the global user
        # object
        $IsLoggedIn = $User->IsLoggedIn();
        $Owner = $Owner instanceof User ? $Owner->Id() : $Owner;
        $Owner = !$Owner && $IsLoggedIn ? $User->Id() : $Owner;

        $FolderFactory = new Folders_FolderFactory($Owner);

        try
        {
            $ResourceFolder = $FolderFactory->GetResourceFolder();
        }

        catch (Exception $Exception)
        {
            return NULL;
        }

        return $ResourceFolder;
    }

    /**
     * Determine whether the given resource is in the currently selected folder.
     * @param Resource $Resource resource
     * @param User|integer User object or user ID
     * @return boolean TRUE if the resource is in the folder, FALSE otherwise
     */
    public function ResourceInSelectedFolder(Resource $Resource, $Owner=NULL)
    {
        return $this->GetSelectedFolder($Owner)->ContainsItem($Resource->Id());
    }

    /**
    * Prevent accounts of users from removal if they meet one of the following
    * conditions:
    * @li The user has created a new folder or modified the
    *      automatically-created one.
    * @li The user has added at least one resource to at least one folder.
    * @param int $UserId User identifier.
    * @return Returns TRUE if the user account shouldn't be removed and FALSE if
    *      the account can be removed as far as the Folders plugin is concerned.
    */
    public function PreventAccountPruning($UserId)
    {
        $UserId = intval($UserId);
        $Database = new Database();

        # query for the number of folders for the user that are not the
        # automatically-created folder or are that folder but it has been
        # changed
        $NumFolders = $Database->Query("
            SELECT COUNT(*) AS NumFolders
            FROM Folders
            WHERE OwnerId = '".addslashes($UserId)."'
            AND FolderName != 'ResourceFolderRoot'
            AND FolderName != 'Main Folder'", "NumFolders");

        # the user has manually created a folder or has modified the name of the
        # automatically-created one and shouldn't be removed
        if ($NumFolders > 0)
        {
            return TRUE;
        }

        $ResourceItemTypeId = Folder::GetItemTypeId("Resource");

        # query for the number of resources the user has put into folders
        $NumResources = $Database->Query("
            SELECT COUNT(*) AS NumResources
            FROM Folders F
            LEFT JOIN FolderItemInts FII on F.FolderId = FII.Folderid
            WHERE FII.FolderId IS NOT NULL
            AND F.OwnerId = '".addslashes($UserId)."'
            AND F.ContentType = '".addslashes($ResourceItemTypeId)."'", "NumResources");

        # the user has put at least one resource into a folder and shouldn't be
        # removed
        if ($NumResources > 0)
        {
            return TRUE;
        }

        # don't determine whether or not the user should be removed
        return FALSE;
    }

    /**
    * Callback for constructing clean URLs to be inserted by the application
    * framework when more than regular expression replacement is required.
    * This method is passed to ApplicationFramework::AddCleanURL().
    * @param array $Matches Array of matches from preg_replace().
    * @param string $Pattern Original pattern for clean URL mapping.
    * @param string $Page Page name for clean URL mapping.
    * @param string $SearchPattern Full pattern passed to preg_replace().
    * @return string Replacement to be inserted in place of match.
    */
    public function CleanUrlTemplate($Matches, $Pattern, $Page, $SearchPattern)
    {
        if ($Page == "P_Folders_ViewFolder")
        {
            # if no ID found
            if (count($Matches) <= 2)
            {
                # return match unchanged
                return $Matches[0];
            }

            # get the URL for the folder
            $Url = Folders_Common::GetShareUrl(new Folders_Folder($Matches[2]));

            return "href=\"".defaulthtmlentities($Url)."\"";
        }

        # return match unchanged
        return $Matches[0];
    }

}
