<?php
/*
 * ----------------------------------------------------------------------
 *
 *                          Borlabs Cookie
 *                      developed by Borlabs
 *
 * ----------------------------------------------------------------------
 *
 * Copyright 2018 Borlabs - Benjamin A. Bornschein. All rights reserved.
 * This file may not be redistributed in whole or significant part.
 * Content of this file is protected by international copyright laws.
 *
 * ----------------- Borlabs Cookie IS NOT FREE SOFTWARE -----------------
 *
 * @copyright Borlabs - Benjamin A. Bornschein, https://borlabs.io
 * @author Benjamin A. Bornschein, Borlabs ben@borlabs.io
 *
 */

namespace BorlabsCookie\Cookie\Frontend;

use BorlabsCookie\Cookie\Config;
use BorlabsCookie\Cookie\Multilanguage;
use BorlabsCookie\Cookie\Tools;
use BorlabsCookie\Cookie\Frontend\JavaScript;

class ContentBlocker
{
    private static $instance;

    private $hosts = [];
    private $hostWhitelist = [];
    private $blockedContentTypes = [];
    private $cacheFolder = '';
    private $currentContent = '';
    private $currentTitle = '';
    private $currentURL = '';
    private $defaultClassMapping = [
        'facebook'=>'Facebook',
        'default'=>'Fallback', // Default
        'googlemaps'=>'GoogleMaps',
        'instagram'=>'Instagram',
        'twitter'=>'Twitter',
        'vimeo'=>'Vimeo',
        'youtube'=>'YouTube',
    ];
    private $siteHost = '';

    public static function getInstance()
    {
        if (null === self::$instance) {
            self::$instance = new self;
        }

        return self::$instance;
    }

    /**
     * __construct function.
     *
     * @access public
     * @return void
     */
    public function __construct()
    {
        global $wpdb;

        $this->hostWhitelist = Config::getInstance()->get('bctHostExceptions');

        $this->cacheFolder = WP_CONTENT_DIR.'/cache/borlabs_cookie';
        $this->siteHost = parse_url(get_site_url(), PHP_URL_HOST);

        // Check if main cache folders exists
        if (!file_exists($this->cacheFolder)) {

            // Check if /cache folder exists
            if (!file_exists(WP_CONTENT_DIR.'/cache') && is_writable(WP_CONTENT_DIR)) {
                mkdir(WP_CONTENT_DIR.'/cache');
            }

            if (file_exists(WP_CONTENT_DIR.'/cache') && is_writable(WP_CONTENT_DIR.'/cache')) {
                // Create /borlabs_cookie folder
                mkdir($this->cacheFolder);
            }
        }

        $tableName = $wpdb->prefix.'borlabs_cookie_blocked_content_types';

        // Load active Blocked Content Types
        $blockedContentTypes = $wpdb->get_results('
            SELECT
                `id`,
                `type_id`,
                `name`,
                `hosts`,
                `preview_html`,
                `global_js`,
                `init_js`,
                `settings`
            FROM
                `'.$tableName.'`
            WHERE
                `language`="'.esc_sql(Multilanguage::getInstance()->getCurrentLanguageCode()).'"
                AND
                `status`=1
        ');

        if (!empty($blockedContentTypes)) {
            foreach ($blockedContentTypes as $key => $bctData) {

                $blockedContentTypes[$key]->hosts = unserialize($bctData->hosts);

                // Collect infos about all active Blocked Content Types
                $this->blockedContentTypes[$bctData->type_id] = [
                    'typeId'=>$bctData->type_id,
                    'name'=>$bctData->name,
                    'hosts'=>$bctData->hosts,
                    'previewHTML'=>$bctData->preview_html,
                    'globalJS'=>$bctData->global_js,
                    'initJS'=>$bctData->init_js,
                    'settings'=>unserialize($bctData->settings),
                ];

                // Build list of available hosts => Blocked Content Types for faster detection
                if (!empty($blockedContentTypes[$key]->hosts)) {
                    foreach ($blockedContentTypes[$key]->hosts as $host) {
                        $this->hosts[$host] = $bctData->type_id;
                    }
                }

                // Add settings, global js, and init js of the BCT
                JavaScript::getInstance()->addBlockedContentType($bctData->type_id, $bctData->global_js, $bctData->init_js, unserialize($bctData->settings));

                // Register action filter of default Blocked Content Type classes
                if (!empty($this->defaultClassMapping[$bctData->type_id])) {
                    $className = '\BorlabsCookie\Cookie\Frontend\BlockedContentTypes\\'.$this->defaultClassMapping[$bctData->type_id];
                    add_filter('borlabsCookie/bct/modify_content/'.$bctData->type_id, [$className::getInstance(), 'modify'], 100, 2);
                }
            }
        }
    }

    private function __clone()
    {
    }

    private function __wakeup()
    {
    }

    /**
     * getCacheFolder function.
     *
     * @access public
     * @return void
     */
    public function getCacheFolder()
    {
        return $this->cacheFolder;
    }

    /**
     * getCurrentContent function.
     *
     * @access public
     * @return void
     */
    public function getCurrentContent()
    {
        return $this->currentContent;
    }

    /**
     * getCurrentTitle function.
     *
     * @access public
     * @return void
     */
    public function getCurrentTitle()
    {
        return $this->currentTitle;
    }

    /**
     * getCurrentURL function.
     *
     * @access public
     * @return void
     */
    public function getCurrentURL()
    {
        return $this->currentURL;
    }

    /**
     * getDataOfBlockedContentType function.
     *
     * @access public
     * @param mixed $typeId
     * @return void
     */
    public function getDataOfBlockedContentType($typeId)
    {
        if (!empty($this->blockedContentTypes[$typeId])) {
            return $this->blockedContentTypes[$typeId];
        } else {
            return false;
        }
    }

    /**
     * isHostWhitelisted function.
     *
     * @access public
     * @param mixed $host
     * @return true: host is whitelisted, false: host is not whitelisted
     */
    public function isHostWhitelisted($host)
    {
        $status = false;

        if (!empty($this->hostWhitelist)) {
            foreach ($this->hostWhitelist as $whitelistHost) {
                if (strpos($host, $whitelistHost) !== false) {
                    $status = true;
                }
            }
        }

        return $status;
    }

    /**
     * detectIframes function.
     *
     * @access public
     * @param mixed $content
     * @param mixed $postId (default: null)
     * @param mixed $field (default: null)
     * @return void
     */
    public function detectIframes($content, $postId = null, $field = null)
    {
        global $wpdb;

        if (function_exists('is_feed') && is_feed()) {
            if (Config::getInstance()->get('removeIframesInFeeds') == true) {
                $content = preg_replace('/(\<p\>)?(<iframe[^<]+<\/iframe>)+(\<\/p\>)?/i', '', $content);
            }
        } else {
            $content = preg_replace_callback('/(\<p\>)?(<iframe[^<]+<\/iframe>)+(\<\/p\>)?/i', [$this, 'handleIframe'], $content);
        }

        return $content;
    }

    /**
     * handleIframe function.
     *
     * @access public
     * @param mixed $tags
     * @return void
     */
    public function handleIframe($tags)
    {
        $content = $tags[0];

        if (strpos($tags[0], 'data-borlabs-cookie-iframe-spared') === false) {

            // Detect host
            $srcMatch = [];

            preg_match('/src=("|\')([^"\']{1,})(\1)/i', $tags[2], $srcMatch);

            // Skip iframes without src attribute of where src is about:blank
            if (!empty($srcMatch[2]) && $srcMatch[2] !== 'about:blank') {
                $content = $this->handleContentBlocking($tags[0], $srcMatch[2]);
            }
        }

        return $content;
    }

    /**
     * handleShortcode function.
     *
     * @access public
     * @param mixed $atts
     * @param mixed $content (default: null)
     * @return void
     */
    public function handleShortcode($atts, $content = null)
    {
        $blockedContentType = '';
        $title = '';
        $url = '';

        if (!empty($atts['type'])) {
            $blockedContentType = $atts['type'];
        }

        if (!empty($atts['title'])) {
            $title = $atts['title'];
        }

        // If the content is just an URL it is maybe oembed content
        if (filter_var(trim($content), FILTER_VALIDATE_URL) !== false) {
            $url = $content;
            $content = wp_oembed_get($content);
        } else {
            $content = do_shortcode($content);

            // Try to detect iframe
            $iframeMatch = [];

            preg_match('/<iframe[^<]+<\/iframe>/i', $content, $iframeMatch);

            if (!empty($iframeMatch[0])) {
                // Detect host

                $srcMatch = [];

                preg_match('/src=("|\')([^"\']{1,})(\1)/i', $iframeMatch[0], $srcMatch);

                if (!empty($srcMatch[2]) && $srcMatch[2] !== 'about:blank') {
                    $url = $srcMatch[2];
                }
            }
        }

        return $this->handleContentBlocking($content, $url, $blockedContentType, $title);
    }

    /**
     * handleOembed function.
     *
     * @access public
     * @param mixed $html
     * @param mixed $url
     * @param mixed $atts
     * @param mixed $postId
     * @return void
     */
    public function handleOembed($html, $url, $atts, $postId)
    {
        return $this->handleContentBlocking($html, $url);
    }

    /**
     * handleACFOembed function.
     *
     * @access public
     * @param mixed $html
     * @param mixed $id
     * @param mixed $atts
     * @return void
     */
    public function handleACFOembed($html, $id, $atts)
    {
        // Detect URL
        $url = '';
        $match = [];

        preg_match_all('#\bhttps?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $html, $match);

        // Let's just hope the first URL is the right one...
        if (!empty($match[0][0])) {
            $url = $match[0][0];
        }

        return $this->handleContentBlocking($html, $url);
    }

    /**
     * handleContentBlocking function.
     *
     * @access public
     * @param mixed $content
     * @param string $url (default: '')
     * @param string $blockedContentType (default: '')
     * @param string $title (default: '')
     * @return void
     */
    public function handleContentBlocking($content, $url = '', $blockedContentType = '', $title = '')
    {
        // Check if host is on the whitelist
        if(empty($url) || $this->isHostWhitelisted($url) !== true) {

            // Set currentContent for third party filter that needs the content unmodified
            $this->currentContent = $content;
            $this->currentURL = !empty($url) ? $url : '';
            $this->currentTitle = $title;

            $detectedBCT = null;

            // Detect Blocked Content Type by Host
            if (!empty($this->hosts) && !empty($this->currentURL)) {

                $currentURLData = parse_url($this->currentURL);
                $currentHost = $currentURLData['host'].$currentURLData['path'];

                foreach ($this->hosts as $host => $bct) {
                    if (strpos($currentHost, $host) !== false) {
                        $detectedBCT = $bct;

                        break;
                    }
                }

            }

            // When $blockedContentTypes is set - overwrites the by URL detected bct
            if (!empty($blockedContentType) && !empty($this->blockedContentTypes[$blockedContentType])) {
                $detectedBCT = $blockedContentType;
            }

            // Fallback but only if Fallback was not disabled
            if (empty($detectedBCT) && !empty($this->blockedContentTypes['default'])) {
                $detectedBCT = 'default';
            }

            // Do not block oEmbed of own blog
            if (!empty($this->currentURL) && strpos($this->currentURL, $this->siteHost) !== false) {
                $detectedBCT = null;
            }

            if (!empty($detectedBCT)) {

                $id = 'bc'.Tools::getInstance()->generateRandomString(8);

                JavaScript::getInstance()->addBlockedContent($detectedBCT, $id, $content);

                if (has_filter('borlabsCookie/bct/modify_content/'.$detectedBCT)) {
                    $content = apply_filters('borlabsCookie/bct/modify_content/'.$detectedBCT, $id, $content);
                } else {
                    $content = BlockedContentTypes\Custom::getInstance()->modify($id, $content, $detectedBCT);
                }

                if (Config::getInstance()->get('testEnvironment') === true) {
                    $content .= '<span style="color: #f00; background: #fff;">'._x('Test environment active!', 'Status message', 'borlabs-cookie').'</span>';
                }

                $content = '<div id="'.$id.'" data-borlabs-cookie-blocked-content>'.$content.'</div>';

                // Remove whitespace to avoid WordPress' automatic br- & p-tags
                $content = preg_replace('/[\s]+/mu', ' ', $content);
            }
        }

        return $content;
    }
}
