From 84bbf37daa95d287dec5451ec7e9b2670afa52cc Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Thu, 18 Jun 2015 11:54:02 -0400
Subject: [PATCH] Added formatting classes for hierarchy output.

---
 module/VuFind/config/module.config.php        |   7 +
 .../TreeDataFormatter/AbstractBase.php        | 223 ++++++++++++++++++
 .../Hierarchy/TreeDataFormatter/Json.php      | 107 +++++++++
 .../TreeDataFormatter/PluginManager.php       |  51 ++++
 .../Hierarchy/TreeDataFormatter/Xml.php       | 106 +++++++++
 module/VuFind/src/VuFind/Service/Factory.php  |  13 +
 6 files changed, 507 insertions(+)
 create mode 100644 module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/AbstractBase.php
 create mode 100644 module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Json.php
 create mode 100644 module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/PluginManager.php
 create mode 100644 module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Xml.php

diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 7ac922c378c..113ab49fb91 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -147,6 +147,7 @@ $config = [
             'VuFind\DbTablePluginManager' => 'VuFind\Service\Factory::getDbTablePluginManager',
             'VuFind\Export' => 'VuFind\Service\Factory::getExport',
             'VuFind\HierarchyDriverPluginManager' => 'VuFind\Service\Factory::getHierarchyDriverPluginManager',
+            'VuFind\HierarchyTreeDataFormatterPluginManager' => 'VuFind\Service\Factory::getHierarchyTreeDataFormatterPluginManager',
             'VuFind\HierarchyTreeDataSourcePluginManager' => 'VuFind\Service\Factory::getHierarchyTreeDataSourcePluginManager',
             'VuFind\HierarchyTreeRendererPluginManager' => 'VuFind\Service\Factory::getHierarchyTreeRendererPluginManager',
             'VuFind\Http' => 'VuFind\Service\Factory::getHttp',
@@ -350,6 +351,12 @@ $config = [
                     'flat' => 'VuFind\Hierarchy\Driver\Factory::getHierarchyFlat',
                 ],
             ],
+            'hierarchy_treedataformatter' => [
+                'invokables' => [
+                    'json' => 'VuFind\Hierarchy\TreeDataFormatter\Json',
+                    'xml' => 'VuFind\Hierarchy\TreeDataFormatter\Xml',
+                ],
+            ],
             'hierarchy_treedatasource' => [
                 'factories' => [
                     'solr' => 'VuFind\Hierarchy\TreeDataSource\Factory::getSolr',
diff --git a/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/AbstractBase.php b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/AbstractBase.php
new file mode 100644
index 00000000000..29702ee8470
--- /dev/null
+++ b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/AbstractBase.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * Hierarchy Tree Data Formatter (abstract base)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2015.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataFormatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+namespace VuFind\Hierarchy\TreeDataFormatter;
+
+/**
+ * Hierarchy Tree Data Formatter (abstract base)
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataFormatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+abstract class AbstractBase
+{
+    /**
+     * Top-level record from index
+     *
+     * @var object
+     */
+    protected $topNode;
+
+    /**
+     * Child data map from index
+     *
+     * @var array
+     */
+    protected $childMap;
+
+    /**
+     * Is sorting enabled?
+     *
+     * @var bool
+     */
+    protected $sort;
+
+    /**
+     * Collection mode
+     *
+     * @var string
+     */
+    protected $collectionType;
+
+    /**
+     * How many nodes have we formatted?
+     *
+     * @var int;
+     */
+    protected $count = 0;
+
+    /**
+     * Set raw data.
+     *
+     * @param object $topNode  Full record for top node
+     * @param array  $childMap Data map from index
+     * @param bool   $sort     Is sorting enabled?
+     * @param string $cType    Collection type
+     *
+     * @return void
+     */
+    public function setRawData($topNode, $childMap, $sort = false, $cType = 'All')
+    {
+        $this->topNode = $topNode;
+        $this->childMap = $childMap;
+        $this->sort = $sort;
+        $this->collectionType = $cType;
+    }
+
+    /**
+     * Get number of nodes formatted.
+     *
+     * @return int;
+     */
+    public function getCount()
+    {
+        return $this->count;
+    }
+
+    /**
+     * Get the formatted metadata.
+     *
+     * @return string
+     */
+    abstract public function getData();
+
+    /**
+     * Get the positions of this item within parent collections. Returns an array
+     * of parent ID => sequence number.
+     *
+     * @param object $fields Solr fields
+     *
+     * @return array
+     */
+    protected function getHierarchyPositionsInParents($fields)
+    {
+        $retVal = [];
+        if (isset($fields->hierarchy_parent_id)
+            && isset($fields->hierarchy_sequence)
+        ) {
+            foreach ($fields->hierarchy_parent_id as $key => $val) {
+                $retVal[$val] = $fields->hierarchy_sequence[$key];
+            }
+        }
+        return $retVal;
+    }
+
+     /**
+     * Get the titles of this item within parent collections. Returns an array
+     * of parent ID => sequence number.
+     *
+     * @param object $fields Solr fields
+     *
+     * @return Array
+     */
+    protected function getTitlesInHierarchy($fields)
+    {
+        $retVal = [];
+        if (isset($fields->title_in_hierarchy)
+            && is_array($fields->title_in_hierarchy)
+        ) {
+            $titles = $fields->title_in_hierarchy;
+            $parentIDs = $fields->hierarchy_parent_id;
+            if (count($titles) === count($parentIDs)) {
+                foreach ($parentIDs as $key => $val) {
+                    $retVal[$val] = $titles[$key];
+                }
+            }
+        }
+        return $retVal;
+    }
+
+    /**
+     * Identify whether the provided record is a collection.
+     *
+     * NOTE: \VuFind\RecordDriver\SolrDefault::isCollection() duplicates some of\
+     * this logic.
+     *
+     * @param object $fields Solr fields
+     *
+     * @return bool
+     */
+    protected function isCollection($fields)
+    {
+        // Check config setting for what constitutes a collection
+        switch ($this->collectionType) {
+        case 'All':
+            return (isset($fields->is_hierarchy_id));
+        case 'Top':
+            return isset($fields->is_hierarchy_id)
+                && in_array($fields->is_hierarchy_id, $fields->hierarchy_top_id);
+        default:
+            // Default to not be a collection level record
+            return false;
+        }
+    }
+
+    /**
+     * Choose a title for the record.
+     *
+     * @param object $record   Solr record to format
+     * @param string $parentID The starting point for the current recursion
+     * (equivalent to Solr field hierarchy_parent_id)
+     *
+     * @return string
+     */
+    protected function pickTitle($record, $parentID)
+    {
+        $titles = $this->getTitlesInHierarchy($record);
+        // TODO: handle missing titles more gracefully (title not available?)
+        $title = isset($record->title) ? $record->title : $record->id;
+        return null != $parentID && isset($titles[$parentID])
+            ? $titles[$parentID] : $title;
+    }
+
+    /**
+     * Sort Nodes
+     * Convert an unsorted array of [ key, value ] pairs into a sorted array
+     * of values.
+     *
+     * @param array $array The array of arrays to sort
+     *
+     * @return array
+     */
+    protected function sortNodes($array)
+    {
+        // Sort arrays based on first element
+        $sorter = function ($a, $b) {
+            return strcmp($a[0], $b[0]);
+        };
+        usort($array, $sorter);
+
+        // Collapse array to remove sort values
+        $mapper = function ($i) {
+            return $i[1];
+        };
+        return array_map($mapper, $array);
+    }
+}
diff --git a/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Json.php b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Json.php
new file mode 100644
index 00000000000..d0fb8d03c70
--- /dev/null
+++ b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Json.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Hierarchy Tree Data Formatter (JSON)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2015.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataFormatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+namespace VuFind\Hierarchy\TreeDataFormatter;
+
+/**
+ * Hierarchy Tree Data Formatter (JSON)
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataFormatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+class Json extends AbstractBase
+{
+    /**
+     * Get the formatted metadata.
+     *
+     * @return string
+     */
+    public function getData()
+    {
+        return json_encode($this->formatNode($this->topNode));
+    }
+
+    /**
+     * Get Solr Children for JSON
+     *
+     * @param object $record   Solr record to format
+     * @param string $parentID The starting point for the current recursion
+     * (equivalent to Solr field hierarchy_parent_id)
+     *
+     * @return string
+     */
+    protected function formatNode($record, $parentID = null)
+    {
+        $raw = [
+            'id' => $record->id,
+            'type' => $this->isCollection($record) ? 'collection' : 'record',
+            'title' => $this->pickTitle($record, $parentID)
+        ];
+
+        if (isset($this->childMap[$record->id])) {
+            $children = $this->mapChildren($record->id);
+            if (!empty($children)) {
+                $raw['children'] = $children;
+            }
+        }
+
+        return (object)$raw;
+    }
+
+    /**
+     * Get Solr Children for JSON
+     *
+     * @param string $parentID The starting point for the current recursion
+     * (equivalent to Solr field hierarchy_parent_id)
+     *
+     * @return string
+     */
+    protected function mapChildren($parentID)
+    {
+        $json = [];
+        foreach ($this->childMap[$parentID] as $current) {
+            ++$this->count;
+
+            $childNode = $this->formatNode($current, $parentID);
+
+            // If we're in sorting mode, we need to create key-value arrays;
+            // otherwise, we can just collect flat values.
+            if ($this->sort) {
+                $positions = $this->getHierarchyPositionsInParents($current);
+                $sequence = isset($positions[$parentID]) ? $positions[$parentID] : 0;
+                $json[] = [$sequence, $childNode];
+            } else {
+                $json[] = $childNode;
+            }
+        }
+
+        return $this->sort ? $this->sortNodes($json) : $json;
+    }
+}
diff --git a/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/PluginManager.php b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/PluginManager.php
new file mode 100644
index 00000000000..143b893d683
--- /dev/null
+++ b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/PluginManager.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Hierarchy tree data formatter plugin manager
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataSource
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+namespace VuFind\Hierarchy\TreeDataFormatter;
+
+/**
+ * Hierarchy tree data formatter plugin manager
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataSource
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
+{
+    /**
+     * Return the name of the base class or interface that plug-ins must conform
+     * to.
+     *
+     * @return string
+     */
+    protected function getExpectedInterface()
+    {
+        return 'VuFind\Hierarchy\TreeDataFormatter\AbstractBase';
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Xml.php b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Xml.php
new file mode 100644
index 00000000000..65a068b1798
--- /dev/null
+++ b/module/VuFind/src/VuFind/Hierarchy/TreeDataFormatter/Xml.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Hierarchy Tree Data Formatter (XML)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2015.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataFormatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+namespace VuFind\Hierarchy\TreeDataFormatter;
+
+/**
+ * Hierarchy Tree Data Formatter (XML)
+ *
+ * @category VuFind2
+ * @package  HierarchyTree_DataFormatter
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:hierarchy_components Wiki
+ */
+class Xml extends AbstractBase
+{
+    /**
+     * Get the formatted metadata.
+     *
+     * @return string
+     */
+    public function getData()
+    {
+        return '<root>'
+            . $this->formatNode($this->topNode)
+            . '</root>';
+    }
+
+    /**
+     * Get Solr Children for JSON
+     *
+     * @param object $record   Solr record to format
+     * @param string $parentID The starting point for the current recursion
+     * (equivalent to Solr field hierarchy_parent_id)
+     *
+     * @return string
+     */
+    protected function formatNode($record, $parentID = null)
+    {
+        $isCollection = $this->isCollection($record) ? "true" : "false";
+        return '<item id="' . htmlspecialchars($record->id)
+            . '" isCollection="' . $isCollection . '">'
+            . '<content><name>'
+            . htmlspecialchars($this->pickTitle($record, $parentID))
+            . '</name></content>'
+            . $this->mapChildren($record->id)
+            . '</item>';
+    }
+
+    /**
+     * Get Solr Children for JSON
+     *
+     * @param string $parentID The starting point for the current recursion
+     * (equivalent to Solr field hierarchy_parent_id)
+     *
+     * @return string
+     */
+    protected function mapChildren($parentID)
+    {
+        if (!isset($this->childMap[$parentID])) {
+            return '';
+        }
+        $parts = [];
+        foreach ($this->childMap[$parentID] as $current) {
+            ++$this->count;
+
+            $childNode = $this->formatNode($current, $parentID);
+
+            // If we're in sorting mode, we need to create key-value arrays;
+            // otherwise, we can just collect flat values.
+            if ($this->sort) {
+                $positions = $this->getHierarchyPositionsInParents($current);
+                $sequence = isset($positions[$parentID]) ? $positions[$parentID] : 0;
+                $parts[] = [$sequence, $childNode];
+            } else {
+                $parts[] = $childNode;
+            }
+        }
+
+        return implode('', $this->sort ? $this->sortNodes($parts) : $parts);
+    }
+}
diff --git a/module/VuFind/src/VuFind/Service/Factory.php b/module/VuFind/src/VuFind/Service/Factory.php
index d7fdc978897..dd279d35644 100644
--- a/module/VuFind/src/VuFind/Service/Factory.php
+++ b/module/VuFind/src/VuFind/Service/Factory.php
@@ -299,6 +299,19 @@ class Factory
         return static::getGenericPluginManager($sm, 'Hierarchy\Driver');
     }
 
+    /**
+     * Construct the Hierarchy\TreeDataFormatter Plugin Manager.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return \VuFind\Hierarchy\TreeDataFormatter\PluginManager
+     */
+    public static function getHierarchyTreeDataFormatterPluginManager(
+        ServiceManager $sm
+    ) {
+        return static::getGenericPluginManager($sm, 'Hierarchy\TreeDataFormatter');
+    }
+
     /**
      * Construct the Hierarchy\TreeDataSource Plugin Manager.
      *
-- 
GitLab