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

/**
* Class to provide a user interface for displaying a list of items.
*/
class ItemListUI
{

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

    /**
    * Constructor for item list UI class.  Possible values in the $Fields
    * parameter:
    *   AllowHTML - if defined, values from the field will not have HTML
    *       characters escaped before the value is displayed
    *   DefaultSortField - if defined, will mark field as being the default
    *       sort field
    *   DefaultToDescendingSort - if defined, will mark field as defaulting
    *       to descending (as opposed to ascending) sort order
    *   Heading - heading text for field column (if not supplied, the
    *       field name is used),
    *   Link - URL to which to link the field content (if present, $ID will
    *       be replaced with the field ID),
    *   MaxLength - maximum length in characters for the field text,
    *   ValueFunction - callback that accepts an item and field name, and
    *       returns the value to be printed.
    * @param string $Heading Heading text to be printed above list.
    * @param array $Fields Associative array of associative arrays of
    *       information about fields to be displayed, with metadata field IDs
    *       or item method names for the index and possible values as above.
    * @param int $ItemsPerPage Maximum number of items per page.
    * @param string $BaseLink Base URL for current page for any links that
    *       need to be constructed.
    * @param int $SchemaId Metadata schema ID for items.  (OPTIONAL)
    */
    public function __construct($Heading, $Fields, $ItemsPerPage, $BaseLink,
            $SchemaId = TransportControlsUI::NO_ITEM_TYPE)
    {
        # normalize and save field info
        if ($SchemaId == TransportControlsUI::NO_ITEM_TYPE)
        {
            $this->Fields = $Fields;
        }
        else
        {
            $this->Fields = array();
            foreach ($Fields as $FieldId => $FieldInfo)
            {
                $CanonicalId = MetadataSchema::GetCanonicalFieldIdentifier($FieldId);
                $this->Fields[$CanonicalId] = $FieldInfo;
            }
        }

        # save other supplied settings for later use
        $this->BaseLink = $BaseLink;
        $this->Heading = $Heading;
        $this->ItemsPerPage = $ItemsPerPage;
        $this->SchemaId = $SchemaId;
    }

    /**
    * Add "button" above list.
    * @param string $Label Label for button.
    * @param string $Link URL that button should link to.
    * @param string $Icon Name of image file to display before the label.
    *       (OPTIONAL, defaults to no image)
    */
    public function AddTopButton($Label, $Link, $Icon = NULL)
    {
        $this->Buttons[] = array(
                "Label" => $Label,
                "Link" => $Link,
                "Icon" => $Icon);
    }

    /**
    * Add action "button" to each item in list.
    * @param string $Label Label for button.
    * @param string $Link URL that button should link to, with $ID optionally
    *       in the string to indicate where the item ID should be inserted.
    * @param string $Icon Name of image file to display before the label.
    *       (OPTIONAL, defaults to no image)
    * @param callable $DisplayTestFunc Function that is passed the item, and
    *       should return TRUE if the buttons should be displayed for that
    *       item, and FALSE otherwise.  (OPTIONAL, defaults to NULL, which
    *       will call "UserCanEdit()" method on item, if available, with
    *       current active user (G_User))
    * @param array $AdditionalAttributes Additional attributes to add to
    *       the button, with HTML attribute name for the index and attribute
    *       value for the value.  (OPTIONAL)
    */
    public function AddActionButton(
            $Label, $Link, $Icon = NULL, $DisplayTestFunc = NULL,
            $AdditionalAttributes = array())
    {
        $this->Actions[] = array(
                "Label" => $Label,
                "Link" => $Link,
                "Icon" => $Icon,
                "TestFunc" => $DisplayTestFunc,
                "AddAttribs" => $AdditionalAttributes);
    }

    /**
    * Get/set message to display when there are no items to list.
    * @param string $NewValue New message.
    * @return string Current "no items" message.
    */
    public function NoItemsMessage($NewValue = NULL)
    {
        if ($NewValue !== NULL)
        {
            $this->NoItemsMsg = $NewValue;
        }
        return $this->NoItemsMsg;
    }

    /**
    * Print list HTML with specified items.  If an array of items objects is
    * passed in, the item IDs in the array index are only used if the objects
    * do not have an Id() method.
    * @param array $Items Array of item objects or array of associative
    *       arrays, with item IDs for the base index and (if an array of
    *       arrays) field IDs for the associative array indexes.
    * @param int $TotalItemCount Total number of items.  (OPTIONAL, defaults
    *       to number of items passed in)
    * @param int $StartingIndex Current starting index.  (OPTIONAL, defaults
    *       to value retrieved from $GET, or zero if no $GET value)
    * @param string $TransportMsg Text to display on transport control line.
    *       (OPTIONAL, defaults to "Items X - Y of Z")
    */
    public function Display($Items, $TotalItemCount = NULL,
            $StartingIndex = NULL, $TransportMsg = NULL)
    {
        # set up transport controls (doing it early to retrieve sort info)
        foreach ($this->Fields as $FieldId => $FieldInfo)
        {
            if (isset($FieldInfo["DefaultSortField"]))
            {
                TransportControlsUI::DefaultSortField($FieldId);
            }
        }
        $TransportUI = new TransportControlsUI($this->SchemaId, $this->ItemsPerPage);
        $StartingIndex = $TransportUI->StartingIndex($StartingIndex);
        $SortFieldId = $TransportUI->SortField();
        $ReverseSort = $TransportUI->ReverseSortFlag();

        # display buttons above list
        if (count($this->Buttons))
        {
            print '<span style="float: right; padding-top: 20px;">';
            foreach ($this->Buttons as $Info)
            {
                if ($Info["Icon"])
                {
                    $IconFile = $GLOBALS["AF"]->GUIFile($Info["Icon"]);
                    $IconTag = $IconFile
                            ? '<img class="cw-button-icon" src="'
                                    .$IconFile.'" alt=""> '
                            : "";
                    $IconButtonClass = " cw-button-iconed";
                }
                else
                {
                    $IconTag = "";
                    $IconButtonClass = "";
                }
                ?><a class="cw-button cw-button-elegant cw-button-constrained<?=
                        $IconButtonClass ?>"
                        href="<?= $Info["Link"] ?>"><?= $IconTag ?><?=
                        htmlspecialchars($Info["Label"]) ?></a><?PHP
            }
            print "</span>";
        }

        # display heading
        if ($this->Heading !== NULL)
        {
            print "<h1>".$this->Heading."</h1>\n";
        }

        # display "no items" message and exit if no items
        if (count($Items) == 0)
        {
            print "<span class=\"cw-itemlist-empty\">";
            if (strlen($this->NoItemsMsg))
            {
                print $this->NoItemsMsg;
            }
            elseif ($this->SchemaId !== TransportControlsUI::NO_ITEM_TYPE)
            {
                $Schema = new MetadataSchema($this->SchemaId);
                print "(no ".strtolower(StdLib::Pluralize(
                        $Schema->ResourceName()))." to display)";
            }
            else
            {
                print "(no items to display)";
            }
            print "</span>";
            return;
        }

        # begin item table
        print '<table class="cw-table cw-table-sideheaders cw-table-fullsize
                cw-table-padded cw-table-striped">';

        # begin header row
        print "<thead><tr>";

        # for each field
        foreach ($this->Fields as $FieldId => $FieldInfo)
        {
            # if header value supplied
            if (isset($FieldInfo["Heading"]))
            {
                # use supplied value
                $Heading = $FieldInfo["Heading"];
            }
            # else if we can get header from schema
            elseif ($this->SchemaId !== TransportControlsUI::NO_ITEM_TYPE)
            {
                # use name of field (with any leading schema name stripped)
                $Heading = MetadataSchema::GetPrintableFieldName($FieldId);
                $Heading = preg_replace("/.+\: /", "", $Heading);
            }
            else
            {
                $Heading = "(NO HEADER SET)";
            }

            # build sort link
            $SortLink = $this->BaseLink."&amp;SF=".$FieldId
                    .$TransportUI->UrlParameterString(TRUE, array("SF", "RS"));

            # determine current sort direction
            if (isset($FieldInfo["DefaultToDescendingSort"]))
            {
                $SortAscending = $ReverseSort ? TRUE : FALSE;
            }
            else
            {
                $SortAscending = $ReverseSort ? FALSE : TRUE;
            }

            # set sort direction indicator (if any)
            if ($FieldId == $SortFieldId)
            {
                $DirIndicator = ($SortAscending) ? "&uarr;" : "&darr;";
                if (!$ReverseSort)
                {
                    $SortLink .= "&amp;RS=1";
                }
            }
            else
            {
                $DirIndicator = "";
            }

            # print header
            print "<th><a href=\"".$SortLink."\">".$Heading."</a>"
                    .$DirIndicator."</th>\n";
        }

        # add action header if needed
        if (count($this->Actions))
        {
            print "<th>Actions</th>\n";
        }

        # end header row
        print "</tr></thead>\n";

        # for each item
        print "<tbody>\n";
        foreach ($Items as $ItemId => $Item)
        {
            # start row
            print "<tr>\n";

            # for each field
            foreach ($this->Fields as $FieldId => $FieldInfo)
            {
                # if there is value function defined for field
                if (isset($FieldInfo["ValueFunction"]))
                {
                    # call function for value
                    $Value = $FieldInfo["ValueFunction"]($Item, $FieldId);
                }
                else
                {
                    # if item is associative array
                    if (is_array($Item))
                    {
                        # retrieve value for field (if any) from item
                        $Value = isset($Item[$FieldId])
                                ? $Item[$FieldId] : "";
                    }
                    # else if field ID is item method
                    elseif (method_exists($Item, $FieldId))
                    {
                        # get field value via item method
                        $Value = $Item->$FieldId();
                    }
                    else
                    {
                        # get field value from item via Get()
                        $Values = $Item->Get($FieldId);
                        $Value = is_array($Values) ? array_shift($Values) : $Values;
                    }

                    # if max length specified for field
                    if (isset($FieldInfo["MaxLength"]))
                    {
                        $Value = NeatlyTruncateString(
                                $Value, $FieldInfo["MaxLength"]);
                    }

                    # encode any HTML-significant chars in value
                    if (!isset($FieldInfo["AllowHTML"]))
                    {
                        $Value = htmlspecialchars($Value);
                    }
                }

                # get link value (if any)
                if (isset($FieldInfo["Link"]))
                {
                    if (method_exists($Item, "Id"))
                    {
                        $Link = preg_replace('/\$ID/', $Item->Id(),
                                $FieldInfo["Link"]);
                    }
                    else
                    {
                        $Link = preg_replace('/\$ID/', $ItemId,
                                $FieldInfo["Link"]);
                    }
                    $LinkStart = '<a href="'.$Link.'">';
                    $LinkEnd = "</a>";
                }
                else
                {
                    $LinkStart = "";
                    $LinkEnd = "";
                }

                # display cell with value
                print "<td>".$LinkStart.$Value.$LinkEnd."</td>\n";
            }

            # add action buttons
            if (count($this->Actions))
            {
                print "<td>";
                foreach ($this->Actions as $ActionInfo)
                {
                    if ($ActionInfo["TestFunc"] !== NULL)
                    {
                        $DisplayButton = $ActionInfo["TestFunc"]($Item);
                    }
                    elseif (method_exists($Item, "UserCanEdit"))
                    {
                        $DisplayButton = $Item->UserCanEdit($GLOBALS["G_User"]);
                    }
                    else
                    {
                        $DisplayButton = TRUE;
                    }
                    if ($DisplayButton)
                    {
                        $ButtonClasses = "cw-button cw-button-elegant"
                                ." cw-button-constrained";
                        $ExtraAttribs = "";
                        foreach ($ActionInfo["AddAttribs"]
                                as $AttribName => $AttribValue)
                        {
                            $AttribValue = htmlspecialchars($AttribValue);
                            if (strtolower($AttribName) == "class")
                            {
                                $ButtonClasses .= " ".$AttribValue;
                            }
                            else
                            {
                                $ExtraAttribs .= " ".$AttribName
                                        .'="'.$AttribValue.'"';
                            }
                        }
                        if ($ActionInfo["Icon"])
                        {
                            $IconFile = $GLOBALS["AF"]->GUIFile($ActionInfo["Icon"]);
                            $IconTag = $IconFile
                                    ? '<img class="cw-button-icon" src="'
                                            .$IconFile.'" alt=""> '
                                    : "";
                            $ButtonClasses .= " cw-button-iconed";
                        }
                        else
                        {
                            $IconTag = "";
                        }
                        if (method_exists($Item, "Id"))
                        {
                            $Link = preg_replace('/\$ID/', $Item->Id(),
                                    $ActionInfo["Link"]);
                        }
                        else
                        {
                            $Link = preg_replace('/\$ID/', $ItemId,
                                    $ActionInfo["Link"]);
                        }
                        print '<a class="'.$ButtonClasses.'"'.$ExtraAttribs
                                .' href="'.$Link.'">'.$IconTag
                                .htmlspecialchars($ActionInfo["Label"]).'</a>';
                    }
                }
                if ($this->SchemaId !== TransportControlsUI::NO_ITEM_TYPE)
                {
                    $GLOBALS["AF"]->SignalEvent("EVENT_HTML_INSERTION_POINT",
                            array($GLOBALS["AF"]->GetPageName(),
                                    "Resource Summary Buttons", array(
                                                "Resource" => $Item)));
                }
                print "</td>\n";
            }

            # end row
            print "</tr>\n";
        }
        print "</tbody>\n";

        # end item table
        print "</table>\n";

        # if there are more items than are displayed
        if ($TotalItemCount > count($Items))
        {
            # craft transport control message (if not supplied)
            if ($TransportMsg === NULL)
            {
                $ItemsLabel = ($this->SchemaId !== TransportControlsUI::NO_ITEM_TYPE)
                        ? StdLib::Pluralize(
                                $Item->Schema()->ResourceName())
                        : "Items";
                $TransportMsg = $ItemsLabel
                        ." <b>".($TransportUI->StartingIndex() + 1)
                        ."</b> - <b>"
                        .min(($TransportUI->StartingIndex() + $this->ItemsPerPage),
                                $TotalItemCount)
                        ."</b> of <b>".$TotalItemCount."</b>";
            }

            # display transport controls
            $TransportUI->StartingIndex($StartingIndex);
            $TransportUI->ItemCount($TotalItemCount);
            $TransportUI->PrintControls(
                    $this->SchemaId, $this->BaseLink, $TransportMsg);
        }
    }


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

    private $Actions;
    private $BaseLink;
    private $Buttons;
    private $Fields;
    private $Heading;
    private $ItemsPerPage;
    private $NoItemsMsg;
    private $SchemaId;
}
