r23543 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r23542‎ | r23543 | r23544 >
Date:08:29, 29 June 2007
Author:robchurch
Status:old
Tags:
Comment:
Introduce the Export Watchlist extension:

* Dump watchlist to a plain text list of titles
* Bulk-add pages from a list of titles
Modified paths:
  • /trunk/extensions/ExportWatchlist (added) (history)
  • /trunk/extensions/ExportWatchlist/ExportWatchlist.i18n.php (added) (history)
  • /trunk/extensions/ExportWatchlist/ExportWatchlist.php (added) (history)
  • /trunk/extensions/ExportWatchlist/LICENSE (added) (history)
  • /trunk/extensions/ExportWatchlist/README (added) (history)
  • /trunk/extensions/ExportWatchlist/SpecialExportWatchlist.php (added) (history)
  • /trunk/extensions/ExportWatchlist/SpecialImportWatchlist.php (added) (history)

Diff [purge]

Index: trunk/extensions/ExportWatchlist/LICENSE
@@ -0,0 +1,27 @@
 2+Copyright © 2007 Rob Church.
 3+All rights reserved.
 4+
 5+Redistribution and use in source and binary forms, with or without
 6+modification, are permitted provided that the following conditions
 7+are met:
 8+
 9+ 1. Redistributions of source code must retain the above copyright
 10+ notice, this list of conditions and the following disclaimer.
 11+
 12+ 2. Redistributions in binary form must reproduce the above
 13+ copyright notice, this list of conditions and the following
 14+ disclaimer in the documentation and/or other materials provided
 15+ with the distribution.
 16+
 17+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 18+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 19+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 20+DISCLAIMED.
 21+
 22+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 23+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 24+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 25+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 26+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 27+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 28+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
Property changes on: trunk/extensions/ExportWatchlist/LICENSE
___________________________________________________________________
Added: svn:eol-style
129 + native
Index: trunk/extensions/ExportWatchlist/SpecialImportWatchlist.php
@@ -0,0 +1,147 @@
 2+<?php
 3+
 4+/**
 5+ * Special page bulk-adds items from a list to the
 6+ * current user's watchlist
 7+ *
 8+ * @addtogroup Extensions
 9+ * @author Rob Church <robchur@gmail.com>
 10+ */
 11+class SpecialImportWatchlist extends SpecialPage {
 12+
 13+ /**
 14+ * Constructor
 15+ */
 16+ public function __construct() {
 17+ parent::__construct( 'ImportWatchlist' );
 18+ }
 19+
 20+ /**
 21+ * Main execution point
 22+ *
 23+ * @param mixed $par Parameter passed to the page
 24+ */
 25+ public function execute( $par = false ) {
 26+ global $wgUser, $wgRequest, $wgOut, $wgLang;
 27+ $this->setHeaders();
 28+
 29+ if( wfReadOnly() ) {
 30+ $wgOut->readOnlyPage();
 31+ return;
 32+ }
 33+
 34+ $wgOut->addHtml( wfMsgExt( 'importwatchlist-header', 'parse' ) );
 35+ $wgOut->addHtml( $this->buildForm() );
 36+
 37+ if( $wgRequest->wasPosted() ) {
 38+ $list = $this->extractTitles( $wgRequest->getText( 'titles' ) );
 39+ if( ( $count = count( $list ) ) > 0 ) {
 40+ $count = $wgLang->formatNum( $count );
 41+ $this->bulkWatch( $wgUser, $list );
 42+ $wgOut->addHtml( wfMsgExt( 'importwatchlist-success', 'parse', $count ) );
 43+ $this->showTitles( $wgOut, $wgUser->getSkin(), $list );
 44+ }
 45+ }
 46+ }
 47+
 48+ /**
 49+ * Explode a blob of text into a list of titles
 50+ *
 51+ * @param string $text List of titles
 52+ * @return array
 53+ */
 54+ private function extractTitles( $text ) {
 55+ $titles = explode( "\n", $text );
 56+ for( $i = 0; $i < count( $titles ); $i++ ) {
 57+ $titles[$i] = Title::newFromText( $titles[$i] );
 58+ if( !$titles[$i] instanceof Title )
 59+ unset( $titles[$i] );
 60+ }
 61+ return $titles;
 62+ }
 63+
 64+ /**
 65+ * Bulk-insert titles into a specified user's watchlist
 66+ *
 67+ * @param User $user User to insert for
 68+ * @param array $titles Titles to watch
 69+ */
 70+ private function bulkWatch( $user, $titles ) {
 71+ $dbw = wfGetDB( DB_MASTER );
 72+ $values = array();
 73+ foreach( $titles as $title ) {
 74+ $values[] = array(
 75+ 'wl_user' => $user->getId(),
 76+ 'wl_namespace' => ( $title->getNamespace() & ~1 ),
 77+ 'wl_title' => $title->getDBkey(),
 78+ 'wl_notificationtimestamp' => null,
 79+ );
 80+ $values[] = array(
 81+ 'wl_user' => $user->getId(),
 82+ 'wl_namespace' => ( $title->getNamespace() | 1 ),
 83+ 'wl_title' => $title->getDBkey(),
 84+ 'wl_notificationtimestamp' => null,
 85+ );
 86+ }
 87+ $dbw->insert(
 88+ 'watchlist',
 89+ $values,
 90+ __METHOD__,
 91+ 'IGNORE'
 92+ );
 93+ $user->invalidateCache();
 94+ }
 95+
 96+ /**
 97+ * Build the title input form
 98+ *
 99+ * @return string
 100+ */
 101+ private function buildForm() {
 102+ $form = Xml::openElement( 'form', array( 'method' => 'post',
 103+ 'action' => $this->getTitle()->getLocalUrl() ) );
 104+ $form .= '<fieldset><legend>' . wfMsgHtml( 'importwatchlist-legend' ) . '</legend>';
 105+ $form .= Xml::label( wfMsg( 'importwatchlist-titles' ), 'titles' ) . '<br />';
 106+ $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles',
 107+ 'rows' => 8, 'cols' => 80 ) ) . Xml::closeElement( 'textarea' );
 108+ $form .= '<p>' . Xml::submitButton( wfMsg( 'importwatchlist-submit' ) ) . '</p>';
 109+ $form .= '</fieldset></form>';
 110+ return $form;
 111+ }
 112+
 113+ /**
 114+ * Print out a list of titles
 115+ *
 116+ * @param OutputPage $out
 117+ * @param Skin $skin
 118+ * @param array $titles
 119+ */
 120+ private function showTitles( $out, $skin, $titles ) {
 121+ $batch = new LinkBatch();
 122+ foreach( $titles as $title )
 123+ $batch->addObj( $title );
 124+ $batch->execute();
 125+ $out->addHtml( '<ul>' );
 126+ foreach( $titles as $title )
 127+ $out->addHtml( '<li>' . $skin->makeLinkObj( $title ) . '</li>' );
 128+ $out->addHtml( '</ul>' );
 129+ }
 130+
 131+ /**
 132+ * Show a "please log in" page
 133+ *
 134+ * @param OutputPage $out
 135+ * @param User $user
 136+ */
 137+ private function showLoginPage( $out, $user ) {
 138+ $link = $user->getSkin()->makeKnownLinkObj(
 139+ SpecialPage::getTitleFor( 'Userlogin' ),
 140+ wfMsgHtml( 'exportwatchlist-login-link' ),
 141+ 'returnto=' . $this->getTitle()->getPrefixedUrl()
 142+ );
 143+ $out->addHtml( wfMsgHtml( 'importwatchlist-login', $link ) );
 144+ }
 145+
 146+}
 147+
 148+?>
\ No newline at end of file
Property changes on: trunk/extensions/ExportWatchlist/SpecialImportWatchlist.php
___________________________________________________________________
Added: svn:eol-style
1149 + native
Index: trunk/extensions/ExportWatchlist/ExportWatchlist.i18n.php
@@ -0,0 +1,46 @@
 2+<?php
 3+
 4+/**
 5+ * Messages file for ExportWatchlist extension
 6+ *
 7+ * @addtogroup Extensions
 8+ * @author Rob Church <robchur@gmail.com>
 9+ */
 10+
 11+/**
 12+ * Prepare extension messages
 13+ *
 14+ * @return array
 15+ */
 16+function efExportWatchlistMessages() {
 17+ $messages = array(
 18+
 19+/**
 20+ * English
 21+ */
 22+'en' => array(
 23+ 'exportwatchlist' => 'Export watchlist',
 24+ 'exportwatchlist-header' => 'Use this page to export [[Special:Watchlist|your watchlist]]
 25+ to a list of titles so you can move it to another account or share it with other users.',
 26+ 'exportwatchlist-login' => 'You need to have an account and be $1 to export a watchlist.',
 27+ 'exportwatchlist-login-link' => 'logged in',
 28+ 'exportwatchlist-legend' => 'Export watchlist',
 29+ 'exportwatchlist-namespace' => 'Namespace:',
 30+ 'exportwatchlist-submit' => 'Export',
 31+ 'exportwatchlist-none' => 'Your watchlist contains no pages.',
 32+ 'exportwatchlist-none-ns' => 'Your watchlist contains no pages in this namespace.',
 33+ 'importwatchlist' => 'Import watchlist',
 34+ 'importwatchlist-header' => 'Use this page to bulk-add a list of titles to
 35+ [[Special:Watchlist|your watchlist]].',
 36+ 'importwatchlist-login' => 'You need to have an account and be $1 to import a watchlist.',
 37+ 'importwatchlist-legend' => 'Import titles into watchlist',
 38+ 'importwatchlist-titles' => 'Enter a list of titles below, one per line:',
 39+ 'importwatchlist-submit' => 'Import',
 40+ 'importwatchlist-success' => 'Imported $1 item(s) into the watchlist:',
 41+),
 42+
 43+ );
 44+ return $messages;
 45+}
 46+
 47+?>
\ No newline at end of file
Property changes on: trunk/extensions/ExportWatchlist/ExportWatchlist.i18n.php
___________________________________________________________________
Added: svn:eol-style
148 + native
Index: trunk/extensions/ExportWatchlist/ExportWatchlist.php
@@ -0,0 +1,40 @@
 2+<?php
 3+
 4+/**
 5+ * Extension allows users to export their watchlist into
 6+ * list format and import from a list into their watchlist
 7+ *
 8+ * @addtogroup Extensions
 9+ * @author Rob Church <robchur@gmail.com>
 10+ */
 11+if( defined( 'MEDIAWIKI' ) ) {
 12+
 13+ $wgExtensionCredits['specialpage'][] = array(
 14+ 'name' => 'Export Watchlist',
 15+ 'author' => 'Rob Church',
 16+ 'url' => 'http://www.mediawiki.org/wiki/Extension:Export_Watchlist',
 17+ 'description' => 'Allows users to export/import watchlists as XML files',
 18+ );
 19+
 20+ $wgAutoloadClasses['SpecialExportWatchlist'] = dirname( __FILE__ ) . '/SpecialExportWatchlist.php';
 21+ $wgAutoloadClasses['SpecialImportWatchlist'] = dirname( __FILE__ ) . '/SpecialImportWatchlist.php';
 22+ $wgSpecialPages['ExportWatchlist'] = 'SpecialExportWatchlist';
 23+ $wgSpecialPages['ImportWatchlist'] = 'SpecialImportWatchlist';
 24+ $wgExtensionFunctions[] = 'efExportWatchlist';
 25+
 26+ /**
 27+ * Register messages with the message cache
 28+ */
 29+ function efExportWatchlist() {
 30+ global $wgMessageCache;
 31+ require_once( dirname( __FILE__ ) . '/ExportWatchlist.i18n.php' );
 32+ foreach( efExportWatchlistMessages() as $lang => $messages )
 33+ $wgMessageCache->addMessages( $messages, $lang );
 34+ }
 35+
 36+} else {
 37+ echo( "This file is an extension to the MediaWiki software and cannot be used standlone.\n" );
 38+ exit( 1 );
 39+}
 40+
 41+?>
\ No newline at end of file
Property changes on: trunk/extensions/ExportWatchlist/ExportWatchlist.php
___________________________________________________________________
Added: svn:eol-style
142 + native
Index: trunk/extensions/ExportWatchlist/SpecialExportWatchlist.php
@@ -0,0 +1,140 @@
 2+<?php
 3+
 4+/**
 5+ * Special page exports the current user's watchlist
 6+ * into list format
 7+ *
 8+ * @addtogroup Extensions
 9+ * @author Rob Church <robchur@gmail.com>
 10+ */
 11+class SpecialExportWatchlist extends SpecialPage {
 12+
 13+ /**
 14+ * Constructor
 15+ */
 16+ public function __construct() {
 17+ parent::__construct( 'ExportWatchlist' );
 18+ }
 19+
 20+ /**
 21+ * Main execution point
 22+ *
 23+ * @param mixed $par Parameter passed to the page
 24+ */
 25+ public function execute( $par = false ) {
 26+ global $wgUser, $wgRequest, $wgOut;
 27+ $this->setHeaders();
 28+ $namespace = $wgRequest->getIntOrNull( 'namespace' );
 29+
 30+ if( !$wgUser->isLoggedIn() ) {
 31+ $this->showLoginPage( $wgOut, $wgUser );
 32+ return;
 33+ }
 34+
 35+ $wgOut->addHtml( wfMsgExt( 'exportwatchlist-header', 'parse' ) );
 36+ $wgOut->addHtml( $this->buildForm( $namespace ) );
 37+
 38+ if( $wgRequest->getCheck( 'export' ) ) {
 39+ $list = $this->getWatchlist( $wgUser, $namespace );
 40+ if( $list->numRows() > 0 ) {
 41+ $wgOut->addHtml( '<pre>' );
 42+ while( $row = $list->fetchObject() ) {
 43+ $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
 44+ if( $title instanceof Title ) {
 45+ if( !$title->isTalkPage() )
 46+ $wgOut->addHtml( htmlspecialchars( $title->getPrefixedText() ) . "\n" );
 47+ } else {
 48+ $text = htmlspecialchars( $row->wl_title );
 49+ $wgOut->addHtml( "<!-- Invalid title: {$row->wl_namespace}, '{$text}' -->\n" );
 50+ }
 51+ }
 52+ $wgOut->addHtml( '</pre>' );
 53+ } else {
 54+ $msg = $namespace === false
 55+ ? 'exportwatchlist-none'
 56+ : 'exportwatchlist-none-ns';
 57+ $wgOut->addHtml( wfMsgExt( $msg, 'parse' ) );
 58+ }
 59+ }
 60+
 61+ }
 62+
 63+ /**
 64+ * Get all items on a user's watchlist
 65+ *
 66+ * @param User $user User to fetch for
 67+ * @param mixed $namespace Namespace to fetch
 68+ * @return ResultWrapper
 69+ */
 70+ private function getWatchlist( $user, $namespace ) {
 71+ $dbr = wfGetDB( DB_SLAVE );
 72+ $conds = array( 'wl_user' => $user->getId() );
 73+ if( !is_null( $namespace ) )
 74+ $conds['wl_namespace'] = $namespace;
 75+ $res = $dbr->select(
 76+ 'watchlist',
 77+ '*',
 78+ $conds,
 79+ __METHOD__
 80+ );
 81+ return new ResultWrapper( $dbr, $res );
 82+ }
 83+
 84+ /**
 85+ * Build the options form
 86+ *
 87+ * @param mixed $namespace Pre-select namespace
 88+ * @return string
 89+ */
 90+ private function buildForm( $namespace ) {
 91+ global $wgScript;
 92+ $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
 93+ $form .= Xml::hidden( 'title', $this->getTitle()->getPrefixedUrl() );
 94+ $form .= Xml::hidden( 'export', 1 );
 95+ $form .= '<fieldset><legend>' . wfMsgHtml( 'exportwatchlist-legend' ) . '</legend>';
 96+ $form .= '<table><tr><td>' . Xml::label( wfMsg( 'exportwatchlist-namespace' ), 'namespace' ) . '</td>';
 97+ $form .= '<td>' . $this->buildNamespaceSelector( $namespace ) . '</td></tr>';
 98+ $form .= '<tr><td></td><td>' . Xml::submitButton( wfMsg( 'exportwatchlist-submit' ) ) . '</td></tr></table>';
 99+ $form .= '</fieldset>';
 100+ $form .= Xml::closeElement( 'form' );
 101+ return $form;
 102+ }
 103+
 104+ /**
 105+ * Build a namespace selector which omits discussion namespaces
 106+ *
 107+ * @param mixed $namespace Pre-select namespace
 108+ * @return string
 109+ */
 110+ private function buildNamespaceSelector( $namespace ) {
 111+ global $wgContLang;
 112+ $options[] = Xml::option( wfMsg( 'namespacesall' ), '' );
 113+ foreach( $wgContLang->getFormattedNamespaces() as $index => $name ) {
 114+ if( $index < 0 || Namespace::isTalk( $index ) )
 115+ continue;
 116+ if( $index == NS_MAIN )
 117+ $name = wfMsg( 'blanknamespace' );
 118+ $options[] = Xml::option( $name, $index, $index === $namespace );
 119+ }
 120+ return Xml::openElement( 'select', array( 'id' => 'namespace', 'name' => 'namespace' ) )
 121+ . implode( "\n", $options ) . Xml::closeElement( 'select' );
 122+ }
 123+
 124+ /**
 125+ * Show a "please log in" page
 126+ *
 127+ * @param OutputPage $out
 128+ * @param User $user
 129+ */
 130+ private function showLoginPage( $out, $user ) {
 131+ $link = $user->getSkin()->makeKnownLinkObj(
 132+ SpecialPage::getTitleFor( 'Userlogin' ),
 133+ wfMsgHtml( 'exportwatchlist-login-link' ),
 134+ 'returnto=' . $this->getTitle()->getPrefixedUrl()
 135+ );
 136+ $out->addHtml( wfMsgHtml( 'exportwatchlist-login', $link ) );
 137+ }
 138+
 139+}
 140+
 141+?>
\ No newline at end of file
Property changes on: trunk/extensions/ExportWatchlist/SpecialExportWatchlist.php
___________________________________________________________________
Added: svn:eol-style
1142 + native
Index: trunk/extensions/ExportWatchlist/README
@@ -0,0 +1,49 @@
 2+Export Watchlist Extension
 3+© 2007 Rob Church
 4+See LICENSE file for full licencing information
 5+
 6+The Export Watchlist extension provides two special pages which can be
 7+used to dump one's watchlist to a plain text list of titles, and to bulk-add
 8+pages to the watchlist from such a list.
 9+
 10+This is convenient for keeping copies of watchlists, duplicating watchlists
 11+across accounts, sharing watchlists with other users (and making them public)
 12+and also for bulk-adding titles from a number of sources to the watchlist.
 13+
 14+== Requirements ==
 15+
 16+The Export Watchlist extension requires MediaWiki 1.8.0 or above.
 17+
 18+== Installation ==
 19+
 20+1. Place extension files into an "ExportWatchlist" directory in your
 21+ MediaWiki "extensions/" directory
 22+
 23+2. Add the line
 24+ `require_once( "{$IP}/extensions/ExportWatchlist/ExportWatchlist.php" );`
 25+ to LocalSettings.php.
 26+
 27+Installation can be verified through the Special:Version page on the wiki.
 28+
 29+== Usage ==
 30+
 31+The extension introduces two new special pages:
 32+
 33+* Special:ExportWatchlist
 34+* Special:ImportWatchlist
 35+
 36+=== Export ===
 37+
 38+Logged-in users can export pages from their watchlist using the
 39+Special:ExportWatchlist page, and can filter according to namespace.
 40+
 41+Note: Discussion namespaces are not included in the exported list. This would be
 42+redundant, as a watch is considered to exist for both a page and the associated
 43+discussion page at the same time.
 44+
 45+=== Import ===
 46+
 47+Logged-in users can bulk-add/import pages to their watchlist using the
 48+Special:ImportWatchlist page; when accessing the page, enter one or more titles,
 49+one per line, in the text box provided, then click "Import". A list of watched
 50+pages will be shown.
\ No newline at end of file
Property changes on: trunk/extensions/ExportWatchlist/README
___________________________________________________________________
Added: svn:eol-style
151 + native

Status & tagging log