Пример #1
0
require_once './start.php';
//submitted info for making a new thread
//(name / password already handled in 'start.php')
define('TITLE', mb_substr(@$_POST['title'], 0, SIZE_TITLE));
define('TEXT', mb_substr(@$_POST['text'], 0, SIZE_TEXT));
//can the current user post new threads in the current forum?
//(posting replies is dependent on the the thread -- if locked -- so tested in 'thread.php')
define('CAN_POST', FORUM_ENABLED && (IS_MOD || IS_MEMBER || !FORUM_LOCK));
/* ======================================================================================================================
   new thread submitted
   ====================================================================================================================== */
//has the user submitted a new thread?
//(`AUTH` will be true if username and password submitted and correct, `TITLE` and `TEXT` are checked to not be blank)
if (CAN_POST && AUTH && TITLE && TEXT) {
    //the file on disk is a simplified version of the title; see 'lib/functions.php' for `safeTransliterate`
    $translit = safeTransliterate(TITLE);
    //if a thread already exsits with that name, append a number until an available filename is found.
    //we also check for directories with the same name so as to avoid problematic Apache behaviour
    $c = 0;
    do {
        $file = $translit . ($c++ ? '_' . ($c - 1) : '');
    } while (file_exists("{$file}") || file_exists("{$file}.rss"));
    //write out the new thread as an RSS file:
    $post_id = base_convert(microtime(), 10, 36);
    $rss = new DOMTemplate(file_get_contents(FORUM_LIB . 'rss-template.xml'));
    $rss->set(array('/rss/channel/title' => TITLE, '/rss/channel/link' => FORUM_URL . url(PATH_URL, $file), '/rss/channel/item/title' => TITLE, '/rss/channel/item/link' => FORUM_URL . url(PATH_URL, $file) . '#' . $post_id, '/rss/channel/item/author' => NAME, '/rss/channel/item/pubDate' => gmdate('r'), '/rss/channel/item/description' => formatText(TEXT, FORUM_URL . url(PATH_URL, $file, 1), $post_id)))->remove('//category');
    file_put_contents("{$file}.rss", $rss) or (require FORUM_LIB . 'error_permissions.php');
    //regenerate the folder's RSS file
    indexRSS();
    //redirect to newley created thread
    header('Location: ' . FORUM_URL . url(PATH_URL, $file), true, 303);
Пример #2
0
function formatText($text, $permalink = '', $post_id = '', $rss = NULL)
{
    //unify carriage returns between Windows / UNIX, and sanitise HTML against injection
    $text = safeHTML(preg_replace('/\\r\\n?/', "\n", $text));
    //these arrays will hold any portions of text that have to be temporarily removed to avoid interference with the
    //markup processing, i.e code spans / blocks
    $pre = array();
    $code = array();
    /* preformatted text (code blocks):
       -------------------------------------------------------------------------------------------------------------- */
    /* example:                     or: (latex in particular since it uses % as a comment marker)
       
               % title                 $ title
               ⋮                       ⋮
               %                       $
       */
    while (preg_match('/^(?-s:(\\h*)([%$])(.*?))\\n(.*?)\\n\\h*\\2(["”»]?)$/msu', $text, $m, PREG_OFFSET_CAPTURE)) {
        //format the code block
        $pre[] = "<pre><span class=\"ct\">{$m[2][0]}{$m[3][0]}</span>\n" . (strlen($m[1][0]) ? preg_replace("/^\\s{1," . strlen($m[1][0]) . "}/m", '', $m[4][0]) : $m[4][0]) . "\n<span class=\"cb\">{$m[2][0]}</span></pre>";
        //replace the code block with a placeholder:
        //(we will have to remove the code chunks from the source text to avoid the other markup processing from
        //munging it and then restore the chunks back later)
        $text = substr_replace($text, "\n&PRE_" . (count($pre) - 1) . ";\n" . $m[5][0], $m[0][1], strlen($m[0][0]));
    }
    /* inline code / teletype text:
       -------------------------------------------------------------------------------------------------------------- */
    // example: `code` or ``code``
    while (preg_match('/(?<=[\\s\\p{Z}\\p{P}]|^)(`+)(.*?)(?<!`)\\1(?!`)/m', $text, $m, PREG_OFFSET_CAPTURE)) {
        //format the code block
        $code[] = '<code>' . $m[1][0] . $m[2][0] . $m[1][0] . '</code>';
        //same as with normal code blocks, replace them with a placeholder
        $text = substr_replace($text, '&CODE_' . (count($code) - 1) . ';', $m[0][1], strlen($m[0][0]));
    }
    /* hyperlinks:
       -------------------------------------------------------------------------------------------------------------- */
    //find full URLs and turn into HTML hyperlinks. we also detect e-mail addresses automatically
    while (preg_match('/(?:
                        ((?:(?:http|ftp)s?|irc)?:\\/\\/)                  # $1 = protocol
                |       ([a-z0-9\\._%+\\-]+@)                             # $2 = email name
                )(                                                      # $3 = friendly URL (no protocol)
                        [-\\.\\p{L}\\p{M}\\p{N}]+                           # domain (letters, diacritics, numbers & dash only)
                        (?:\\.[\\p{L}\\p{M}\\p{N}]+)+                       # TLDs (also letters, diacritics & numbers only)
                )(?(2)|                                                 # email ends here
                        (\\/)?                                           # $4 = slash is excluded from friendly URL
                        (?(4)(                                          # $5 = folders and filename, relative URL
                                (?>                                     # folders and filename
                                        "(?!\\/?&gt;|\\s|$)|              # ignore the end of an HTML hyperlink
                                        \\)(?![:\\.,"”»]?(?:\\s|$))|       # ignore brackets on end with punctuation
                                        [:\\.,”»](?!\\s|$)|               # ignore various characters on the end
                                        [^\\s:)\\.,"”»]                   # the rest, including bookmark
                                )*
                        )?)
                )/xiu', $text, $m, PREG_OFFSET_CAPTURE, @($m[0][1] + strlen($replace)))) {
        $text = substr_replace($text, $replace = '<a href="' . ($p = @$m[2][0] ? 'mailto:' . $m[2][0] : ($m[1][0] ? $m[1][0] : 'http://')) . htmlspecialchars($m[3][0] . @$m[4][0] . @$m[5][0], ENT_COMPAT, 'UTF-8', false) . '"' . ($p . $m[3][0] !== FORUM_URL ? ' rel="nofollow external"' : '') . '>' . $m[0][0] . '</a>', $m[0][1], strlen($m[0][0]));
    }
    /* inline formatting:
       -------------------------------------------------------------------------------------------------------------- */
    $text = preg_replace(array('/(?<=\\s|^)_(?!_)(.*?)(?<!_)_(?=\\s|$)/m', '/(?<![*\\w])\\*(?!\\*)(.*?)(?<!\\*)\\*(?![*\\w])/'), array('<em>_$1_</em>', '<strong>*$1*</strong>'), $text);
    /* divider: "---"
       -------------------------------------------------------------------------------------------------------------- */
    $text = preg_replace('/(?:\\n|\\A)\\h*(---+)\\h*(?:\\n?$|\\Z)/m', "\n\n<p class=\"hr\">\$1</p>\n", $text);
    /* blockquotes:
       -------------------------------------------------------------------------------------------------------------- */
    /* example:
       
               “this is the first quote level.
               
               “this is the second quote level.”
               
               back to the first quote level.”
       */
    do {
        $text = preg_replace(array('/(?:\\n|\\A)\\h*("(?!\\s+)((?>(?1)|.)*?)\\s*")\\h*(?:\\n?$|\\Z)/msu', '/(?:\\n|\\A)\\h*(“(?!\\s+)((?>(?1)|.)*?)\\s*”)\\h*(?:\\n?$|\\Z)/msu', '/(?:\\n|\\A)\\h*(«(?!\\s+)((?>(?1)|.)*?)\\s*»)\\h*(?:\\n?$|\\Z)/msu'), "\n\n<blockquote>\n\n" . "<span class=\"ql\">&ldquo;</span>\n\$2\n<span class=\"qr\">&rdquo;</span>\n\n" . "</blockquote>\n", $text, -1, $c);
    } while ($c);
    //remove the extra linebreaks addeded between our theme quotes
    //(required so that extra `<br />`s don’t get added!)
    $text = preg_replace(array('/&ldquo;<\\/span>\\n(?!\\n)/', '/\\n<span class="qr">/'), array('&ldquo;</span>', '<span class="qr">'), $text);
    /* name references:
       -------------------------------------------------------------------------------------------------------------- */
    //name references (e.g. "@bob") will link back to the last reply in the thread made by that person.
    //this requires that the whole RSS thread is passed to this function to refer to
    if (!is_null($rss)) {
        //first, produce a list of all authors in the thread
        $names = array();
        foreach ($rss->channel->xpath('./item/author') as $name) {
            $names[] = $name[0];
        }
        $names = array_unique($names);
        //remove duplicates
        $names = array_map('strtolower', $names);
        //set all to lowercase
        $names = array_map('safeHTML', $names);
        //HTML encode names as they will be in the source text
        //sort the list of names Z-A so that longer names and names with spaces occur first,
        //this is so that we don’t choose "Bob" over "Bob Monkhouse" when matching names
        rsort($names);
        //find all possible name references in the text:
        //(that is, any "@" followed by text up to the end of a line. note that this means that what might be
        //matched may include additional text that *isn't* part of the name, e.g. "@bob How are you?")
        $offset = 0;
        while (preg_match('/(?:^|\\s+)(@.+)/m', $text, $m, PREG_OFFSET_CAPTURE, $offset)) {
            //check each of the known names in the thread and see if one fits the source text reference
            //e.g. does "@bob How are you?" begin with "bob"
            foreach ($names as $name) {
                if (stripos($m[1][0], $name) === 1) {
                    //locate the last post made by that author in the thread to link to
                    foreach ($rss->channel->item as $item) {
                        if (safeHTML(strtolower($item->author)) == $name) {
                            //replace the reference with the link to the post
                            $text = substr_replace($text, '<a href="' . safeHTML($item->link) . '"' . (isMod($name) ? ' class="nnf_mod"' : '') . '>' . substr($m[1][0], 0, strlen($name) + 1) . '</a>', $m[1][1], strlen($name) + 1);
                            //move on to the next reference, no need to check any further names for this one
                            $offset = $m[1][1] + strlen($name) + strlen($item->link) + 15 + 1;
                            break 2;
                        }
                    }
                }
            }
            //failing any match, continue searching
            //(avoid getting stuck in an infinite loop)
            $offset = $m[1][1] + 1;
        }
    }
    /* titles
       -------------------------------------------------------------------------------------------------------------- */
    //example: :: title
    $replace = '';
    $titles = array();
    while (preg_match('/(?:\\n|\\A)(::.*)(?:\\n?$|\\Z)/mu', $text, $m, PREG_OFFSET_CAPTURE, @($m[0][1] + strlen($replace)))) {
        //generate a unique HTML ID for the title:
        //flatten the title text into a URL-safe string of [a-z0-9_]
        $translit = safeTransliterate(strip_tags($m[1][0]));
        //if a title already exsits with that ID, append a number until an available ID is found.
        $c = 0;
        do {
            $id = $translit . ($c++ ? '_' . ($c - 1) : '');
        } while (in_array($id, $titles));
        //add the current ID to the list of used IDs
        $titles[] = $id;
        //remove hyperlinks in the title (since the title will be a hyperlink too)
        //if a user-link is present, keep the mod class if present
        $m[1][0] = preg_replace('/<a href="[^"]+"( class="nnf_mod")?>(.*?)<\\/a>/', "<b\$1>\$2</b>", $m[1][0]);
        //create the replacement HTML, including an anchor link
        $text = substr_replace($text, $replace = "\n\n<h2 id=\"{$post_id}::{$id}\">" . "<a href=\"" . safeHTML($permalink) . "#{$post_id}::{$id}\">" . $m[1][0] . "</a>" . "</h2>\n", $m[0][1], strlen($m[0][0]));
    }
    /* finalise:
       -------------------------------------------------------------------------------------------------------------- */
    //add paragraph tags between blank lines
    foreach (preg_split('/\\n{2,}/', safeTrim($text), -1, PREG_SPLIT_NO_EMPTY) as $chunk) {
        //if not a blockquote, title, hr or pre-block, wrap in a paragraph
        if (!preg_match('/^<\\/?(?:bl|h2|p)|^&PRE_/', $chunk)) {
            $chunk = "<p>\n" . str_replace("\n", "<br />\n", $chunk) . "\n</p>";
        }
        $text = @($result .= "\n{$chunk}");
    }
    //restore code spans/blocks
    foreach ($code as $i => $html) {
        $text = str_replace("&CODE_{$i};", $html, $text);
    }
    foreach ($pre as $i => $html) {
        $text = str_replace("&PRE_{$i};", $html, $text);
    }
    return $text;
}