code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Adds vimline to files
 */

chdir(dirname(__FILE__) . '/..');
$FS = new FSTools();

$vimline = 'vim: et sw=4 sts=4';

$files = $FS->globr('.', '*');
foreach ($files as $file) {
    if (
        !is_file($file) ||
        prefix_is('./docs/doxygen', $file) ||
        prefix_is('./library/standalone', $file) ||
        prefix_is('./docs/specimens', $file) ||
        postfix_is('.ser', $file) ||
        postfix_is('.tgz', $file) ||
        postfix_is('.patch', $file) ||
        postfix_is('.dtd', $file) ||
        postfix_is('.ent', $file) ||
        postfix_is('.png', $file) ||
        postfix_is('.ico', $file) ||
        // wontfix
        postfix_is('.vtest', $file) ||
        postfix_is('.svg', $file) ||
        postfix_is('.phpt', $file) ||
        postfix_is('VERSION', $file) ||
        postfix_is('WHATSNEW', $file) ||
        postfix_is('configdoc/usage.xml', $file) ||
        postfix_is('library/HTMLPurifier.includes.php', $file) ||
        postfix_is('library/HTMLPurifier.safe-includes.php', $file) ||
        postfix_is('smoketests/xssAttacks.xml', $file) ||
        // phpt files
        postfix_is('.diff', $file) ||
        postfix_is('.exp', $file) ||
        postfix_is('.log', $file) ||
        postfix_is('.out', $file) ||

        $file == './library/HTMLPurifier/Lexer/PH5P.php' ||
        $file == './maintenance/PH5P.php'
    ) continue;
    $ext = strrchr($file, '.');
    if (
        postfix_is('README', $file) ||
        postfix_is('LICENSE', $file) ||
        postfix_is('CREDITS', $file) ||
        postfix_is('INSTALL', $file) ||
        postfix_is('NEWS', $file) ||
        postfix_is('TODO', $file) ||
        postfix_is('WYSIWYG', $file) ||
        postfix_is('Changelog', $file)
    ) $ext = '.txt';
    if (postfix_is('Doxyfile', $file)) $ext = 'Doxyfile';
    if (postfix_is('.php.in', $file)) $ext = '.php';
    $no_nl = false;
    switch ($ext) {
        case '.php':
        case '.inc':
        case '.js':
            $line = '// %s';
            break;
        case '.html':
        case '.xsl':
        case '.xml':
        case '.htc':
            $line = "<!-- %s\n-->";
            break;
        case '.htmlt':
            $no_nl = true;
            $line = '--# %s';
            break;
        case '.ini':
            $line = '; %s';
            break;
        case '.css':
            $line = '/* %s */';
            break;
        case '.bat':
            $line = 'rem %s';
            break;
        case '.txt':
        case '.utf8':
            if (
                prefix_is('./library/HTMLPurifier/ConfigSchema', $file) ||
                prefix_is('./smoketests/test-schema', $file) ||
                prefix_is('./tests/HTMLPurifier/StringHashParser', $file)
            ) {
                $no_nl = true;
                $line = '--# %s';
            } else {
                $line = '    %s';
            }
            break;
        case 'Doxyfile':
            $line = '# %s';
            break;
        default:
            throw new Exception('Unknown file: ' . $file);
    }

    echo "$file\n";
    $contents = file_get_contents($file);

    $regex = '~' . str_replace('%s', 'vim: .+', preg_quote($line, '~')) .  '~m';
    $contents = preg_replace($regex, '', $contents);

    $contents = rtrim($contents);

    if (strpos($contents, "\r\n") !== false) $nl = "\r\n";
    elseif (strpos($contents, "\n") !== false) $nl = "\n";
    elseif (strpos($contents, "\r") !== false) $nl = "\r";
    else $nl = PHP_EOL;

    if (!$no_nl) $contents .= $nl;
    $contents .= $nl . str_replace('%s', $vimline, $line) . $nl;

    file_put_contents($file, $contents);

}

// vim: et sw=4 sts=4

repo_name: spookie/gnu

license: mit


code:
<?php

function assertCli()
{
    if (php_sapi_name() != 'cli' && !getenv('PHP_IS_CLI')) {
        echo 'Script cannot be called from web-browser (if you are indeed calling via cli,
set environment variable PHP_IS_CLI to work around this).';
        exit(1);
    }
}

function prefix_is($comp, $subject)
{
    return strncmp($comp, $subject, strlen($comp)) === 0;
}

function postfix_is($comp, $subject)
{
    return strlen($subject) < $comp ? false : substr($subject, -strlen($comp)) === $comp;
}

// Load useful stuff like FSTools
require_once dirname(__FILE__) . '/../extras/HTMLPurifierExtras.auto.php';

// vim: et sw=4 sts=4


code:
#!/bin/bash
cd ..
mkdir docs/doxygen
rm -Rf docs/doxygen/*
doxygen 1>docs/doxygen/info.log 2>docs/doxygen/errors.log
if [ "$?" != 0 ]; then
    cat docs/doxygen/errors.log
    exit
fi
cd docs
tar czf doxygen.tgz doxygen


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
require_once '../library/HTMLPurifier.auto.php';
assertCli();

if (version_compare(PHP_VERSION, '5.2.2', '<')) {
    echo "This script requires PHP 5.2.2 or later, for tokenizer line numbers.";
    exit(1);
}

/**
 * @file
 * Scans HTML Purifier source code for $config tokens and records the
 * directive being used; configdoc can use this info later.
 *
 * Currently, this just dumps all the info onto the console. Eventually, it
 * will create an XML file that our XSLT transform can use.
 */

$FS = new FSTools();
chdir(dirname(__FILE__) . '/../library/');
$raw_files = $FS->globr('.', '*.php');
$files = array();
foreach ($raw_files as $file) {
    $file = substr($file, 2); // rm leading './'
    if (strncmp('standalone/', $file, 11) === 0) continue; // rm generated files
    if (substr_count($file, '.') > 1) continue; // rm meta files
    $files[] = $file;
}

/**
 * Moves the $i cursor to the next non-whitespace token
 */
function consumeWhitespace($tokens, &$i)
{
    do {$i++;} while (is_array($tokens[$i]) && $tokens[$i][0] === T_WHITESPACE);
}

/**
 * Tests whether or not a token is a particular type. There are three run-cases:
 *      - ($token, $expect_token): tests if the token is $expect_token type;
 *      - ($token, $expect_value): tests if the token is the string $expect_value;
 *      - ($token, $expect_token, $expect_value): tests if token is $expect_token type, and
 *        its string representation is $expect_value
 */
function testToken($token, $value_or_token, $value = null)
{
    if (is_null($value)) {
        if (is_int($value_or_token)) return is_array($token) && $token[0] === $value_or_token;
        else return $token === $value_or_token;
    } else {
        return is_array($token) && $token[0] === $value_or_token && $token[1] === $value;
    }
}

$counter = 0;
$full_counter = 0;
$tracker = array();

foreach ($files as $file) {
    $tokens = token_get_all(file_get_contents($file));
    $file = str_replace('\\', '/', $file);
    for ($i = 0, $c = count($tokens); $i < $c; $i++) {
        $ok = false;
        // Match $config
        if (!$ok && testToken($tokens[$i], T_VARIABLE, '$config')) $ok = true;
        // Match $this->config
        while (!$ok && testToken($tokens[$i], T_VARIABLE, '$this')) {
            consumeWhitespace($tokens, $i);
            if (!testToken($tokens[$i], T_OBJECT_OPERATOR)) break;
            consumeWhitespace($tokens, $i);
            if (testToken($tokens[$i], T_STRING, 'config')) $ok = true;
            break;
        }
        if (!$ok) continue;

        $ok = false;
        for($i++; $i < $c; $i++) {
            if ($tokens[$i] === ',' || $tokens[$i] === ')' || $tokens[$i] === ';') {
                break;
            }
            if (is_string($tokens[$i])) continue;
            if ($tokens[$i][0] === T_OBJECT_OPERATOR) {
                $ok = true;
                break;
            }
        }
        if (!$ok) continue;

        $line = $tokens[$i][2];

        consumeWhitespace($tokens, $i);
        if (!testToken($tokens[$i], T_STRING, 'get')) continue;

        consumeWhitespace($tokens, $i);
        if (!testToken($tokens[$i], '(')) continue;

        $full_counter++;

        $matched = false;
        do {

            // What we currently don't match are batch retrievals, and
            // wildcard retrievals. This data might be useful in the future,
            // which is why we have a do {} while loop that doesn't actually
            // do anything.

            consumeWhitespace($tokens, $i);
            if (!testToken($tokens[$i], T_CONSTANT_ENCAPSED_STRING)) continue;
            $id = substr($tokens[$i][1], 1, -1);

            $counter++;
            $matched = true;

            if (!isset($tracker[$id])) $tracker[$id] = array();
            if (!isset($tracker[$id][$file])) $tracker[$id][$file] = array();
            $tracker[$id][$file][] = $line;

        } while (0);

        //echo "$file:$line uses $namespace.$directive\n";
    }
}

echo "\n$counter/$full_counter instances of \$config or \$this->config found in source code.\n";

echo "Generating XML... ";

$xw = new XMLWriter();
$xw->openURI('../configdoc/usage.xml');
$xw->setIndent(true);
$xw->startDocument('1.0', 'UTF-8');
$xw->startElement('usage');
foreach ($tracker as $id => $files) {
    $xw->startElement('directive');
    $xw->writeAttribute('id', $id);
    foreach ($files as $file => $lines) {
        $xw->startElement('file');
        $xw->writeAttribute('name', $file);
        foreach ($lines as $line) {
            $xw->writeElement('line', $line);
        }
        $xw->endElement();
    }
    $xw->endElement();
}
$xw->endElement();
$xw->flush();

echo "done!\n";

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Flushes the definition serial cache. This file should be
 * called if changes to any subclasses of HTMLPurifier_Definition
 * or related classes (such as HTMLPurifier_HTMLModule) are made. This
 * may also be necessary if you've modified a customized version.
 *
 * @param Accepts one argument, cache type to flush; otherwise flushes all
 *      the caches.
 */

echo "Flushing cache... \n";

require_once(dirname(__FILE__) . '/../library/HTMLPurifier.auto.php');

$config = HTMLPurifier_Config::createDefault();

$names = array('HTML', 'CSS', 'URI', 'Test');
if (isset($argv[1])) {
    if (in_array($argv[1], $names)) {
        $names = array($argv[1]);
    } else {
        throw new Exception("Cache parameter {$argv[1]} is not a valid cache");
    }
}

foreach ($names as $name) {
    echo " - Flushing $name\n";
    $cache = new HTMLPurifier_DefinitionCache_Serializer($name);
    $cache->flush($config);
}

echo "Cache flushed successfully.\n";

// vim: et sw=4 sts=4


code:
#!/bin/bash
set -ex
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
php "$DIR/generate-includes.php"
php "$DIR/generate-schema-cache.php"
php "$DIR/flush-definition-cache.php"
php "$DIR/generate-standalone.php"
php "$DIR/config-scanner.php"


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Parses *.ent files into an entity lookup table, and then serializes and
 * writes the whole kaboodle to a file. The resulting file is cached so
 * that this script does not need to be run. This script should rarely,
 * if ever, be run, since HTML's entities are fairly immutable.
 */

// here's where the entity files are located, assuming working directory
// is the same as the location of this PHP file. Needs trailing slash.
$entity_dir = '../docs/entities/';

// defines the output file for the serialized content.
$output_file = '../library/HTMLPurifier/EntityLookup/entities.ser';

// courtesy of a PHP manual comment
function unichr($dec)
{
    if ($dec < 128) {
        $utf  = chr($dec);
    } elseif ($dec < 2048) {
        $utf  = chr(192 + (($dec - ($dec % 64)) / 64));
        $utf .= chr(128 + ($dec % 64));
    } else {
        $utf  = chr(224 + (($dec - ($dec % 4096)) / 4096));
        $utf .= chr(128 + ((($dec % 4096) - ($dec % 64)) / 64));
        $utf .= chr(128 + ($dec % 64));
    }
    return $utf;
}

if ( !is_dir($entity_dir) ) exit("Fatal Error: Can't find entity directory.\n");
if ( file_exists($output_file) ) exit("Fatal Error: output file already exists.\n");

$dh = @opendir($entity_dir);
if ( !$dh ) exit("Fatal Error: Cannot read entity directory.\n");

$entity_files = array();
while (($file = readdir($dh)) !== false) {
    if (@$file[0] === '.') continue;
    if (substr(strrchr($file, "."), 1) !== 'ent') continue;
    $entity_files[] = $file;
}
closedir($dh);

if ( !$entity_files ) exit("Fatal Error: No entity files to parse.\n");

$entity_table = array();
$regexp = '/<!ENTITY\s+([A-Za-z0-9]+)\s+"&#(?:38;#)?([0-9]+);">/';

foreach ( $entity_files as $file ) {
    $contents = file_get_contents($entity_dir . $file);
    $matches = array();
    preg_match_all($regexp, $contents, $matches, PREG_SET_ORDER);
    foreach ($matches as $match) {
        $entity_table[$match[1]] = unichr($match[2]);
    }
}

$output = serialize($entity_table);

$fh = fopen($output_file, 'w');
fwrite($fh, $output);
fclose($fh);

echo "Completed successfully.";

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
require_once '../tests/path2class.func.php';
require_once '../library/HTMLPurifier/Bootstrap.php';
assertCli();

/**
 * @file
 * Generates an include stub for users who do not want to use the autoloader.
 * When new files are added to HTML Purifier's main codebase, this file should
 * be called.
 */

chdir(dirname(__FILE__) . '/../library/');
$FS = new FSTools();

$exclude_dirs = array(
    'HTMLPurifier/Language/',
    'HTMLPurifier/ConfigSchema/',
    'HTMLPurifier/Filter/',
    'HTMLPurifier/Printer/',
    /* These should be excluded, but need to have ConfigSchema support first

    */
);
$exclude_files = array(
    'HTMLPurifier/Lexer/PEARSax3.php',
    'HTMLPurifier/Lexer/PH5P.php',
    'HTMLPurifier/Printer.php',
);

// Determine what files need to be included:
echo 'Scanning for files... ';
$raw_files = $FS->globr('.', '*.php');
if (!$raw_files) throw new Exception('Did not find any PHP source files');
$files = array();
foreach ($raw_files as $file) {
    $file = substr($file, 2); // rm leading './'
    if (strncmp('standalone/', $file, 11) === 0) continue; // rm generated files
    if (substr_count($file, '.') > 1) continue; // rm meta files
    $ok = true;
    foreach ($exclude_dirs as $dir) {
        if (strncmp($dir, $file, strlen($dir)) === 0) {
            $ok = false;
            break;
        }
    }
    if (!$ok) continue; // rm excluded directories
    if (in_array($file, $exclude_files)) continue; // rm excluded files
    $files[] = $file;
}
echo "done!\n";

// Reorder list so that dependencies are included first:

/**
 * Returns a lookup array of dependencies for a file.
 *
 * @note This function expects that format $name extends $parent on one line
 *
 * @param string $file
 *      File to check dependencies of.
 * @return array
 *      Lookup array of files the file is dependent on, sorted accordingly.
 */
function get_dependency_lookup($file)
{
    static $cache = array();
    if (isset($cache[$file])) return $cache[$file];
    if (!file_exists($file)) {
        echo "File doesn't exist: $file\n";
        return array();
    }
    $fh = fopen($file, 'r');
    $deps = array();
    while (!feof($fh)) {
        $line = fgets($fh);
        if (strncmp('class', $line, 5) === 0) {
            // The implementation here is fragile and will break if we attempt
            // to use interfaces. Beware!
            $arr = explode(' extends ', trim($line, ' {'."\n\r"), 2);
            if (count($arr) < 2) break;
            $parent = $arr[1];
            $dep_file = HTMLPurifier_Bootstrap::getPath($parent);
            if (!$dep_file) break;
            $deps[$dep_file] = true;
            break;
        }
    }
    fclose($fh);
    foreach (array_keys($deps) as $file) {
        // Extra dependencies must come *before* base dependencies
        $deps = get_dependency_lookup($file) + $deps;
    }
    $cache[$file] = $deps;
    return $deps;
}

/**
 * Sorts files based on dependencies. This function is lazy and will not
 * group files with dependencies together; it will merely ensure that a file
 * is never included before its dependencies are.
 *
 * @param $files
 *      Files array to sort.
 * @return
 *      Sorted array ($files is not modified by reference!)
 */
function dep_sort($files)
{
    $ret = array();
    $cache = array();
    foreach ($files as $file) {
        if (isset($cache[$file])) continue;
        $deps = get_dependency_lookup($file);
        foreach (array_keys($deps) as $dep) {
            if (!isset($cache[$dep])) {
                $ret[] = $dep;
                $cache[$dep] = true;
            }
        }
        $cache[$file] = true;
        $ret[] = $file;
    }
    return $ret;
}

$files = dep_sort($files);

// Build the actual include stub:

$version = trim(file_get_contents('../VERSION'));

// stub
$php = "<?php

/**
 * @file
 * This file was auto-generated by generate-includes.php and includes all of
 * the core files required by HTML Purifier. Use this if performance is a
 * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
 * FILE, changes will be overwritten the next time the script is run.
 *
 * @version $version
 *
 * @warning
 *      You must *not* include any other HTML Purifier files before this file,
 *      because 'require' not 'require_once' is used.
 *
 * @warning
 *      This file requires that the include path contains the HTML Purifier
 *      library directory; this is not auto-set.
 */

";

foreach ($files as $file) {
    $php .= "require '$file';" . PHP_EOL;
}

echo "Writing HTMLPurifier.includes.php... ";
file_put_contents('HTMLPurifier.includes.php', $php);
echo "done!\n";

$php = "<?php

/**
 * @file
 * This file was auto-generated by generate-includes.php and includes all of
 * the core files required by HTML Purifier. This is a convenience stub that
 * includes all files using dirname(__FILE__) and require_once. PLEASE DO NOT
 * EDIT THIS FILE, changes will be overwritten the next time the script is run.
 *
 * Changes to include_path are not necessary.
 */

\$__dir = dirname(__FILE__);

";

foreach ($files as $file) {
    $php .= "require_once \$__dir . '/$file';" . PHP_EOL;
}

echo "Writing HTMLPurifier.safe-includes.php... ";
file_put_contents('HTMLPurifier.safe-includes.php', $php);
echo "done!\n";

// vim: et sw=4 sts=4


code:
<?php

/**
 * @file
 * This file compares our version of PH5P with Jero's original version, and
 * generates a patch of the differences. This script should be run whenever
 * library/HTMLPurifier/Lexer/PH5P.php is modified.
 */

$orig = realpath(dirname(__FILE__) . '/PH5P.php');
$new  = realpath(dirname(__FILE__) . '/../library/HTMLPurifier/Lexer/PH5P.php');
$newt = dirname(__FILE__) . '/PH5P.new.php'; // temporary file

// minor text-processing of new file to get into same format as original
$new_src = file_get_contents($new);
$new_src = '<?php' . PHP_EOL . substr($new_src, strpos($new_src, 'class HTML5 {'));

file_put_contents($newt, $new_src);
shell_exec("diff -u \"$orig\" \"$newt\" > PH5P.patch");
unlink($newt);

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

require_once dirname(__FILE__) . '/common.php';
require_once dirname(__FILE__) . '/../library/HTMLPurifier.auto.php';
assertCli();

/**
 * @file
 * Generates a schema cache file, saving it to
 * library/HTMLPurifier/ConfigSchema/schema.ser.
 *
 * This should be run when new configuration options are added to
 * HTML Purifier. A cached version is available via the repository
 * so this does not normally have to be regenerated.
 *
 * If you have a directory containing custom configuration schema files,
 * you can simple add a path to that directory as a parameter to
 * this, and they will get included.
 */

$target = dirname(__FILE__) . '/../library/HTMLPurifier/ConfigSchema/schema.ser';

$builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
$interchange = new HTMLPurifier_ConfigSchema_Interchange();

$builder->buildDir($interchange);

$loader = dirname(__FILE__) . '/../config-schema.php';
if (file_exists($loader)) include $loader;
foreach ($_SERVER['argv'] as $i => $dir) {
    if ($i === 0) continue;
    $builder->buildDir($interchange, realpath($dir));
}

$interchange->validate();

$schema_builder = new HTMLPurifier_ConfigSchema_Builder_ConfigSchema();
$schema = $schema_builder->build($interchange);

echo "Saving schema... ";
file_put_contents($target, serialize($schema));
echo "done!\n";

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Compiles all of HTML Purifier's library files into one big file
 * named HTMLPurifier.standalone.php. This is usually called during the
 * release process.
 */

/**
 * Global hash that tracks already loaded includes
 */
$GLOBALS['loaded'] = array();

/**
 * Custom FSTools for this script that overloads some behavior
 * @warning The overloading of copy() is not necessarily global for
 *          this script. Watch out!
 */
class MergeLibraryFSTools extends FSTools
{
    public function copyable($entry)
    {
        // Skip hidden files
        if ($entry[0] == '.') {
            return false;
        }
        return true;
    }
    public function copy($source, $dest)
    {
        copy_and_remove_includes($source, $dest);
    }
}
$FS = new MergeLibraryFSTools();

/**
 * Replaces the includes inside PHP source code with the corresponding
 * source.
 * @param string $text PHP source code to replace includes from
 */
function replace_includes($text)
{
    // also remove vim modelines
    return preg_replace_callback(
        "/require(?:_once)? ['\"]([^'\"]+)['\"];/",
        'replace_includes_callback',
        $text
    );
}

/**
 * Removes leading PHP tags from included files. Assumes that there is
 * no trailing tag. Also removes vim modelines.
 * @note This is safe for files that have internal <?php
 * @param string $text Text to have leading PHP tag from
 */
function remove_php_tags($text)
{
    $text = preg_replace('#// vim:.+#', '', $text);
    return substr($text, 5);
}

/**
 * Copies the contents of a directory to the standalone directory
 * @param string $dir Directory to copy
 */
function make_dir_standalone($dir)
{
    global $FS;
    return $FS->copyr($dir, 'standalone/' . $dir);
}

/**
 * Copies the contents of a file to the standalone directory
 * @param string $file File to copy
 */
function make_file_standalone($file)
{
    global $FS;
    $FS->mkdirr('standalone/' . dirname($file));
    copy_and_remove_includes($file, 'standalone/' . $file);
    return true;
}

/**
 * Copies a file to another location recursively, if it is a PHP file
 * remove includes
 * @param string $file Original file
 * @param string $sfile New location of file
 */
function copy_and_remove_includes($file, $sfile)
{
    $contents = file_get_contents($file);
    if (strrchr($file, '.') === '.php') $contents = replace_includes($contents);
    return file_put_contents($sfile, $contents);
}

/**
 * @param $matches preg_replace_callback matches array, where index 1
 *        is the filename to include
 */
function replace_includes_callback($matches)
{
    $file = $matches[1];
    $preserve = array(
      // PEAR (external)
      'XML/HTMLSax3.php' => 1
    );
    if (isset($preserve[$file])) {
        return $matches[0];
    }
    if (isset($GLOBALS['loaded'][$file])) return '';
    $GLOBALS['loaded'][$file] = true;
    return replace_includes(remove_php_tags(file_get_contents($file)));
}

echo 'Generating includes file... ';
shell_exec('php generate-includes.php');
echo "done!\n";

chdir(dirname(__FILE__) . '/../library/');

echo 'Creating full file...';
$contents = replace_includes(file_get_contents('HTMLPurifier.includes.php'));
$contents = str_replace(
    // Note that bootstrap is now inside the standalone file
    "define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..'));",
    "define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
    set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());",
    $contents
);
file_put_contents('HTMLPurifier.standalone.php', $contents);
echo ' done!' . PHP_EOL;

echo 'Creating standalone directory...';
$FS->rmdirr('standalone'); // ensure a clean copy

// data files
$FS->mkdirr('standalone/HTMLPurifier/DefinitionCache/Serializer');
make_file_standalone('HTMLPurifier/EntityLookup/entities.ser');
make_file_standalone('HTMLPurifier/ConfigSchema/schema.ser');

// non-standard inclusion setup
make_dir_standalone('HTMLPurifier/ConfigSchema');
make_dir_standalone('HTMLPurifier/Language');
make_dir_standalone('HTMLPurifier/Filter');
make_dir_standalone('HTMLPurifier/Printer');
make_file_standalone('HTMLPurifier/Printer.php');
make_file_standalone('HTMLPurifier/Lexer/PH5P.php');

echo ' done!' . PHP_EOL;

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

/**
 * @file
 * Deprecated in favor of generate-standalone.php.
 */

require dirname(__FILE__) . '/generate-standalone.php';

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

echo "Please do not run this script. It is here for historical purposes only.";
exit;

/**
 * @file
 * Extracts all definitions inside a configuration schema
 * (HTMLPurifier_ConfigSchema) and exports them as plain text files.
 *
 * @todo Extract version numbers.
 */

define('HTMLPURIFIER_SCHEMA_STRICT', true); // description data needs to be collected
require_once dirname(__FILE__) . '/../library/HTMLPurifier.auto.php';

// We need includes to ensure all HTMLPurifier_ConfigSchema calls are
// performed.
require_once 'HTMLPurifier.includes.php';

// Also, these extra files will be necessary.
require_once 'HTMLPurifier/Filter/ExtractStyleBlocks.php';

/**
 * Takes a hash and saves its contents to library/HTMLPurifier/ConfigSchema/
 */
function saveHash($hash)
{
    if ($hash === false) return;
    $dir = realpath(dirname(__FILE__) . '/../library/HTMLPurifier/ConfigSchema');
    $name = $hash['ID'] . '.txt';
    $file = $dir . '/' . $name;
    if (file_exists($file)) {
        trigger_error("File already exists; skipped $name");
        return;
    }
    $file = new FSTools_File($file);
    $file->open('w');
    $multiline = false;
    foreach ($hash as $key => $value) {
        $multiline = $multiline || (strpos($value, "\n") !== false);
        if ($multiline) {
            $file->put("--$key--" . PHP_EOL);
            $file->put(str_replace("\n", PHP_EOL, $value) . PHP_EOL);
        } else {
            if ($key == 'ID') {
                $file->put("$value" . PHP_EOL);
            } else {
                $file->put("$key: $value" . PHP_EOL);
            }
        }
    }
    $file->close();
}

$schema  = HTMLPurifier_ConfigSchema::instance();
$adapter = new HTMLPurifier_ConfigSchema_StringHashReverseAdapter($schema);

foreach ($schema->info as $ns => $ns_array) {
    saveHash($adapter->get($ns));
    foreach ($ns_array as $dir => $x) {
        saveHash($adapter->get($ns, $dir));
    }
}

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

echo "Please do not run this script. It is here for historical purposes only.";
exit;

/**
 * @file
 * Removes leading includes from files.
 *
 * @note
 *      This does not remove inline includes; those must be handled manually.
 */

chdir(dirname(__FILE__) . '/../tests/HTMLPurifier');
$FS = new FSTools();

$files = $FS->globr('.', '*.php');
foreach ($files as $file) {
    if (substr_count(basename($file), '.') > 1) continue;
    $old_code = file_get_contents($file);
    $new_code = preg_replace("#^require_once .+[\n\r]*#m", '', $old_code);
    if ($old_code !== $new_code) {
        file_put_contents($file, $new_code);
    }
}

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

echo "Please do not run this script. It is here for historical purposes only.";
exit;

/**
 * @file
 * Removes ConfigSchema function calls from source files.
 */

chdir(dirname(__FILE__) . '/../library/');
$FS = new FSTools();

$files = $FS->globr('.', '*.php');
foreach ($files as $file) {
    if (substr_count(basename($file), '.') > 1) continue;
    $old_code = file_get_contents($file);
    $new_code = preg_replace("#^HTMLPurifier_ConfigSchema::.+?\);[\n\r]*#ms", '', $old_code);
    if ($old_code !== $new_code) {
        file_put_contents($file, $new_code);
    }
    if (preg_match('#^\s+HTMLPurifier_ConfigSchema::#m', $new_code)) {
        echo "Indented ConfigSchema call in $file\n";
    }
}

// vim: et sw=4 sts=4


code:
#!/bin/bash -e
./compile-doxygen.sh
cd ../docs
scp doxygen.tgz htmlpurifier.org:/home/ezyang/htmlpurifier.org
ssh htmlpurifier.org "cd /home/ezyang/htmlpurifier.org && ./reload-docs.sh"


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Removes trailing whitespace from files.
 */

chdir(dirname(__FILE__) . '/..');
$FS = new FSTools();

$files = $FS->globr('.', '{,.}*', GLOB_BRACE);
foreach ($files as $file) {
    if (
        !is_file($file) ||
        prefix_is('./.git', $file) ||
        prefix_is('./docs/doxygen', $file) ||
        postfix_is('.ser', $file) ||
        postfix_is('.tgz', $file) ||
        postfix_is('.patch', $file) ||
        postfix_is('.dtd', $file) ||
        postfix_is('.ent', $file) ||
        $file == './library/HTMLPurifier/Lexer/PH5P.php' ||
        $file == './maintenance/PH5P.php'
    ) continue;
    $contents = file_get_contents($file);
    $result = preg_replace('/^(.*?)[ \t]+(\r?)$/m', '\1\2', $contents, -1, $count);
    if (!$count) continue;
    echo "$file\n";
    file_put_contents($file, $result);
}

// vim: et sw=4 sts=4


code:
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
require_once '../library/HTMLPurifier.auto.php';
assertCli();

/**
 * @file
 * Renames a configuration directive.  This involves renaming the file,
 * adding an alias, and then regenerating the cache.  You still have to
 * manually go through and fix any calls to the directive.
 * @warning This script doesn't handle multi-stringhash files.
 */

$argv = $_SERVER['argv'];
if (count($argv) < 3) {
    echo "Usage: {$argv[0]} OldName NewName\n";
    exit(1);
}

chdir('../library/HTMLPurifier/ConfigSchema/schema');

$old = $argv[1];
$new = $argv[2];

if (!file_exists("$old.txt")) {
    echo "Cannot move undefined configuration directive $old\n";
    exit(1);
}

if ($old === $new) {
    echo "Attempting to move to self, aborting\n";
    exit(1);
}

if (file_exists("$new.txt")) {
    echo "Cannot move to already defined directive $new\n";
    exit(1);
}

$file = "$old.txt";
$builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
$interchange = new HTMLPurifier_ConfigSchema_Interchange();
$builder->buildFile($interchange, $file);
$contents = file_get_contents($file);

if (strpos($contents, "\r\n") !== false) {
    $nl = "\r\n";
} elseif (strpos($contents, "\r") !== false) {
    $nl = "\r";
} else {
    $nl = "\n";
}

// replace name with new name
$contents = str_replace($old, $new, $contents);

if ($interchange->directives[$old]->aliases) {
    $pos_alias = strpos($contents, 'ALIASES:');
    $pos_ins = strpos($contents, $nl, $pos_alias);
    if ($pos_ins === false) $pos_ins = strlen($contents);
    $contents =
        substr($contents, 0, $pos_ins) . ", $old" . substr($contents, $pos_ins);
    file_put_contents($file, $contents);
} else {
    $lines = explode($nl, $contents);
    $insert = false;
    foreach ($lines as $n => $line) {
        if (strncmp($line, '--', 2) === 0) {
            $insert = $n;
            break;
        }
    }
    if (!$insert) {
        $lines[] = "ALIASES: $old";
    } else {
        array_splice($lines, $insert, 0, "ALIASES: $old");
    }
    file_put_contents($file, implode($nl, $lines));
}

rename("$old.txt", "$new.txt") || exit(1);


code:
<?php

set_time_limit(0);

require_once 'PEAR/PackageFileManager2.php';
require_once 'PEAR/PackageFileManager/File.php';
PEAR::setErrorHandling(PEAR_ERROR_PRINT);
$pkg = new PEAR_PackageFileManager2;

$pkg->setOptions(
    array(
        'baseinstalldir' => '/',
        'packagefile' => 'package.xml',
        'packagedirectory' => realpath(dirname(__FILE__) . '/library'),
        'filelistgenerator' => 'file',
        'include' => array('*'),
        'dir_roles' => array('/' => 'php'), // hack to put *.ser files in the right place
        'ignore' => array(
            'HTMLPurifier.standalone.php',
            'HTMLPurifier.path.php',
            '*.tar.gz',
            '*.tgz',
            'standalone/'
        ),
    )
);

$pkg->setPackage('HTMLPurifier');
$pkg->setLicense('LGPL', 'http://www.gnu.org/licenses/lgpl.html');
$pkg->setSummary('Standards-compliant HTML filter');
$pkg->setDescription(
    'HTML Purifier is an HTML filter that will remove all malicious code
    (better known as XSS) with a thoroughly audited, secure yet permissive
    whitelist and will also make sure your documents are standards
    compliant.'
);

$pkg->addMaintainer('lead', 'ezyang', 'Edward Z. Yang', 'admin@htmlpurifier.org', 'yes');

$version = trim(file_get_contents('VERSION'));
$api_version = substr($version, 0, strrpos($version, '.'));

$pkg->setChannel('htmlpurifier.org');
$pkg->setAPIVersion($api_version);
$pkg->setAPIStability('stable');
$pkg->setReleaseVersion($version);
$pkg->setReleaseStability('stable');

$pkg->addRelease();

$pkg->setNotes(file_get_contents('WHATSNEW'));
$pkg->setPackageType('php');

$pkg->setPhpDep('5.0.0');
$pkg->setPearinstallerDep('1.4.3');

$pkg->generateContents();

$pkg->writePackageFile();

// vim: et sw=4 sts=4


code:
;; phpDocumentor parse configuration file
;;
;; This file is designed to cut down on repetitive typing on the command-line or web interface
;; You can copy this file to create a number of configuration files that can be used with the
;; command-line switch -c, as in phpdoc -c default.ini or phpdoc -c myini.ini.  The web
;; interface will automatically generate a list of .ini files that can be used.
;;
;; default.ini is used to generate the online manual at http://www.phpdoc.org/docs
;;
;; ALL .ini files must be in the user subdirectory of phpDocumentor with an extension of .ini
;;
;; Copyright 2002, Greg Beaver <cellog@users.sourceforge.net>
;;
;; WARNING: do not change the name of any command-line parameters, phpDocumentor will ignore them

[Parse Data]
;; title of all the documentation
;; legal values: any string
title = HTML Purifier API Documentation

;; parse files that start with a . like .bash_profile
;; legal values: true, false
hidden = false

;; show elements marked @access private in documentation by setting this to on
;; legal values: on, off
parseprivate = off

;; parse with javadoc-like description (first sentence is always the short description)
;; legal values: on, off
javadocdesc = on

;; add any custom @tags separated by commas here
;; legal values: any legal tagname separated by commas.
;customtags = mytag1,mytag2

;; This is only used by the XML:DocBook/peardoc2 converter
defaultcategoryname = Documentation

;; what is the main package?
;; legal values: alphanumeric string plus - and _
defaultpackagename = HTMLPurifier

;; output any parsing information?  set to on for cron jobs
;; legal values: on
;quiet = on

;; parse a PEAR-style repository.  Do not turn this on if your project does
;; not have a parent directory named "pear"
;; legal values: on/off
;pear = on

;; where should the documentation be written?
;; legal values: a legal path
target = docs/phpdoc

;; Which files should be parsed out as special documentation files, such as README,
;; INSTALL and CHANGELOG?  This overrides the default files found in
;; phpDocumentor.ini (this file is not a user .ini file, but the global file)
readmeinstallchangelog = README, INSTALL, NEWS, WYSIWYG, SLOW, LICENSE, CREDITS

;; limit output to the specified packages, even if others are parsed
;; legal values: package names separated by commas
;packageoutput = package1,package2

;; comma-separated list of files to parse
;; legal values: paths separated by commas
;filename = /path/to/file1,/path/to/file2,fileincurrentdirectory

;; comma-separated list of directories to parse
;; legal values: directory paths separated by commas
;directory = /path1,/path2,.,..,subdirectory
;directory = /home/jeichorn/cvs/pear
directory = .

;; template base directory (the equivalent directory of <installdir>/phpDocumentor)
;templatebase = /path/to/my/templates

;; directory to find any example files in through @example and {@example} tags
;examplesdir = /path/to/my/templates

;; comma-separated list of files, directories or wildcards ? and * (any wildcard) to ignore
;; legal values: any wildcard strings separated by commas
;ignore = /path/to/ignore*,*list.php,myfile.php,subdirectory/
ignore = *tests*,*benchmarks*,*docs*,*test-settings.php,*configdoc*,*maintenance*,*smoketests*,*standalone*,*.svn*,*conf*

sourcecode = on

;; comma-separated list of Converters to use in outputformat:Convertername:templatedirectory format
;; legal values: HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib,
;;               HTML:frames:earthli,
;;               HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de,
;;               HTML:frames:DOM/phphtmllib,HTML:frames:DOM/earthli
;;               HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS
;;               PDF:default:default,CHM:default:default,XML:DocBook/peardoc2:default
output=HTML:frames:default

;; turn this option on if you want highlighted source code for every file
;; legal values: on/off
sourcecode = on

; vim: et sw=4 sts=4


code:
MODx Plugin

MODx <http://www.modxcms.com/> is an open source PHP application framework.
I first came across them in my referrer logs when tillda asked if anyone
could implement an HTML Purifier plugin.  This forum thread
<http://modxcms.com/forums/index.php/topic,6604.0.html> eventually resulted
in the fruition of this plugin that davidm says, "is on top of my favorite
list."  HTML Purifier goes great with WYSIWYG editors!

1. Credits

PaulGregory wrote the overall structure of the code.  I added the
slashes hack.

2. Install

First, you need to place HTML Purifier library somewhere.  The code here
assumes that you've placed in MODx's assets/plugins/htmlpurifier (no version
number).

Log into the manager, and navigate:

Resources > Manage Resources > Plugins tab > New Plugin

Type in a name (probably HTML Purifier), and copy paste this code into the
textarea:

--------------------------------------------------------------------------------
$e = &$modx->Event;
if ($e->name == 'OnBeforeDocFormSave') {
    global $content;

    include_once '../assets/plugins/htmlpurifier/library/HTMLPurifier.auto.php';
    $purifier = new HTMLPurifier();

    static $magic_quotes = null;
    if ($magic_quotes === null) {
        // this is an ugly hack because this hook hasn't
        // had the backslashes removed yet when magic_quotes_gpc is on,
        // but HTMLPurifier must not have the quotes slashed.
        $magic_quotes = get_magic_quotes_gpc();
    }

    if ($magic_quotes) $content = stripslashes($content);
    $content = $purifier->purify($content);
    if ($magic_quotes) $content = addslashes($content);
}
--------------------------------------------------------------------------------

Then navigate to the System Events tab and check "OnBeforeDocFormSave".
Save the plugin.  HTML Purifier now is integrated!

3. Making sure it works

You can test HTML Purifier by deliberately putting in crappy HTML and seeing
whether or not it gets fixed.  A better way is to put in something like this:

Il est bon

...and seeing whether or not the content comes out as:

Il est bon

(lang to xml:lang synchronization is one of the many features HTML Purifier
has).

4. Caveat Emptor

This code does not intercept save requests from the QuickEdit plugin, this may
be added in a later version.  It also modifies things on save, so there's a
slight chance that HTML Purifier may make a boo-boo and accidently mess things
up (the original version is not saved).

Finally, make sure that MODx is using UTF-8.  If you are using, say, a French
localisation, you may be using Latin-1, if that's the case, configure
HTML Purifier properly like this:

$config = HTMLPurifier_Config::createDefault();
$config->set('Core', 'Encoding', 'ISO-8859-1'); // or whatever encoding
$purifier = new HTMLPurifier($config);

5. Known Bugs

'rn' characters sometimes mysteriously appear after purification. We are
currently investigating this issue. See: <http://htmlpurifier.org/phorum/read.php?3,1866>

6. See Also

A modified version of Jot 1.1.3 is available, which integrates with HTML
Purifier. You can check it out here: <http://modxcms.com/forums/index.php/topic,25621.msg161970.html>

X. Changelog

2008-06-16
- Updated code to work with 3.1.0 and later
- Add Known Bugs and See Also section

    vim: et sw=4 sts=4


code:
migrate.php
htmlpurifier/*


code:
Changelog                                         HTMLPurifier : Phorum Mod
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

= KEY ====================
    # Breaks back-compat
    ! Feature
    - Bugfix
      + Sub-comment
    . Internal change
==========================

Version 4.0.0 for Phorum 5.2, released July 9, 2009
# Works only with HTML Purifier 4.0.0
! Better installation documentation
- Fixed double encoded quotes
- Fixed fatal error when migrate.php is blank

Version 3.0.0 for Phorum 5.2, released January 12, 2008
# WYSIWYG and suppress_message options are now configurable via web
  interface.
- Module now compatible with Phorum 5.2, primary bugs were in migration
  code as well as signature and edit message handling. This module is NOT
  compatible with Phorum 5.1.
- Buggy WYSIWYG mode refined
. AutoFormatParam added to list of default configuration namespaces

    vim: et sw=4 sts=4


code:
Install
    How to install the Phorum HTML Purifier plugin

0. PREREQUISITES
----------------
This Phorum module only works on PHP5 and with HTML Purifier 4.0.0
or later.

1. UNZIP
--------
Unzip phorum-htmlpurifier-x.y.z, producing an htmlpurifier folder.
You've already done this step if you're reading this!

2. MOVE
-------
Move the htmlpurifier folder to the mods/ folder of your Phorum
installation, so the directory structure looks like:

phorum/
    mods/
        htmlpurifier/
            INSTALL - this install file
            info.txt, ... - the module files
            htmlpurifier/

3. INSTALL HTML PURIFIER
------------------------
Download and unzip HTML Purifier <htmlpurifier.org>. Place the contents of
the library/ folder in the htmlpurifier/htmlpurifier folder. Your directory
structure will look like:

phorum/
    mods/
        htmlpurifier/
            htmlpurifier/
                HTMLPurifier.auto.php
                ... - other files
                HTMLPurifier/

Advanced users:
    If you have HTML Purifier installed elsewhere on your server,
    all you need is an HTMLPurifier.auto.php file in the library folder which
    includes the HTMLPurifier.auto.php file in your install.

4. MIGRATE
----------
If you're setting up a new Phorum installation, all you need to do is create
a blank migrate.php file in the htmlpurifier module folder (NOT the library
folder.

If you have an old Phorum installation and was using BBCode,
copy migrate.bbcode.php to migrate.php. If you were using a different input
format, follow the instructions in migrate.bbcode.php to create your own custom
migrate.php file.

Your directory structure should now look like this:

phorum/
    mods/
        htmlpurifier/
            migrate.php

5. ENABLE
---------
Navigate to your Phorum admin panel at http://example.com/phorum/admin.php,
click on Global Settings > Modules, scroll to "HTML Purifier Phorum Mod" and
turn it On.

6. MIGRATE SIGNATURES
---------------------
If you're setting up a new Phorum installation, skip this step.

If you allowed your users to make signatures, navigate to the module settings
page of HTML Purifier (Global Settings > Modules > HTML Purifier Phorum Mod >
Configure), type in "yes" in the "Confirm" box, and press "Migrate."

ONLY DO THIS ONCE! BE SURE TO BACK UP YOUR DATABASE!

7. CONFIGURE
------------
Configure using Edit settings. See that page for more information.

    vim: et sw=4 sts=4


code:
HTML Purifier Phorum Mod - Filter your HTML the Standards-Compliant Way!

This Phorum mod enables HTML posting on Phorum.  Under normal circumstances,
this would cause a huge security risk, but because we are running
HTML through HTML Purifier, output is guaranteed to be XSS free and
standards-compliant.

This mod requires HTML input, and previous markup languages need to be
converted accordingly.  Thus, it is vital that you create a 'migrate.php'
file that works with your installation. If you're using the built-in
BBCode formatting, simply move migrate.bbcode.php to that place; for
other markup languages, consult said file for instructions on how
to adapt it to your needs.

    -- NOTE -------------------------------------------------
    You can also run this module in parallel with another
    formatting module; this module attempts to place itself
    at the end of the filtering chain. However, if any
    previous modules produce insecure HTML (for instance,
    a JavaScript email obfuscator) they will get cleaned.

This module will not work if 'migrate.php' is not created, and an improperly
made migration file may *CORRUPT* Phorum, so please take your time to
do this correctly. It should go without saying to *BACKUP YOUR DATABASE*
before attempting anything here. If no migration is necessary, you can
simply create a blank migrate.php file. HTML Purifier is smart and will
not re-migrate already processed messages. However, the original code
is irretrievably lost (we may change this in the future.)

This module will not automatically migrate user signatures, because this
process may take a long time. After installing the HTML Purifier module and
then configuring 'migrate.php', navigate to Settings and click 'Migrate
Signatures' to migrate all user signatures to HTML.

All of HTML Purifier's usual functions are configurable via the mod settings
page. If you require custom configuration, create config.php file in
the mod directory that edits a $config variable. Be sure, also, to
set $PHORUM['mod_htmlpurifier']['wysiwyg'] to TRUE if you are using a
WYSIWYG editor (you can do this through a common hook or the web
configuration form).

Visit HTML Purifier at <http://htmlpurifier.org/>.

    vim: et sw=4 sts=4


code:
<?php

if(!defined("PHORUM")) exit;

// default HTML Purifier configuration settings
$config->set('HTML.Allowed',
  // alphabetically sorted
'a[href|title]
abbr[title]
acronym[title]
b
blockquote[cite]
br
caption
cite
code
dd
del
dfn
div
dl
dt
em
i
img[src|alt|title|class]
ins
kbd
li
ol
p
pre
s
strike
strong
sub
sup
table
tbody
td
tfoot
th
thead
tr
tt
u
ul
var');
$config->set('AutoFormat.AutoParagraph', true);
$config->set('AutoFormat.Linkify', true);
$config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
$config->set('Core.AggressivelyFixLt', true);
$config->set('Core.Encoding', $GLOBALS['PHORUM']['DATA']['CHARSET']); // we'll change this eventually
if (strtolower($GLOBALS['PHORUM']['DATA']['CHARSET']) !== 'utf-8') {
  $config->set('Core.EscapeNonASCIICharacters', true);
}
$config->set('Core.AllowParseManyTags', false);

// vim: et sw=4 sts=4


code:
<?php

/**
 * HTML Purifier Phorum Mod. Filter your HTML the Standards-Compliant Way!
 *
 * This Phorum mod enables users to post raw HTML into Phorum.  But never
 * fear: with the help of HTML Purifier, this HTML will be beat into
 * de-XSSed and standards-compliant form, safe for general consumption.
 * It is not recommended, but possible to run this mod in parallel
 * with other formatters (in short, please DISABLE the BBcode mod).
 *
 * For help migrating from your previous markup language to pure HTML
 * please check the migrate.bbcode.php file.
 *
 * If you'd like to use this with a WYSIWYG editor, make sure that
 * editor sets $PHORUM['mod_htmlpurifier']['wysiwyg'] to true. Otherwise,
 * administrators who need to edit other people's comments may be at
 * risk for some nasty attacks.
 *
 * Tested with Phorum 5.2.11.
 */

// Note: Cache data is base64 encoded because Phorum insists on flinging
// to the user and expecting it to come back unharmed, newlines and
// all, which ain't happening. It's slower, it takes up more space, but
// at least it won't get mutilated

/**
 * Purifies a data array
 */
function phorum_htmlpurifier_format($data)
{
    $PHORUM = $GLOBALS["PHORUM"];

    $purifier =& HTMLPurifier::getInstance();
    $cache_serial = $PHORUM['mod_htmlpurifier']['body_cache_serial'];

    foreach($data as $message_id => $message){
        if(isset($message['body'])) {

            if ($message_id) {
                // we're dealing with a real message, not a fake, so
                // there a number of shortcuts that can be taken

                if (isset($message['meta']['htmlpurifier_light'])) {
                    // format hook was called outside of Phorum's normal
                    // functions, do the abridged purification
                    $data[$message_id]['body'] = $purifier->purify($message['body']);
                    continue;
                }

                if (!empty($PHORUM['args']['purge'])) {
                    // purge the cache, must be below the following if
                    unset($message['meta']['body_cache']);
                }

                if (
                    isset($message['meta']['body_cache']) &&
                    isset($message['meta']['body_cache_serial']) &&
                    $message['meta']['body_cache_serial'] == $cache_serial
                ) {
                    // cached version is present, bail out early
                    $data[$message_id]['body'] = base64_decode($message['meta']['body_cache']);
                    continue;
                }
            }

            // migration might edit this array, that's why it's defined
            // so early
            $updated_message = array();

            // create the $body variable
            if (
                $message_id && // message must be real to migrate
                !isset($message['meta']['body_cache_serial'])
            ) {
                // perform migration
                $fake_data = array();
                list($signature, $edit_message) = phorum_htmlpurifier_remove_sig_and_editmessage($message);
                $fake_data[$message_id] = $message;
                $fake_data = phorum_htmlpurifier_migrate($fake_data);
                $body = $fake_data[$message_id]['body'];
                $body = str_replace("<phorum break>\n", "\n", $body);
                $updated_message['body'] = $body; // save it in
                $body .= $signature . $edit_message; // add it back in
            } else {
                // reverse Phorum's pre-processing
                $body = $message['body'];
                // order is important
                $body = str_replace("<phorum break>\n", "\n", $body);
                $body = str_replace(array('<','>','&', '"'), array('<','>','&','"'), $body);
                if (!$message_id && defined('PHORUM_CONTROL_CENTER')) {
                    // we're in control.php, so it was double-escaped
                    $body = str_replace(array('<','>','&', '"'), array('<','>','&','"'), $body);
                }
            }

            $body = $purifier->purify($body);

            // dynamically update the cache (MUST BE DONE HERE!)
            // this is inefficient because it's one db call per
            // cache miss, but once the cache is in place things are
            // a lot zippier.

            if ($message_id) { // make sure it's not a fake id
                $updated_message['meta'] = $message['meta'];
                $updated_message['meta']['body_cache'] = base64_encode($body);
                $updated_message['meta']['body_cache_serial'] = $cache_serial;
                phorum_db_update_message($message_id, $updated_message);
            }

            // must not get overloaded until after we cache it, otherwise
            // we'll inadvertently change the original text
            $data[$message_id]['body'] = $body;

        }
    }

    return $data;
}

// -----------------------------------------------------------------------
// This is fragile code, copied from read.php:596 (Phorum 5.2.6). Please
// keep this code in-sync with Phorum

/**
 * Generates a signature based on a message array
 */
function phorum_htmlpurifier_generate_sig($row)
{
    $phorum_sig = '';
    if(isset($row["user"]["signature"])
       && isset($row['meta']['show_signature']) && $row['meta']['show_signature']==1){
           $phorum_sig=trim($row["user"]["signature"]);
           if(!empty($phorum_sig)){
               $phorum_sig="\n\n$phorum_sig";
           }
    }
    return $phorum_sig;
}

/**
 * Generates an edit message based on a message array
 */
function phorum_htmlpurifier_generate_editmessage($row)
{
    $PHORUM = $GLOBALS['PHORUM'];
    $editmessage = '';
    if(isset($row['meta']['edit_count']) && $row['meta']['edit_count'] > 0) {
        $editmessage = str_replace ("%count%", $row['meta']['edit_count'], $PHORUM["DATA"]["LANG"]["EditedMessage"]);
        $editmessage = str_replace ("%lastedit%", phorum_date($PHORUM["short_date_time"],$row['meta']['edit_date']),  $editmessage);
        $editmessage = str_replace ("%lastuser%", $row['meta']['edit_username'],  $editmessage);
        $editmessage = "\n\n\n\n$editmessage";
    }
    return $editmessage;
}

// End fragile code
// -----------------------------------------------------------------------

/**
 * Removes the signature and edit message from a message
 * @param $row Message passed by reference
 */
function phorum_htmlpurifier_remove_sig_and_editmessage(&$row)
{
    $signature = phorum_htmlpurifier_generate_sig($row);
    $editmessage = phorum_htmlpurifier_generate_editmessage($row);
    $replacements = array();
    // we need to remove add <phorum break> as that is the form these
    // extra bits are in.
    if ($signature) $replacements[str_replace("\n", "<phorum break>\n", $signature)] = '';
    if ($editmessage) $replacements[str_replace("\n", "<phorum break>\n", $editmessage)] = '';
    $row['body'] = strtr($row['body'], $replacements);
    return array($signature, $editmessage);
}

/**
 * Indicate that data is fully HTML and not from migration, invalidate
 * previous caches
 * @note This function could generate the actual cache entries, but
 *       since there's data missing that must be deferred to the first read
 */
function phorum_htmlpurifier_posting($message)
{
    $PHORUM = $GLOBALS["PHORUM"];
    unset($message['meta']['body_cache']); // invalidate the cache
    $message['meta']['body_cache_serial'] = $PHORUM['mod_htmlpurifier']['body_cache_serial'];
    return $message;
}

/**
 * Overload quoting mechanism to prevent default, mail-style quote from happening
 */
function phorum_htmlpurifier_quote($array)
{
    $PHORUM = $GLOBALS["PHORUM"];
    $purifier =& HTMLPurifier::getInstance();
    $text = $purifier->purify($array[1]);
    $source = htmlspecialchars($array[0]);
    return "
\n$text\n
";
}

/**
 * Ensure that our format hook is processed last. Also, loads the library.
 * @credits <http://secretsauce.phorum.org/snippets/make_bbcode_last_formatter.php.txt>
 */
function phorum_htmlpurifier_common()
{
    require_once(dirname(__FILE__).'/htmlpurifier/HTMLPurifier.auto.php');
    require(dirname(__FILE__).'/init-config.php');

    $config = phorum_htmlpurifier_get_config();
    HTMLPurifier::getInstance($config);

    // increment revision.txt if you want to invalidate the cache
    $GLOBALS['PHORUM']['mod_htmlpurifier']['body_cache_serial'] = $config->getSerial();

    // load migration
    if (file_exists(dirname(__FILE__) . '/migrate.php')) {
        include(dirname(__FILE__) . '/migrate.php');
    } else {
        echo 'Error: No migration path specified for HTML Purifier, please check
        <tt>modes/htmlpurifier/migrate.bbcode.php</tt> for instructions on
        how to migrate from your previous markup language.';
        exit;
    }

    if (!function_exists('phorum_htmlpurifier_migrate')) {
        // Dummy function
        function phorum_htmlpurifier_migrate($data) {return $data;}
    }

}

/**
 * Pre-emptively performs purification if it looks like a WYSIWYG editor
 * is being used
 */
function phorum_htmlpurifier_before_editor($message)
{
    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) {
        if (!empty($message['body'])) {
            $body = $message['body'];
            // de-entity-ize contents
            $body = str_replace(array('<','>','&'), array('<','>','&'), $body);
            $purifier =& HTMLPurifier::getInstance();
            $body = $purifier->purify($body);
            // re-entity-ize contents
            $body = htmlspecialchars($body, ENT_QUOTES, $GLOBALS['PHORUM']['DATA']['CHARSET']);
            $message['body'] = $body;
        }
    }
    return $message;
}

function phorum_htmlpurifier_editor_after_subject()
{
    // don't show this message if it's a WYSIWYG editor, since it will
    // then be handled automatically
    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) {
        $i = $GLOBALS['PHORUM']['DATA']['MODE'];
        if ($i == 'quote' || $i == 'edit' || $i == 'moderation') {
          ?>

              Notice: HTML has been scrubbed for your safety.
              If you would like to see the original, turn off WYSIWYG mode
              (consult your administrator for details.)

          <?php
        }
        return;
    }
    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message'])) return;
    ?>

        HTML input is enabled. Make sure you escape all HTML and
        angled brackets with &lt; and &gt;.

<?php
            $purifier =& HTMLPurifier::getInstance();
            $config = $purifier->config;
            if ($config->get('AutoFormat.AutoParagraph')) {
                ?>

                    Auto-paragraphing is enabled. Double
                    newlines will be converted to paragraphs; for single
                    newlines, use the pre tag.

<?php
            }
            $html_definition = $config->getDefinition('HTML');
            $allowed = array();
            foreach ($html_definition->info as $name => $x) $allowed[] = "$name";
            sort($allowed);
            $allowed_text = implode(', ', $allowed);
            ?>
Allowed tags: <?php
            echo $allowed_text;
            ?>.
<?php
        ?>

        For inputting literal code such as HTML and PHP for display, use
        CDATA tags to auto-escape your angled brackets, and pre
        to preserve newlines:

<pre><![CDATA[
Place code here
]]></pre>

        Power users, you can hide this notice with:

.htmlpurifier-help {display:none;}

<?php
}

// vim: et sw=4 sts=4


code:
The contents of the library/ folder should be here.

    vim: et sw=4 sts=4


code:
title:   HTML Purifier Phorum Mod
desc:    This module enables standards-compliant HTML filtering on Phorum. Please check migrate.bbcode.php before enabling this mod.
author:  Edward Z. Yang
url:     http://htmlpurifier.org/
version: 4.0.0

hook:  format|phorum_htmlpurifier_format
hook:  quote|phorum_htmlpurifier_quote
hook:  posting_custom_action|phorum_htmlpurifier_posting
hook:  common|phorum_htmlpurifier_common
hook:  before_editor|phorum_htmlpurifier_before_editor
hook:  tpl_editor_after_subject|phorum_htmlpurifier_editor_after_subject

# This module is meant to be a drop-in for bbcode, so make it run last.
priority: run module after *
priority: run hook format after *

    vim: et sw=4 sts=4


code:
<?php

/**
 * Initializes the appropriate configuration from either a PHP file
 * or a module configuration value
 * @return Instance of HTMLPurifier_Config
 */
function phorum_htmlpurifier_get_config($default = false)
{
    global $PHORUM;
    $config_exists = phorum_htmlpurifier_config_file_exists();
    if ($default || $config_exists || !isset($PHORUM['mod_htmlpurifier']['config'])) {
        $config = HTMLPurifier_Config::createDefault();
        include(dirname(__FILE__) . '/config.default.php');
        if ($config_exists) {
            include(dirname(__FILE__) . '/config.php');
        }
        unset($PHORUM['mod_htmlpurifier']['config']); // unnecessary
    } else {
        $config = HTMLPurifier_Config::create($PHORUM['mod_htmlpurifier']['config']);
    }
    return $config;
}

function phorum_htmlpurifier_config_file_exists()
{
    return file_exists(dirname(__FILE__) . '/config.php');
}

// vim: et sw=4 sts=4


code:
<?php

/**
 * This file is responsible for migrating from a specific markup language
 * like BBCode or Markdown to HTML. WARNING: THIS PROCESS IS NOT REVERSIBLE
 *
 * Copy this file to 'migrate.php' and it will automatically work for
 * BBCode; you may need to tweak this a little to get it to work for other
 * languages (usually, just replace the include name and the function name).
 *
 * If you do NOT want to have any migration performed (for instance, you
 * are installing the module on a new forum with no posts), simply remove
 * phorum_htmlpurifier_migrate() function. You still need migrate.php
 * present, otherwise the module won't work. This ensures that the user
 * explicitly says, "No, I do not need to migrate."
 */

if(!defined("PHORUM")) exit;

require_once(dirname(__FILE__) . "/../bbcode/bbcode.php");

/**
 * 'format' hook style function that will be called to convert
 * legacy markup into HTML.
 */
function phorum_htmlpurifier_migrate($data)
{
    return phorum_mod_bbcode_format($data); // bbcode's 'format' hook
}

// vim: et sw=4 sts=4


code:
<?php

// based off of BBCode's settings file

/**
 * HTML Purifier Phorum mod settings configuration. This provides
 * a convenient web-interface for editing the most common HTML Purifier
 * configuration directives. You can also specify custom configuration
 * by creating a 'config.php' file.
 */

if(!defined("PHORUM_ADMIN")) exit;

// error reporting is good!
error_reporting(E_ALL ^ E_NOTICE);

// load library and other paraphenalia
require_once './include/admin/PhorumInputForm.php';
require_once (dirname(__FILE__) . '/htmlpurifier/HTMLPurifier.auto.php');
require_once (dirname(__FILE__) . '/init-config.php');
require_once (dirname(__FILE__) . '/settings/migrate-sigs-form.php');
require_once (dirname(__FILE__) . '/settings/migrate-sigs.php');
require_once (dirname(__FILE__) . '/settings/form.php');
require_once (dirname(__FILE__) . '/settings/save.php');

// define friendly configuration directives. you can expand this array
// to get more web-definable directives
$PHORUM['mod_htmlpurifier']['directives'] = array(
    'URI.Host', // auto-detectable
    'URI.DisableExternal',
    'URI.DisableExternalResources',
    'URI.DisableResources',
    'URI.Munge',
    'URI.HostBlacklist',
    'URI.Disable',
    'HTML.TidyLevel',
    'HTML.Doctype', // auto-detectable
    'HTML.Allowed',
    'AutoFormat',
    '-AutoFormat.Custom',
    'AutoFormatParam',
    'Output.TidyFormat',
);

// lower this setting if you're getting time outs/out of memory
$PHORUM['mod_htmlpurifier']['migrate-sigs-increment'] = 100;

if (isset($_POST['reset'])) {
    unset($PHORUM['mod_htmlpurifier']['config']);
}

if ($offset = phorum_htmlpurifier_migrate_sigs_check()) {
    // migrate signatures
    phorum_htmlpurifier_migrate_sigs($offset);
} elseif(!empty($_POST)){
    // save settings
    phorum_htmlpurifier_save_settings();
}

phorum_htmlpurifier_show_migrate_sigs_form();
echo '<br />';
phorum_htmlpurifier_show_form();

// vim: et sw=4 sts=4


code:
<?php

function phorum_htmlpurifier_show_form()
{
    if (phorum_htmlpurifier_config_file_exists()) {
        phorum_htmlpurifier_show_config_info();
        return;
    }

    global $PHORUM;

    $config = phorum_htmlpurifier_get_config();

    $frm = new PhorumInputForm ("", "post", "Save");
    $frm->hidden("module", "modsettings");
    $frm->hidden("mod", "htmlpurifier"); // this is the directory name that the Settings file lives in

    if (!empty($error)){
        echo "$error
";
    }

    $frm->addbreak("Edit settings for the HTML Purifier module");

    $frm->addMessage('
The box below sets $PHORUM[\'mod_htmlpurifier\'][\'wysiwyg\'].
    When checked, contents sent for edit are now purified and the
    informative message is disabled. If your WYSIWYG editor is disabled for
    admin edits, you can safely keep this unchecked.
');
    $frm->addRow('Use WYSIWYG?', $frm->checkbox('wysiwyg', '1', '', $PHORUM['mod_htmlpurifier']['wysiwyg']));

    $frm->addMessage('
The box below sets $PHORUM[\'mod_htmlpurifier\'][\'suppress_message\'],
    which removes the big how-to use
    HTML Purifier message.
');
    $frm->addRow('Suppress information?', $frm->checkbox('suppress_message', '1', '', $PHORUM['mod_htmlpurifier']['suppress_message']));

    $frm->addMessage('
Click on directive links to read what each option does
    (links do not open in new windows).

For more flexibility (for instance, you want to edit the full
    range of configuration directives), you can create a <tt>config.php</tt>
    file in your <tt>mods/htmlpurifier/</tt> directory. Doing so will,
    however, make the web configuration interface unavailable.
');

    require_once 'HTMLPurifier/Printer/ConfigForm.php';
    $htmlpurifier_form = new HTMLPurifier_Printer_ConfigForm('config', 'http://htmlpurifier.org/live/configdoc/plain.html#%s');
    $htmlpurifier_form->setTextareaDimensions(23, 7); // widen a little, since we have space

    $frm->addMessage($htmlpurifier_form->render(
        $config, $PHORUM['mod_htmlpurifier']['directives'], false));

    $frm->addMessage("Warning: Changing HTML Purifier's configuration will invalidate
      the cache. Expect to see a flurry of database activity after you change
      any of these settings.");

    $frm->addrow('Reset to defaults:', $frm->checkbox("reset", "1", "", false));

    // hack to include extra styling
    echo '';
    $js = $htmlpurifier_form->getJavaScript();
    echo '';

    $frm->show();
}

function phorum_htmlpurifier_show_config_info()
{
    global $PHORUM;

    // update mod_htmlpurifier for housekeeping
    phorum_htmlpurifier_commit_settings();

    // politely tell user how to edit settings manually
?>

How to edit settings for HTML Purifier module

          A <tt>config.php</tt> file exists in your <tt>mods/htmlpurifier/</tt>
          directory. This file contains your custom configuration: in order to
          change it, please navigate to that file and edit it accordingly.
          You can also set $GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg']
          or $GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message']

          To use the web interface, delete <tt>config.php</tt> (or rename it to
          <tt>config.php.bak</tt>).

          Warning: Changing HTML Purifier's configuration will invalidate
          the cache. Expect to see a flurry of database activity after you change
          any of these settings.

<?php

}

// vim: et sw=4 sts=4


code:
<?php

function phorum_htmlpurifier_show_migrate_sigs_form()
{
    $frm = new PhorumInputForm ('', "post", "Migrate");
    $frm->hidden("module", "modsettings");
    $frm->hidden("mod", "htmlpurifier");
    $frm->hidden("migrate-sigs", "1");
    $frm->addbreak("Migrate user signatures to HTML");
    $frm->addMessage('This operation will migrate your users signatures
        to HTML. This process is irreversible and must only be performed once.
        Type in yes in the confirmation field to migrate.');
    if (!file_exists(dirname(__FILE__) . '/../migrate.php')) {
        $frm->addMessage('Migration file does not exist, cannot migrate signatures.
            Please check <tt>migrate.bbcode.php</tt> on how to create an appropriate file.');
    } else {
        $frm->addrow('Confirm:', $frm->text_box("confirmation", ""));
    }
    $frm->show();
}

// vim: et sw=4 sts=4


code:
<?php

function phorum_htmlpurifier_migrate_sigs_check()
{
    global $PHORUM;
    $offset = 0;
    if (!empty($_POST['migrate-sigs'])) {
        if (!isset($_POST['confirmation']) || strtolower($_POST['confirmation']) !== 'yes') {
            echo 'Invalid confirmation code.';
            exit;
        }
        $PHORUM['mod_htmlpurifier']['migrate-sigs'] = true;
        phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
        $offset = 1;
    } elseif (!empty($_GET['migrate-sigs']) && $PHORUM['mod_htmlpurifier']['migrate-sigs']) {
        $offset = (int) $_GET['migrate-sigs'];
    }
    return $offset;
}

function phorum_htmlpurifier_migrate_sigs($offset)
{
    global $PHORUM;

    if(!$offset) return; // bail out quick if $offset == 0

    // theoretically, we could get rid of this multi-request
    // doo-hickery if safe mode is off
    @set_time_limit(0); // attempt to let this run
    $increment = $PHORUM['mod_htmlpurifier']['migrate-sigs-increment'];

    require_once(dirname(__FILE__) . '/../migrate.php');
    // migrate signatures
    // do this in batches so we don't run out of time/space
    $end = $offset + $increment;
    $user_ids = array();
    for ($i = $offset; $i < $end; $i++) {
        $user_ids[] = $i;
    }
    $userinfos = phorum_db_user_get_fields($user_ids, 'signature');
    foreach ($userinfos as $i => $user) {
        if (empty($user['signature'])) continue;
        $sig = $user['signature'];
        // perform standard Phorum processing on the sig
        $sig = str_replace(array("&","<",">"), array("&","<",">"), $sig);
        $sig = preg_replace("/<((http|https|ftp):\/\/[a-z0-9;\/\?:@=\&\$\-_\.\+!*'\(\),~%]+?)>/i", "$1", $sig);
        // prepare fake data to pass to migration function
        $fake_data = array(array("author"=>"", "email"=>"", "subject"=>"", 'body' => $sig));
        list($fake_message) = phorum_htmlpurifier_migrate($fake_data);
        $user['signature'] = $fake_message['body'];
        if (!phorum_api_user_save($user)) {
            exit('Error while saving user data');
        }
    }
    unset($userinfos); // free up memory

    // query for highest ID in database
    $type = $PHORUM['DBCONFIG']['type'];
    $sql = "select MAX(user_id) from {$PHORUM['user_table']}";
    $row = phorum_db_interact(DB_RETURN_ROW, $sql);
    $top_id = (int) $row[0];

    $offset += $increment;
    if ($offset > $top_id) { // test for end condition
        echo 'Migration finished';
        $PHORUM['mod_htmlpurifier']['migrate-sigs'] = false;
        phorum_htmlpurifier_commit_settings();
        return true;
    }
    $host  = $_SERVER['HTTP_HOST'];
    $uri   = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
    $extra = 'admin.php?module=modsettings&mod=htmlpurifier&migrate-sigs=' . $offset;
    // relies on output buffering to work
    header("Location: http://$host$uri/$extra");
    exit;

}

// vim: et sw=4 sts=4


code:
<?php

function phorum_htmlpurifier_save_settings()
{
    global $PHORUM;
    if (phorum_htmlpurifier_config_file_exists()) {
        echo "Cannot update settings, mods/htmlpurifier/config.php already exists. To change
        settings, edit that file. To use the web form, delete that file.
";
    } else {
        $config = phorum_htmlpurifier_get_config(true);
        if (!isset($_POST['reset'])) $config->mergeArrayFromForm($_POST, 'config', $PHORUM['mod_htmlpurifier']['directives']);
        $PHORUM['mod_htmlpurifier']['config'] = $config->getAll();
    }
    $PHORUM['mod_htmlpurifier']['wysiwyg'] = !empty($_POST['wysiwyg']);
    $PHORUM['mod_htmlpurifier']['suppress_message'] = !empty($_POST['suppress_message']);
    if(!phorum_htmlpurifier_commit_settings()){
        $error="Database error while updating settings.";
    } else {
        echo "Settings Updated
";
    }
}

function phorum_htmlpurifier_commit_settings()
{
    global $PHORUM;
    return phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
}

// vim: et sw=4 sts=4


code:
<?php

// ATTENTION! DO NOT EDIT THIS FILE!
// This file is necessary to run the unit tests and profiling scripts.
// Please copy it to 'test-settings.php' and make the necessary edits.

// Note: The only external library you *need* is SimpleTest; everything else
//       is optional.

// We've got a lot of tests, so we recommend turning the limit off.
set_time_limit(0);

// Turning off output buffering will prevent mysterious errors from core dumps.
$data = @ob_get_clean();
if ($data !== false && $data !== '') {
    echo "Output buffer contains data [".urlencode($data)."]\n";
    exit;
}

// -----------------------------------------------------------------------------
// REQUIRED SETTINGS

// Note on running SimpleTest:
//      You want the Git copy of SimpleTest, found here:
//          https://github.com/simpletest/simpletest/
//
//      If SimpleTest is borked with HTML Purifier, please contact me or
//      the SimpleTest devs; I am a developer for SimpleTest so I should be
//      able to quickly assess a fix. SimpleTest's problem is my problem!

// Where is SimpleTest located? Remember to include a trailing slash!
$simpletest_location = '/path/to/simpletest/';

// -----------------------------------------------------------------------------
// OPTIONAL SETTINGS

// Note on running PHPT:
//      Vanilla PHPT from https://github.com/tswicegood/PHPT_Core should
//      work fine on Linux w/o multitest.
//
//      To do multitest or Windows testing, you'll need some more
//      patches at https://github.com/ezyang/PHPT_Core
//
//      I haven't tested the Windows setup in a while so I don't know if
//      it still works.

// Should PHPT tests be enabled?
$GLOBALS['HTMLPurifierTest']['PHPT'] = false;

// If PHPT isn't in your Path via PEAR, set that here:
// set_include_path('/path/to/phpt/Core/src' . PATH_SEPARATOR . get_include_path());

// Where is CSSTidy located? (Include trailing slash. Leave false to disable.)
$csstidy_location    = false;

// For tests/multitest.php, which versions to test?
$versions_to_test    = array();

// Stable PHP binary to use when invoking maintenance scripts.
$php = 'php';

// For tests/multitest.php, what is the multi-version executable? It must
// accept an extra parameter (version number) before all other arguments
$phpv = false;

// Should PEAR tests be run? If you've got a valid PEAR installation, set this
// to true (or, if it's not in the include path, to its install directory).
$GLOBALS['HTMLPurifierTest']['PEAR'] = false;

// If PEAR is enabled, what PEAR tests should be run? (Note: you will
// need to ensure these libraries are installed)
$GLOBALS['HTMLPurifierTest']['Net_IDNA2'] = true;

// vim: et sw=4 sts=4


code:
<?php

// This file is the configuration for Travis testing.

// Note: The only external library you *need* is SimpleTest; everything else
//       is optional.

// We've got a lot of tests, so we recommend turning the limit off.
set_time_limit(0);

// Turning off output buffering will prevent mysterious errors from core dumps.
$data = @ob_get_clean();
if ($data !== false && $data !== '') {
    echo "Output buffer contains data [".urlencode($data)."]\n";
    exit;
}

// -----------------------------------------------------------------------------
// REQUIRED SETTINGS

// Note on running SimpleTest:
//      You want the Git copy of SimpleTest, found here:
//          https://github.com/simpletest/simpletest/
//
//      If SimpleTest is borked with HTML Purifier, please contact me or
//      the SimpleTest devs; I am a developer for SimpleTest so I should be
//      able to quickly assess a fix. SimpleTest's problem is my problem!

// Where is SimpleTest located? Remember to include a trailing slash!
$simpletest_location = dirname(__FILE__) . '/simpletest/';

// -----------------------------------------------------------------------------
// OPTIONAL SETTINGS

// Note on running PHPT:
//      Vanilla PHPT from https://github.com/tswicegood/PHPT_Core should
//      work fine on Linux w/o multitest.
//
//      To do multitest or Windows testing, you'll need some more
//      patches at https://github.com/ezyang/PHPT_Core
//
//      I haven't tested the Windows setup in a while so I don't know if
//      it still works.

// Should PHPT tests be enabled?
$GLOBALS['HTMLPurifierTest']['PHPT'] = false;

// If PHPT isn't in your Path via PEAR, set that here:
// set_include_path('/path/to/phpt/Core/src' . PATH_SEPARATOR . get_include_path());

// Where is CSSTidy located? (Include trailing slash. Leave false to disable.)
$csstidy_location    = false;

// For tests/multitest.php, which versions to test?
$versions_to_test    = array();

// Stable PHP binary to use when invoking maintenance scripts.
$php = 'php';

// For tests/multitest.php, what is the multi-version executable? It must
// accept an extra parameter (version number) before all other arguments
$phpv = false;

// Should PEAR tests be run? If you've got a valid PEAR installation, set this
// to true (or, if it's not in the include path, to its install directory).
$GLOBALS['HTMLPurifierTest']['PEAR'] = false;

// If PEAR is enabled, what PEAR tests should be run? (Note: you will
// need to ensure these libraries are installed)
$GLOBALS['HTMLPurifierTest']['Net_IDNA2'] = true;

// vim: et sw=4 sts=4


code:
<?php

function path2class($path)
{
    $temp = $path;
    $temp = str_replace('./', '',  $temp); // remove leading './'
    $temp = str_replace('.\\', '',  $temp); // remove leading '.\'
    $temp = str_replace('\\', '_', $temp); // normalize \ to _
    $temp = str_replace('/',  '_', $temp); // normalize / to _
    while(strpos($temp, '__') !== false) $temp = str_replace('__', '_', $temp);
    $temp = str_replace('.php', '', $temp);
    return $temp;
}

// vim: et sw=4 sts=4


code:
<?php

// release script
// PHP 5.0 only

if (php_sapi_name() != 'cli') {
    echo 'Release script cannot be called from web-browser.';
    exit;
}

if (!isset($argv[1])) {
    echo
'php release.php [version]
    HTML Purifier release script
';
    exit;
}

$version = trim($argv[1]);

// Bump version numbers:

// ...in VERSION
file_put_contents('VERSION', $version);

// ...in NEWS
if ($is_dev = (strpos($version, 'dev') === false)) {
  $date = date('Y-m-d');
  $news_c = str_replace(
      $l = "$version, unknown release date",
      "$version, released $date",
      file_get_contents('NEWS'),
      $c
  );
  if (!$c) {
      echo 'Could not update NEWS, missing ' . $l . PHP_EOL;
      exit;
  } elseif ($c > 1) {
      echo 'More than one release declaration in NEWS replaced' . PHP_EOL;
      exit;
  }
  file_put_contents('NEWS', $news_c);
}

// ...in Doxyfile
$doxyfile_c = preg_replace(
    '/(?<=PROJECT_NUMBER {9}= )[^\s]+/m', // brittle
    $version,
    file_get_contents('Doxyfile'),
    1, $c
);
if (!$c) {
    echo 'Could not update Doxyfile, missing PROJECT_NUMBER.' . PHP_EOL;
    exit;
}
file_put_contents('Doxyfile', $doxyfile_c);

// ...in HTMLPurifier.php
$htmlpurifier_c = file_get_contents('library/HTMLPurifier.php');
$htmlpurifier_c = preg_replace(
    '/HTML Purifier .+? - /',
    "HTML Purifier $version - ",
    $htmlpurifier_c,
    1, $c
);
if (!$c) {
    echo 'Could not update HTMLPurifier.php, missing HTML Purifier [version] header.' . PHP_EOL;
    exit;
}
$htmlpurifier_c = preg_replace(
    '/public \$version = \'.+?\';/',
    "public \$version = '$version';",
    $htmlpurifier_c,
    1, $c
);
if (!$c) {
    echo 'Could not update HTMLPurifier.php, missing public $version.' . PHP_EOL;
    exit;
}
$htmlpurifier_c = preg_replace(
    '/const VERSION = \'.+?\';/',
    "const VERSION = '$version';",
    $htmlpurifier_c,
    1, $c
);
if (!$c) {
    echo 'Could not update HTMLPurifier.php, missing const $version.' . PHP_EOL;
    exit;
}
file_put_contents('library/HTMLPurifier.php', $htmlpurifier_c);

$config_c = file_get_contents('library/HTMLPurifier/Config.php');
$config_c = preg_replace(
    '/public \$version = \'.+?\';/',
    "public \$version = '$version';",
    $config_c,
    1, $c
);
if (!$c) {
    echo 'Could not update Config.php, missing public $version.' . PHP_EOL;
    exit;
}
file_put_contents('library/HTMLPurifier/Config.php', $config_c);

passthru('maintenance/flush.sh');

if ($is_dev) echo "Review changes, write something in WHATSNEW and FOCUS, and then commit with log 'Release $version.'" . PHP_EOL;
else echo "Numbers updated to dev, no other modifications necessary!";

// vim: et sw=4 sts=4