<?php
/**
 * The Horde_Text_Filter_Emails:: class finds email addresses in a block of
 * text and turns them into links.
 *
 * Parameters:
 * <pre>
 * class - (string) CSS class of the generated <a> tag.
 *         DEFAULT: ''
 * encode - (boolean) Whether to escape special HTML characters in the URLs
 *          and finally "encode" the complete tag so that it can be decoded
 *          later with the decode() method. This is useful if you want to run
 *          htmlspecialchars() or similar *after* using this filter.
 *          DEFAULT: false
 * </pre>
 *
 * Copyright 2003-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 *
 * @author   Tyler Colbert <tyler@colberts.us>
 * @author   Jan Schneider <jan@horde.org>
 * @category Horde
 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 * @package  Text_Filter
 */
class Horde_Text_Filter_Emails extends Horde_Text_Filter_Base
{
    /**
     * Filter parameters.
     *
     * @var array
     */
    protected $_params = array(
        'class' => '',
        'encode' => false,
        'secret' => null
    );

    /**
     * Returns a hash with replace patterns.
     *
     * @return array  Patterns hash.
     */
    public function getPatterns()
    {
        $this->_regexp = <<<EOR
        /
            # Version 1: mailto: links with any valid email characters.
            # Pattern 1: Outlook parenthesizes in square brackets
            (\[\s*)?
            # Pattern 2: mailto: protocol prefix
            (mailto:\s?)
            # Pattern 3: email address
            ([^\s\?"<&]*)
            # Pattern 4: closing angle brackets?
            (&gt;)?
            # Pattern 5 to 7: Optional parameters
            ((\?)([^\s"<]*[\w+#?\/&=]))?
            # Pattern 8: Closing Outlook square bracket
            ((?(1)\s*\]))
        |
            # Version 2 Pattern 9 and 10: simple email addresses.
            (^|\s|&lt;|<|\[)([\w\-+.=]+@[-A-Z0-9.]*[A-Z0-9])
            # Pattern 11 to 13: Optional parameters
            ((\?)([^\s"<]*[\w+#?\/&=]))?
            # Pattern 14: Optional closing bracket
            (>)?
        /ix
EOR;

        return array('regexp_callback' => array(
            $this->_regexp => array($this, 'regexCallback')
        ));
    }

    /**
     * Regular expression callback.
     *
     * @param array $matches  preg_replace_callback() matches. See regex above
     *                        for description of matching data.
     *
     * @return string  Replacement string.
     */
    public function regexCallback($matches)
    {
        $data = $this->_regexCallback($matches);
        $secret = new Horde_Secret();
        if (empty($this->_params['secretKey'])) {
            $this->_params['secretKey'] = $secret->setKey();
        }
        if ($this->_params['encode']) {
            $data = "\01\01\01" . base64_encode($secret->write($this->_params['secretKey'], $data)) . "\01\01\01";
        }

        return $matches[1] . $matches[2] . (isset($matches[9]) ? $matches[9] : '') .
            $data .
            $matches[4] . $matches[8] . (isset($matches[14]) ? $matches[14] : '');
    }

    /**
     * Regular expression callback.
     *
     * @param array $matches  preg_replace_callback() matches. See regex above
     *                        for description of matching data.
     *
     * @return string  Replacement string.
     */
    protected function _regexCallback($matches)
    {
        $class = empty($this->_params['class'])
            ? ''
            : ' class="' . $this->_params['class'] . '"';
        $email = (!isset($matches[10]) || $matches[10] === '')
            ? $matches[3] . $matches[5]
            : $matches[10] . (isset($matches[11]) ? $matches[11] : '');

        return '<a' . $class . ' href="mailto:' . htmlspecialchars($email) . '">' . htmlspecialchars($email) . '</a>';
    }

    /**
     * "Decodes" the text formerly encoded by using the "encode" parameter.
     *
     * @param string $text  An encoded text.
     * @param string $key   An optional key to use with Horde_Secret encryption.
     *                      If omitted a key will be fetched from a Horde_Secret
     *                      instance.
     *
     * @return string  The decoded text.
     */
    public static function decode($text, $key = null)
    {
        $secret = new Horde_Secret();
        if (empty($key)) {
            $key = $secret->getKey();
        }
        return preg_replace_callback(
            '/\01\01\01([\w=+\/]*)\01\01\01/',
            function($hex) use ($secret, $key) {
                return  $secret->read($key, base64_decode($hex[1]));
            },
            $text);
    }

}
