Extension:WikiFeeds/SpecialWikiFeeds.php

From MediaWiki.org
Jump to: navigation, search
<?php
 
/*
 Wiki Feed Generator for MediaWiki
 Gregory Szorc <gregory.szorc@gmail.com>
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.
 
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 
 Directions:
 This script requires MediaWiki 1.5+ and PHP 5 to run.  It was developed against MediaWiki
 1.8.2 and PHP 5.1.6.  If it doesn't work, upgrade!
 
 To use this script, copy it to your extensions/ subdirectory inside the MediaWiki install.
 Add the following in LocalSettings.php:
 
 include_once('SpecialWikiFeeds.php');
 
 This script also needs my GenericXmlSyndicationFeed class, which can be found at
 http://opensource.case.edu/svn/MediaWikiHacks/classes/GenericXmlSyndicationFeed/GenericXmlSyndicationFeed.php
 
 You will need to manually include this file before the require/include SpecialWikiFeeds.php
 in LocalSettings.php or else when this file loads, you will get a big, fat error message.
 
 Example LocalSettings.php entry:
 
 require("$IP/extensions/GenericXmlSyndicationFeed.php");
 require("$IP/extensions/SpecialWikiFeeds.php");
 $wgWikiFeedsSettings['cacheEnable'] = true;
 
 Once WikiFeeds is enabled in LocalSettings.php,
 go to Special:WikiFeeds in your wiki.  Everything should be set!
 
 WikiFeeds can be slightly customized.  Settings which can be changed are located
 in the $wgWikiFeedsSettings array (defined and documentation below).  If you wish
 to change a setting, re-set it in LocalSettings.php, after including this file
 (see above example)
 
 If you encounter a bug, please file it at http://opensource.case.edu/projects/MediaWikiHacks
 or e-mail me.
 
 Other:
 The script supports ATOM 1.0 better than RSS 2.0.  ATOM is the future.  I'm not
 wasting my time adding full support for RSS.
 
 ToDo:
 Use MediaWiki language support through system messages (partially done)
 Better error checking
 Optimize SQL queries
 
 */
 
if (!defined('MEDIAWIKI')) die();
 
$wgExtensionFunctions[] = 'wfWikiFeeds';
$wgExtensionCredits['specialpage'][] = array(
  'name'=>'Wiki Feeds',
  'author'=>'Gregory Szorc <gregory.szorc@gmail.com>',
  'url'=>'http://wiki.case.edu/User:Gregory.Szorc',
  'description'=>'Produces syndicated feeds for MediaWiki.',
  'version'=>'0.5'
);
 
/**
 * Holds default settings for WikiFeeds
 *
 * Override values in LocalSettings.php after you include this file
 */
$wgWikiFeedsSettings = array(
        'cacheEnable' => false, //whether to enable the cache
        'cacheRoot'             => '/nfsn/content/ds-x/public/tmp/', //cache directory, with trailing slash
        'cacheMaxAge'   => 600, //max age of cached files, in seconds
        'cachePruneFactor' => 100, //prune stale cache entries 1 out of every this many requests
    'watchlistPrivate' => false, //when true, make per-user watchlists require special access token
);
 
function wfWikiFeeds() {
        global $wgMessageCache, $wgHooks;
 
        if (!class_exists('GenericXmlSyndicationFeed')) {
                throw new MWException('GenericXmlSyndicationFeed class not loaded.  Please read the install directions!');
        }
 
        require_once('SpecialPage.php');
 
        $wgMessageCache->addmessages(
        array(
                'wikifeeds'=>'Wiki Feeds',
                'wikifeeds_unknownformat' => "==Unknown format==\nThe feed format you requested is not available.",
                'wikifeeds_unknownfeed' => "==Unknown feed==\nThe feed you selected is not available.",
                'wikifeeds_undefinedcategory' => "==Undefined category==\nThe feed you requested requires that the ''category'' parameter be defined",
                'wikifeeds_categorynoexist' => "==Unknown category==\nThe category you tried to request does not exist.  Category names are case sensitive.  Be sure the category is listed at [[Special:Categories]]",
                'wikifeeds_undefineduser' => "==Undefined user==\nThe feed you requested requires that the ''user'' parameter be defined",
                'wikifeeds_unknownuser' => "==Unknown user==\nThe user specified does not exist. Be sure the user is listed at [[Special:User]]",
                'wikifeeds_nowatchlisttoken' => "==No Token==\nYou must supply a token to access your user watchlist",
                'wikifeeds_invalidwatchlisttoken' => "==Invalid Token==\nThe token specified to access the watchlist is invalid",
                'wikifeeds_namespacenoexist' => "==Unknown namespace==\nThe namespace you tried to request does not exist. You can type either name string or id of namespace.",
                'wikifeeds_tokeninfo' => "==Watchlist Token==\nYour private watchlist token is $1.  Access your watchlist via $2",
                'wikifeeds_feed_newestarticles_title' => 'Newest Articles',
                'wikifeeds_feed_newestarticles_description' => 'Newest articles in the wiki',
                'wikifeeds_feed_recentarticlechanges_title' => 'Recently Changed Articles',
                'wikifeeds_feed_recentarticlechanges_description' => 'Recently changed articles in the wiki',
                'wikifeeds_feed_newestarticlesbyuser_title' => 'Newest articles created by $1',
                'wikifeeds_feed_newestarticlesbyuser_description' => 'Newest articles created by $1',
                'wikifeeds_feed_recentuserchanges_title' => 'Recent changes by $1',
                'wikifeeds_feed_recentuserchanges_description' => 'Recent changes made by user $1',
                'wikifeeds_feed_userwatchlist_title' => 'Watchlist for $1',
                'wikifeeds_feed_userwatchlist_description' => 'Watchlist for $1',
                'wikifeeds_feed_recentcategorychanges_title' => 'Recently changed articles in $1',
                'wikifeeds_feed_recentcategorychanges_description' => 'Recently changed articles in category $1',
                'wikifeeds_feed_newestarticlesincategory_title' => 'Newest articles in the category $1',
                'wikifeeds_feed_newestarticlesincategory_description' => 'Newly created articles that are part of the category $1',
                'wikifeeds_feed_recentchangesnotincategory_title' => 'Recently changed articles not in $1',
                'wikifeeds_feed_recentchangesnotincategory_description' => 'Recently changed articles that are not part of the category $1',
                'wikifeeds_feed_newestarticlesnotincategory_title' => 'Newest articles not in the category $1',
                'wikifeeds_feed_newestarticlesnotincategory_description' => 'Newly created articles that are not part of the category $1',      
 
                'wikifeeds_mainpage' =>
"WikiFeeds generates syndicated feeds for this wiki.
 
==Available feeds==
Feeds are requested using an intuitive URL scheme.  All requests take the form of ''Special:WikiFeeds/'''format'''/'''feed'''/'''param1'''/'''value1'''/'''param2'''/'''value2''''' etc
 
===Formats===
Feeds ara available in the following formats
*ATOM 1.0 ('''format''' = ''atom'')
*RSS 2.0 ('''format''' = ''rss'')
 
===Feeds===
*Newest articles ('''newestarticles''') ([[Special:WikiFeeds/atom/newestarticles|atom]], [[Special:WikiFeeds/rss/newestarticles|rss]])
*Recently changed articles ('''recentarticlechanges''') ([[Special:WikiFeeds/atom/recentarticlechanges|atom]], [[Special:WikiFeeds/rss/recentarticlechanges|rss]])
*Recent changes by user ('''recentuserchanges''')
*Newest articles for a user ('''newestuserarticles''')
*User watchlist ('''watchlist''')
*Recent changes for articles in category ('''recentcategorychanges''')
*Recent changes for articles except changes made in selected category ('''recentchangesnotincategory''')
*Newest articles in category ('''newestcategoryarticles''')
*Newest articles except articles in selected category ('''newestarticlesnotincategory''')
 
===Parameters===
*user (required for user-related feeds)
*category (required for category-related feed)
*count - overrides default item limit
*namespace - filters feeds by namespace
 
==Example requests==
 
*{{fullurl:Special:WikiFeeds/atom/newestarticles}} - ATOM 1.0 feed of newest articles in the wiki
*{{fullurl:Special:WikiFeeds/atom/recentuserchanges/user/Gregory.Szorc}} - ATOM 1.0 feed for recent changes by [[User:Gregory.Szorc|Gregory.Szorc]]
*{{fullurl:Special:WikiFeeds/rss/watchlist/user/Jeremy.Smith/count/50}} - RSS feed for [[User:Jeremy.Smith|Jeremy.Smith]]'s watchlist
*{{fullurl:Special:WikiFeeds/rss/recentcategorychanges/category/Buildings}} - RSS feed for recently changed articles in the [[:Category:Buildings|Buildings]] category
*{{fullurl:Special:WikiFeeds/rss/recentchangesnotincategory/category/Buildings}} - RSS feed for recently changed articles, but not articles in the [[:Category:Buildings|Buildings]] category 
*{{fullurl:Special:WikiFeeds/atom/newestarticlesnotincategory/category/Buildings}} - ATOM 1.0 feed of newest articles, but not articles in the [[:Category:Buildings|Buildings]] category
*{{fullurl:Special:WikiFeeds/atom/newestarticles/namespace/main}} - ATOM 1.0 feed of newest articles in the wiki in main namespace
__NOTOC__
",
 
        )
        );
 
        $wgHooks['ParserAfterTidy'][] = 'wfWikiFeeds_Linker';
 
        class SpecialWikiFeeds extends SpecialPage {
                const FEED_NEWESTARTICLES = 1;
                const FEED_RECENTARTICLECHANGES = 2;
                const FEED_RECENTUSERCHANGES = 3;
                const FEED_USERWATCHLIST = 4;
                const FEED_RECENTCATEGORYCHANGES = 5;
                const FEED_NEWESTCATEGORYARTICLES = 6;
                const FEED_NEWESTARTICLESBYUSER = 7;
                const FEED_RECENTCHANGESNOTINCATEGORY = 8;
                const FEED_NEWESTARTICLESNOTINCATEGORY = 9;
                const DEFAULT_COUNT = 15;
 
                protected $_settings = array();
 
                public function __construct() {
                        global $wgWikiFeedsSettings;
 
                        $this->_settings = $wgWikiFeedsSettings;
 
                        SpecialPage::SpecialPage('WikiFeeds');
 
                        //we automatically prune the cache randomly
                        if ($this->_settings['cacheEnable']) {
                                if (rand(1, $this->_settings['cachePruneFactor']) === 1) {
                                        $this->_cachePrune();
                                }
                        }
 
                }
 
                public function execute($par = null) {
                        global $wgOut, $wgRequest, $wgServer, $wgWikiFeedsSettings, $wgUser;
 
                        $request = isset($par) ? $par : $wgRequest->getText('request');
 
                        if (!$request) {
                                $wgOut->addWikiText(wfMsg('wikifeeds_mainpage'));
 
                                // do special voodoo if private watchlist is enabled
                                if ($wgWikiFeedsSettings['watchlistPrivate'] && $wgUser->isLoggedIn()) {
                                        if (!$wgUser->getOption('watchlistToken')) {
                                                $token = md5(microtime() . $wgUser->getID());
 
                                                $wgUser->setOption('watchlistToken', $token);
                                                $wgUser->saveSettings();
                                        }
 
                                        $token = $wgUser->getOption('watchlistToken');
 
                                        $privateFeedUrl = Title::newFromText('WikiFeeds/atom/watchlist/user/' . $wgUser->getName() . '/token/' . $token, NS_SPECIAL);
 
                                        // and display blurb about token
                                        $wgOut->addWikiText(wfMsg('wikifeeds_tokeninfo', $token, $privateFeedUrl->getFullUrl()));
                                }
                        }
                        else {
                                $arr = explode('/', $request);
 
 
                                //might have a valid request for a feed
                                if (count($arr) >= 2) {
                                        $format = null;
                                        $feed = null;
                                        $count = self::DEFAULT_COUNT;
                                        $namespace = null;
                                        $params = array();
                                        $areSane = true;
 
                                        if (strtolower($arr[0]) == 'atom') {
                                                $format = GenericXmlSyndicationFeed::FORMAT_ATOM10;
                                        }
                                        else if (strtolower($arr[0]) == 'rss') {
                                                $format = GenericXmlSyndicationFeed::FORMAT_RSS20;
                                        }
                                        else {
                                                $wgOut->addWikiText(wfMsg('wikifeeds_unknownformat'));
                                                $areSane = false;
                                        }
 
                                        switch (strtolower($arr[1])) {
                                                case 'newestarticles':
                                                        $feed = self:: FEED_NEWESTARTICLES;
                                                        break;
 
                                                case 'recentarticlechanges':
                                                        $feed = self::FEED_RECENTARTICLECHANGES;
                                                        break;
 
                                                case 'recentuserchanges':
                                                        $feed = self::FEED_RECENTUSERCHANGES;
                                                        break;
 
                                                case 'newestuserarticles':
                                                        $feed = self::FEED_NEWESTARTICLESBYUSER;
                                                        break;
 
                                                case 'watchlist':
                                                        $feed = self::FEED_USERWATCHLIST;
                                                        break;
 
                                                case 'recentcategorychanges':
                                                        $feed = self::FEED_RECENTCATEGORYCHANGES;
                                                        break;
 
                                                case 'newestcategoryarticles':
                                                        $feed = self::FEED_NEWESTCATEGORYARTICLES;
                                                        break;
 
                                                case 'recentchangesnotincategory':
                                                        $feed = self::FEED_RECENTCHANGESNOTINCATEGORY;
                                                        break;
 
                                                case 'newestarticlesnotincategory':
                                                        $feed = self::FEED_NEWESTARTICLESNOTINCATEGORY;
                                                        break; 
 
                                                default:
                                                        $wgOut->addWikiText(wfMsg('wikifeeds_unknownfeed'));
                                                        $areSane = false;
                                        }
 
                                        //now we look for additional parameters
                                        if ( (count($arr) > 3) && ((count($arr) % 2) == 0) ) {
                                                for ($i = 2; $i < count($arr); $i += 2) {
                                                        $params[$arr[$i]] = $arr[$i+1];
                                                }
                                        }
 
                                        //if we are dealing with a category feed, we need a category specified
                                        if ($feed === self::FEED_RECENTCATEGORYCHANGES || $feed === self::FEED_NEWESTCATEGORYARTICLES ||
                                                $feed === self::FEED_RECENTCHANGESNOTINCATEGORY || $feed === self::FEED_NEWESTARTICLESNOTINCATEGORY) {
                                                if (!array_key_exists('category', $params)) {
                                                        $wgOut->addWikiText(wfMsg('wikifeeds_undefinedcategory'));
                                                        $areSane = false;
                                                }
                                                else {
                                                        //verify category is valid
                                                        if (!$this->categoryExists($params['category'])) {
                                                                $wgOut->addWikiText(wfMsg('wikifeeds_categorynoexist'));
                                                                $areSane = false;
                                                        }
                                                }
                                        }                                       
 
                                        //if we are asking for a user feed, we need the user parameter
                                        if ($feed === self::FEED_RECENTUSERCHANGES || $feed === self::FEED_USERWATCHLIST || $feed === self::FEED_NEWESTARTICLESBYUSER) {
                                                if (!array_key_exists('user', $params)) {
                                                        $wgOut->addWikiText(wfMsg('wikifeeds_undefineduser'));
                                                        $areSane = false;
                                                }
                                                else {
                                                        //verify the user exists
                                                        $userId = User::idFromName($params['user']);
 
                                                        if (is_null($userId) || $userId === 0) {
                                                                $wgOut->addWikiText(wfMsg('wikifeeds_unknownuser'));
                                                                $areSane = false;
                                                        } else if ($feed === self::FEED_USERWATCHLIST && $wgWikiFeedsSettings['watchlistPrivate']) {
                                                                if (!array_key_exists('token', $params)) {
                                                                        $wgOut->addWikiText(wfMsg('wikifeeds_nowatchlisttoken'));
                                                                        $areSane = false;
                                                                } else {
                                                                        $token = $params['token'];
 
                                                                        // verify token is sane
                                                                        $user = User::newFromId($userId);
 
                                                                        if ($token != $user->getOption('watchlistToken')) {
                                                                                $wgOut->addWikiText(wfMsg('wikifeeds_invalidwatchlisttoken'));
                                                                                $areSane = false;
                                                                        }
                                                                }
                                                        }
                                                }
                                        }
 
                                        if (array_key_exists('count', $params) && ctype_digit($params['count']) && $params['count'] > 0) {
                                                $count = $params['count'];
                                        }
 
                                        if (array_key_exists('namespace', $params)) {
 
                                                $ns = $params['namespace'];
 
                                                if (ctype_digit($ns) && (MWNamespace::exists($ns)) ) {  
                                                        // parameter is a number
                                                        $namespace = $ns;
                                                } else if (MWNamespace::exists(MWNamespace::getCanonicalIndex($ns))) { 
                                                        // parameter is a text
                                                        $namespace = MWNamespace::getCanonicalIndex($ns);
                                                } else if (strtolower($ns) == 'main' || $ns == '0'){ 
                                                        // parameter is MAIN namespace (ns_id:0)
                                                        $namespace = 0; 
                                                } else {
                                                        //namespace does not exist, show nothing
                                                        $wgOut->addWikiText(wfMsg('wikifeeds_namespacenoexist'));
                                                        $areSane = false;
                                                        $namespace = "NULL"; 
                                                }
                                        }                                       
 
                                        if (array_key_exists('unique', $params) && $params['count'] != 'false') {
                                                $unique = true;
                                        } else {
                                                $unique = false;
                                        }
 
                                        //we are sane, so let's create a feed
                                        if ($areSane) {
                                                $wgOut->disable();
 
 
                                                //if we successfully fetched a feed from the cache
                                                if ($this->_settings['cacheEnable'] && $cached = $this->_cacheFetchFeed($feed, $format, $params)) {
                                                        //$cached is an array containing feed text and some metadata
                                                        header('Content-Type: ' . $cached['content-type']);
                                                        print $cached['content'];
 
                                                } else { //no cache was hit, assemble the feed
 
                                                        $Feed = new GenericXmlSyndicationFeed($format);
 
                                                        switch ($feed) {
                                                                case self::FEED_NEWESTARTICLES:
                                                                        $this->makeNewestArticles($Feed, $namespace, $count);
                                                                        break;
 
                                                                case self::FEED_RECENTARTICLECHANGES:
                                                                        $this->makeRecentArticleChanges($Feed, $namespace, $count, $unique);
                                                                        break;
 
                                                                case self::FEED_NEWESTARTICLESBYUSER:
                                                                        $this->makeNewestArticleByUser($Feed, $params['user'], $namespace, $count);
                                                                        break;
 
                                                                case self::FEED_RECENTUSERCHANGES:
                                                                        $this->makeRecentUserChanges($Feed, $params['user'], $namespace, $count);
                                                                        break;
 
                                                                case self::FEED_USERWATCHLIST:
                                                                        $this->makeUserWatchlist($Feed, $params['user'], $namespace, $count);
                                                                        break;
 
                                                                case self::FEED_RECENTCATEGORYCHANGES:
                                                                        $this->makeRecentCategoryPageChanges($Feed, $params['category'], $namespace, $count);
                                                                        break;
 
                                                                case self::FEED_NEWESTCATEGORYARTICLES:
                                                                        $this->makeNewestArticlesInCategory($Feed, $params['category'], $namespace, $count);
                                                                        break;
 
                                                                case self::FEED_RECENTCHANGESNOTINCATEGORY:
                                                                        $this->makeRecentPageChangesNotInCategory($Feed, $params['category'], $namespace, $count);
                                                                        break;
 
                                                                case self::FEED_NEWESTARTICLESNOTINCATEGORY:
                                                                        $this->makeNewestArticlesNotInCategory($Feed, $params['category'], $namespace, $count);
                                                                        break; 
 
                                                                default:
 
                                                                        echo 'unknown feed';
                                                        }
 
                                                        $feedDate = 0;
                                                        foreach ($Feed->items as $i) {
                                                                if ($i->mArticle->mTimestamp > $feedDate) $feedDate = $i->mArticle->mTimestamp;
                                                        }
 
                                                        $Feed->lastUpdated = wfTimestamp(TS_UNIX, $feedDate);
 
                                                        $Feed->linkSelf = $wgServer.$_SERVER['REQUEST_URI'];
 
                                                        $Feed->sendOutput();
 
                                                        //finally, cache the feed
                                                        if ($this->_settings['cacheEnable']) {
                                                                $this->_cacheSaveFeed($Feed, $feed, $format, $params);
                                                        }
                                                }
                                        }
                                }
 
                        }
 
 
                        $this->setHeaders();
 
                }
 
                /**
                 * This function checks to see if a category exists
                 * It would be really nice if MediaWiki had this method available, but all the SQL is inline
                 * Comparison is case sensitive
                 */
                protected function categoryExists($cat) {
                        $dbr =& wfGetDB( DB_SLAVE );
                        $categorylinks = $dbr->tableName( 'categorylinks' );
                        if ($result = $dbr->safeQuery("SELECT count(*) FROM $categorylinks WHERE cl_to=?", $cat)) {
                                if ($row = $dbr->fetchRow($result)) {
                                        return $row[0] > 0 ? true : false;
                                }
                        }
 
                        return false;
 
                }
 
                /**
                 * Create a feed for newest articles in the wiki
                 */
                protected function makeNewestArticles(GenericXmlSyndicationFeed &$feed, $namespace, $count = self::DEFAULT_COUNT) {
                        $feed->title = wfMsg('wikifeeds_feed_newestarticles_title');
                        $feed->description = wfMsg('wikifeeds_feed_newestarticles_description');
 
                        $altUrlTitle = Title::makeTitle(NS_SPECIAL, 'Newpages');
                        $feed->linkAlternate = $altUrlTitle->getFullURL();
 
                        // filter namespace
                        if(isset($namespace)) {
                                $namespace_sql = "AND rc_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }
 
                        $dbr = wfGetDB(DB_SLAVE);
 
                        extract($dbr->tableNames('recentchanges', 'page', 'text'));
 
                        $sql = "SELECT 'Newpages' as type, rc_namespace AS namespace, rc_title AS value, rc_user AS user,
        rc_user_text AS user_text,
        rc_comment AS comment,
        rc_timestamp AS timestamp,
        rc_id AS rcid,
        page_id AS page,
        page_len AS length,
        page_latest AS latest
        FROM $recentchanges,$page
        WHERE rc_cur_id=page_id AND rc_new=1 AND page_is_redirect=0 $namespace_sql
        ORDER BY rc_timestamp DESC LIMIT $count";
 
                        if ($result = $dbr->doQuery($sql)) {
                                if ($dbr->numRows($result)) {
                                        while ($row = $dbr->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title);                                          
                                                $feed->addItem($item);
                                        }
                                }
                        }
                }
 
                /**
                 * Feed for recently changed articles
                 *
                 * @todo Implement $unique changes to SQL query
                 */
                protected function makeRecentArticleChanges(GenericXmlSyndicationFeed &$feed, $namespace, $count = self::DEFAULT_COUNT, $unique = false) {
                        $feed->title = wfMsg('wikifeeds_feed_recentarticlechanges_title');
                        $feed->description = wfMsg('wikifeeds_feed_recentarticlechanges_description');
 
                        $altUrlTitle = Title::makeTitle(NS_SPECIAL, 'Recentchanges');
                        $this->linkAlternate = $altUrlTitle->getFullURL();
 
                        // filter namespace
                        if(isset($namespace)) {
                                $namespace_sql = "AND rc_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }
 
                        $db = wfGetDB(DB_SLAVE);
 
                        extract($db->tableNames('recentchanges','page', 'revision'));
 
                        $sql = "SELECT rev_id AS revid, rev_page AS page, rev_user_text as user, rc_id AS rcid 
      FROM $recentchanges, $revision
      WHERE rc_this_oldid=rev_id AND rev_deleted=0 AND rev_minor_edit=0 $namespace_sql
      ORDER BY rev_id DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title, $row['revid'], $row['rcid']);
                                                $feed->addItem($item);
                                        }
                                }
                        }
                }
 
                /**
                 * Recent articles created by a specified user
                 */
                protected function makeNewestArticleByUser(GenericXmlSyndicationFeed &$feed, $user, $namespace, $count = self::DEFAULT_COUNT) {
 
                        $u = User::newFromName($user);
 
                        if (!$u) return;
 
                        $feed->title = wfMsg('wikifeeds_feed_newestarticlesbyuser_title', $user);
                        $feed->description = wfMsg('wikifeeds_feed_newestarticlesbyuser_description', $user);
 
                        $altUrlTitle = Title::makeTitle(NS_SPECIAL, "Contributions/$user");
                        $feed->linkAlternate = $altUrlTitle->getFullURL();
 
                        $author = array();
                        $author['name'] = $user;
                        $author['email'] = $u->getEmail();
                        $userPage = $u->getUserPage();
                        $author['uri'] = $userPage->getFullURL();
 
                        $feed->addAuthor($author);
 
                        // filter namespace
                        if(isset($namespace)) {
                                $namespace_sql = "AND rc_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }
 
                        $db = wfGetDB(DB_SLAVE);
 
                        extract($db->tableNames('recentchanges','page', 'revision'));
 
                        $sql = "SELECT
      rc_id AS rcid,
      page_id AS page,
      page_latest AS latest
      FROM $recentchanges,$page
      WHERE rc_cur_id=page_id AND rc_new=1 AND page_is_redirect=0 AND rc_user_text='{$u->getName()}' $namespace_sql
      ORDER BY rc_timestamp DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title);
                                                $feed->addItem($item);
                                        }
                                }
                        }
                }
 
                /**
                 * Recent article changes by a specified user
                 */
                protected function makeRecentUserChanges(GenericXmlSyndicationFeed &$feed, $user, $namespace, $count = self::DEFAULT_COUNT) {
                        $u = User::newFromName($user);
 
                        if (!$u) return;
 
                        $feed->title = wfMsg('wikifeeds_feed_recentuserchanges_title', $user);
                        $feed->description = wfMsg('wikifeeds_feed_recentuserchanges_description', $user);
 
                        $altUrlTitle = Title::makeTitle(NS_SPECIAL, "Contributions/$user");
                        $feed->linkAlternate = $altUrlTitle->getFullURL();
 
                        $author = array();
                        $author['name'] = $user;
                        $author['email'] = $u->getEmail();
                        $userPage = $u->getUserPage();
                        $author['uri'] = $userPage->getFullURL();
 
                        $feed->addAuthor($author);
 
                        // filter namespace
                        if (isset($namespace)) {
                                $namespace_sql = "AND rc_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }                       
 
                        $db = wfGetDB(DB_SLAVE);
 
                        extract($db->tableNames('recentchanges', 'page', 'revision'));
 
                        $sql = "SELECT rev_id AS revid, rev_page AS page, rev_user_text as user, rc_id AS rcid
      FROM $recentchanges, $revision
      WHERE rc_this_oldid=rev_id AND rev_deleted=0 AND rev_user_text='{$u->getName()}' AND rev_minor_edit=0 $namespace_sql
      ORDER BY rev_id DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title, $row['revid'], $row['rcid']); 
                                                $feed->addItem($item);
                                        }
                                }
                        }
                }
 
                /**
                 * Watchlist for a specified user
                 */
                protected function makeUserWatchlist(GenericXmlSyndicationFeed &$feed, $user, $namespace, $count = self::DEFAULT_COUNT) {
                        $feed->title = wfMsg('wikifeeds_feed_userwatchlist_title', $user);
                        $feed->description = wfMsg('wikifeeds_feed_userwatchlist_description', $user);
 
                        $u = User::newFromName($user);
 
                        if (!$u) return;
 
                        $author = array();
                        $author['name'] = $user;
                        $author['email'] = $u->getEmail();
                        $userPage = $u->getUserPage();
                        $author['uri'] = $userPage->getFullURL();
 
                        $feed->addAuthor($author);
 
                        // filter namespace
                        if (isset($namespace)) {
                                $namespace_sql = "AND rc_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }
 
                        $db = wfGetDB(DB_SLAVE);
 
                        extract($db->tableNames('recentchanges', 'page', 'revision', 'watchlist'));
 
                        $sql = "SELECT rev_id as revid, rev_page AS page, rc_id AS rcid
      FROM $recentchanges, $revision, $watchlist
      WHERE wl_user={$u->getID()} AND rc_namespace=wl_namespace AND rc_title=wl_title
      AND rev_id=rc_this_oldid $namespace_sql
      ORDER BY rev_id DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title, $row['revid'], $row['rcid']);
                                                $feed->addItem($item);
                                        }
                                }
                        }
                }
 
                /**
                 * Recently changes articles in a specified category
                 */
                protected function makeRecentCategoryPageChanges(GenericXmlSyndicationFeed &$feed, $category, $namespace, $count = self::DEFAULT_COUNT) {
                        $feed->title = wfMsg('wikifeeds_feed_recentcategorychanges_title', $category);
                        $feed->description = wfMsg('wikifeeds_feed_recentcategorychanges_description', $category);
 
                        $pagesIn = $this->getPagesInCategory($category);
 
                        // filter namespace
                        if (isset($namespace)) {
                                $namespace_sql = "AND rc_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }                       
 
                        $db = wfGetDB(DB_SLAVE);
                        extract($db->tableNames('recentchanges', 'page', 'revision'));
 
                        $sql = "SELECT rev_id AS revid, rev_page AS page, rc_id AS rcid
      FROM $recentchanges, $revision
      WHERE rev_page IN (".implode(',', $pagesIn).")
      AND rev_deleted=0 AND rc_this_oldid=rev_id AND rev_minor_edit=0 $namespace_sql
      ORDER BY rev_id DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title, $row['revid'], $row['rcid']);
                                                $feed->addItem($item);
                                        }
                                }
                        }
 
                }
 
                /**
                 * Newest articles in a specified category
                 */
                protected function makeNewestArticlesInCategory(GenericXmlSyndicationFeed &$feed, $category, $namespace, $count = self::DEFAULT_COUNT) {
                        $catTitle = Title::newFromText($category, NS_CATEGORY);
 
                        $feed->title = wfMsg('wikifeeds_feed_newestarticlesincategory_title', $catTitle->getText());
                        $feed->description = wfMsg('wikifeeds_feed_newestarticlesincategory_description', $catTitle->getText());
 
                        // filter namespace
                        if (isset($namespace)) {
                                $namespace_sql = "AND page_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }       
 
                        $db = wfGetDB(DB_SLAVE);
 
                        extract($db->tableNames('page','categorylinks'));
 
                        $sql = "SELECT
      page_id AS page
      FROM $page,$categorylinks
      WHERE cl_to='{$catTitle->getDBkey()}' AND cl_from=page_id $namespace_sql
      ORDER BY cl_timestamp DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title); 
                                                $feed->addItem($item);
                                        }
                                }
                        }
                }
 
                        /**
                 * Recently changes articles in a specified category
                 */
                protected function makeRecentPageChangesNotInCategory(GenericXmlSyndicationFeed &$feed, $category, $namespace, $count = self::DEFAULT_COUNT) {
                        $feed->title = wfMsg('wikifeeds_feed_recentchangesnotincategory_title', $category);
                        $feed->description = wfMsg('wikifeeds_feed_recentchangesnotincategory_description', $category);
 
                        $pagesIn = $this->getPagesInCategory($category);
 
                        // filter namespace
                        if (isset($namespace)) {
                                $namespace_sql = "AND rc_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }                       
 
                        $db = wfGetDB(DB_SLAVE);
                        extract($db->tableNames('recentchanges', 'page', 'revision'));
 
                        $sql = "SELECT rev_id AS revid, rev_page AS page, rc_id AS rcid
      FROM $recentchanges, $revision
      WHERE rev_page NOT IN (".implode(',', $pagesIn).")
      AND rev_deleted=0 AND rc_this_oldid=rev_id AND rev_minor_edit=0 $namespace_sql
      ORDER BY rev_id DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title, $row['revid'], $row['rcid']);
                                                $feed->addItem($item);
                                        }
                                }
                        }
 
                }
 
                /**
                 * Newest articles NOT in a specified category
                 */
                protected function makeNewestArticlesNotInCategory(GenericXmlSyndicationFeed &$feed, $category, $namespace, $count = self::DEFAULT_COUNT) {
                        $catTitle = Title::newFromText($category, NS_CATEGORY);
 
                        $feed->title = wfMsg('wikifeeds_feed_newestarticlesnotincategory_title', $catTitle->getText());
                        $feed->description = wfMsg('wikifeeds_feed_newestarticlesnotincategory_description', $catTitle->getText());
 
                        $pagesIn = $this->getPagesInCategory($category);
 
                        // filter namespace
                        if (isset($namespace)) {
                                $namespace_sql = "AND page_namespace = $namespace";
                        } else {
                                $namespace_sql = "";
                        }       
 
                        $db = wfGetDB(DB_SLAVE);
 
                        extract($db->tableNames('page','recentchanges'));
 
                        $sql = "SELECT page_id AS page
        FROM $recentchanges,$page
        WHERE page_id NOT IN (".implode(',', $pagesIn).") AND rc_cur_id=page_id AND rc_new=1 AND page_is_redirect=0 $namespace_sql
        ORDER BY rc_timestamp DESC LIMIT $count";
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $title = Title::newFromID($row['page']);
                                                $item = new MediaWikiFeedItem($title); 
                                                $feed->addItem($item);
                                        }
                                }
                        }
                }
 
                /**
                 * Return all the pages in a given category
                 */
                protected function getPagesInCategory($catName) {
                        $db = wfGetDB(DB_SLAVE);
 
                        $catTitle = Title::newFromText($catName, NS_CATEGORY);
 
                        $db = wfGetDB(DB_SLAVE);
 
                        extract($db->tableNames('categorylinks'));
 
                        $sql = "SELECT cl_from AS page FROM $categorylinks WHERE cl_to='{$catTitle->getDBkey()}'";
 
                        $r = array();
 
                        if ($result = $db->doQuery($sql)) {
                                if ($db->numRows($result)) {
                                        while ($row = $db->fetchRow($result)) {
                                                $r[] = $row['page'];
                                        }
                                }
                        }
 
                        return $r;
 
                }
 
                /**
                 * Fetch a feed from the cache
                 *
                 * This function will try to fetch a feed specified by type, format
                 * and parameters from the cache.  If the feed does not exist in the
                 * cache or if the cache entry is stale, we don't return anything
                 *
                 * @param $feed Feed type constant
                 * @param $format Feed format constant
                 * @param array $params Parameters to pass to the feed constructor
                 *
                 * @return array Array that describes the feed|false on failure
                 */
                protected function _cacheFetchFeed($feed, $format, $params) {
                        $filename = $this->_cacheFilename($feed, $format, $params);
 
                        if (file_exists($filename) && is_readable($filename)) {
                                if ( (time() - filemtime($filename)) < $this->_settings['cacheMaxAge']) {
                                        $content = unserialize(file_get_contents($filename));
 
                                        if (is_array($content)) {
                                                return $content;
                                        }
                                } else {
                                        //the file is old, we might as well prune it now
                                        //to save a few system calls
                                        unlink($filename);
                                }
                        }
 
                        return false;
                }
 
                /**
                 * Save a specified feed object corresponding to given parameters to the
                 * cache
                 */
                protected function _cacheSaveFeed(GenericXmlSyndicationFeed $feedObj, $feed, $format, $params) {
                        $filename = $this->_cacheFilename($feed, $format, $params);
 
                        $cacheContent = array(
                'content-type' => $feedObj->contentType,
                'content' => $feedObj->getContent(false)
                        );
 
                        file_put_contents($filename, serialize($cacheContent), LOCK_EX);
                }
 
 
                /**
                 * Returns the filename a specified feed should have
                 *
                 * @param $feed Feed type constant
                 * @param $foramt Feed format constant
                 * @param array $params Array of feed parameters
                 */
                protected function _cacheFilename($feed, $format, $params) {
                        $s = $this->_settings['cacheRoot'] . 'wikifeeds_cache-';
 
                        $s .= md5($feed.$format.serialize($params));
 
                        return $s;
                }
 
                /**
                 * Prune the cache of stale entries
                 */
                protected function _cachePrune() {
                        foreach ($this->_getCacheFiles() as $file) {
                                if ((time() - filemtime($file)) > $this->_settings['cacheMaxAge']) {
                                        unlink($file);
                                }
 
                        }
                }
 
                /**
                 * Purge the cache of all entries
                 */
                protected function _cachePurge() {
                        foreach ($this->_getCacheFiles() as $file) {
                                unlink($file);
                        }
                }
 
                /**
                 * Returns an array of all files in the cache
                 */
                protected function _getCacheFiles() {
                        $return = array();
 
                        $directory = new DirectoryIterator($this->_settings['cacheRoot']);
 
                        foreach ($directory as $file) {
                                if ($file->isFile()) {
                                        if (strpos($file->getPathname(), $this->_settings['cacheRoot'] . 'wikifeeds_cache-') === 0) {
                                                $return[] = $file->getPathname();
                                        }
                                }
                        }
 
                        return $return;
                }
 
        }
 
        /**
         * This is a MediaWiki-specific definition of a feed item
         */
        class MediaWikiFeedItem extends GenericXmlSyndicationFeedItem {
 
                public function __construct($title, $revId = 0, $rcid = 0) {
                        parent::__construct();
 
                        $this->allowedVars = array_merge($this->allowedVars, array(
        'mTitle','mRevId','mRCid','mArticle'
        )
        );
 
        $this->mTitle = $title;
        $this->mRevId = $revId;
        $this->mRCid = $rcid;
 
        $this->mArticle = new Article($this->mTitle);
 
        $this->mArticle->fetchContent($this->mRevId);
 
        $this->publishTime = wfTimestamp(TS_UNIX, $this->mArticle->mTimestamp);
        $this->title = $this->mTitle->getFullText();
 
        if ($rcid == 0) {
                $this->guid = $this->mTitle->getFullURL();
        }
        else {
                $this->guid = $this->mTitle->escapeFullURL("oldid={$this->mRevId}");
        }
 
        $this->linkSelf = $this->_guid;
        $this->linkAlternate = $this->mTitle->getFullURL();
 
        $author = array();
 
        $author['name'] = $this->mArticle->getUserText();
 
        if ($u = User::newFromName($author['name'])) {
                $author['email'] = $u->getEmail();
 
                $userPage = $u->getUserPage();
                $author['uri'] = $userPage->getFullURL();
        }
 
        $this->_vars['authors'][] = $author;
 
        //create a new default user to invoke the parser
        $u = new User();
 
        global $wgParser;
 
        $output = $wgParser->parse($this->mArticle->getContent(false), $this->mTitle, ParserOptions::newFromuser($u));
        $text = $output->mText;
 
        //$this->_content = html_entity_decode($text);
        $this->content = $text;
 
        $categories = $this->mTitle->getParentCategories();
 
        if (is_array($categories)) {
 
                foreach ($categories as $c=>$v) {
          $categoryTitle = Title::newFromText($c);
 
          $this->_vars['categories'][] = $categoryTitle->getText();
                }
        }
                }
 
        }
 
        SpecialPage::addPage(new SpecialWikiFeeds);
}
 
/**
 * Hooks registered on ParserAfterTidy hook
 * 
 * Injects links to feeds in HTML <head>
 * 
 */
function wfWikiFeeds_Linker($a, $b) {
        global $wgWikiFeedsSettings, $wgTitle;
 
        // only link against each article once
        if (!array_key_exists('__linkerTracker', $wgWikiFeedsSettings)) {
                $wgWikiFeedsSettings['__linkerTracker'] = array();
        }
 
        // if we have already processed this title, don't do it again
        if (in_array($wgTitle->getFullText(), $wgWikiFeedsSettings['__linkerTracker'])) {               
                return true;
        } else {
                // mark as processed
                $wgWikiFeedsSettings['__linkerTracker'][] = $wgTitle->getFullText();
        }
 
        switch ($wgTitle->getNamespace()) {
                case NS_USER:
                case NS_USER_TALK:
                        {
                                $username = $wgTitle->getPartialURL();
 
                                //strip out subpage if we are on one
                                if ($pos = strpos($username, '/')) {
                                        $username = substr($username, 0, $pos);
                                }
 
                                $t0 = Title::newFromText("WikiFeeds/atom/recentuserchanges/user/$username", NS_SPECIAL);
                                $t1 = Title::newFromText("WikiFeeds/rss/recentuserchanges/user/$username", NS_SPECIAL);
                                $t2 = Title::newFromText("WikiFeeds/atom/newestuserarticles/user/$username", NS_SPECIAL);
                                $t3 = Title::newFromText("WikiFeeds/rss/newestuserarticles/user/$username", NS_SPECIAL);
                                $t4 = Title::newFromText("WikiFeeds/atom/watchlist/user/$username", NS_SPECIAL);
                                $t5 = Title::newFromText("WikiFeeds/rss/watchlist/user/$username", NS_SPECIAL);
 
                                wfWikiFeeds_AddLink('atom', "Recently changed articles by $username (ATOM 1.0)", $t0->getFullUrl());
                                wfWikiFeeds_AddLink('atom', "Latest articles created by $username (ATOM 1.0)", $t2->getFullUrl());
                                wfWikiFeeds_AddLink('atom', "Watchlist for $username (ATOM 1.0)", $t4->getFullUrl());
                                wfWikiFeeds_AddLink('rss', "Recently changes articles by $username (RSS 2.0)", $t1->getFullUrl());
                                wfWikiFeeds_AddLink('rss', "Latest articles created by $username (RSS 2.0)", $t3->getFullUrl());
                                wfWikiFeeds_AddLink('rss', "Watchlist for $username (RSS 2.0)", $t5->getFullUrl());
 
                        }
                        break;
 
                case NS_CATEGORY:
                case NS_CATEGORY_TALK:
                        {
                                $category = $wgTitle->getPartialURL();
                                $catName = $wgTitle->getText();
 
                                $atomCatNew = Title::newFromText("WikiFeeds/atom/newestcategoryarticles/category/$catName", NS_SPECIAL);
                                $rssCatNew = Title::newFromText("WikiFeeds/rss/newestcategoryarticles/category/$catName", NS_SPECIAL);
                                $atomCatRecent = Title::newFromText("WikiFeeds/atom/recentcategorychanges/category/$catName", NS_SPECIAL);
                                $rssCatRecent = Title::newFromText("WikiFeeds/rss/recentcategorychanges/category/$catName", NS_SPECIAL);
 
                                wfWikiFeeds_AddLink('atom', "Newest pages to join the $catName category (ATOM 1.0)", $atomCatNew->getFullUrl());
                                wfWikiFeeds_AddLink('atom', "Recently changed articles in the $catName category (ATOM 1.0)", $atomCatRecent->getFullUrl());
                                wfWikiFeeds_AddLink('rss', "Newest pages to join the $catName category (RSS 2.0)", $rssCatNew->getFullUrl());
                                wfWikiFeeds_AddLink('rss', "Recently changed articles in the $catName category (RSS 2.0)", $rssCatRecent->getFullUrl());
                        }
        }
 
        $atomRecentChanges = Title::newFromText("WikiFeeds/atom/recentarticlechanges", NS_SPECIAL);
        $atomNewestArticles = Title::newFromText("WikiFeeds/atom/newestarticles", NS_SPECIAL);
        $rssRecentChanges = Title::newFromText("WikiFeeds/rss/recentarticlechanges", NS_SPECIAL);
        $rssNewestArticles = Title::newFromText("WikiFeeds/rss/newestarticles", NS_SPECIAL);
 
        wfWikiFeeds_AddLink('atom', 'Recently changed articles (ATOM 1.0)', $atomRecentChanges->getFullUrl());
        wfWikiFeeds_AddLink('atom', 'Newest articles (ATOM 1.0)', $atomNewestArticles->getFullUrl());
        wfWikiFeeds_AddLink('rss', 'Recently changed articles (RSS 2.0)', $rssRecentChanges->getFullUrl());
        wfWikiFeeds_AddLink('rss', 'Newest articles (RSS 2.0)', $rssNewestArticles->getFullUrl());
 
        return true;
}
 
/**
 * Helper function for wfWikiFeeds_Linker
 */
function wfWikiFeeds_AddLink($format, $title, $url) {
        global $wgOut;
 
        $typeApp = 'application/atom+xml';
 
        if ($format == 'rss') {
                $typeApp = 'application/rss+xml';
        }
 
        //else
        $wgOut->addLink(array('rel'=>'alternate', 'type'=>$typeApp, 'title'=>$title, 'href'=>$url));
}
Personal tools
Namespaces

Variants
Actions
Navigation
Support
Download
Development
Communication
Print/export
Toolbox