<?php

/**
 * Part of the Joomla Framework Database Package
 *
 * @copyright  Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Database;

/**
 * Joomla Framework Database Exporter Class
 *
 * @since  1.0
 */
abstract class DatabaseExporter
{
    /**
     * The type of output format.
     *
     * @var    string
     * @since  1.0
     */
    protected $asFormat = 'xml';

    /**
     * An array of cached data.
     *
     * @var    array
     * @since  1.0
     */
    protected $cache = ['columns' => [], 'keys' => []];

    /**
     * The database connector to use for exporting structure and/or data.
     *
     * @var    DatabaseInterface
     * @since  1.0
     */
    protected $db;

    /**
     * An array input sources (table names).
     *
     * @var    string[]
     * @since  1.0
     */
    protected $from = [];

    /**
     * An array of options for the exporter.
     *
     * @var    \stdClass
     * @since  1.0
     */
    protected $options;

    /**
     * Constructor.
     *
     * Sets up the default options for the exporter.
     *
     * @since   1.0
     */
    public function __construct()
    {
        $this->options = new \stdClass();

        // Set up the class defaults:

        // Export not only structure
        $this->withStructure();
        $this->withData();

        // Export as xml.
        $this->asXml();

        // Default destination is a string using $output = (string) $exporter;
    }

    /**
     * Magic function to exports the data to a string.
     *
     * @return  string
     *
     * @since   1.0
     */
    public function __toString()
    {
        $buffer = '';

        try {
            // Check everything is ok to run first.
            $this->check();

            // Get the format.
            switch ($this->asFormat) {
                case 'xml':
                default:
                    $buffer = $this->buildXml();

                    break;
            }
        } catch (\Exception $e) {
            // Do nothing
        }

        return $buffer;
    }

    /**
     * Set the output option for the exporter to XML format.
     *
     * @return  $this
     *
     * @since   1.0
     */
    public function asXml()
    {
        $this->asFormat = 'xml';

        return $this;
    }

    /**
     * Builds the XML data for the tables to export.
     *
     * @return  string  An XML string
     *
     * @since   1.0
     * @throws  \Exception if an error occurs.
     */
    abstract protected function buildXml();

    /**
     * Builds the XML structure to export.
     *
     * @return  array  An array of XML lines (strings).
     *
     * @since   1.0
     * @throws  \Exception if an error occurs.
     */
    abstract protected function buildXmlStructure();

    /**
     * Checks if all data and options are in order prior to exporting.
     *
     * @return  $this
     *
     * @since   1.0
     * @throws  \Exception if an error is encountered.
     */
    abstract public function check();

    /**
     * Specifies a list of table names to export.
     *
     * @param   string[]|string  $from  The name of a single table, or an array of the table names to export.
     *
     * @return  $this
     *
     * @since   1.0
     * @throws  \InvalidArgumentException
     */
    public function from($from)
    {
        if (\is_string($from)) {
            $this->from = [$from];
        } elseif (\is_array($from)) {
            $this->from = $from;
        } else {
            throw new \InvalidArgumentException('The exporter requires either a single table name or array of table names');
        }

        return $this;
    }

    /**
     * Get the generic name of the table, converting the database prefix to the wildcard string.
     *
     * @param   string  $table  The name of the table.
     *
     * @return  string  The name of the table with the database prefix replaced with #__.
     *
     * @since   1.0
     */
    protected function getGenericTableName($table)
    {
        $prefix = $this->db->getPrefix();

        // Replace the magic prefix if found.
        return preg_replace("|^$prefix|", '#__', $table);
    }

    /**
     * Sets the database connector to use for importing structure and/or data.
     *
     * @param   DatabaseInterface  $db  The database connector.
     *
     * @return  $this
     *
     * @since   1.0
     */
    public function setDbo(DatabaseInterface $db)
    {
        $this->db = $db;

        return $this;
    }

    /**
     * Sets an internal option to export the structure of the input table(s).
     *
     * @param   boolean  $setting  True to export the structure, false to not.
     *
     * @return  $this
     *
     * @since   1.0
     */
    public function withStructure($setting = true)
    {
        $this->options->withStructure = (bool) $setting;

        return $this;
    }

    /**
     * Sets an internal option to export the data of the input table(s).
     *
     * @param   boolean  $setting  True to export the data, false to not.
     *
     * @return  $this
     *
     * @since   2.0.0
     */
    public function withData($setting = false)
    {
        $this->options->withData = (bool) $setting;

        return $this;
    }

    /**
     * Builds the XML data to export.
     *
     * @return  array  An array of XML lines (strings).
     *
     * @since   2.0.0
     * @throws  \Exception if an error occurs.
     */
    protected function buildXmlData()
    {
        $buffer = [];

        foreach ($this->from as $table) {
            // Replace the magic prefix if found.
            $table = $this->getGenericTableName($table);

            // Get the details columns information.
            $fields  = $this->db->getTableColumns($table, false);
            $colblob = [];

            foreach ($fields as $field) {
                // Catch blob for conversion xml
                if ($field->Type == 'mediumblob') {
                    $colblob[] = $field->Field;
                }
            }

            $this->db->setQuery(
                $this->db->getQuery(true)
                    ->select($this->db->quoteName(array_keys($fields)))
                    ->from($this->db->quoteName($table))
            );

            $rows = $this->db->loadObjectList();

            if (!count($rows)) {
                continue;
            }

            $buffer[] = '  <table_data name="' . $table . '">';

            foreach ($rows as $row) {
                $buffer[] = '   <row>';

                foreach ($row as $key => $value) {
                    if (!in_array($key, $colblob)) {
                        if (is_null($value)) {
                            $buffer[] = '    <field name="' . $key . '" value_is_null></field>';
                        } else {
                            $buffer[] = '    <field name="' . $key . '">' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '</field>';
                        }
                    } else {
                        $buffer[] = '    <field name="' . $key . '">' . base64_encode($value) . '</field>';
                    }
                }

                $buffer[] = '   </row>';
            }

            $buffer[] = '  </table_data>';
        }

        return $buffer;
    }
}
