<?php
namespace intec\regionality\seo\converters;

use intec\core\helpers\Html;
use intec\core\helpers\StringHelper;
use intec\core\helpers\Type;
use intec\regionality\seo\Converter;

/**
 * Класс, предназначенный для преобразования карты сайта в исполняемый код.
 * Class SitemapConverter
 * @package intec\regionality\seo\converters
 * @author apocalypsisdimon@gmail.com
 */
class SitemapConverter extends Converter
{
    /** Текстовый макрос домена в пользовательском содержимом. */
    const DOMAIN_MACRO = '{domain}';

    /** PHP-подстановка домена в сгенерированном файле. */
    const DOMAIN_REPLACEMENT = '<?= $domain ?>';

    /**
     * Заменяет текстовый макрос домена на PHP-подстановку в генерируемом файле.
     * @param string $content
     * @return string
     */
    protected function applyDomainMacro($content)
    {
        return StringHelper::replace($content, [
            static::DOMAIN_MACRO => static::DOMAIN_REPLACEMENT
        ]);
    }

    /**
     * Заменяет PHP-подстановку домена на текстовый макрос для редактирования.
     * @param string $content
     * @return string
     */
    protected function revertDomainMacro($content)
    {
        return StringHelper::replace($content, [
            static::DOMAIN_REPLACEMENT => static::DOMAIN_MACRO
        ]);
    }

    /**
     * Возвращает заголовок ответа для sitemap.
     * @return string
     */
    public function getHeader()
    {
        return <<<'HEADER'
<?php header('Content-Type: text/xml'); ?>
<?= '<?xml version="1.0" encoding="UTF-8"?>' ?>
HEADER;
    }

    /**
     * Возвращает автогенерируемый префикс файла (пролог и заголовок ответа).
     * @return string
     */
    public function getGeneratedPrefix()
    {
        return $this->getPrologue().$this->getHeader();
    }

    /**
     * Проверяет, что содержимое файла начинается с автогенерируемого префикса.
     * @param string|null $content
     * @return boolean
     */
    public function hasGeneratedPrefix($content)
    {
        if ($content === null || $content === '')
            return false;

        $prefix = $this->normalizeForCompare($this->getGeneratedPrefix());
        $content = $this->normalizeForCompare($content);

        return StringHelper::startsWith($content, $prefix);
    }

    /**
     * Извлекает пользовательское тело файла без прologa и заголовка ответа.
     * @param string|null $content
     * @return string
     */
    public function extractBody($content)
    {
        if ($content === null || $content === '')
            return '';

        $prefix = $this->normalizeForCompare($this->getGeneratedPrefix());
        $content = $this->normalizeForCompare($content);

        if (!StringHelper::startsWith($content, $prefix))
            return '';

        $body = StringHelper::cut($content, StringHelper::length($prefix));

        return $this->revertDomainMacro($body);
    }

    /**
     * Удаляет PHP-теги из пользовательского содержимого.
     * @param string|null $content
     * @return string
     */
    public function sanitizeBody($content)
    {
        if ($content === null || $content === '')
            return '';

        $content = (string)$content;
        $content = preg_replace('/<\?(?:php|=)?.*?\?>/is', '', $content);

        return str_ireplace(['<?php', '<?=', '<?'], '', $content);
    }

    /**
     * Собирает исполняемый файл из пользовательского тела sitemap (действие save).
     * @param string|null $content
     * @return string
     */
    public function convertSavedBody($content)
    {
        $content = $this->sanitizeBody($content);
        $content = $this->applyDomainMacro($content);

        return $this->wrap($this->getHeader().$content);
    }

    /**
     * @param string $content
     * @param array $parts
     */
    protected function getParts(&$content, &$parts)
    {
        $node = simplexml_load_string($content);
        $result = [];

        if (empty($node))
            return $result;

        if ($node->getName() === 'sitemapindex') {
            foreach ($node->sitemap as $part) {
                if (empty($part->loc))
                    continue;

                $code = Type::toString($part->loc);
                $code = explode('/', $code);

                if (count($code) < 4)
                    continue;

                unset($code[0], $code[1], $code[2]);

                $code = implode('/', $code);

                if (!isset($parts[$code]))
                    continue;

                $result = array_merge(
                    $result,
                    $this->getParts($parts[$code], $parts)
                );
            }
        } else if ($node->getName() === 'urlset') {
            foreach ($node->url as $part) {
                if (empty($part->loc))
                    continue;

                $loc = Type::toString($part->loc);
                $loc = Html::encode($loc);
                $loc = explode('/', $loc);

                if (count($loc) < 4)
                    continue;

                $loc[2] = static::DOMAIN_REPLACEMENT;
                $loc = implode('/', $loc);

                $string = "\t".'<url>'."\n".
                    "\t\t".'<loc>'.$loc.'</loc>'."\n";

                if (!empty($part->lastmod))
                    $string .= "\t\t".'<lastmod>'.Html::encode(Type::toString($part->lastmod)).'</lastmod>'."\n";

                if (!empty($part->changefreq))
                    $string .= "\t\t".'<changefreq>'.Html::encode(Type::toString($part->changefreq)).'</changefreq>'."\n";

                if (!empty($part->priority))
                    $string .= "\t\t".'<priority>'.Html::encode(Type::toString($part->priority)).'</priority>'."\n";

                $string .= "\t".'</url>';
                $result[] = $string;
            }
        }

        return $result;
    }

    /**
     * Конвертирует sitemap.xml в исполняемый код (действие generate).
     * @param string|null $content
     * @param array $parts
     * @return string|null
     */
    public function convert($content, $parts = [])
    {
        if (!Type::isArray($parts))
            $parts = [];

        libxml_use_internal_errors(true);

        $result = $this->getParts($content, $parts);

        if (empty($result))
            return null;

        $result = $this->getHeader().
            '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'."\n".
            implode("\n", $result)."\n".
            '</urlset>';

        return $this->wrap($result);
    }
}
