<?php
/**
 * Omeka
 * 
 * @copyright Copyright 2007-2012 Roy Rosenzweig Center for History and New Media
 * @license http://www.gnu.org/licenses/gpl-3.0.txt GNU GPLv3
 */

/**
 * A Zend_Filter implementation that uses HtmlPurifier to purify a string.
 * 
 * @package Omeka\Filter
 */
class Omeka_Filter_HtmlPurifier implements Zend_Filter_Interface
{
    private static $_purifier = null;

    private static $_purifierConfig = [
        'Core.Encoding' => 'UTF-8',
        'Cache.DefinitionImpl' => null, // Caching disabled
        'Attr.AllowedFrameTargets' => ['_blank'],
        'Attr.EnableID' => true,
        'Attr.ID.HTML5' => true,
        'HTML.TidyLevel' => 'none',
        'HTML.AllowedElements' => [
            'p', 'br', 'strong', 'em', 'span', 'div', 'ul', 'ol', 'li', 'a',
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'address', 'pre', 'table',
            'tr', 'td', 'blockquote', 'thead', 'tfoot', 'tbody', 'th', 'dl',
            'dt', 'dd', 'q', 'small', 'strike', 'sup', 'sub', 'b', 'i', 'big',
            'small', 'tt'
        ],
        'HTML.AllowedAttributes' => [
            '*.style', '*.class', 'a.href', 'a.title', 'a.target'
        ],
        // Note: this allows "unsafe" elements/attributes, but only when they
        // are also in the Allowed lists configured by the user.
        'HTML.Trusted' => true
    ];

    /**
     * Filter the value
     * 
     * @param string
     * @return string An html purified string
     */
    public function filter($value)
    {
        $purifier = self::getHtmlPurifier();
        $cleanValue = $purifier->purify($value);
        return $cleanValue;
    }

    /**
     * Get the default allowed html elements.
     *
     * @return array An array of strings corresponding to the allowed html elements
     */
    public static function getDefaultAllowedHtmlElements()
    {
        return self::$_purifierConfig['HTML.AllowedElements'];
    }

    /**
     * Get the default allowed html attributes.
     *
     * @return array An array of strings corresponding to the allowed html 
     * attributes
     */
    public static function getDefaultAllowedHtmlAttributes()
    {
        return self::$_purifierConfig['HTML.AllowedAttributes'];
    }

    /**
     * Gets the html purifier singleton
     *
     * @return HTMLPurifier $purifier
     */
    public static function getHtmlPurifier()
    {
        if (!self::$_purifier) {
            self::$_purifier = self::createHtmlPurifier();
        }
        return self::$_purifier;
    }

    /**
     * Sets the html purifier singleton
     *
     * @param HTMLPurifier $purifier
     */
    public static function setHtmlPurifier($purifier)
    {
        self::$_purifier = $purifier;

        // Set this in the registry so that other plugins can get to it.
        Zend_Registry::set('html_purifier', $purifier);
    }

    /**
     * @param array $allowedHtmlElements An array of strings representing 
     * allowed HTML elements
     * @param array $allowedHtmlAttributes An array of strings representing 
     * allowed HTML attributes
     * @return HTMLPurifier 
     **/
    public static function createHtmlPurifier(
        $allowedHtmlElements = null,
        $allowedHtmlAttributes = null
    ) {
        // Require the HTML Purfier autoloader.
        require_once 'htmlpurifier/HTMLPurifier.auto.php';

        // Get the allowed HTML elements from the configuration file
        // Setting this as NULL allows a subest of TinyMCE's
        // valid_elements whitelist. Setting this as an empty string disallows
        // all HTML elements.
        if ($allowedHtmlElements === null) {
            $allowedHtmlElements = explode(',',
                get_option('html_purifier_allowed_html_elements'));
        }

        // Get the allowed HTML attributes from the configuration file
        if ($allowedHtmlAttributes === null) {
            $allowedHtmlAttributes = explode(',',
                get_option('html_purifier_allowed_html_attributes'));
        }

        // Filter the allowed html attributes of any attributes that are
        // missing elements.
        // For example, if there is no 'a' element then filter out the
        // attribute 'a.href' and any other attribute associated with the 'a'
        // element
        $allowedHtmlAttributes =
            self::filterAttributesWithMissingElements(
                $allowedHtmlAttributes, $allowedHtmlElements);

        $purifierConfig = HTMLPurifier_Config::createDefault();
        foreach (self::$_purifierConfig as $key => $value) {
            $purifierConfig->set($key, $value);
        }
        $purifierConfig->set('HTML.AllowedElements', $allowedHtmlElements);
        $purifierConfig->set('HTML.AllowedAttributes', $allowedHtmlAttributes);

        // allow plugins to inject HTMLPurifier config/definitions
        $purifierConfig = apply_filters('html_purifier_config_setup', $purifierConfig, [
            'defaults' => self::$_purifierConfig,
            'allowedHtmlElements' => $allowedHtmlElements,
            'allowedHtmlAttributes' => $allowedHtmlAttributes
        ]);

        $purifier = HTMLPurifier::instance($purifierConfig);
        return $purifier;
    }

    public static function filterAttributesWithMissingElements(
        $htmlAttributes = [],
        $htmlElements = []
    ) {
        if (!count($htmlElements)) {
            return [];
        }
        $cleanHtmlAttributes = [];
        foreach ($htmlAttributes as $attr) {
            $attr = trim($attr);
            $attrParts = explode('.', $attr);
            if (count($attrParts) == 2
                && ($attrParts[0] == '*'
                    || in_array($attrParts[0], $htmlElements))
            ) {
                $cleanHtmlAttributes[] = $attr;
            }
        }
        return $cleanHtmlAttributes;
    }
}
