<?php

/**
 * @package SP Page Builder
 * @author JoomShaper http://www.joomshaper.com
 * @copyright Copyright (c) 2010 - 2024 JoomShaper
 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 or later
 */

use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Uri\Uri;

// No direct access
defined('_JEXEC') or die('Restricted access');

/**
 * Trait for managing bulk export API endpoint.
 */
trait BulkExportTrait
{
    public function bulkExport()
    {
        $method = $this->getInputMethod();
        $this->checkNotAllowedMethods(['GET', 'DELETE', 'PUT', 'PATCH'], $method);

        if ($method === 'POST') {
            $this->exportBulk();
        }
    }

    /**
     * Bulk export pages.
     *
     * @return void
     * @since 5.2.10
     */
    public function exportBulk()
    {
        $config = ApplicationHelper::getAppConfig();
        $user = Factory::getUser();
        $authorised = $user->authorise('core.edit', 'com_sppagebuilder');
        $canEditOwn = $user->authorise('core.edit.own', 'com_sppagebuilder');

        $pageIds = $this->getInput('pageIds', '', 'STRING');
        $isSeoChecked = $this->getInput('isSeoChecked', '', 'STRING');
        $isExportMedia = $this->getInput('isMediaChecked', '', 'STRING');

        if (empty($pageIds)) {
            $this->sendResponse(['message' => 'Page Ids missing.'], 400);
        }

        $pageIdsArray = explode(',', $pageIds);

        $pageContents = new stdClass;
        $model = $this->getModel('Editor');
        

        if($isExportMedia){
            $this->exportBulkWithMedia($pageIdsArray, $isSeoChecked, $model, $user, $authorised, $canEditOwn, $config);
            die();
        }

        foreach ($pageIdsArray as $pageId) {
            $trimmedPageId = trim($pageId);
            
            if (!$trimmedPageId) {
                continue;
            }

            if ($canEditOwn && !empty($trimmedPageId)) {
                JLoader::register('SppagebuilderModelPage', JPATH_ADMINISTRATOR . '/components/com_sppagebuilder/models/page.php');

                $item_info  = SppagebuilderModelPage::getPageInfoById($trimmedPageId);
                $canEditOwn = $item_info->created_by == $user->id;
            }

            if (!$authorised && !$canEditOwn) {
                die('Restricted Access');
            }

            $content = $model->getPageContent($trimmedPageId);

            if (empty($content)) {
                $this->sendResponse(['message' => 'Requesting page not found!'], 404);
            }

            $content = ApplicationHelper::preparePageData($content);

            $seoSettings = [];
            
            if ($isSeoChecked) {
                $seoSettings = [
                    'og_description' => isset($content->og_description) ? $content->og_description : '',
                    'og_image' => isset($content->og_image) ? $content->og_image : '',
                    'og_title' => isset($content->og_title) ? $content->og_title : '',
                    'meta_description' => isset($content->attribs) && isset($content->attribs->meta_description) ?  $content->attribs->meta_description : '',
                    'meta_keywords' => isset($content->attribs) && isset($content->attribs->meta_keywords) ?  $content->attribs->meta_keywords : '',
                    'og_type' => isset($content->attribs) && isset($content->attribs->og_type) ?  $content->attribs->og_type : '',
                    'robots' => isset($content->attribs) && isset($content->attribs->robots) ?  $content->attribs->robots : '',
                    'seo_spacer' => isset($content->attribs) && isset($content->attribs->seo_spacer) ?  $content->attribs->seo_spacer : '',
                    'author' => isset($content->attribs) && isset($content->attribs->author) ?  $content->attribs->author : '',
                ];
            }

            $pageContent = (object) [
                'template' => isset($content->content) ? $content->content : $content->text,
                'css' => isset($content->css) ? $content->css : '',
                'seo' => json_encode($seoSettings),
                'title' => $content->title,
                'language' => isset($content->language) ? $content->language : '*',
            ];

            if (isset($content->extension_view) && $content->extension_view === 'popup') {
                $pageContent->attribs = isset($content->attribs) ? json_encode($content->attribs) : '';
                $pageContent->type = 'popup';
            }

            $title = $pageId . '_' . $this->generateRandomId() . '.json';
            $pageContents->$title = $pageContent;
        }

        $zip = new ZipArchive();
        $zipFileName = 'sp-page-builder-pages-' . $this->generateRandomId() . '.zip';
        $tmpPath = $config->get('tmp_path');
        $zipFilePath = $tmpPath . '/' . $zipFileName;

        if ($zip->open($zipFilePath, ZipArchive::CREATE) === TRUE) {
            foreach ($pageContents as $fileName => $content) {
                $stringContent = json_encode($content);

                try {
                    $zip->addFromString($fileName, $stringContent);
                } catch (\Throwable $th) {
                    $this->sendResponse([
                        'message' => 'Failed to add file to the ZIP archive',
                    ], 500);
                }
            }

            $zip->close();

            header("Pragma: public");
            header("Expires: 0");
            header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
            header("Content-Type: application/force-download");
            header("Content-Type: application/octet-stream");
            header("Content-Type: application/download");
            header('Content-Disposition: attachment; filename="' . basename($zipFilePath) . '"');
            header("Content-Type: application/zip");
            header("Content-Transfer-Encoding: binary ");
            header('Content-Length: ' . filesize($zipFilePath));

            echo file_get_contents($zipFilePath);
            unlink($zipFilePath);

            die();
        }

        $this->sendResponse([
            'message' => 'Failed to create zip file',
        ], 500);
    }

    function generateRandomId($length = 8)
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

        $randomId = '';
        $maxIndex = strlen($characters) - 1;
        for ($i = 0; $i < $length; $i++) {
            $randomId .= $characters[mt_rand(0, $maxIndex)];
        }

        return $randomId;
    }

    private function exportBulkWithMedia($pageIdsArray, $isSeoChecked, $model, $user, $authorised, $canEditOwn, $config)
{
    $zip = new ZipArchive();
    $zipFileName = 'sp-page-builder-pages-' . $this->generateRandomId() . '.zip';
    $tmpPath = $config->get('tmp_path');
    $zipFilePath = $tmpPath . '/' . $zipFileName;
    $commonExportDir = $tmpPath . '/common_export_' . $this->generateRandomId();

    if ((!Folder::create($commonExportDir, 0755))){
        $this->sendResponse([
            'message' => 'Failed to create common export directory',
        ], 500);
    }

    foreach ($pageIdsArray as $pageId)
    {
        $trimmedPageId = trim($pageId);

        if (!$trimmedPageId)
        {
            continue;
        }

        if ($canEditOwn && !empty($trimmedPageId))
        {
            JLoader::register('SppagebuilderModelPage', JPATH_ADMINISTRATOR . '/components/com_sppagebuilder/models/page.php');

            $item_info  = SppagebuilderModelPage::getPageInfoById($trimmedPageId);
            $canEditOwn = $item_info->created_by == $user->id;
        }

        if (!$authorised && !$canEditOwn)
        {
            die('Restricted Access');
        }

        $content = $model->getPageContent($trimmedPageId);

        if (empty($content))
        {
            $this->sendResponse(['message' => 'Requesting page not found!'], 404);
        }
        
        $fieldContent = isset($content->content) ? $content->content : $content->text;
        $contentParsed = [];

        if(!empty($fieldContent) && is_string($fieldContent)){
            $contentParsed = json_decode($fieldContent, true);
        }

        if($isSeoChecked){
            $ogImageSrc = $content->og_image->src ?? '';
            array_push($contentParsed, ['src' => $ogImageSrc]);
        }
        
        $localMediaSources = $this->getSrcValues($contentParsed);
        $mediaSources = [];
        $content = ApplicationHelper::preparePageData($content);

        foreach ($localMediaSources as $source)
        {
            $sourcePath = Uri::root() . $source;
            array_push($mediaSources, $sourcePath);
        }

        $seoSettings = [];

        if ($isSeoChecked)
        {
            $seoSettings = 
            [
                'og_description' => isset($content->og_description) ? $content->og_description : '',
                'og_image' => isset($content->og_image) ? $content->og_image : '',
                'og_title' => isset($content->og_title) ? $content->og_title : '',
                'meta_description' => isset($content->attribs) && isset($content->attribs->meta_description) ?  $content->attribs->meta_description : '',
                'meta_keywords' => isset($content->attribs) && isset($content->attribs->meta_keywords) ?  $content->attribs->meta_keywords : '',
                'og_type' => isset($content->attribs) && isset($content->attribs->og_type) ?  $content->attribs->og_type : '',
                'robots' => isset($content->attribs) && isset($content->attribs->robots) ?  $content->attribs->robots : '',
                'seo_spacer' => isset($content->attribs) && isset($content->attribs->seo_spacer) ?  $content->attribs->seo_spacer : '',
                'author' => isset($content->attribs) && isset($content->attribs->author) ?  $content->attribs->author : '',
            ];
        }

        $pageContent = (object)
        [
            'template' => isset($content->content) ? $content->content : $content->text,
            'css' => isset($content->css) ? $content->css : '',
            'title' => $content->title,
            'seo' => json_encode($seoSettings),
            'language' => isset($content->language) ? $content->language : '*',
            'localMediaSources' => $localMediaSources ? $localMediaSources : '[]',
        ];

        $mediaTempDir = $commonExportDir . '/media_' . $this->generateRandomId();
        $parentDir = $commonExportDir . '/page_'. $content->title . '_' . $this->generateRandomId();

        if ((!Folder::create($mediaTempDir, 0755)) ||
            (!Folder::create($parentDir, 0755)))
        {
            $this->sendResponse([
                'message' => 'Failed to create necessary directories',
            ], 500);
        }

        $options = new \Joomla\Registry\Registry;
		$http = HttpFactory::getHttp($options);

        foreach ($mediaSources as $source)
        {
            $encodedBaseName = rawurlencode(basename($source));
			$destination = $mediaTempDir . '/' . $encodedBaseName;

            try {
                
                $options = new \Joomla\Registry\Registry;
                $http = HttpFactory::getHttp($options);
                $encodedUrl = dirname($source) . '/' . $encodedBaseName;
				$response = $http->get($encodedUrl);

                if ($response->code === 200) {
                    
                    File::write($destination, $response->body);
                } else {
                    $this->sendResponse([
                        'message' => 'Failed to copy media file: ' . $source,
                        'error' => 'HTTP response code: ' . $response->code
                    ], 500);
                }
            } catch (Exception $e) {
                $this->sendResponse([
                    'message' => 'Failed to copy media file: ' . $source,
                    'error' => $e->getMessage()
                ], 500);
            }
        }

        $stringContent = json_encode($pageContent);
        $fileName = $pageId . '_' . $this->generateRandomId() . '.json';
        file_put_contents($parentDir . '/' . $fileName, $stringContent);

        rename($mediaTempDir, $parentDir . '/media');
    }

    if ($zip->open($zipFilePath, ZipArchive::CREATE) === TRUE)
    {
        try {
            $files = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($commonExportDir),
                RecursiveIteratorIterator::SELF_FIRST
            );

            foreach ($files as $name => $file)
            {
                $filePath = $file->getRealPath();
                $relativePath = substr($filePath, strlen($commonExportDir) + 1);

                if ($file->isDir())
                {
                    $zip->addEmptyDir($relativePath);
                }
                else
                {
                    $zip->addFile($filePath, $relativePath);
                }
            }
        } catch (\Throwable $th)
        {
            $this->sendResponse([
                'message' => 'Failed to add file to the ZIP archive',
            ], 500);
        }
    }

    $zip->close();

    header("Pragma: public");
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Content-Type: application/zip");
    header('Content-Disposition: attachment; filename="' . basename($zipFilePath) . '"');
    header('Content-Length: ' . filesize($zipFilePath));

    echo file_get_contents($zipFilePath);

    if (is_file($zipFilePath))
    {
        unlink($zipFilePath);
    }

    Folder::delete($commonExportDir);

    die();
}

private function getSrcValues($data)
    {
        $accepted_file_formats = array(
            'image' => array('jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'avif'),
            'video' => array('mp4', 'mov', 'wmv', 'avi', 'mpg', 'ogv', '3gp', '3g2'),
            'audio' => array('mp3', 'm4a', 'ogg', 'wav'),
            'attachment' => array('pdf', 'doc', 'docx', 'key', 'ppt', 'pptx', 'pps', 'ppsx', 'odt', 'xls', 'xlsx', 'zip', 'json'),
        );
        
        $srcValues = [];
        
        foreach ($data as $key => $value)
        {
            if (is_array($value))
            {
                $srcValues = array_merge($srcValues, $this->getSrcValues($value));
            }
            elseif ($key === 'src' && !empty($value))
            {
                $path_info = pathinfo($value);
                $extension = isset($path_info['extension']) ? strtolower($path_info['extension']) : '';
                
                foreach ($accepted_file_formats as $formats)
                {
                    if (in_array($extension, $formats))
                    {
                        if (!preg_match('/^(http:\/\/|https:\/\/|\/\/)/i', $value)) {
                            $srcValues[] = $value;
                        }
                        break;
                    }
                }
            }
        }

        return $srcValues;
    }
}
