<?php //{{MediaWikiExtension}}<source lang="php">
/*
 * ArticleCommentsLite.php - A MediaWiki extension for adding comment sections to articles.
 * @author Jim R. Wilson, たちゃな
 * @version 0.4-20101003
 * @copyright Copyright (C) 2007 Jim R. Wilson, たちゃな
 * @license The MIT License - http://www.opensource.org/licenses/mit-license.php 
 * -----------------------------------------------------------------------
 * Description:
 *     This is a MediaWiki (http://www.mediawiki.org/) extension which adds support
 *     for comment sections within article pages, or directly into all pages.
 * Requirements:
 *     MediaWiki 1.6.x, 1.8.x, 1.9.x or higher
 *     PHP 4.x, 5.x or higher
 * Installation:
 *     1. Drop this script (ArticleCommentsLite.php) in $IP/extensions
 *         Note: $IP is your MediaWiki install dir.
 *     2. Enable the extension by adding this line to your LocalSettings.php:
 *            require_once("$IP/extensions/ArticleCommentsLite.php");
 * Usage:
 *     Once installed, you may utilize ArticleComments/Lite by adding the following flag in the article text:
 *         <comments />
 *     Note: Typically this would be placed at the end of the article text.
 * Version Notes:
 *     version 0.4-20101003:
 *         MediaWiki 1.16.x で動作するようにした。(head)
 *         * MediaWiki 1.15.[45] で動かす場合は ./for_1.15.4/ ディレクトリ内のバージョン (r711) を使うこと。
 *         i18n 対応。配布物に日本語メッセージを含めた。
 *         細かな修正。(Thanks to lionkid)
 *     version 0.4-20070507b:
 *         書き込んだコメントの視認性を高めるべく、コメントの直前に水平線を入れてみた。
 *     version 0.4-20070507:
 *         ログインしている場合にはコメントに仮名が含まれていなくても受け付けるようにした。
 *         細かな修正。
 *     version 0.4-20070430:
 *         $wgArticleCommentLiteDefaults['nokana'] を true に設定することで、
 *         MacWiki.cgi 準拠の簡単な spam 除けが機能するようにした。（非推奨）
 *         入力されたコメントを念のため htmlspecialchars() でサニタイズするようにした。
 *         スクエアブラケット、ブレースも同様に無効化。
 *         細かな修正。
 *     version 0.4-20070429:
 *         単純な一行コメントに作り替えた。
 *     version 0.4:
 *         Updated default spam filtering code.
 *         Abstracted Spam filter via hook (ArticleCommentsSpamCheck) to aid future spam checkers
 *     version 0.3:
 *         Added rudimentary spam filtering based on common abuses.
 *     version 0.2:
 *         Fixed form post method to use localized version of "Special"
 *         Added option for making the form automatically visible (no "Leave a comment..." link)
 *         Added option of diabling the "Website" field
 *         Added system message for prepopulating the comment box.
 *         Added system message for structuring comment submission text.
 *         Added abstracted method for form creation (for insertion into skins)
 *         Added option to "Whitelist" Namespaces for comment submission (as by skin-level form).
 *         Added check for user blocked status prior to comment submission.
 *     version 0.1:
 *         Initial release.
 * -----------------------------------------------------------------------
 * Copyright (c) 2007 Jim R. Wilson, たちゃな
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal 
 * in the Software without restriction, including without limitation the rights to 
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
 * the Software, and to permit persons to whom the Software is furnished to do 
 * so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all 
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE. 
 * -----------------------------------------------------------------------
 */
 
# Confirm MW environment
if (!defined('MEDIAWIKI')) die();

# Credits
$wgExtensionCredits['other'][] = array(
    'path' => __FILE__,
    'name'=>'ArticleComments/Lite',
    'author'=>'Jim R. Wilson, The MacWiki Project',
    'url'=>'http://macwiki.sourceforge.jp/wiki/index.php/MacWiki%3aMediaWiki%e3%81%ae%e3%82%a4%e3%83%b3%e3%82%b9%e3%83%88%e3%83%bc%e3%83%ab%23ArticleComments%2e2FLite',
    'description'=>'Enables comment sections on article pages.',
    'svn-date' => '$LastChangedDate: 2011-11-05 19:29:32 +0900 (Sat, 05 Nov 2011) $',
    'svn-revision' => '$LastChangedRevision: 740 $'
);

# Add Extension Functions
$wgExtensionFunctions[] = 'wfArticleCommentsParserSetup';

# Sets up the ArticleComments Parser hook for <comments />
function wfArticleCommentsParserSetup() {
    global $wgParser;
    $wgParser->setHook( 'comments', 'wfArticleCommentsParserHook' );
}
function wfArticleCommentsParserHook( $text, $params = array(), $parser ) {

    # Generate a comment form for display
    $commentForm = wfArticleCommentForm( $parser->mTitle, $params );
    
    # Hide content from the Parser using base64 to avoid mangling.
    # Note: Content will be decoded after Tidy has finished its processing of the page.
    return '<pre>@ENCODED@'.base64_encode($commentForm).'@ENCODED@</pre>';
}

/**
 * Echos out a comment form depending on the page action and namespace.
 * @param Title $title The title of the article on which the form will appear.
 * @param Array $params A hash of parameters containing rendering options.
 */
function displayArticleCommentForm( $title = null, $params = array() ) {

    global $wgRequest, $wgArticleCommentsNSDisplayList;
    
    # Short circuit for anything other than action=view or action=purge
    if ($wgRequest->getVal('action') && 
        $wgRequest->getVal('action')!='view' &&
        $wgRequest->getVal('action')!='purge'
    ) return;
    
    # Short-circuit if displayl ist is undefined or null
    if ($wgArticleCommentsNSDisplayList==null) return;

    # Use wgTitle if title is not specified
    if ($title==null) {
        global $wgTitle;
        $title = $wgTitle;
    }

    # Ensure that the namespace list is an actual list
    $nsList = $wgArticleCommentsNSDisplayList;
    if (!is_array($nsList)) $nsList = array($nsList);
    
    # Display the form
    if (in_array($title->getNamespace(), $nsList)) {
        echo(wfArticleCommentForm($title, $params));
    }
    
}

/**
 * Generates and returns an ArticleComment form.
 * @param Title $title The title of the article on which the form will appear.
 * @param Array $params A hash of parameters containing rendering options.
 */
function wfArticleCommentForm( $title = null, $params = array() ) {

    global $wgScript, $wgArticleCommentLiteDefaults, $wgContentLang, $wgContLang;
    $wcl = ($wgContentLang ? $wgContentLang : $wgContLang);
 
    # Merge in global defaults if specified    
    if (is_array($wgArticleCommentLiteDefaults) &&
        !empty($wgArticleCommentLiteDefaults)) {
        $tmp = array();
        foreach ($wgArticleCommentLiteDefaults as $k=>$v) {
            $tmp[strtolower($k)] = $v;
        }
        $params = array_merge($tmp, $params);
    }
    
    # Use wgTitle if title is not specified
    if ($title==null) {
        global $wgTitle;
        $title = $wgTitle;
    }
    
    $ac = 'article-comments-';
    $formAction = $wgScript.'?title='.$wcl->getNsText(NS_SPECIAL).':ProcessComment';

    # Build out the comment form.
    $content = 
        '<div id="commentForm">'.
        '<form method="post" action="'.$formAction.'">'.
        '<input type="hidden" id="titleKey" name="titleKey" '.
        'value="'.$title->getDBKey().'" />'.
        '<input type="hidden" id="titleNS" name="titleNS" '.
        'value="'.$title->getNamespace().'" />'.
        '<p>'.wfMsgForContent($ac.'comment-field').
        '<input type="text" id="comment" name="comment" size="40" maxlength="512" />'.
        '<input id="submit" type="submit" value="'.wfMsgForContent($ac.'submit-button').'" /></p>'.
        '</form></div>';
        
    return $content;
}

# Attach Hooks
$wgHooks['ParserAfterTidy'][] = 'wfProcessEncodedContent';
#$wgHooks['ArticleCommentsSpamCheck'][] = 'defaultArticleCommentSpamCheck';

/**
 * Processes HTML comments with encoded content.
 * Usage: $wgHooks['OutputPageBeforeHTML'][] = 'wfProcessEncodedContent';
 * @param OutputPage $out Handle to an OutputPage object presumably $wgOut (passed by reference).
 * @param String $text Article/Output text (passed by reference)
 * @return Boolean Always tru to give other hooking methods a chance to run.
 */
function wfProcessEncodedContent($out, $text) {
    $text = preg_replace(
        '/<pre>@ENCODED@([0-9a-zA-Z\\+\\/]+=*)@ENCODED@<\\/pre>/e',
        'base64_decode("$1")',
        $text
    );
    return true;
}

# Sets up special page to handle comment submission
$wgExtensionFunctions[] = 'setupSpecialProcessComment';
function setupSpecialProcessComment() {
    global $IP, $wgMessageCache;
    require_once($IP.'/includes/SpecialPage.php');
    SpecialPage::addPage(new SpecialPage('ProcessComment', '', false, 'specialProcessComment', false));

}

# Messages used in this extension
$wgArticleCommentsLiteIP = dirname( __FILE__ );
$wgExtensionMessagesFiles['ArticleCommentsLite'] = "$wgArticleCommentsLiteIP/ArticleCommentsLite.i18n.php";

/**
* Special page for comment processing.
*/
function specialProcessComment() {

    global $wgOut, $wgParser, $wgUser, $wgContentLang, $wgContLang, $wgArticleCommentLiteDefaults;
    $wcl = ($wgContentLang ? $wgContentLang : $wgContLang);

    # Retrieve submitted values
    $titleKey = $_POST['titleKey'];
    $titleNS = intval($_POST['titleNS']);
    $commenterName = '';
    $commenterURL = '';
    $comment = htmlspecialchars($_POST['comment']);
    $comment = preg_replace('/([\[\]{}])/e', "'&#x'.bin2hex('\\1').';'", $comment);

    # Perform validation checks on supplied fields
    $ac = 'article-comments-';
    $messages = array();
    if (!$titleKey) {
        $messages[] = wfMsgForContent($ac.'invalid-field', wfMsgForContent($ac.'title-field'), '(null)');
    }
    if (!$comment) {
        $messages[] = wfMsgForContent($ac.'required-field', wfMsgForContent($ac.'comment-string'));
    } else {
        if (strlen($comment)>4096) { # max length: 4096 bytes
            $wgOut->setPageTitle(wfMsgForContent($ac.'submission-failed'));
            $wgOut->addWikiText("<div class='errorbox'>".wfMsgForContent($ac.'toolong-field', wfMsgForContent($ac.'comment-string'))."</div>");
            return;
        }

        if ($wgArticleCommentLiteDefaults['nokana']) {
            if (!($wgUser->isLoggedIn())) {
                #if (!mb_ereg('[あ-んア-ン]', $comment)) { # fixme
                if (!preg_match('/あ|い|う|え|お|か|き|く|け|こ|さ|し|す|せ|そ|た|ち|つ|て|と|な|に|ぬ|ね|の|は|ひ|ふ|へ|ほ|ま|み|む|め|も|や|ゆ|よ|ら|り|る|れ|ろ|わ|ゐ|ゑ|を|ん|が|ぎ|ぐ|げ|ご|ざ|じ|ず|ぜ|ぞ|だ|ぢ|づ|で|ど|ば|び|ぶ|べ|ぼ|ぱ|ぴ|ぷ|ぺ|ぽ|ぁ|ぃ|ぅ|ぇ|ぉ|っ|ゃ|ゅ|ょ|ア|イ|ウ|エ|オ|カ|キ|ク|ケ|コ|サ|シ|ス|セ|ソ|タ|チ|ツ|テ|ト|ナ|ニ|ヌ|ネ|ノ|ハ|ヒ|フ|ヘ|ホ|マ|ミ|ム|メ|モ|ヤ|ユ|ヨ|ラ|リ|ル|レ|ロ|ワ|ヰ|ヱ|ヲ|ン|ガ|ギ|グ|ゲ|ゴ|ザ|ジ|ズ|ゼ|ゾ|ダ|ヂ|ヅ|デ|ド|バ|ビ|ブ|ベ|ボ|パ|ピ|プ|ペ|ポ|ァ|ィ|ゥ|ェ|ォ|ッ|ャ|ュ|ョ|ヴ/', $comment)) {
                    $messages[] = wfMsgForContent($ac.'nokana-field', wfMsgForContent($ac.'comment-string'));
                }
            }
        }

        # Run spam checks
        $isspam = false;
        #wfRunHooks( 'ArticleCommentsSpamCheck', array( $comment , $commenterName, $commenterURL, &$isspam ) );
        # If it's spam - it's gone!
        if ($isspam) {
            $wgOut->setPageTitle(wfMsgForContent($ac.'submission-failed'));
            $wgOut->addWikiText("<div class='errorbox'>".wfMsgForContent($ac.'no-spam')."</div>");
            return;
        }
    }
    if (!empty($messages)) {
        $wgOut->setPageTitle(wfMsgForContent($ac.'submission-failed'));
        $wikiText = "<div class='errorbox'>";
        $wikiText .= wfMsgForContent($ac.'failure-reasons')."\n\n";
        foreach ($messages as $message) {
            $wikiText .= "* $message\n";
        }
        $wgOut->addWikiText($wikiText . "</div>");
        return;
    }

    # Setup title object
    $title = Title::newFromDBkey($titleKey);
    $title->mNamespace = $titleNS;
    $article = new Article($title);

    # Check whether user is blocked from editing the page
    if ($wgUser->isBlockedFrom($title)) {
        $wgOut->setPageTitle(wfMsgForContent($ac.'submission-failed'));
        $wikiText = "<div class='errorbox'>";
        $wikiText .= wfMsgForContent($ac.'failure-reasons')."\n\n";
        $wikiText .= '* '.wfMsgForContent($ac.'user-is-blocked', $title->getPrefixedText())."\n";
        $wgOut->addWikiText($wikiText . "</div>");
        return;
    }

    # Check if the article is exist or not.
    if (!$article->exists()) {
       $wgOut->setPageTitle(wfMsgForContent($ac.'submission-failed'));
       $wgOut->addWikiText("<div class='errorbox'>".wfMsgForContent($ac.'no-exist', $title->getPrefixedText())."</div>");
       return;
    }

    # Retrieve article content
    $articleContent = $article->getContent();

    # Check if the article NS is in the Namespace display list
    global $wgArticleCommentsNSDisplayList;
    if (is_array($wgArticleCommentsNSDisplayList && !in_array($title->getNamespace(),$wgArticleCommentsNSDisplayList))) {
       $wgOut->setPageTitle(wfMsgForContent($ac.'submission-failed'));
       $wgOut->addWikiText("<div class='errorbox'>".wfMsgForContent($ac.'invalid-field', wfMsgForContent($ac.'namespace-string'), $title->getNamespace())."</div>");
       return;
    }

    # Check whether the article page contains a <comments /> flag
    if (!preg_match('/^(.*<comments[^>]*>)(.*)$/s', $articleContent, $match)) {
        $wgOut->setPageTitle(wfMsgForContent($ac.'submission-failed'));
        $wgOut->addWikiText("<div class='errorbox'>".wfMsgForContent($ac.'no-comments', $title->getPrefixedText())."</div>");
        return;
    }
    # Append most recent comment
    $articleContent = $match[1] . wfMsgForContent($ac.'new-comment', $comment) . $match[2];

    # Update the article with the new comment
    $summary = wfMsgForContent($ac.'summary');
    if (method_exists($article, 'doEdit')) {
        $article->doEdit($articleContent, $summary);
    } else {
        $method = ($article->exists() ? 'updateArticle' : 'insertNewArticle' );
        $article->$method($articleContent, $summary, false, false);
        return;
    }

    $wgOut->addWikiText(wfMsgForContent($ac.'submission-success', $title->getPrefixedText()));
    $wgOut->setPageTitle(wfMsgForContent($ac.'submission-succeeded'));
}

/**
 * Checks ArticleComment fields for SPAM.
 * Usage: $wgHooks['ArticleCommentsSpamCheck'][] = 'defaultArticleCommentSpamCheck';
 * @param String $comment The comment body submitted (passed by value)
 * @param String $commenterName Name of commenter (passed by value)
 * @param String $commenterURL Website URL provided for comment (passed by value)
 * @param Boolean $isspam Whether the comment is spam (passed by reference)
 * @return Boolean Always true to indicate other hooking methods may continue to check for spam.
 */
function defaultArticleCommentSpamCheck($comment, $commenterName, $commenterURL, $isspam) {

    # Short-circuit if spam has already been determined
    if ($isspam) return true;

    # Rudimentary spam protection
    $spampatterns = array(
        '%\\[url=(https?|ftp)://%smi',
        '%<a\\s+[^>]*href\\s*=\\s*[\'"]?\\s*(https?|ftp)://%smi'
    );
    foreach ($spampatterns as $sp) {
        foreach (array($comment, $commenterName, $commenterURL) as $field) {
            $isspam = $isspam || preg_match($sp, $field);
        }
    }
    
    # Check for bad input for commenterName (seems to be a popular spam location)
    $spampatterns = array(
        '%(https?|ftp)://%smi',
        '%(\\n|\\r)%smi'
    );
    foreach ($spampatterns as $sp) {
        $isspam = $isspam || preg_match($sp, $commenterName);
    }
    
    # Fail for length violations
    $isspam = $isspam || strlen($commenterName)>255 || strlen($commenterURL)>300;
    
    # Give other implementors a chance.
    return true;
}

//</source>
?>
