permanent
clone your own copy | download snapshot

Snapshots | iceberg

Inside this repository

data-manager.php
text/x-php

Download raw (9.7 KB)

<?php
namespace Grav\Plugin;

use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Filesystem\Folder;

use Grav\Common\Plugin;
use Grav\Common\Twig\Twig;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Plugin\Admin\Admin;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\JsonFile;

class DataManagerPlugin extends Plugin
{
    protected $route = 'data-manager';

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            'onPluginsInitialized' => ['onPluginsInitialized', 0],
        ];
    }

    /**
     * Enable only if url matches to the configuration.
     */
    public function onPluginsInitialized()
    {
        if (!$this->isAdmin()) {
            return;
        }

        $this->enable([
            'onPagesInitialized' => ['onPagesInitialized', 0],
            'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
            'onAdminMenu' => ['onAdminMenu', 0],
        ]);
    }

    /**
     * @throws \Exception
     */
    public function onPagesInitialized()
    {
        /** @var Uri $uri */
        $uri = $this->grav['uri'];

        if (strpos($uri->path(), $this->config->get('plugins.admin.route') . '/' . $this->route) === false) {
            return;
        }

        /** @var Twig $twig */
        $twig = $this->grav['twig'];
        $pathParts = $uri->paths();
        $extension = '.' . $uri->extension();
        $csv = false;

        if (isset($pathParts[1]) && $pathParts[1] === $this->route) {
            $type = isset($pathParts[2]) ? $pathParts[2] : null;
            if (preg_match( '/\.csv$/', $type)) {
                $type = basename($type, '.csv');
                $file = null;
                $csv = true;
            } else {
                $file = isset($pathParts[3]) ? $pathParts[3] : null;
                $ext = $this->getExtension($type, $file);
                if ($extension && $ext !== $extension) {
                    $filename = $file . $extension;
                } else {
                    $filename = $file;
                }
            }

            if ($file && !$csv) {
                // Individual data entry.
                $twig->itemData = $this->getFileContent($type, $filename);
            } elseif ($type) {
                // List of data entries.
                $twig->items = $this->getDataType($type);
            } else {
                // List of data types.
                $twig->types = $this->getDataTypes();
            }
        }

        // Handle CSV call
        if (isset($twig->items) && $csv) {

            $data = array_column($twig->items, 'content');

            $this->downloadCSV($data);
        }
    }

    private function getExtension($type, $filename)
    {
        $extension = $this->config->get("plugins.data-manager.types.{$type}.file_extension");
        if (!$extension) {
            $pos = strrpos($filename, '.');
            $extension = $pos ? substr($filename, $pos) : null;
        }

        return $extension;
    }

    /**
     * Given a data file route, return the file content.
     *
     * @param string $type
     * @param string $filename
     * @return array|string|null
     */
    private function getFileContent($type, $filename)
    {
        $extension = $this->getExtension($type, $filename);

        switch ($extension) {
            case '.txt':
            case '.yaml':
                $file = CompiledYamlFile::instance(DATA_DIR . $type . '/' . $filename);
                break;
            case '.json':
                $file = JsonFile::instance(DATA_DIR . $type . '/' . $filename);
                break;
            case '.html':
            default:
                $file = File::instance(DATA_DIR . $type . '/' . $filename);
        }

        if (!$file->exists()) {
            return null;
        }

        try {
            return $file->content();
        } catch (\Exception $e) {
            return $file->raw();
        }
    }

    /**
     * @param string $type
     * @return array
     */
    protected function getDataType($type)
    {
        $extension = $this->config->get("plugins.data-manager.types.{$type}.file_extension");

        $items = [];
        $fileIterator = new \FilesystemIterator(DATA_DIR . $type, \FilesystemIterator::SKIP_DOTS);
        /** @var \FilesystemIterator $entry */
        foreach ($fileIterator as $entry) {
            $file = $entry->getFilename();
            if (!$entry->isFile() || ($extension && !preg_match('/' . preg_quote($extension, '/') . '$/', $file))) {
                // Is not file or file extension does not match.
                continue;
            }

            $name = substr($file, 0, strrpos($file, '.'));
            $items[] = [
                'name' => $name,
                'route' => $file,
                'content' => $this->getFileContent($type, $file)
            ];
        }

        return $this->sortArrayByKey($items, 'name', SORT_DESC, SORT_NATURAL);
    }

    /**
     * @return array
     */
    protected function getDataTypes()
    {
        $types = [];
        $entry = null;

        //Find data types excluded by plugins
        $this->grav->fireEvent('onDataTypeExcludeFromDataManagerPluginHook');

        $typesIterator = new \FilesystemIterator(DATA_DIR, \FilesystemIterator::SKIP_DOTS);
        foreach ($typesIterator as $type) {
            $typeName = $type->getFilename();
            if ($typeName[0] === '.') {
                continue;
            }

            if (!is_dir(DATA_DIR . $typeName)) {
                continue;
            }

            $iterator = new \FilesystemIterator(DATA_DIR . $typeName, \FilesystemIterator::SKIP_DOTS);
            $count = 0;
            foreach ($iterator as $fileinfo) {
                if ($fileinfo->getFilename()[0] === '.') {
                    continue;
                }
                $count++;
            }

            if (isset($this->grav['admin']->dataTypesExcludedFromDataManagerPlugin)) {
                if (!\in_array($typeName, $this->grav['admin']->dataTypesExcludedFromDataManagerPlugin, true)) {
                    $types[$typeName] = $count;
                }
            } else {
                $types[$typeName] = $count;
            }
        }

        return $types;
    }

    /**
     * @param array $data
     * @throws \Exception
     */
    protected function downloadCSV(array $data)
    {
        $csv_data = $this->arrayToCsv($data);

        /** @var File $csv_file */
        $tmp_dir  = Admin::getTempDir();
        $tmp_file = uniqid() . '.csv';
        $tmp      = $tmp_dir . '/data-manager/' . basename($tmp_file);

        Folder::create(dirname($tmp));

        $csv_file = File::instance($tmp_file);
        $csv_file->save($csv_data);
        Utils::download($csv_file->filename(), true);
        exit;
    }

    /**
     * Add plugin templates path
     */
    public function onTwigTemplatePaths()
    {
        $this->grav['twig']->twig_paths[] = __DIR__ . '/admin/templates';
    }

    /**
     * Add navigation item to the admin plugin
     */
    public function onAdminMenu()
    {
        $this->grav['twig']->plugins_hooked_nav['PLUGIN_DATA_MANAGER.DATA_MANAGER'] = ['route' => $this->route, 'icon' => 'fa-database'];
    }

    /**
     * sort a multidimensional array by a key
     * Local version until Grav 1.4.3 is released
     *
     * @param $array
     * @param $array_key
     * @param int $direction
     * @param int $sort_flags
     * @return array
     */
    public function sortArrayByKey($array, $array_key, $direction = SORT_DESC, $sort_flags = SORT_REGULAR)
    {
        $output = [];

        if (!is_array($array) || !$array) {
            return $output;
        }

        foreach ($array as $key => $row) {
            $output[$key] = $row[$array_key];
        }

        array_multisort($output, $direction, $sort_flags, $array);

        return $array;
    }

    /**
     *
     *
     * @param array $array
     * @return null|string
     */
    private function arrayToCsv(array $array)
    {
        $rows = [];
        foreach ($array as $row) {
            $row = $this->csvFlatten($row);
            if ($row) {
                $rows[] = $row;
            }
        }

        if (count($rows) === 0) {
            return null;
        }

        $fields = array_map(function() { return ''; }, call_user_func_array('array_replace', $rows));
        $values = [];
        foreach ($rows as $row) {
            $values[] = array_merge($fields, $row);
        }

        ob_start();
        $df = fopen('php://output', 'wb');
        fputcsv($df, array_keys($fields));
        foreach ($values as $value) {
            fputcsv($df, $value);
        }
        fclose($df);
        return ob_get_clean();
    }

    private function csvFlatten($row)
    {
        if (!is_array($row)) {
            return [];
        }
        if (isset($row['_data_type'], $row['content']) && is_array($row['content'])) {
            return ['timestamp' => $row['timestamp']] + $this->arrayFlatten($row['content']);
        }

        $flat_data = [];
        foreach ($row as $key => $item) {
            if (is_array($item)) {
                $flat_data[$key] = json_encode($item);
            } else {
                $flat_data[$key] = $item;
            }
        }

        return $flat_data;
    }

    /**
     * Flatten an array
     *
     * @param array $array
     * @param string $prefix
     * @return array
     */
    private function arrayFlatten($array, $prefix = '')
    {
        $flatten = [];
        foreach ($array as $key => $inner) {
            if (is_array($inner)) {
                $flatten += $this->arrayFlatten($inner, $prefix . $key . '.');
            } else {
                $flatten[$prefix . $key] = $inner;
            }
        }
        return $flatten;
    }
}