diff options
Diffstat (limited to 'includes/specials')
58 files changed, 5318 insertions, 3114 deletions
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php index c2a8de4e..0ff94b49 100644 --- a/includes/specials/SpecialAllmessages.php +++ b/includes/specials/SpecialAllmessages.php @@ -29,15 +29,19 @@ function wfSpecialAllmessages() { $wgMessageCache->loadAllMessages(); - $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) ); + $sortedArray = array_merge( Language::getMessagesFor( 'en' ), + $wgMessageCache->getExtensionMessagesFor( 'en' ) ); ksort( $sortedArray ); - $messages = array(); - foreach ( $sortedArray as $key => $value ) { + $messages = array(); + foreach( $sortedArray as $key => $value ) { $messages[$key]['enmsg'] = $value; - $messages[$key]['statmsg'] = wfMsgReal( $key, array(), false, false, false ); // wfMsgNoDbNoTrans doesn't exist + $messages[$key]['statmsg'] = wfMsgReal( $key, array(), false, false, false ); $messages[$key]['msg'] = wfMsgNoTrans( $key ); + $sortedArray[$key] = NULL; // trade bytes from $sortedArray to this + } + unset($sortedArray); // trade bytes from $sortedArray to this wfProfileOut( __METHOD__ . '-setup' ); @@ -63,13 +67,14 @@ function wfSpecialAllmessages() { wfProfileOut( __METHOD__ ); } -function wfAllMessagesMakeXml( $messages ) { +function wfAllMessagesMakeXml( &$messages ) { global $wgLang; $lang = $wgLang->getCode(); $txt = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; $txt .= "<messages lang=\"$lang\">\n"; foreach( $messages as $key => $m ) { $txt .= "\t" . Xml::element( 'message', array( 'name' => $key ), $m['msg'] ) . "\n"; + $messages[$key] = NULL; // trade bytes } $txt .= "</messages>"; return $txt; @@ -81,7 +86,7 @@ function wfAllMessagesMakeXml( $messages ) { * @return The PHP messages array. * @todo Make suitable for language files. */ -function wfAllMessagesMakePhp( $messages ) { +function wfAllMessagesMakePhp( &$messages ) { global $wgLang; $txt = "\n\n\$messages = array(\n"; foreach( $messages as $key => $m ) { @@ -94,6 +99,7 @@ function wfAllMessagesMakePhp( $messages ) { $comment = ''; } $txt .= "'$key' => '" . preg_replace( '/(?<!\\\\)\'/', "\'", $m['msg']) . "',$comment\n"; + $messages[$key] = NULL; // trade bytes } $txt .= ');'; return $txt; @@ -104,7 +110,7 @@ function wfAllMessagesMakePhp( $messages ) { * @param $messages Messages array. * @return The HTML list of messages. */ -function wfAllMessagesMakeHTMLText( $messages ) { +function wfAllMessagesMakeHTMLText( &$messages ) { global $wgLang, $wgContLang, $wgUser; wfProfileIn( __METHOD__ ); @@ -123,7 +129,8 @@ function wfAllMessagesMakeHTMLText( $messages ) { 'onclick' => 'allmessagesmodified()' ), '' ); - $txt = '<span id="allmessagesfilter" style="display: none;">' . wfMsgHtml( 'allmessagesfilter' ) . " {$input}{$checkbox} " . '</span>'; + $txt = '<span id="allmessagesfilter" style="display: none;">' . wfMsgHtml( 'allmessagesfilter' ) . + " {$input}{$checkbox} " . '</span>'; $txt .= ' <table border="1" cellspacing="0" width="100%" id="allmessagestable"> @@ -144,11 +151,14 @@ function wfAllMessagesMakeHTMLText( $messages ) { NS_MEDIAWIKI_TALK => array() ); $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $sql = "SELECT page_namespace,page_title FROM $page WHERE page_namespace IN (" . NS_MEDIAWIKI . ", " . NS_MEDIAWIKI_TALK . ")"; - $res = $dbr->query( $sql ); + $res = $dbr->select( 'page', + array( 'page_namespace', 'page_title' ), + array( 'page_namespace' => array(NS_MEDIAWIKI,NS_MEDIAWIKI_TALK) ), + __METHOD__, + array( 'USE INDEX' => 'name_title' ) + ); while( $s = $dbr->fetchObject( $res ) ) { - $pageExists[$s->page_namespace][$s->page_title] = true; + $pageExists[$s->page_namespace][$s->page_title] = 1; } $dbr->freeResult( $res ); wfProfileOut( __METHOD__ . "-check" ); @@ -163,19 +173,21 @@ function wfAllMessagesMakeHTMLText( $messages ) { $title .= '/' . $wgLang->getCode(); } - $titleObj =& Title::makeTitle( NS_MEDIAWIKI, $title ); - $talkPage =& Title::makeTitle( NS_MEDIAWIKI_TALK, $title ); + $titleObj = Title::makeTitle( NS_MEDIAWIKI, $title ); + $talkPage = Title::makeTitle( NS_MEDIAWIKI_TALK, $title ); $changed = ( $m['statmsg'] != $m['msg'] ); $message = htmlspecialchars( $m['statmsg'] ); $mw = htmlspecialchars( $m['msg'] ); - if( isset( $pageExists[NS_MEDIAWIKI][$title] ) ) { - $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' ); + if( array_key_exists( $title, $pageExists[NS_MEDIAWIKI] ) ) { + $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . + htmlspecialchars( $key ) . '</span>' ); } else { - $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' ); + $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . + htmlspecialchars( $key ) . '</span>' ); } - if( isset( $pageExists[NS_MEDIAWIKI_TALK][$title] ) ) { + if( array_key_exists( $title, $pageExists[NS_MEDIAWIKI_TALK] ) ) { $talkLink = $sk->makeKnownLinkObj( $talkPage, htmlspecialchars( $talk ) ); } else { $talkLink = $sk->makeBrokenLinkObj( $talkPage, htmlspecialchars( $talk ) ); @@ -186,27 +198,28 @@ function wfAllMessagesMakeHTMLText( $messages ) { if( $changed ) { $txt .= " - <tr class=\"orig\" id=\"sp-allmessages-r1-$i\"> - <td rowspan=\"2\"> - $anchor$pageLink<br />$talkLink - </td><td> -$message - </td> - </tr><tr class=\"new\" id=\"sp-allmessages-r2-$i\"> - <td> -$mw - </td> - </tr>"; + <tr class=\"orig\" id=\"sp-allmessages-r1-$i\"> + <td rowspan=\"2\"> + $anchor$pageLink<br />$talkLink + </td><td> + $message + </td> + </tr><tr class=\"new\" id=\"sp-allmessages-r2-$i\"> + <td> + $mw + </td> + </tr>"; } else { $txt .= " - <tr class=\"def\" id=\"sp-allmessages-r1-$i\"> - <td> - $anchor$pageLink<br />$talkLink - </td><td> -$mw - </td> - </tr>"; + <tr class=\"def\" id=\"sp-allmessages-r1-$i\"> + <td> + $anchor$pageLink<br />$talkLink + </td><td> + $mw + </td> + </tr>"; } + $messages[$key] = NULL; // trade bytes $i++; } $txt .= '</table>'; diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php index 7223e317..bf68dfa6 100644 --- a/includes/specials/SpecialAllpages.php +++ b/includes/specials/SpecialAllpages.php @@ -1,404 +1,445 @@ <?php -/** - * @file - * @ingroup SpecialPage - */ - -/** - * Entry point : initialise variables and call subfunctions. - * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL) - * @param $specialPage See the SpecialPage object. - */ -function wfSpecialAllpages( $par=NULL, $specialPage ) { - global $wgRequest, $wgOut, $wgContLang; - - # GET values - $from = $wgRequest->getVal( 'from' ); - $namespace = $wgRequest->getInt( 'namespace' ); - - $namespaces = $wgContLang->getNamespaces(); - - $indexPage = new SpecialAllpages(); - - $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ? - wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : - wfMsg( 'allarticles' ) - ); - - if ( isset($par) ) { - $indexPage->showChunk( $namespace, $par, $specialPage->including() ); - } elseif ( isset($from) ) { - $indexPage->showChunk( $namespace, $from, $specialPage->including() ); - } else { - $indexPage->showToplevel ( $namespace, $specialPage->including() ); - } -} /** * Implements Special:Allpages * @ingroup SpecialPage */ -class SpecialAllpages { +class SpecialAllpages extends IncludableSpecialPage { + /** * Maximum number of pages to show on single subpage. */ - protected $maxPerPage = 960; + protected $maxPerPage = 345; /** - * Name of this special page. Used to make title objects that reference back - * to this page. + * Maximum number of pages to show on single index subpage. */ - protected $name = 'Allpages'; + protected $maxLineCount = 200; + + /** + * Maximum number of chars to show for an entry. + */ + protected $maxPageLength = 70; /** * Determines, which message describes the input field 'nsfrom'. */ protected $nsfromMsg = 'allpagesfrom'; -/** - * HTML for the top form - * @param integer $namespace A namespace constant (default NS_MAIN). - * @param string $from Article name we are starting listing at. - */ -function namespaceForm ( $namespace = NS_MAIN, $from = '' ) { - global $wgScript; - $t = SpecialPage::getTitleFor( $this->name ); - - $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); - $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $out .= Xml::hidden( 'title', $t->getPrefixedText() ); - $out .= Xml::openElement( 'fieldset' ); - $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) ); - $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); - $out .= "<tr> - <td class='mw-label'>" . - Xml::label( wfMsg( $this->nsfromMsg ), 'nsfrom' ) . - "</td> - <td class='mw-input'>" . - Xml::input( 'from', 20, $from, array( 'id' => 'nsfrom' ) ) . - "</td> - </tr> - <tr> - <td class='mw-label'>" . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . - "</td> - <td class='mw-input'>" . - Xml::namespaceSelector( $namespace, null ) . ' ' . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - "</td> - </tr>"; - $out .= Xml::closeElement( 'table' ); - $out .= Xml::closeElement( 'fieldset' ); - $out .= Xml::closeElement( 'form' ); - $out .= Xml::closeElement( 'div' ); - return $out; -} + function __construct( $name = 'Allpages' ){ + parent::__construct( $name ); + } -/** - * @param integer $namespace (default NS_MAIN) - */ -function showToplevel ( $namespace = NS_MAIN, $including = false ) { - global $wgOut, $wgContLang; - $align = $wgContLang->isRtl() ? 'left' : 'right'; + /** + * Entry point : initialise variables and call subfunctions. + * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL) + * @param $specialPage See the SpecialPage object. + */ + function execute( $par ) { + global $wgRequest, $wgOut, $wgContLang; - # TODO: Either make this *much* faster or cache the title index points - # in the querycache table. + $this->setHeaders(); + $this->outputHeader(); - $dbr = wfGetDB( DB_SLAVE ); - $out = ""; - $where = array( 'page_namespace' => $namespace ); + # GET values + $from = $wgRequest->getVal( 'from', null ); + $to = $wgRequest->getVal( 'to', null ); + $namespace = $wgRequest->getInt( 'namespace' ); - global $wgMemc; - $key = wfMemcKey( 'allpages', 'ns', $namespace ); - $lines = $wgMemc->get( $key ); + $namespaces = $wgContLang->getNamespaces(); - if( !is_array( $lines ) ) { - $options = array( 'LIMIT' => 1 ); - if ( ! $dbr->implicitOrderby() ) { - $options['ORDER BY'] = 'page_title'; + $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ? + wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : + wfMsg( 'allarticles' ) + ); + + if( isset($par) ) { + $this->showChunk( $namespace, $par, $to ); + } elseif( isset($from) && !isset($to) ) { + $this->showChunk( $namespace, $from, $to ); + } else { + $this->showToplevel( $namespace, $from, $to ); } - $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options ); - $lastTitle = $firstTitle; - - # This array is going to hold the page_titles in order. - $lines = array( $firstTitle ); - - # If we are going to show n rows, we need n+1 queries to find the relevant titles. - $done = false; - for( $i = 0; !$done; ++$i ) { - // Fetch the last title of this chunk and the first of the next - $chunk = is_null( $lastTitle ) - ? '' - : 'page_title >= ' . $dbr->addQuotes( $lastTitle ); - $res = $dbr->select( - 'page', /* FROM */ - 'page_title', /* WHAT */ - $where + array($chunk), - __METHOD__, - array ('LIMIT' => 2, 'OFFSET' => $this->maxPerPage - 1, 'ORDER BY' => 'page_title') ); + } - if ( $s = $dbr->fetchObject( $res ) ) { - array_push( $lines, $s->page_title ); - } else { - // Final chunk, but ended prematurely. Go back and find the end. - $endTitle = $dbr->selectField( 'page', 'MAX(page_title)', - array( - 'page_namespace' => $namespace, - $chunk - ), __METHOD__ ); - array_push( $lines, $endTitle ); - $done = true; + /** + * HTML for the top form + * @param integer $namespace A namespace constant (default NS_MAIN). + * @param string $from dbKey we are starting listing at. + * @param string $to dbKey we are ending listing at. + */ + function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '' ) { + global $wgScript; + $t = $this->getTitle(); + + $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); + $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); + $out .= Xml::hidden( 'title', $t->getPrefixedText() ); + $out .= Xml::openElement( 'fieldset' ); + $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) ); + $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); + $out .= "<tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'allpagesto' ), 'nsto' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . + "</td> + <td class='mw-input'>" . + Xml::namespaceSelector( $namespace, null ) . ' ' . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . + "</td> + </tr>"; + $out .= Xml::closeElement( 'table' ); + $out .= Xml::closeElement( 'fieldset' ); + $out .= Xml::closeElement( 'form' ); + $out .= Xml::closeElement( 'div' ); + return $out; + } + + /** + * @param integer $namespace (default NS_MAIN) + */ + function showToplevel( $namespace = NS_MAIN, $from = '', $to = '' ) { + global $wgOut, $wgContLang; + $align = $wgContLang->isRtl() ? 'left' : 'right'; + + # TODO: Either make this *much* faster or cache the title index points + # in the querycache table. + + $dbr = wfGetDB( DB_SLAVE ); + $out = ""; + $where = array( 'page_namespace' => $namespace ); + + $from = Title::makeTitleSafe( $namespace, $from ); + $to = Title::makeTitleSafe( $namespace, $to ); + $from = ( $from && $from->isLocal() ) ? $from->getDBKey() : null; + $to = ( $to && $to->isLocal() ) ? $to->getDBKey() : null; + + if( isset($from) ) + $where[] = 'page_title >= '.$dbr->addQuotes( $from ); + if( isset($to) ) + $where[] = 'page_title <= '.$dbr->addQuotes( $to ); + + global $wgMemc; + $key = wfMemcKey( 'allpages', 'ns', $namespace, $from, $to ); + $lines = $wgMemc->get( $key ); + + $count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ ); + $maxPerSubpage = intval($count/$this->maxLineCount); + $maxPerSubpage = max($maxPerSubpage,$this->maxPerPage); + + if( !is_array( $lines ) ) { + $options = array( 'LIMIT' => 1 ); + $options['ORDER BY'] = 'page_title ASC'; + $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options ); + $lastTitle = $firstTitle; + # This array is going to hold the page_titles in order. + $lines = array( $firstTitle ); + # If we are going to show n rows, we need n+1 queries to find the relevant titles. + $done = false; + while( !$done ) { + // Fetch the last title of this chunk and the first of the next + $chunk = ( $lastTitle === false ) + ? array() + : array( 'page_title >= ' . $dbr->addQuotes( $lastTitle ) ); + $res = $dbr->select( 'page', /* FROM */ + 'page_title', /* WHAT */ + array_merge($where,$chunk), + __METHOD__, + array ('LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC') + ); + + if( $s = $dbr->fetchObject( $res ) ) { + array_push( $lines, $s->page_title ); + } else { + // Final chunk, but ended prematurely. Go back and find the end. + $endTitle = $dbr->selectField( 'page', 'MAX(page_title)', + array_merge($where,$chunk), + __METHOD__ ); + array_push( $lines, $endTitle ); + $done = true; + } + if( $s = $res->fetchObject() ) { + array_push( $lines, $s->page_title ); + $lastTitle = $s->page_title; + } else { + // This was a final chunk and ended exactly at the limit. + // Rare but convenient! + $done = true; + } + $res->free(); } - if( $s = $dbr->fetchObject( $res ) ) { - array_push( $lines, $s->page_title ); - $lastTitle = $s->page_title; + $wgMemc->add( $key, $lines, 3600 ); + } + + // If there are only two or less sections, don't even display them. + // Instead, display the first section directly. + if( count( $lines ) <= 2 ) { + if( !empty($lines) ) { + $this->showChunk( $namespace, $lines[0], $lines[count($lines)-1] ); } else { - // This was a final chunk and ended exactly at the limit. - // Rare but convenient! - $done = true; + $wgOut->addHTML( $this->namespaceForm( $namespace, $from, $to ) ); } - $dbr->freeResult( $res ); + return; } - $wgMemc->add( $key, $lines, 3600 ); - } - // If there are only two or less sections, don't even display them. - // Instead, display the first section directly. - if( count( $lines ) <= 2 ) { - $this->showChunk( $namespace, '', $including ); - return; - } + # At this point, $lines should contain an even number of elements. + $out .= "<table class='allpageslist' style='background: inherit;'>"; + while( count ( $lines ) > 0 ) { + $inpoint = array_shift( $lines ); + $outpoint = array_shift( $lines ); + $out .= $this->showline( $inpoint, $outpoint, $namespace ); + } + $out .= '</table>'; + $nsForm = $this->namespaceForm( $namespace, $from, $to ); - # At this point, $lines should contain an even number of elements. - $out .= "<table class='allpageslist' style='background: inherit;'>"; - while ( count ( $lines ) > 0 ) { - $inpoint = array_shift ( $lines ); - $outpoint = array_shift ( $lines ); - $out .= $this->showline ( $inpoint, $outpoint, $namespace, false ); - } - $out .= '</table>'; - $nsForm = $this->namespaceForm( $namespace, '', false ); - - # Is there more? - if ( $including ) { - $out2 = ''; - } else { - $morelinks = ''; - if ( $morelinks != '' ) { - $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; - $out2 .= '<tr valign="top"><td>' . $nsForm; - $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">'; - $out2 .= $morelinks . '</td></tr></table><hr />'; + # Is there more? + if( $this->including() ) { + $out2 = ''; } else { - $out2 = $nsForm . '<hr />'; + if( isset($from) || isset($to) ) { + global $wgUser; + $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; + $out2 .= '<tr valign="top"><td>' . $nsForm; + $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . + $wgUser->getSkin()->makeKnownLinkObj( $this->getTitle(), wfMsgHtml ( 'allpages' ) ); + $out2 .= "</td></tr></table><hr />"; + } else { + $out2 = $nsForm . '<hr />'; + } } + $wgOut->addHTML( $out2 . $out ); } - $wgOut->addHtml( $out2 . $out ); -} - -/** - * @todo Document - * @param string $from - * @param integer $namespace (Default NS_MAIN) - */ -function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) { - global $wgContLang; - $align = $wgContLang->isRtl() ? 'left' : 'right'; - $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) ); - $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) ); - $queryparams = ($namespace ? "namespace=$namespace" : ''); - $special = SpecialPage::getTitleFor( $this->name, $inpoint ); - $link = $special->escapeLocalUrl( $queryparams ); - - $out = wfMsgHtml( - 'alphaindexline', - "<a href=\"$link\">$inpointf</a></td><td><a href=\"$link\">", - "</a></td><td><a href=\"$link\">$outpointf</a>" - ); - return '<tr><td align="' . $align . '">'.$out.'</td></tr>'; -} + /** + * Show a line of "ABC to DEF" ranges of articles + * @param string $inpoint Lower limit of pagenames + * @param string $outpout Upper limit of pagenames + * @param integer $namespace (Default NS_MAIN) + */ + function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) { + global $wgContLang; + $align = $wgContLang->isRtl() ? 'left' : 'right'; + $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) ); + $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) ); + // Don't let the length runaway + $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength, '...' ); + $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength, '...' ); + + $queryparams = $namespace ? "namespace=$namespace&" : ''; + $special = $this->getTitle(); + $link = $special->escapeLocalUrl( $queryparams . 'from=' . urlencode($inpoint) . '&to=' . urlencode($outpoint) ); + + $out = wfMsgHtml( 'alphaindexline', + "<a href=\"$link\">$inpointf</a></td><td>", + "</td><td><a href=\"$link\">$outpointf</a>" + ); + return '<tr><td align="' . $align . '">'.$out.'</td></tr>'; + } -/** - * @param integer $namespace (Default NS_MAIN) - * @param string $from list all pages from this name (default FALSE) - */ -function showChunk( $namespace = NS_MAIN, $from, $including = false ) { - global $wgOut, $wgUser, $wgContLang; + /** + * @param integer $namespace (Default NS_MAIN) + * @param string $from list all pages from this name (default FALSE) + * @param string $to list all pages to this name (default FALSE) + */ + function showChunk( $namespace = NS_MAIN, $from = false, $to = false ) { + global $wgOut, $wgUser, $wgContLang; - $sk = $wgUser->getSkin(); + $sk = $wgUser->getSkin(); - $fromList = $this->getNamespaceKeyAndText($namespace, $from); - $namespaces = $wgContLang->getNamespaces(); - $align = $wgContLang->isRtl() ? 'left' : 'right'; + $fromList = $this->getNamespaceKeyAndText($namespace, $from); + $toList = $this->getNamespaceKeyAndText( $namespace, $to ); + $namespaces = $wgContLang->getNamespaces(); + $align = $wgContLang->isRtl() ? 'left' : 'right'; - $n = 0; + $n = 0; - if ( !$fromList ) { - $out = wfMsgWikiHtml( 'allpagesbadtitle' ); - } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) { - // Show errormessage and reset to NS_MAIN - $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace ); - $namespace = NS_MAIN; - } else { - list( $namespace, $fromKey, $from ) = $fromList; + if ( !$fromList || !$toList ) { + $out = wfMsgWikiHtml( 'allpagesbadtitle' ); + } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) { + // Show errormessage and reset to NS_MAIN + $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace ); + $namespace = NS_MAIN; + } else { + list( $namespace, $fromKey, $from ) = $fromList; + list( $namespace2, $toKey, $to ) = $toList; - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'page', - array( 'page_namespace', 'page_title', 'page_is_redirect' ), - array( + $dbr = wfGetDB( DB_SLAVE ); + $conds = array( 'page_namespace' => $namespace, 'page_title >= ' . $dbr->addQuotes( $fromKey ) - ), - __METHOD__, - array( - 'ORDER BY' => 'page_title', - 'LIMIT' => $this->maxPerPage + 1, - 'USE INDEX' => 'name_title', - ) - ); + ); + if( $toKey !== "" ) { + $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey ); + } - if( $res->numRows() > 0 ) { - $out = '<table style="background: inherit;" border="0" width="100%">'; - - while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $t = Title::makeTitle( $s->page_namespace, $s->page_title ); - if( $t ) { - $link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . - $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . - ($s->page_is_redirect ? '</div>' : '' ); - } else { - $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; - } - if( $n % 3 == 0 ) { - $out .= '<tr>'; + $res = $dbr->select( 'page', + array( 'page_namespace', 'page_title', 'page_is_redirect' ), + $conds, + __METHOD__, + array( + 'ORDER BY' => 'page_title', + 'LIMIT' => $this->maxPerPage + 1, + 'USE INDEX' => 'name_title', + ) + ); + + if( $res->numRows() > 0 ) { + $out = '<table style="background: inherit;" border="0" width="100%">'; + + while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { + $t = Title::makeTitle( $s->page_namespace, $s->page_title ); + if( $t ) { + $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . + $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . + ($s->page_is_redirect ? '</div>' : '' ); + } else { + $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; + } + if( $n % 3 == 0 ) { + $out .= '<tr>'; + } + $out .= "<td width=\"33%\">$link</td>"; + $n++; + if( $n % 3 == 0 ) { + $out .= '</tr>'; + } } - $out .= "<td width=\"33%\">$link</td>"; - $n++; - if( $n % 3 == 0 ) { + if( ($n % 3) != 0 ) { $out .= '</tr>'; } + $out .= '</table>'; + } else { + $out = ''; } - if( ($n % 3) != 0 ) { - $out .= '</tr>'; - } - $out .= '</table>'; - } else { - $out = ''; } - } - if ( $including ) { - $out2 = ''; - } else { - if( $from == '' ) { - // First chunk; no previous link. - $prevTitle = null; + if ( $this->including() ) { + $out2 = ''; } else { - # Get the last title from previous chunk - $dbr = wfGetDB( DB_SLAVE ); - $res_prev = $dbr->select( - 'page', - 'page_title', - array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), - __METHOD__, - array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) ) - ); - - # Get first title of previous complete chunk - if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { - $pt = $dbr->fetchObject( $res_prev ); - $prevTitle = Title::makeTitle( $namespace, $pt->page_title ); + if( $from == '' ) { + // First chunk; no previous link. + $prevTitle = null; } else { - # The previous chunk is not complete, need to link to the very first title - # available in the database - $options = array( 'LIMIT' => 1 ); - if ( ! $dbr->implicitOrderby() ) { - $options['ORDER BY'] = 'page_title'; - } - $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', array( 'page_namespace' => $namespace ), __METHOD__, $options ); - # Show the previous link if it s not the current requested chunk - if( $from != $reallyFirstPage_title ) { - $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); + # Get the last title from previous chunk + $dbr = wfGetDB( DB_SLAVE ); + $res_prev = $dbr->select( + 'page', + 'page_title', + array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), + __METHOD__, + array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) ) + ); + + # Get first title of previous complete chunk + if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { + $pt = $dbr->fetchObject( $res_prev ); + $prevTitle = Title::makeTitle( $namespace, $pt->page_title ); } else { - $prevTitle = null; + # The previous chunk is not complete, need to link to the very first title + # available in the database + $options = array( 'LIMIT' => 1 ); + if ( ! $dbr->implicitOrderby() ) { + $options['ORDER BY'] = 'page_title'; + } + $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', + array( 'page_namespace' => $namespace ), __METHOD__, $options ); + # Show the previous link if it s not the current requested chunk + if( $from != $reallyFirstPage_title ) { + $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); + } else { + $prevTitle = null; + } } } - } - $nsForm = $this->namespaceForm( $namespace, $from ); - $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; - $out2 .= '<tr valign="top"><td>' . $nsForm; - $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . - $sk->makeKnownLink( $wgContLang->specialPage( "Allpages" ), - wfMsgHtml ( 'allpages' ) ); - - $self = SpecialPage::getTitleFor( 'Allpages' ); - - # Do we put a previous link ? - if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { - $q = 'from=' . $prevTitle->getPartialUrl() - . ( $namespace ? '&namespace=' . $namespace : '' ); - $prevLink = $sk->makeKnownLinkObj( $self, - wfMsgHTML( 'prevpage', htmlspecialchars( $pt ) ), $q ); - $out2 .= ' | ' . $prevLink; - } + $self = $this->getTitle(); - if( $n == $this->maxPerPage && $s = $dbr->fetchObject($res) ) { - # $s is the first link of the next chunk - $t = Title::MakeTitle($namespace, $s->page_title); - $q = 'from=' . $t->getPartialUrl() - . ( $namespace ? '&namespace=' . $namespace : '' ); - $nextLink = $sk->makeKnownLinkObj( $self, - wfMsgHtml( 'nextpage', htmlspecialchars( $t->getText() ) ), $q ); - $out2 .= ' | ' . $nextLink; - } - $out2 .= "</td></tr></table><hr />"; - } + $nsForm = $this->namespaceForm( $namespace, $from, $to ); + $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; + $out2 .= '<tr valign="top"><td>' . $nsForm; + $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . + $sk->makeKnownLinkObj( $self, + wfMsgHtml ( 'allpages' ) ); + + # Do we put a previous link ? + if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { + $q = 'from=' . $prevTitle->getPartialUrl() + . ( $namespace ? '&namespace=' . $namespace : '' ); + $prevLink = $sk->makeKnownLinkObj( $self, + wfMsgHTML( 'prevpage', htmlspecialchars( $pt ) ), $q ); + $out2 .= ' | ' . $prevLink; + } - $wgOut->addHtml( $out2 . $out ); - if( isset($prevLink) or isset($nextLink) ) { - $wgOut->addHtml( '<hr /><p style="font-size: smaller; float: ' . $align . '">' ); - if( isset( $prevLink ) ) { - $wgOut->addHTML( $prevLink ); - } - if( isset( $prevLink ) && isset( $nextLink ) ) { - $wgOut->addHTML( ' | ' ); - } - if( isset( $nextLink ) ) { - $wgOut->addHTML( $nextLink ); + if( $n == $this->maxPerPage && $s = $res->fetchObject() ) { + # $s is the first link of the next chunk + $t = Title::MakeTitle($namespace, $s->page_title); + $q = 'from=' . $t->getPartialUrl() + . ( $namespace ? '&namespace=' . $namespace : '' ); + $nextLink = $sk->makeKnownLinkObj( $self, + wfMsgHtml( 'nextpage', htmlspecialchars( $t->getText() ) ), $q ); + $out2 .= ' | ' . $nextLink; + } + $out2 .= "</td></tr></table><hr />"; } - $wgOut->addHTML( '</p>' ); - } + $wgOut->addHTML( $out2 . $out ); + if( isset($prevLink) or isset($nextLink) ) { + $wgOut->addHTML( '<hr /><p style="font-size: smaller; float: ' . $align . '">' ); + if( isset( $prevLink ) ) { + $wgOut->addHTML( $prevLink ); + } + if( isset( $prevLink ) && isset( $nextLink ) ) { + $wgOut->addHTML( ' | ' ); + } + if( isset( $nextLink ) ) { + $wgOut->addHTML( $nextLink ); + } + $wgOut->addHTML( '</p>' ); -} + } -/** - * @param int $ns the namespace of the article - * @param string $text the name of the article - * @return array( int namespace, string dbkey, string pagename ) or NULL on error - * @static (sort of) - * @access private - */ -function getNamespaceKeyAndText ($ns, $text) { - if ( $text == '' ) - return array( $ns, '', '' ); # shortcut for common case - - $t = Title::makeTitleSafe($ns, $text); - if ( $t && $t->isLocal() ) { - return array( $t->getNamespace(), $t->getDBkey(), $t->getText() ); - } else if ( $t ) { - return NULL; } - # try again, in case the problem was an empty pagename - $text = preg_replace('/(#|$)/', 'X$1', $text); - $t = Title::makeTitleSafe($ns, $text); - if ( $t && $t->isLocal() ) { - return array( $t->getNamespace(), '', '' ); - } else { - return NULL; + /** + * @param int $ns the namespace of the article + * @param string $text the name of the article + * @return array( int namespace, string dbkey, string pagename ) or NULL on error + * @static (sort of) + * @access private + */ + function getNamespaceKeyAndText($ns, $text) { + if ( $text == '' ) + return array( $ns, '', '' ); # shortcut for common case + + $t = Title::makeTitleSafe($ns, $text); + if ( $t && $t->isLocal() ) { + return array( $t->getNamespace(), $t->getDBkey(), $t->getText() ); + } else if ( $t ) { + return NULL; + } + + # try again, in case the problem was an empty pagename + $text = preg_replace('/(#|$)/', 'X$1', $text); + $t = Title::makeTitleSafe($ns, $text); + if ( $t && $t->isLocal() ) { + return array( $t->getNamespace(), '', '' ); + } else { + return NULL; + } } } -} diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php index 52829d92..4d82997f 100644 --- a/includes/specials/SpecialBlockip.php +++ b/includes/specials/SpecialBlockip.php @@ -47,7 +47,7 @@ class IPBlockForm { # var $BlockEmail; function IPBlockForm( $par ) { - global $wgRequest, $wgUser; + global $wgRequest, $wgUser, $wgBlockAllowsUTEdit; $this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) ); $this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' ); @@ -66,6 +66,8 @@ class IPBlockForm { $this->BlockWatchUser = $wgRequest->getBool( 'wpWatchUser', false ); # Re-check user's rights to hide names, very serious, defaults to 0 $this->BlockHideName = ( $wgRequest->getBool( 'wpHideName', 0 ) && $wgUser->isAllowed( 'hideuser' ) ) ? 1 : 0; + $this->BlockAllowUsertalk = ( $wgRequest->getBool( 'wpAllowUsertalk', $byDefault ) && $wgBlockAllowsUTEdit ); + $this->BlockReblock = $wgRequest->getBool( 'wpChangeBlock', false ); } function showForm( $err ) { @@ -85,10 +87,26 @@ class IPBlockForm { $mIpbreason = Xml::label( wfMsg( 'ipbotherreason' ), 'mw-bi-reason' ); $titleObj = SpecialPage::getTitleFor( 'Blockip' ); - - if ( "" != $err ) { + $user = User::newFromName( $this->BlockAddress ); + + $alreadyBlocked = false; + if ( $err && $err[0] != 'ipb_already_blocked' ) { + $key = array_shift($err); + $msg = wfMsgReal($key, $err); $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) ); - $wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $err ) ); + $wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $msg ) ); + } elseif ( $this->BlockAddress ) { + $userId = 0; + if ( is_object( $user ) ) + $userId = $user->getId(); + $currentBlock = Block::newFromDB( $this->BlockAddress, $userId ); + if ( !is_null($currentBlock) && !$currentBlock->mAuto && # The block exists and isn't an autoblock + ( $currentBlock->mRangeStart == $currentBlock->mRangeEnd || # The block isn't a rangeblock + # or if it is, the range is what we're about to block + ( $currentBlock->mAddress == $this->BlockAddress ) ) ) { + $wgOut->addWikiMsg( 'ipb-needreblock', $this->BlockAddress ); + $alreadyBlocked = true; + } } $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' ); @@ -108,7 +126,7 @@ class IPBlockForm { $reasonDropDown = Xml::listDropDown( 'wpBlockReasonList', wfMsgForContent( 'ipbreason-dropdown' ), - wfMsgForContent( 'ipbreasonotherlist' ), '', 'wpBlockDropDown', 4 ); + wfMsgForContent( 'ipbreasonotherlist' ), $this->BlockReasonList, 'wpBlockDropDown', 4 ); global $wgStylePath, $wgStyleVersion; $wgOut->addHTML( @@ -201,7 +219,7 @@ class IPBlockForm { </tr>" ); - global $wgSysopEmailBans; + global $wgSysopEmailBans, $wgBlockAllowsUTEdit; if ( $wgSysopEmailBans && $wgUser->isAllowed( 'blockemail' ) ) { $wgOut->addHTML(" <tr id='wpEnableEmailBan'> @@ -240,25 +258,37 @@ class IPBlockForm { </td> </tr>" ); + if( $wgBlockAllowsUTEdit ){ + $wgOut->addHTML(" + <tr id='wpAllowUsertalkRow'> + <td> </td> + <td class='mw-input'>" . + Xml::checkLabel( wfMsg( 'ipballowusertalk' ), + 'wpAllowUsertalk', 'wpAllowUsertalk', $this->BlockAllowUsertalk, + array( 'tabindex' => '12' ) ) . " + </td> + </tr>" + ); + } $wgOut->addHTML(" <tr> <td style='padding-top: 1em'> </td> <td class='mw-submit' style='padding-top: 1em'>" . - Xml::submitButton( wfMsg( 'ipbsubmit' ), - array( 'name' => 'wpBlock', 'tabindex' => '12' ) ) . " + Xml::submitButton( wfMsg( $alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit' ), + array( 'name' => 'wpBlock', 'tabindex' => '13', 'accesskey' => 's' ) ) . " </td> </tr>" . Xml::closeElement( 'table' ) . Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . + ( $alreadyBlocked ? Xml::hidden( 'wpChangeBlock', 1 ) : "" ) . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . Xml::tags( 'script', array( 'type' => 'text/javascript' ), 'updateBlockOptions()' ) . "\n" ); - $wgOut->addHtml( $this->getConvenienceLinks() ); + $wgOut->addHTML( $this->getConvenienceLinks() ); - $user = User::newFromName( $this->BlockAddress ); if( is_object( $user ) ) { $this->showLogFragment( $wgOut, $user->getUserPage() ); } elseif( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->BlockAddress ) ) { @@ -273,9 +303,8 @@ class IPBlockForm { * $userID and $expiry will be filled accordingly * @return array(message key, arguments) on failure, empty array on success */ - function doBlock(&$userId = null, &$expiry = null) - { - global $wgUser, $wgSysopUserBans, $wgSysopRangeBans; + function doBlock( &$userId = null, &$expiry = null ) { + global $wgUser, $wgSysopUserBans, $wgSysopRangeBans, $wgBlockAllowsUTEdit; $userId = 0; # Expand valid IPv6 addresses, usernames are left as is @@ -327,8 +356,12 @@ class IPBlockForm { } } + if ( $wgUser->isBlocked() && ( $wgUser->getId() !== $userId ) ) { + return array( 'cant-block-while-blocked' ); + } + $reasonstr = $this->BlockReasonList; - if ( $reasonstr != 'other' && $this->BlockReason != '') { + if ( $reasonstr != 'other' && $this->BlockReason != '' ) { // Entry from drop down menu + additional comment $reasonstr .= ': ' . $this->BlockReason; } elseif ( $reasonstr == 'other' ) { @@ -339,7 +372,7 @@ class IPBlockForm { if( $expirestr == 'other' ) $expirestr = $this->BlockOther; - if ((strlen($expirestr) == 0) || (strlen($expirestr) > 50)) { + if ( ( strlen( $expirestr ) == 0) || ( strlen( $expirestr ) > 50) ) { return array('ipb_expiry_invalid'); } @@ -358,14 +391,27 @@ class IPBlockForm { $block = new Block( $this->BlockAddress, $userId, $wgUser->getId(), $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, - $this->BlockEmail ); + $this->BlockEmail, isset( $this->BlockAllowUsertalk ) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit ); if ( wfRunHooks('BlockIp', array(&$block, &$wgUser)) ) { if ( !$block->insert() ) { - return array('ipb_already_blocked', htmlspecialchars($this->BlockAddress)); + if ( !$this->BlockReblock ) { + return array( 'ipb_already_blocked' ); + } else { + # This returns direct blocks before autoblocks/rangeblocks, since we should + # be sure the user is blocked by now it should work for our purposes + $currentBlock = Block::newFromDB( $this->BlockAddress, $userId ); + if( $block->equals( $currentBlock ) ) { + return array( 'ipb_already_blocked' ); + } + $currentBlock->delete(); + $block->insert(); + $log_action = 'reblock'; + } + } else { + $log_action = 'block'; } - wfRunHooks('BlockIpComplete', array($block, $wgUser)); if ( $this->BlockWatchUser ) { @@ -380,7 +426,7 @@ class IPBlockForm { # Make log entry, if the name is hidden, put it in the oversight log $log_type = ($this->BlockHideName) ? 'suppress' : 'block'; $log = new LogPage( $log_type ); - $log->addEntry( 'block', Title::makeTitle( NS_USER, $this->BlockAddress ), + $log->addEntry( $log_action, Title::makeTitle( NS_USER, $this->BlockAddress ), $reasonstr, $logParams ); # Report to the user @@ -404,8 +450,7 @@ class IPBlockForm { urlencode( $this->BlockAddress ) ) ); return; } - $key = array_shift($retval); - $this->showForm(wfMsgReal($key, $retval)); + $this->showForm( $retval ); } function showSuccess() { @@ -414,12 +459,22 @@ class IPBlockForm { $wgOut->setPagetitle( wfMsg( 'blockip' ) ); $wgOut->setSubtitle( wfMsg( 'blockipsuccesssub' ) ); $text = wfMsgExt( 'blockipsuccesstext', array( 'parse' ), $this->BlockAddress ); - $wgOut->addHtml( $text ); + $wgOut->addHTML( $text ); } function showLogFragment( $out, $title ) { - $out->addHtml( Xml::element( 'h2', NULL, LogPage::logName( 'block' ) ) ); - LogEventsList::showLogExtract( $out, 'block', $title->getPrefixedText() ); + global $wgUser; + $out->addHTML( Xml::element( 'h2', NULL, LogPage::logName( 'block' ) ) ); + $count = LogEventsList::showLogExtract( $out, 'block', $title->getPrefixedText(), '', 10 ); + if($count > 10){ + $out->addHTML( $wgUser->getSkin()->link( + SpecialPage::getTitleFor( 'Log' ), + wfMsgHtml( 'blocklog-fulllog' ), + array(), + array( + 'type' => 'block', + 'page' => $title->getPrefixedText() ) ) ); + } } /** @@ -429,6 +484,7 @@ class IPBlockForm { * @return array */ private function blockLogFlags() { + global $wgBlockAllowsUTEdit; $flags = array(); if( $this->BlockAnonOnly && IP::isIPAddress( $this->BlockAddress ) ) // when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log @@ -439,6 +495,8 @@ class IPBlockForm { $flags[] = 'noautoblock'; if ( $this->BlockEmail ) $flags[] = 'noemail'; + if ( !$this->BlockAllowUsertalk && $wgBlockAllowsUTEdit ) + $flags[] = 'nousertalk'; return implode( ',', $flags ); } @@ -450,11 +508,25 @@ class IPBlockForm { private function getConvenienceLinks() { global $wgUser; $skin = $wgUser->getSkin(); - $links[] = $skin->makeLink ( 'MediaWiki:Ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) ); + if( $this->BlockAddress ) + $links[] = $this->getContribsLink( $skin ); $links[] = $this->getUnblockLink( $skin ); $links[] = $this->getBlockListLink( $skin ); + $links[] = $skin->makeLink ( 'MediaWiki:Ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) ); return '<p class="mw-ipb-conveniencelinks">' . implode( ' | ', $links ) . '</p>'; } + + /** + * Build a convenient link to a user or IP's contribs + * form + * + * @param $skin Skin to use + * @return string + */ + private function getContribsLink( $skin ) { + $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->BlockAddress ); + return $skin->link( $contribsPage, wfMsgHtml( 'ipb-blocklist-contribs', $this->BlockAddress ) ); + } /** * Build a convenient link to unblock the given username or IP @@ -491,4 +563,77 @@ class IPBlockForm { return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist' ) ); } } + + /** + * Block a list of selected users + * @param array $users + * @param string $reason + * @param string $tag replaces user pages + * @param string $talkTag replaces user talk pages + * @returns array, list of html-safe usernames + */ + public static function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) { + global $wgUser; + $counter = $blockSize = 0; + $safeUsers = array(); + $log = new LogPage( 'block' ); + foreach( $users as $name ) { + # Enforce limits + $counter++; + $blockSize++; + # Lets not go *too* fast + if( $blockSize >= 20 ) { + $blockSize = 0; + wfWaitForSlaves( 5 ); + } + $u = User::newFromName( $name, false ); + // If user doesn't exist, it ought to be an IP then + if( is_null($u) || (!$u->getId() && !IP::isIPAddress( $u->getName() )) ) { + continue; + } + $userTitle = $u->getUserPage(); + $userTalkTitle = $u->getTalkPage(); + $userpage = new Article( $userTitle ); + $usertalk = new Article( $userTalkTitle ); + $safeUsers[] = '[[' . $userTitle->getPrefixedText() . '|' . $userTitle->getText() . ']]'; + $expirestr = $u->getId() ? 'indefinite' : '1 week'; + $expiry = Block::parseExpiryInput( $expirestr ); + $anonOnly = IP::isIPAddress( $u->getName() ) ? 1 : 0; + // Create the block + $block = new Block( $u->getName(), // victim + $u->getId(), // uid + $wgUser->getId(), // blocker + $reason, // comment + wfTimestampNow(), // block time + 0, // auto ? + $expiry, // duration + $anonOnly, // anononly? + 1, // block account creation? + 1, // autoblocking? + 0, // suppress name? + 0 // block from sending email? + ); + $oldblock = Block::newFromDB( $u->getName(), $u->getId() ); + if( !$oldblock ) { + $block->insert(); + # Prepare log parameters + $logParams = array(); + $logParams[] = $expirestr; + if( $anonOnly ) { + $logParams[] = 'anononly'; + } + $logParams[] = 'nocreate'; + # Add log entry + $log->addEntry( 'block', $userTitle, $reason, $logParams ); + } + # Tag userpage! (check length to avoid mistakes) + if( strlen($tag) > 2 ) { + $userpage->doEdit( $tag, $reason, EDIT_MINOR ); + } + if( strlen($talkTag) > 2 ) { + $usertalk->doEdit( $talkTag, $reason, EDIT_MINOR ); + } + } + return $safeUsers; + } } diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php index 0690c5c0..12b119d8 100644 --- a/includes/specials/SpecialBooksources.php +++ b/includes/specials/SpecialBooksources.php @@ -30,20 +30,62 @@ class SpecialBookSources extends SpecialPage { public function execute( $isbn ) { global $wgOut, $wgRequest; $this->setHeaders(); - $this->isbn = $this->cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); + $this->isbn = self::cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); $wgOut->addWikiMsg( 'booksources-summary' ); - $wgOut->addHtml( $this->makeForm() ); - if( strlen( $this->isbn ) > 0 ) + $wgOut->addHTML( $this->makeForm() ); + if( strlen( $this->isbn ) > 0 ) { + if( !$this->isValidIsbn( $this->isbn ) ) { + $wgOut->wrapWikiMsg( '<div class="error">$1</div>', 'booksources-invalid-isbn' ); + } $this->showList(); + } } /** + * Returns whether a given ISBN (10 or 13) is valid. True indicates validity. + * @param isbn ISBN passed for check + */ + public static function isValidISBN( $isbn ) { + $isbn = self::cleanIsbn( $isbn ); + $sum = 0; + $check = -1; + if( strlen( $isbn ) == 13 ) { + for( $i = 0; $i < 12; $i++ ) { + if($i % 2 == 0) { + $sum += $isbn{$i}; + } else { + $sum += 3 * $isbn{$i}; + } + } + + $check = (10 - ($sum % 10)) % 10; + if ($check == $isbn{12}) { + return true; + } + } elseif( strlen( $isbn ) == 10 ) { + for($i = 0; $i < 9; $i++) { + $sum += $isbn{$i} * ($i + 1); + } + + $check = $sum % 11; + if($check == 10) { + $check = "X"; + } + if($check == $isbn{9}) { + return true; + } + } + return false; + } + + + /** * Trim ISBN and remove characters which aren't required * * @param $isbn Unclean ISBN * @return string */ - private function cleanIsbn( $isbn ) { + private static function cleanIsbn( $isbn ) { return trim( preg_replace( '![^0-9X]!', '', $isbn ) ); } @@ -88,11 +130,11 @@ class SpecialBookSources extends SpecialPage { # Fall back to the defaults given in the language file $wgOut->addWikiMsg( 'booksources-text' ); - $wgOut->addHtml( '<ul>' ); + $wgOut->addHTML( '<ul>' ); $items = $wgContLang->getBookstoreList(); foreach( $items as $label => $url ) - $wgOut->addHtml( $this->makeListItem( $label, $url ) ); - $wgOut->addHtml( '</ul>' ); + $wgOut->addHTML( $this->makeListItem( $label, $url ) ); + $wgOut->addHTML( '</ul>' ); return true; } diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php index 951c2228..8c2ae2b6 100644 --- a/includes/specials/SpecialCategories.php +++ b/includes/specials/SpecialCategories.php @@ -14,11 +14,13 @@ function wfSpecialCategories( $par=null ) { } $cap = new CategoryPager( $from ); $wgOut->addHTML( + XML::openElement( 'div', array('class' => 'mw-spcontent') ) . wfMsgExt( 'categoriespagetext', array( 'parse' ) ) . $cap->getStartForm( $from ) . $cap->getNavigationBar() . '<ul>' . $cap->getBody() . '</ul>' . - $cap->getNavigationBar() + $cap->getNavigationBar() . + XML::closeElement( 'div' ) ); } diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php index 9075fb95..e19aa99b 100644 --- a/includes/specials/SpecialConfirmemail.php +++ b/includes/specials/SpecialConfirmemail.php @@ -36,8 +36,9 @@ class EmailConfirmation extends UnlistedSpecialPage { $title = SpecialPage::getTitleFor( 'Userlogin' ); $self = SpecialPage::getTitleFor( 'Confirmemail' ); $skin = $wgUser->getSkin(); - $llink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $self->getPrefixedUrl() ); - $wgOut->addHtml( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) ); + $llink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'loginreqlink' ), + 'returnto=' . $self->getPrefixedUrl() ); + $wgOut->addHTML( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) ); } } else { $this->attemptConfirm( $code ); @@ -58,19 +59,24 @@ class EmailConfirmation extends UnlistedSpecialPage { } } else { if( $wgUser->isEmailConfirmed() ) { + // date and time are separate parameters to facilitate localisation. + // $time is kept for backward compat reasons. + // 'emailauthenticated' is also used in SpecialPreferences.php $time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true ); - $wgOut->addWikiMsg( 'emailauthenticated', $time ); + $d = $wgLang->date( $wgUser->mEmailAuthenticated, true ); + $t = $wgLang->time( $wgUser->mEmailAuthenticated, true ); + $wgOut->addWikiMsg( 'emailauthenticated', $time, $d, $t ); } if( $wgUser->isEmailConfirmationPending() ) { $wgOut->addWikiMsg( 'confirmemail_pending' ); } $wgOut->addWikiMsg( 'confirmemail_text' ); $self = SpecialPage::getTitleFor( 'Confirmemail' ); - $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) ); - $form .= wfHidden( 'token', $wgUser->editToken() ); - $form .= wfSubmitButton( wfMsgHtml( 'confirmemail_send' ) ); - $form .= wfCloseElement( 'form' ); - $wgOut->addHtml( $form ); + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) ); + $form .= Xml::hidden( 'token', $wgUser->editToken() ); + $form .= Xml::submitButton( wfMsgHtml( 'confirmemail_send' ) ); + $form .= Xml::closeElement( 'form' ); + $wgOut->addHTML( $form ); } } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index 4a131f15..3d8c18dd 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -4,6 +4,363 @@ * @file * @ingroup SpecialPage */ + +class SpecialContributions extends SpecialPage { + + public function __construct() { + parent::__construct( 'Contributions' ); + } + + public function execute( $par ) { + global $wgUser, $wgOut, $wgLang, $wgRequest; + + $this->setHeaders(); + $this->outputHeader(); + + $this->opts = array(); + + if( $par == 'newbies' ) { + $target = 'newbies'; + $this->opts['contribs'] = 'newbie'; + } elseif( isset( $par ) ) { + $target = $par; + } else { + $target = $wgRequest->getVal( 'target' ); + } + + // check for radiobox + if( $wgRequest->getVal( 'contribs' ) == 'newbie' ) { + $target = 'newbies'; + $this->opts['contribs'] = 'newbie'; + } + + if( !strlen( $target ) ) { + $wgOut->addHTML( $this->getForm( '' ) ); + return; + } + + $this->opts['limit'] = $wgRequest->getInt( 'limit', 50 ); + $this->opts['target'] = $target; + + $nt = Title::makeTitleSafe( NS_USER, $target ); + if( !$nt ) { + $wgOut->addHTML( $this->getForm( '' ) ); + return; + } + $id = User::idFromName( $nt->getText() ); + + if( $target != 'newbies' ) { + $target = $nt->getText(); + $wgOut->setSubtitle( $this->contributionsSub( $nt, $id ) ); + $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'contributions-title', $target ) ) ); + } else { + $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); + $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'sp-contributions-newbies-title' ) ) ); + } + + if( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { + $this->opts['namespace'] = intval( $ns ); + } else { + $this->opts['namespace'] = ''; + } + + // Allows reverts to have the bot flag in recent changes. It is just here to + // be passed in the form at the top of the page + if( $wgUser->isAllowed( 'markbotedits' ) && $wgRequest->getBool( 'bot' ) ) { + $this->opts['bot'] = '1'; + } + + $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; + # Offset overrides year/month selection + if( ( $month = $wgRequest->getIntOrNull( 'month' ) ) !== null && $month !== -1 ) { + $this->opts['month'] = intval( $month ); + } else { + $this->opts['month'] = ''; + } + if( ( $year = $wgRequest->getIntOrNull( 'year' ) ) !== null ) { + $this->opts['year'] = intval( $year ); + } else if( $this->opts['month'] ) { + $thisMonth = intval( gmdate( 'n' ) ); + $thisYear = intval( gmdate( 'Y' ) ); + if( intval( $this->opts['month'] ) > $thisMonth ) { + $thisYear--; + } + $this->opts['year'] = $thisYear; + } else { + $this->opts['year'] = ''; + } + + if( $skip ) { + $this->opts['year'] = ''; + $this->opts['month'] = ''; + } + + // Add RSS/atom links + $this->setSyndicated(); + $feedType = $wgRequest->getVal( 'feed' ); + if( $feedType ) { + return $this->feed( $feedType ); + } + + wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id ); + + $wgOut->addHTML( $this->getForm( $this->opts ) ); + + $pager = new ContribsPager( $target, $this->opts['namespace'], $this->opts['year'], $this->opts['month'] ); + if( !$pager->getNumRows() ) { + $wgOut->addWikiMsg( 'nocontribs' ); + return; + } + + # Show a message about slave lag, if applicable + if( ( $lag = $pager->getDatabase()->getLag() ) > 0 ) + $wgOut->showLagWarning( $lag ); + + $wgOut->addHTML( + '<p>' . $pager->getNavigationBar() . '</p>' . + $pager->getBody() . + '<p>' . $pager->getNavigationBar() . '</p>' + ); + + # If there were contributions, and it was a valid user or IP, show + # the appropriate "footer" message - WHOIS tools, etc. + if( $target != 'newbies' ) { + $message = IP::isIPAddress( $target ) ? + 'sp-contributions-footer-anon' : 'sp-contributions-footer'; + + $text = wfMsgNoTrans( $message, $target ); + if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { + $wgOut->addHTML( '<div class="mw-contributions-footer">' ); + $wgOut->addWikiText( $text ); + $wgOut->addHTML( '</div>' ); + } + } + } + + protected function setSyndicated() { + global $wgOut; + $queryParams = array( + 'namespace' => $this->opts['namespace'], + 'target' => $this->opts['target'] + ); + $wgOut->setSyndicated( true ); + $wgOut->setFeedAppendQuery( wfArrayToCGI( $queryParams ) ); + } + + /** + * Generates the subheading with links + * @param Title $nt Title object for the target + * @param integer $id User ID for the target + * @return String: appropriately-escaped HTML to be output literally + */ + protected function contributionsSub( $nt, $id ) { + global $wgSysopUserBans, $wgLang, $wgUser; + + $sk = $wgUser->getSkin(); + + if( 0 == $id ) { + $user = $nt->getText(); + } else { + $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); + } + $talk = $nt->getTalkPage(); + if( $talk ) { + # Talk page link + $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) ); + if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && IP::isIPAddress( $nt->getText() ) ) ) { + # Block link + if( $wgUser->isAllowed( 'block' ) ) + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', + $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) ); + # Block log link + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), + wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() ); + } + # Other logs link + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), + 'user=' . $nt->getPartialUrl() ); + + # Add link to deleted user contributions for priviledged users + if( $wgUser->isAllowed( 'deletedhistory' ) ) { + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'DeletedContributions', + $nt->getDBkey() ), wfMsgHtml( 'deletedcontributions' ) ); + } + + wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); + + $links = implode( ' | ', $tools ); + } + + // Old message 'contribsub' had one parameter, but that doesn't work for + // languages that want to put the "for" bit right after $user but before + // $links. If 'contribsub' is around, use it for reverse compatibility, + // otherwise use 'contribsub2'. + if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { + return wfMsgHtml( 'contribsub2', $user, $links ); + } else { + return wfMsgHtml( 'contribsub', "$user ($links)" ); + } + } + + /** + * Generates the namespace selector form with hidden attributes. + * @param $this->opts Array: the options to be included. + */ + protected function getForm() { + global $wgScript, $wgTitle; + + $this->opts['title'] = $wgTitle->getPrefixedText(); + if( !isset( $this->opts['target'] ) ) { + $this->opts['target'] = ''; + } else { + $this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] ); + } + + if( !isset( $this->opts['namespace'] ) ) { + $this->opts['namespace'] = ''; + } + + if( !isset( $this->opts['contribs'] ) ) { + $this->opts['contribs'] = 'user'; + } + + if( !isset( $this->opts['year'] ) ) { + $this->opts['year'] = ''; + } + + if( !isset( $this->opts['month'] ) ) { + $this->opts['month'] = ''; + } + + if( $this->opts['contribs'] == 'newbie' ) { + $this->opts['target'] = ''; + } + + $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); + + foreach ( $this->opts as $name => $value ) { + if( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) { + continue; + } + $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; + } + + $f .= '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . + Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), + 'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ? true : false ) . '<br />' . + Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ), + 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ? true : false ) . ' ' . + Xml::input( 'target', 20, $this->opts['target']) . ' '. + '<span style="white-space: nowrap">' . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $this->opts['namespace'], '' ) . + '</span>' . + Xml::openElement( 'p' ) . + '<span style="white-space: nowrap">' . + Xml::label( wfMsg( 'year' ), 'year' ) . ' '. + Xml::input( 'year', 4, $this->opts['year'], array('id' => 'year', 'maxlength' => 4) ) . + '</span>' . + ' '. + '<span style="white-space: nowrap">' . + Xml::label( wfMsg( 'month' ), 'month' ) . ' '. + Xml::monthSelector( $this->opts['month'], -1 ) . ' '. + '</span>' . + Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . + Xml::closeElement( 'p' ); + + $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); + if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) + $f .= "<p>{$explain}</p>"; + + $f .= '</fieldset>' . + Xml::closeElement( 'form' ); + return $f; + } + + /** + * Output a subscription feed listing recent edits to this page. + * @param string $type + */ + protected function feed( $type ) { + global $wgRequest, $wgFeed, $wgFeedClasses, $wgFeedLimit; + + if( !$wgFeed ) { + global $wgOut; + $wgOut->addWikiMsg( 'feed-unavailable' ); + return; + } + + if( !isset( $wgFeedClasses[$type] ) ) { + global $wgOut; + $wgOut->addWikiMsg( 'feed-invalid' ); + return; + } + + $feed = new $wgFeedClasses[$type]( + $this->feedTitle(), + wfMsgExt( 'tagline', 'parsemag' ), + $this->getTitle()->getFullUrl() ); + + // Already valid title + $nt = Title::makeTitleSafe( NS_USER, $this->opts['target'] ); + $target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText(); + + $pager = new ContribsPager( $target, $this->opts['namespace'], + $this->opts['year'], $this->opts['month'] ); + + $pager->mLimit = min( $this->opts['limit'], $wgFeedLimit ); + + $feed->outHeader(); + if( $pager->getNumRows() > 0 ) { + while( $row = $pager->mResult->fetchObject() ) { + $feed->outItem( $this->feedItem( $row ) ); + } + } + $feed->outFooter(); + } + + protected function feedTitle() { + global $wgContLanguageCode, $wgSitename; + $page = SpecialPage::getPage( 'Contributions' ); + $desc = $page->getDescription(); + return "$wgSitename - $desc [$wgContLanguageCode]"; + } + + protected function feedItem( $row ) { + $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title ); + if( $title ) { + $date = $row->rev_timestamp; + $comments = $title->getTalkPage()->getFullURL(); + $revision = Revision::newFromTitle( $title, $row->rev_id ); + + return new FeedItem( + $title->getPrefixedText(), + $this->feedItemDesc( $revision ), + $title->getFullURL(), + $date, + $this->feedItemAuthor( $revision ), + $comments + ); + } else { + return NULL; + } + } + + protected function feedItemAuthor( $revision ) { + return $revision->getUserText(); + } + + protected function feedItemDesc( $revision ) { + if( $revision ) { + return '<p>' . htmlspecialchars( $revision->getUserText() ) . ': ' . + htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . + "</p>\n<hr />\n<div>" . + nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; + } + return ''; + } +} /** * Pager for Special:Contributions @@ -12,7 +369,7 @@ class ContribsPager extends ReverseChronologicalPager { public $mDefaultDirection = true; var $messages, $target; - var $namespace = '', $year = '', $month = '', $mDb; + var $namespace = '', $mDb; function __construct( $target, $namespace = false, $year = false, $month = false ) { parent::__construct(); @@ -22,12 +379,7 @@ class ContribsPager extends ReverseChronologicalPager { $this->target = $target; $this->namespace = $namespace; - $year = intval($year); - $month = intval($month); - - $this->year = $year > 0 ? $year : false; - $this->month = ($month > 0 && $month < 13) ? $month : false; - $this->getDateCond(); + $this->getDateCond( $year, $month ); $this->mDb = wfGetDB( DB_SLAVE, 'contributions' ); } @@ -35,23 +387,22 @@ class ContribsPager extends ReverseChronologicalPager { function getDefaultQuery() { $query = parent::getDefaultQuery(); $query['target'] = $this->target; - $query['month'] = $this->month; - $query['year'] = $this->year; return $query; } function getQueryInfo() { - list( $index, $userCond ) = $this->getUserCond(); + list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond(); $conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() ); $queryInfo = array( - 'tables' => array( 'page', 'revision' ), + 'tables' => $tables, 'fields' => array( 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment', 'rev_minor_edit', 'rev_user', 'rev_user_text', 'rev_parent_id', 'rev_deleted' ), 'conds' => $conds, - 'options' => array( 'USE INDEX' => array('revision' => $index) ) + 'options' => array( 'USE INDEX' => array('revision' => $index) ), + 'join_conds' => $join_cond ); wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) ); return $queryInfo; @@ -59,73 +410,31 @@ class ContribsPager extends ReverseChronologicalPager { function getUserCond() { $condition = array(); - - if ( $this->target == 'newbies' ) { + $join_conds = array(); + if( $this->target == 'newbies' ) { + $tables = array( 'user_groups', 'page', 'revision' ); $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ ); $condition[] = 'rev_user >' . (int)($max - $max / 100); + $condition[] = 'ug_group IS NULL'; $index = 'user_timestamp'; + # FIXME: other groups may have 'bot' rights + $join_conds['user_groups'] = array( 'LEFT JOIN', "ug_user = rev_user AND ug_group = 'bot'" ); } else { + $tables = array( 'page', 'revision' ); $condition['rev_user_text'] = $this->target; $index = 'usertext_timestamp'; } - return array( $index, $condition ); + return array( $tables, $index, $condition, $join_conds ); } function getNamespaceCond() { - if ( $this->namespace !== '' ) { + if( $this->namespace !== '' ) { return array( 'page_namespace' => (int)$this->namespace ); } else { return array(); } } - function getDateCond() { - // Given an optional year and month, we need to generate a timestamp - // to use as "WHERE rev_timestamp <= result" - // Examples: year = 2006 equals < 20070101 (+000000) - // year=2005, month=1 equals < 20050201 - // year=2005, month=12 equals < 20060101 - - if (!$this->year && !$this->month) - return; - - if ( $this->year ) { - $year = $this->year; - } - else { - // If no year given, assume the current one - $year = gmdate( 'Y' ); - // If this month hasn't happened yet this year, go back to last year's month - if( $this->month > gmdate( 'n' ) ) { - $year--; - } - } - - if ( $this->month ) { - $month = $this->month + 1; - // For December, we want January 1 of the next year - if ($month > 12) { - $month = 1; - $year++; - } - } - else { - // No month implies we want up to the end of the year in question - $month = 1; - $year++; - } - - if ($year > 2032) - $year = 2032; - $ymd = (int)sprintf( "%04d%02d01", $year, $month ); - - // Y2K38 bug - if ($ymd > 20320101) - $ymd = 20320101; - - $this->mOffset = $this->mDb->timestamp( "${ymd}000000" ); - } - function getIndexField() { return 'rev_timestamp'; } @@ -167,8 +476,7 @@ class ContribsPager extends ReverseChronologicalPager { $difftext .= $this->messages['newarticle']; } - if( !$page->getUserPermissionsErrors( 'rollback', $wgUser ) - && !$page->getUserPermissionsErrors( 'edit', $wgUser ) ) { + if( $page->userCan( 'rollback') && $page->userCan( 'edit' ) ) { $topmarktext .= ' '.$sk->generateRollback( $rev ); } @@ -182,7 +490,8 @@ class ContribsPager extends ReverseChronologicalPager { $histlink='('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')'; $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true ); - $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); + $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); + $d = $sk->makeKnownLinkObj( $page, $date, 'oldid='.intval($row->rev_id) ); if( $this->target == 'newbies' ) { $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text ); @@ -207,7 +516,7 @@ class ContribsPager extends ReverseChronologicalPager { $mflag = ''; } - $ret = "{$d} {$histlink} {$difftext} {$nflag}{$mflag} {$link}{$userlink}{$comment} {$topmarktext}"; + $ret = "{$d} {$histlink} {$difftext} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}"; if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $ret .= ' ' . wfMsgHtml( 'deletedrev' ); } @@ -229,242 +538,3 @@ class ContribsPager extends ReverseChronologicalPager { } } - -/** - * Special page "user contributions". - * Shows a list of the contributions of a user. - * - * @return none - * @param $par String: (optional) user name of the user for which to show the contributions - */ -function wfSpecialContributions( $par = null ) { - global $wgUser, $wgOut, $wgLang, $wgRequest; - - $options = array(); - - if ( isset( $par ) && $par == 'newbies' ) { - $target = 'newbies'; - $options['contribs'] = 'newbie'; - } elseif ( isset( $par ) ) { - $target = $par; - } else { - $target = $wgRequest->getVal( 'target' ); - } - - // check for radiobox - if ( $wgRequest->getVal( 'contribs' ) == 'newbie' ) { - $target = 'newbies'; - $options['contribs'] = 'newbie'; - } - - if ( !strlen( $target ) ) { - $wgOut->addHTML( contributionsForm( '' ) ); - return; - } - - $options['limit'] = $wgRequest->getInt( 'limit', 50 ); - $options['target'] = $target; - - $nt = Title::makeTitleSafe( NS_USER, $target ); - if ( !$nt ) { - $wgOut->addHTML( contributionsForm( '' ) ); - return; - } - $id = User::idFromName( $nt->getText() ); - - if ( $target != 'newbies' ) { - $target = $nt->getText(); - $wgOut->setSubtitle( contributionsSub( $nt, $id ) ); - } else { - $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); - } - - if ( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { - $options['namespace'] = intval( $ns ); - } else { - $options['namespace'] = ''; - } - if ( $wgUser->isAllowed( 'markbotedit' ) && $wgRequest->getBool( 'bot' ) ) { - $options['bot'] = '1'; - } - - $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; - # Offset overrides year/month selection - if ( ( $month = $wgRequest->getIntOrNull( 'month' ) ) !== null && $month !== -1 ) { - $options['month'] = intval( $month ); - } else { - $options['month'] = ''; - } - if ( ( $year = $wgRequest->getIntOrNull( 'year' ) ) !== null ) { - $options['year'] = intval( $year ); - } else if( $options['month'] ) { - $thisMonth = intval( gmdate( 'n' ) ); - $thisYear = intval( gmdate( 'Y' ) ); - if( intval( $options['month'] ) > $thisMonth ) { - $thisYear--; - } - $options['year'] = $thisYear; - } else { - $options['year'] = ''; - } - - wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id ); - - if( $skip ) { - $options['year'] = ''; - $options['month'] = ''; - } - - $wgOut->addHTML( contributionsForm( $options ) ); - - $pager = new ContribsPager( $target, $options['namespace'], $options['year'], $options['month'] ); - if ( !$pager->getNumRows() ) { - $wgOut->addWikiMsg( 'nocontribs' ); - return; - } - - # Show a message about slave lag, if applicable - if( ( $lag = $pager->getDatabase()->getLag() ) > 0 ) - $wgOut->showLagWarning( $lag ); - - $wgOut->addHTML( - '<p>' . $pager->getNavigationBar() . '</p>' . - $pager->getBody() . - '<p>' . $pager->getNavigationBar() . '</p>' ); - - # If there were contributions, and it was a valid user or IP, show - # the appropriate "footer" message - WHOIS tools, etc. - if( $target != 'newbies' ) { - $message = IP::isIPAddress( $target ) - ? 'sp-contributions-footer-anon' - : 'sp-contributions-footer'; - - - $text = wfMsgNoTrans( $message, $target ); - if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { - $wgOut->addHtml( '<div class="mw-contributions-footer">' ); - $wgOut->addWikiText( $text ); - $wgOut->addHtml( '</div>' ); - } - } -} - -/** - * Generates the subheading with links - * @param Title $nt Title object for the target - * @param integer $id User ID for the target - * @return String: appropriately-escaped HTML to be output literally - */ -function contributionsSub( $nt, $id ) { - global $wgSysopUserBans, $wgLang, $wgUser; - - $sk = $wgUser->getSkin(); - - if ( 0 == $id ) { - $user = $nt->getText(); - } else { - $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); - } - $talk = $nt->getTalkPage(); - if( $talk ) { - # Talk page link - $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) ); - if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) { - # Block link - if( $wgUser->isAllowed( 'block' ) ) - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) ); - # Block log link - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() ); - } - # Other logs link - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() ); - - wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); - - $links = implode( ' | ', $tools ); - } - - // Old message 'contribsub' had one parameter, but that doesn't work for - // languages that want to put the "for" bit right after $user but before - // $links. If 'contribsub' is around, use it for reverse compatibility, - // otherwise use 'contribsub2'. - if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { - return wfMsgHtml( 'contribsub2', $user, $links ); - } else { - return wfMsgHtml( 'contribsub', "$user ($links)" ); - } -} - -/** - * Generates the namespace selector form with hidden attributes. - * @param $options Array: the options to be included. - */ -function contributionsForm( $options ) { - global $wgScript, $wgTitle, $wgRequest; - - $options['title'] = $wgTitle->getPrefixedText(); - if ( !isset( $options['target'] ) ) { - $options['target'] = ''; - } else { - $options['target'] = str_replace( '_' , ' ' , $options['target'] ); - } - - if ( !isset( $options['namespace'] ) ) { - $options['namespace'] = ''; - } - - if ( !isset( $options['contribs'] ) ) { - $options['contribs'] = 'user'; - } - - if ( !isset( $options['year'] ) ) { - $options['year'] = ''; - } - - if ( !isset( $options['month'] ) ) { - $options['month'] = ''; - } - - if ( $options['contribs'] == 'newbie' ) { - $options['target'] = ''; - } - - $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - - foreach ( $options as $name => $value ) { - if ( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) { - continue; - } - $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; - } - - $f .= '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . - Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), 'contribs' , 'newbie' , 'newbie', $options['contribs'] == 'newbie' ? true : false ) . '<br />' . - Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ), 'contribs' , 'user', 'user', $options['contribs'] == 'user' ? true : false ) . ' ' . - Xml::input( 'target', 20, $options['target']) . ' '. - '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . - Xml::namespaceSelector( $options['namespace'], '' ) . - '</span>' . - Xml::openElement( 'p' ) . - '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'year' ), 'year' ) . ' '. - Xml::input( 'year', 4, $options['year'], array('id' => 'year', 'maxlength' => 4) ) . - '</span>' . - ' '. - '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'month' ), 'month' ) . ' '. - Xml::monthSelector( $options['month'], -1 ) . ' '. - '</span>' . - Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . - Xml::closeElement( 'p' ); - - $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); - if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) - $f .= "<p>{$explain}</p>"; - - $f .= '</fieldset>' . - Xml::closeElement( 'form' ); - return $f; -} diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php new file mode 100644 index 00000000..513d25e2 --- /dev/null +++ b/includes/specials/SpecialDeletedContributions.php @@ -0,0 +1,369 @@ +<?php +/** + * Implements Special:DeletedContributions to display archived revisions + * @ingroup SpecialPage + */ + +class DeletedContribsPager extends IndexPager { + public $mDefaultDirection = true; + var $messages, $target; + var $namespace = '', $mDb; + + function __construct( $target, $namespace = false ) { + parent::__construct(); + foreach( explode( ' ', 'deletionlog undeletebtn minoreditletter diff' ) as $msg ) { + $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') ); + } + $this->target = $target; + $this->namespace = $namespace; + $this->mDb = wfGetDB( DB_SLAVE, 'contributions' ); + } + + function getDefaultQuery() { + $query = parent::getDefaultQuery(); + $query['target'] = $this->target; + return $query; + } + + function getQueryInfo() { + list( $index, $userCond ) = $this->getUserCond(); + $conds = array_merge( $userCond, $this->getNamespaceCond() ); + + return array( + 'tables' => array( 'archive' ), + 'fields' => array( + 'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp', 'ar_comment', 'ar_minor_edit', + 'ar_user', 'ar_user_text', 'ar_deleted' + ), + 'conds' => $conds, + 'options' => array( 'FORCE INDEX' => $index ) + ); + } + + function getUserCond() { + $condition = array(); + + $condition['ar_user_text'] = $this->target; + $index = 'usertext_timestamp'; + + return array( $index, $condition ); + } + + function getIndexField() { + return 'ar_timestamp'; + } + + function getStartBody() { + return "<ul>\n"; + } + + function getEndBody() { + return "</ul>\n"; + } + + function getNavigationBar() { + if ( isset( $this->mNavigationBar ) ) { + return $this->mNavigationBar; + } + $linkTexts = array( + 'prev' => wfMsgHtml( 'pager-newer-n', $this->mLimit ), + 'next' => wfMsgHtml( 'pager-older-n', $this->mLimit ), + 'first' => wfMsgHtml( 'histlast' ), + 'last' => wfMsgHtml( 'histfirst' ) + ); + + $pagingLinks = $this->getPagingLinks( $linkTexts ); + $limitLinks = $this->getLimitLinks(); + $limits = implode( ' | ', $limitLinks ); + + $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . + wfMsgExt( 'viewprevnext', array( 'parsemag' ), $pagingLinks['prev'], $pagingLinks['next'], $limits ); + return $this->mNavigationBar; + } + + function getNamespaceCond() { + if ( $this->namespace !== '' ) { + return array( 'ar_namespace' => (int)$this->namespace ); + } else { + return array(); + } + } + + /** + * Generates each row in the contributions list. + * + * Contributions which are marked "top" are currently on top of the history. + * For these contributions, a [rollback] link is shown for users with sysop + * privileges. The rollback link restores the most recent version that was not + * written by the target user. + * + * @todo This would probably look a lot nicer in a table. + */ + function formatRow( $row ) { + wfProfileIn( __METHOD__ ); + + global $wgLang, $wgUser; + + $sk = $this->getSkin(); + + $rev = new Revision( array( + 'id' => $row->ar_rev_id, + 'comment' => $row->ar_comment, + 'user' => $row->ar_user, + 'user_text' => $row->ar_user_text, + 'timestamp' => $row->ar_timestamp, + 'minor_edit' => $row->ar_minor_edit, + 'rev_deleted' => $row->ar_deleted, + ) ); + + $page = Title::makeTitle( $row->ar_namespace, $row->ar_title ); + + $undelete = SpecialPage::getTitleFor( 'Undelete' ); + + $logs = SpecialPage::getTitleFor( 'Log' ); + $dellog = $sk->makeKnownLinkObj( $logs, + $this->messages['deletionlog'], + 'type=delete&page=' . $page->getPrefixedUrl() ); + + $reviewlink = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ), + $this->messages['undeletebtn'] ); + + $link = $sk->makeKnownLinkObj( $undelete, + htmlspecialchars( $page->getPrefixedText() ), + 'target=' . $page->getPrefixedUrl() . + '×tamp=' . $rev->getTimestamp() ); + + $last = $sk->makeKnownLinkObj( $undelete, + $this->messages['diff'], + "target=" . $page->getPrefixedUrl() . + "×tamp=" . $rev->getTimestamp() . + "&diff=prev" ); + + $comment = $sk->revComment( $rev ); + $d = $wgLang->timeanddate( $rev->getTimestamp(), true ); + + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $d = '<span class="history-deleted">' . $d . '</span>'; + } else { + $link = $sk->makeKnownLinkObj( $undelete, $d, + 'target=' . $page->getPrefixedUrl() . + '×tamp=' . $rev->getTimestamp() ); + } + + $pagelink = $sk->makeLinkObj( $page ); + + if( $rev->isMinor() ) { + $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> '; + } else { + $mflag = ''; + } + + + $ret = "{$link} ($last) ({$dellog}) ({$reviewlink}) . . {$mflag} {$pagelink} {$comment}"; + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $ret .= ' ' . wfMsgHtml( 'deletedrev' ); + } + + $ret = "<li>$ret</li>\n"; + + wfProfileOut( __METHOD__ ); + return $ret; + } + + /** + * Get the Database object in use + * + * @return Database + */ + public function getDatabase() { + return $this->mDb; + } +} + +class DeletedContributionsPage extends SpecialPage { + function __construct() { + parent::__construct( 'DeletedContributions', 'deletedhistory', + /*listed*/ true, /*function*/ false, /*file*/ false ); + } + + /** + * Special page "deleted user contributions". + * Shows a list of the deleted contributions of a user. + * + * @return none + * @param $par String: (optional) user name of the user for which to show the contributions + */ + function execute( $par ) { + global $wgUser; + $this->setHeaders(); + + if ( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); + return; + } + + global $wgUser, $wgOut, $wgLang, $wgRequest; + + $options = array(); + + if ( isset( $par ) ) { + $target = $par; + } else { + $target = $wgRequest->getVal( 'target' ); + } + + if ( !strlen( $target ) ) { + $wgOut->addHTML( $this->getForm( '' ) ); + return; + } + + $options['limit'] = $wgRequest->getInt( 'limit', 50 ); + $options['target'] = $target; + + $nt = Title::makeTitleSafe( NS_USER, $target ); + if ( !$nt ) { + $wgOut->addHTML( $this->getForm( '' ) ); + return; + } + $id = User::idFromName( $nt->getText() ); + + $target = $nt->getText(); + $wgOut->setSubtitle( $this->getSubTitle( $nt, $id ) ); + + if ( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { + $options['namespace'] = intval( $ns ); + } else { + $options['namespace'] = ''; + } + + $wgOut->addHTML( $this->getForm( $options ) ); + + $pager = new DeletedContribsPager( $target, $options['namespace'] ); + if ( !$pager->getNumRows() ) { + $wgOut->addWikiText( wfMsg( 'nocontribs' ) ); + return; + } + + # Show a message about slave lag, if applicable + if( ( $lag = $pager->getDatabase()->getLag() ) > 0 ) + $wgOut->showLagWarning( $lag ); + + $wgOut->addHTML( + '<p>' . $pager->getNavigationBar() . '</p>' . + $pager->getBody() . + '<p>' . $pager->getNavigationBar() . '</p>' ); + + # If there were contributions, and it was a valid user or IP, show + # the appropriate "footer" message - WHOIS tools, etc. + if( $target != 'newbies' ) { + $message = IP::isIPAddress( $target ) + ? 'sp-contributions-footer-anon' + : 'sp-contributions-footer'; + + + $text = wfMsgNoTrans( $message, $target ); + if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { + $wgOut->addHTML( '<div class="mw-contributions-footer">' ); + $wgOut->addWikiText( $text ); + $wgOut->addHTML( '</div>' ); + } + } + } + + /** + * Generates the subheading with links + * @param $nt @see Title object for the target + */ + function getSubTitle( $nt, $id ) { + global $wgSysopUserBans, $wgLang, $wgUser; + + $sk = $wgUser->getSkin(); + + if ( 0 == $id ) { + $user = $nt->getText(); + } else { + $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); + } + $talk = $nt->getTalkPage(); + if( $talk ) { + # Talk page link + $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) ); + if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) { + # Block link + if( $wgUser->isAllowed( 'block' ) ) + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), + wfMsgHtml( 'blocklink' ) ); + # Block log link + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), + wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() ); + } + # Other logs link + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), + wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() ); + # Link to undeleted contributions + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Contributions', $nt->getDBkey() ), + wfMsgHtml( 'contributions' ) ); + + wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); + + $links = implode( ' | ', $tools ); + } + + // Old message 'contribsub' had one parameter, but that doesn't work for + // languages that want to put the "for" bit right after $user but before + // $links. If 'contribsub' is around, use it for reverse compatibility, + // otherwise use 'contribsub2'. + if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { + return wfMsgHtml( 'contribsub2', $user, $links ); + } else { + return wfMsgHtml( 'contribsub', "$user ($links)" ); + } + } + + /** + * Generates the namespace selector form with hidden attributes. + * @param $options Array: the options to be included. + */ + function getForm( $options ) { + global $wgScript, $wgTitle, $wgRequest; + + $options['title'] = $wgTitle->getPrefixedText(); + if ( !isset( $options['target'] ) ) { + $options['target'] = ''; + } else { + $options['target'] = str_replace( '_' , ' ' , $options['target'] ); + } + + if ( !isset( $options['namespace'] ) ) { + $options['namespace'] = ''; + } + + if ( !isset( $options['contribs'] ) ) { + $options['contribs'] = 'user'; + } + + if ( $options['contribs'] == 'newbie' ) { + $options['target'] = ''; + } + + $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); + + foreach ( $options as $name => $value ) { + if ( in_array( $name, array( 'namespace', 'target', 'contribs' ) ) ) { + continue; + } + $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; + } + + $f .= Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . + Xml::tags( 'label', array( 'for' => 'target' ), wfMsgExt( 'sp-contributions-username', 'parseinline' ) ) . ' ' . + Xml::input( 'target', 20, $options['target']) . ' '. + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $options['namespace'], '' ) . ' ' . + Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ); + return $f; + } +} diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php index 34045660..0a728b68 100644 --- a/includes/specials/SpecialDisambiguations.php +++ b/includes/specials/SpecialDisambiguations.php @@ -84,13 +84,13 @@ class DisambiguationsPage extends PageQueryPage { function formatResult( $skin, $result ) { global $wgContLang; - $title = Title::newFromId( $result->value ); + $title = Title::newFromID( $result->value ); $dp = Title::makeTitle( $result->namespace, $result->title ); - $from = $skin->makeKnownLinkObj( $title, '' ); - $edit = $skin->makeKnownLinkObj( $title, "(".wfMsgHtml("qbedit").")" , 'redirect=no&action=edit' ); + $from = $skin->link( $title ); + $edit = $skin->link( $title, "(".wfMsgHtml("qbedit").")", array(), array( 'redirect' => 'no', 'action' => 'edit' ) ); $arr = $wgContLang->getArrow(); - $to = $skin->makeKnownLinkObj( $dp, '' ); + $to = $skin->link( $dp ); return "$from $edit $arr $to"; } diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php index 3874c6a1..cf90f94d 100644 --- a/includes/specials/SpecialEmailuser.php +++ b/includes/specials/SpecialEmailuser.php @@ -5,17 +5,22 @@ */ /** - * @todo document + * Constructor for Special:Emailuser. */ function wfSpecialEmailuser( $par ) { global $wgRequest, $wgUser, $wgOut; + if ( !EmailUserForm::userEmailEnabled() ) { + $wgOut->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' ); + return; + } + $action = $wgRequest->getVal( 'action' ); $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); $targetUser = EmailUserForm::validateEmailTarget( $target ); if ( !( $targetUser instanceof User ) ) { - $wgOut->showErrorPage( $targetUser[0], $targetUser[1] ); + $wgOut->showErrorPage( $targetUser.'title', $targetUser.'text' ); return; } @@ -30,7 +35,7 @@ function wfSpecialEmailuser( $par ) { $error = EmailUserForm::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) ); if ( $error ) { - switch ( $error[0] ) { + switch ( $error ) { case 'blockedemailuser': $wgOut->blockedPage(); return; @@ -40,12 +45,11 @@ function wfSpecialEmailuser( $par ) { case 'sessionfailure': $form->showForm(); return; - default: - $wgOut->showErrorPage( $error[0], $error[1] ); + case 'mailnologin': + $wgOut->showErrorPage( 'mailnologin', 'mailnologintext' ); return; } } - if ( "submit" == $action && $wgRequest->wasPosted() ) { $result = $form->doSubmit(); @@ -94,46 +98,64 @@ class EmailUserForm { $this->subject = wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ) ); } - $emf = wfMsg( "emailfrom" ); - $senderLink = $skin->makeLinkObj( - $wgUser->getUserPage(), htmlspecialchars( $wgUser->getName() ) ); - $emt = wfMsg( "emailto" ); - $recipientLink = $skin->makeLinkObj( - $this->target->getUserPage(), htmlspecialchars( $this->target->getName() ) ); - $emr = wfMsg( "emailsubject" ); - $emm = wfMsg( "emailmessage" ); - $ems = wfMsg( "emailsend" ); - $emc = wfMsg( "emailccme" ); - $encSubject = htmlspecialchars( $this->subject ); - $titleObj = SpecialPage::getTitleFor( "Emailuser" ); - $action = $titleObj->escapeLocalURL( "target=" . + $action = $titleObj->getLocalURL( "target=" . urlencode( $this->target->getName() ) . "&action=submit" ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( " -<form id=\"emailuser\" method=\"post\" action=\"{$action}\"> -<table border='0' id='mailheader'><tr> -<td align='right'>{$emf}:</td> -<td align='left'><strong>{$senderLink}</strong></td> -</tr><tr> -<td align='right'>{$emt}:</td> -<td align='left'><strong>{$recipientLink}</strong></td> -</tr><tr> -<td align='right'>{$emr}:</td> -<td align='left'> -<input type='text' size='60' maxlength='200' name=\"wpSubject\" value=\"{$encSubject}\" /> -</td> -</tr> -</table> -<span id='wpTextLabel'><label for=\"wpText\">{$emm}:</label><br /></span> -<textarea id=\"wpText\" name=\"wpText\" rows='20' cols='80' style=\"width: 100%;\">" . htmlspecialchars( $this->text ) . -"</textarea> -" . wfCheckLabel( $emc, 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) . "<br /> -<input type='submit' name=\"wpSend\" value=\"{$ems}\" /> -<input type='hidden' name='wpEditToken' value=\"$token\" /> -</form>\n" ); + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'emailuser' ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsgExt( 'email-legend', 'parsemag' ) ) . + Xml::openElement( 'table', array( 'class' => 'mw-emailuser-table' ) ) . + "<tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'emailfrom' ), 'emailfrom' ) . + "</td> + <td class='mw-input' id='mw-emailuser-sender'>" . + $skin->link( $wgUser->getUserPage(), htmlspecialchars( $wgUser->getName() ) ) . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'emailto' ), 'emailto' ) . + "</td> + <td class='mw-input' id='mw-emailuser-recipient'>" . + $skin->link( $this->target->getUserPage(), htmlspecialchars( $this->target->getName() ) ) . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'emailsubject' ), 'wpSubject' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'wpSubject', 60, $this->subject, array( 'type' => 'text', 'maxlength' => 200 ) ) . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'emailmessage' ), 'wpText' ) . + "</td> + <td class='mw-input'>" . + Xml::textarea( 'wpText', $this->text, 80, 20, array( 'id' => 'wpText' ) ) . + "</td> + </tr> + <tr> + <td></td> + <td class='mw-input'>" . + Xml::checkLabel( wfMsg( 'emailccme' ), 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) . + "</td> + </tr> + <tr> + <td></td> + <td class='mw-submit'>" . + Xml::submitButton( wfMsg( 'emailsend' ), array( 'name' => 'wpSend', 'accesskey' => 's' ) ) . + "</td> + </tr>" . + Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); } /* @@ -149,7 +171,7 @@ class EmailUserForm { $subject = $this->subject; // Add a standard footer and trim up trailing newlines - $this->text = rtrim($this->text) . "\n\n---\n" . wfMsgExt( 'emailuserfooter', + $this->text = rtrim($this->text) . "\n\n-- \n" . wfMsgExt( 'emailuserfooter', array( 'content', 'parsemag' ), array( $from->name, $to->name ) ); if( wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$this->text ) ) ) { @@ -228,27 +250,33 @@ class EmailUserForm { return $this->target; } - static function validateEmailTarget ( $target ) { + static function userEmailEnabled() { global $wgEnableEmail, $wgEnableUserEmail; - - if( !( $wgEnableEmail && $wgEnableUserEmail ) ) - return array( "nosuchspecialpage", "nospecialpagetext" ); + return $wgEnableEmail && $wgEnableUserEmail; + } + static function validateEmailTarget ( $target ) { if ( "" == $target ) { wfDebug( "Target is empty.\n" ); - return array( "notargettitle", "notargettext" ); + return "notarget"; } $nt = Title::newFromURL( $target ); if ( is_null( $nt ) ) { wfDebug( "Target is invalid title.\n" ); - return array( "notargettitle", "notargettext" ); + return "notarget"; } $nu = User::newFromName( $nt->getText() ); - if( is_null( $nu ) || !$nu->canReceiveEmail() ) { - wfDebug( "Target is invalid user or can't receive.\n" ); - return array( "noemailtitle", "noemailtext" ); + if( is_null( $nu ) || !$nu->getId() ) { + wfDebug( "Target is invalid user.\n" ); + return "notarget"; + } else if ( !$nu->isEmailConfirmed() ) { + wfDebug( "User has no valid email.\n" ); + return "noemail"; + } else if ( !$nu->canReceiveEmail() ) { + wfDebug( "User does not allow user emails.\n" ); + return "nowikiemail"; } return $nu; @@ -256,22 +284,22 @@ class EmailUserForm { static function getPermissionsError ( $user, $editToken ) { if( !$user->canSendEmail() ) { wfDebug( "User can't send.\n" ); - return array( "mailnologin", "mailnologintext" ); + return "mailnologin"; } if( $user->isBlockedFromEmailuser() ) { wfDebug( "User is blocked from sending e-mail.\n" ); - return array( "blockedemailuser", "" ); + return "blockedemailuser"; } if( $user->pingLimiter( 'emailuser' ) ) { wfDebug( "Ping limiter triggered.\n" ); - return array( 'actionthrottledtext', '' ); + return 'actionthrottledtext'; } if( !$user->matchEditToken( $editToken ) ) { wfDebug( "Matching edit token failed.\n" ); - return array( 'sessionfailure', '' ); + return 'sessionfailure'; } return; diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php index 38bfc83e..898b5a78 100644 --- a/includes/specials/SpecialExport.php +++ b/includes/specials/SpecialExport.php @@ -1,5 +1,5 @@ <?php -# Copyright (C) 2003 Brion Vibber <brion@pobox.com> +# Copyright (C) 2003-2008 Brion Vibber <brion@pobox.com> # http://www.mediawiki.org/ # # This program is free software; you can redistribute it and/or modify @@ -71,7 +71,7 @@ function wfExportGetTemplates( $inputPages, $pageSet ) { function wfExportGetImages( $inputPages, $pageSet ) { return wfExportGetLinks( $inputPages, $pageSet, 'imagelinks', - array( NS_IMAGE . ' AS namespace', 'il_to AS title' ), + array( NS_FILE . ' AS namespace', 'il_to AS title' ), array( 'page_id=il_from' ) ); } @@ -93,7 +93,7 @@ function wfExportGetLinks( $inputPages, $pageSet, $table, $fields, $join ) { array_merge( $join, array( 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDbKey() ) ), + 'page_title' => $title->getDBKey() ) ), __METHOD__ ); foreach( $result as $row ) { $template = Title::makeTitle( $row->namespace, $row->title ); @@ -126,7 +126,7 @@ function wfSpecialExport( $page = '' ) { $catname = $wgRequest->getText( 'catname' ); if ( $catname !== '' && $catname !== NULL && $catname !== false ) { - $t = Title::makeTitleSafe( NS_CATEGORY, $catname ); + $t = Title::makeTitleSafe( NS_MAIN, $catname ); if ( $t ) { /** * @fixme This can lead to hitting memory limit for very large @@ -223,8 +223,23 @@ function wfSpecialExport( $page = '' ) { /* Ok, let's get to it... */ - $db = wfGetDB( DB_SLAVE ); - $exporter = new WikiExporter( $db, $history ); + if( $history == WikiExporter::CURRENT ) { + $lb = false; + $db = wfGetDB( DB_SLAVE ); + $buffer = WikiExporter::BUFFER; + } else { + // Use an unbuffered query; histories may be very long! + $lb = wfGetLBFactory()->newMainLB(); + $db = $lb->getConnection( DB_SLAVE ); + $buffer = WikiExporter::STREAM; + + // This might take a while... :D + wfSuppressWarnings(); + set_time_limit(0); + wfRestoreWarnings(); + } + + $exporter = new WikiExporter( $db, $history, $buffer ); $exporter->list_authors = $list_authors ; $exporter->openStream(); @@ -251,11 +266,14 @@ function wfSpecialExport( $page = '' ) { } $exporter->closeStream(); + if( $lb ) { + $lb->closeAll(); + } return; } $self = SpecialPage::getTitleFor( 'Export' ); - $wgOut->addHtml( wfMsgExt( 'exporttext', 'parse' ) ); + $wgOut->addHTML( wfMsgExt( 'exporttext', 'parse' ) ); $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl( 'action=submit' ) ) ); @@ -271,14 +289,14 @@ function wfSpecialExport( $page = '' ) { if( $wgExportAllowHistory ) { $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />'; } else { - $wgOut->addHtml( wfMsgExt( 'exportnohistory', 'parse' ) ); + $wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) ); } $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />'; // Enable this when we can do something useful exporting/importing image information. :) //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '<br />'; $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '<br />'; - $form .= Xml::submitButton( wfMsg( 'export-submit' ) ); + $form .= Xml::submitButton( wfMsg( 'export-submit' ), array( 'accesskey' => 's' ) ); $form .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $form ); + $wgOut->addHTML( $form ); } diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php index 5236ca25..49a218c8 100644 --- a/includes/specials/SpecialFileDuplicateSearch.php +++ b/includes/specials/SpecialFileDuplicateSearch.php @@ -49,7 +49,7 @@ class FileDuplicateSearchPage extends QueryPage { function formatResult( $skin, $result ) { global $wgContLang, $wgLang; - $nt = Title::makeTitle( NS_IMAGE, $result->title ); + $nt = Title::makeTitle( NS_FILE, $result->title ); $text = $wgContLang->convert( $nt->getText() ); $plink = $skin->makeLink( $nt->getPrefixedText(), $text ); @@ -73,7 +73,7 @@ function wfSpecialFileDuplicateSearch( $par = null ) { if( $title && $title->getText() != '' ) { $dbr = wfGetDB( DB_SLAVE ); $image = $dbr->tableName( 'image' ); - $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDbKey() ) ); + $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDBKey() ) ); $sql = "SELECT img_sha1 from $image where img_name = $encFilename"; $res = $dbr->query( $sql ); $row = $dbr->fetchRow( $res ); @@ -100,7 +100,7 @@ function wfSpecialFileDuplicateSearch( $par = null ) { # Show a thumbnail of the file $img = wfFindFile( $title ); if ( $img ) { - $thumb = $img->getThumbnail( 120, 120 ); + $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); if( $thumb ) { $wgOut->addHTML( '<div style="float:' . $align . '" id="mw-fileduplicatesearch-icon">' . $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' . diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php index a2ba3e57..4a724b1f 100644 --- a/includes/specials/SpecialFilepath.php +++ b/includes/specials/SpecialFilepath.php @@ -9,9 +9,9 @@ function wfSpecialFilepath( $par ) { $file = isset( $par ) ? $par : $wgRequest->getText( 'file' ); - $title = Title::newFromText( $file, NS_IMAGE ); + $title = Title::makeTitleSafe( NS_FILE, $file ); - if ( ! $title instanceof Title || $title->getNamespace() != NS_IMAGE ) { + if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) { $cform = new FilepathForm( $title ); $cform->execute(); } else { diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 1623245d..5e1a6533 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -23,28 +23,55 @@ * @ingroup SpecialPage */ -/** - * Constructor - */ -function wfSpecialImport( $page = '' ) { - global $wgUser, $wgOut, $wgRequest, $wgTitle, $wgImportSources; - global $wgImportTargetNamespace; - - $interwiki = false; - $namespace = $wgImportTargetNamespace; - $frompage = ''; - $history = true; - - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; +class SpecialImport extends SpecialPage { + + private $interwiki = false; + private $namespace; + private $frompage = ''; + private $logcomment= false; + private $history = true; + + /** + * Constructor + */ + public function __construct() { + parent::__construct( 'Import', 'import' ); + global $wgImportTargetNamespace; + $this->namespace = $wgImportTargetNamespace; } - - if( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit') { + + /** + * Execute + */ + function execute( $par ) { + global $wgRequest; + + $this->setHeaders(); + $this->outputHeader(); + + if ( wfReadOnly() ) { + global $wgOut; + $wgOut->readOnlyPage(); + return; + } + + if ( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit' ) { + $this->doImport(); + } + $this->showForm(); + } + + /** + * Do the actual import + */ + private function doImport() { + global $wgOut, $wgRequest, $wgUser, $wgImportSources; $isUpload = false; - $namespace = $wgRequest->getIntOrNull( 'namespace' ); + $this->namespace = $wgRequest->getIntOrNull( 'namespace' ); $sourceName = $wgRequest->getVal( "source" ); + $this->logcomment = $wgRequest->getText( 'log-comment' ); + if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'editToken' ) ) ) { $source = new WikiErrorMsg( 'import-token-mismatch' ); } elseif ( $sourceName == 'upload' ) { @@ -55,16 +82,16 @@ function wfSpecialImport( $page = '' ) { return $wgOut->permissionRequired( 'importupload' ); } } elseif ( $sourceName == "interwiki" ) { - $interwiki = $wgRequest->getVal( 'interwiki' ); - if ( !in_array( $interwiki, $wgImportSources ) ) { + $this->interwiki = $wgRequest->getVal( 'interwiki' ); + if ( !in_array( $this->interwiki, $wgImportSources ) ) { $source = new WikiErrorMsg( "import-invalid-interwiki" ); } else { - $history = $wgRequest->getCheck( 'interwikiHistory' ); - $frompage = $wgRequest->getText( "frompage" ); + $this->history = $wgRequest->getCheck( 'interwikiHistory' ); + $this->frompage = $wgRequest->getText( "frompage" ); $source = ImportStreamSource::newFromInterwiki( - $interwiki, - $frompage, - $history ); + $this->interwiki, + $this->frompage, + $this->history ); } } else { $source = new WikiErrorMsg( "importunknownsource" ); @@ -76,10 +103,10 @@ function wfSpecialImport( $page = '' ) { $wgOut->addWikiMsg( "importstart" ); $importer = new WikiImporter( $source ); - if( !is_null( $namespace ) ) { - $importer->setTargetNamespace( $namespace ); + if( !is_null( $this->namespace ) ) { + $importer->setTargetNamespace( $this->namespace ); } - $reporter = new ImportReporter( $importer, $isUpload, $interwiki ); + $reporter = new ImportReporter( $importer, $isUpload, $this->interwiki , $this->logcomment); $reporter->open(); $result = $importer->doImport(); @@ -99,79 +126,121 @@ function wfSpecialImport( $page = '' ) { } } - $action = $wgTitle->getLocalUrl( 'action=submit' ); - - if( $wgUser->isAllowed( 'importupload' ) ) { - $wgOut->addWikiMsg( "importtext" ); - $wgOut->addHTML( - Xml::openElement( 'fieldset' ). - Xml::element( 'legend', null, wfMsg( 'import-upload' ) ) . - Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post', 'action' => $action ) ) . - Xml::hidden( 'action', 'submit' ) . - Xml::hidden( 'source', 'upload' ) . - Xml::input( 'xmlimport', 50, '', array( 'type' => 'file' ) ) . ' ' . - Xml::hidden( 'editToken', $wgUser->editToken() ) . - Xml::submitButton( wfMsg( 'uploadbtn' ) ) . - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) - ); - } else { - if( empty( $wgImportSources ) ) { - $wgOut->addWikiMsg( 'importnosources' ); + private function showForm() { + global $wgUser, $wgOut, $wgRequest, $wgTitle, $wgImportSources; + # FIXME: Quick hack to disable import for non privileged users /Raymond + # Regression from 43963 + if( !$wgUser->isAllowed( 'import' ) && !$wgUser->isAllowed( 'importupload' ) ) + return $wgOut->permissionRequired( 'import' ); + + $action = $wgTitle->getLocalUrl( 'action=submit' ); + + if( $wgUser->isAllowed( 'importupload' ) ) { + $wgOut->addWikiMsg( "importtext" ); + $wgOut->addHTML( + Xml::openElement( 'fieldset' ). + Xml::element( 'legend', null, wfMsg( 'import-upload' ) ) . + Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post', 'action' => $action ) ) . + Xml::hidden( 'action', 'submit' ) . + Xml::hidden( 'source', 'upload' ) . + Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . + + "<tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'import-upload-filename' ), 'xmlimport' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'xmlimport', 50, '', array( 'type' => 'file' ) ) . ' ' . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'import-comment' ), 'mw-import-comment' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'log-comment', 50, '', + array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' . + "</td> + </tr> + <tr> + <td></td> + <td class='mw-input'>" . + Xml::submitButton( wfMsg( 'uploadbtn' ) ) . + "</td> + </tr>" . + Xml::closeElement( 'table' ). + Xml::hidden( 'editToken', $wgUser->editToken() ) . + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) + ); + } else { + if( empty( $wgImportSources ) ) { + $wgOut->addWikiMsg( 'importnosources' ); + } } - } - if( !empty( $wgImportSources ) ) { - $wgOut->addHTML( - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'importinterwiki' ) ) . - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action ) ) . - wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) . - Xml::hidden( 'action', 'submit' ) . - Xml::hidden( 'source', 'interwiki' ) . - Xml::hidden( 'editToken', $wgUser->editToken() ) . - Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . - "<tr> - <td>" . - Xml::openElement( 'select', array( 'name' => 'interwiki' ) ) - ); - foreach( $wgImportSources as $prefix ) { - $selected = ( $interwiki === $prefix ) ? ' selected="selected"' : ''; - $wgOut->addHTML( Xml::option( $prefix, $prefix, $selected ) ); + if( $wgUser->isAllowed( 'import' ) && !empty( $wgImportSources ) ) { + $wgOut->addHTML( + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'importinterwiki' ) ) . + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action ) ) . + wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) . + Xml::hidden( 'action', 'submit' ) . + Xml::hidden( 'source', 'interwiki' ) . + Xml::hidden( 'editToken', $wgUser->editToken() ) . + Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . + "<tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'import-interwiki-source' ), 'interwiki' ) . + "</td> + <td class='mw-input'>" . + Xml::openElement( 'select', array( 'name' => 'interwiki' ) ) + ); + foreach( $wgImportSources as $prefix ) { + $selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : ''; + $wgOut->addHTML( Xml::option( $prefix, $prefix, $selected ) ); + } + $wgOut->addHTML( + Xml::closeElement( 'select' ) . + Xml::input( 'frompage', 50, $this->frompage ) . + "</td> + </tr> + <tr> + <td> + </td> + <td class='mw-input'>" . + Xml::checkLabel( wfMsg( 'import-interwiki-history' ), 'interwikiHistory', 'interwikiHistory', $this->history ) . + "</td> + </tr> + <tr> + <td>" . + Xml::label( wfMsg( 'import-interwiki-namespace' ), 'namespace' ) . + "</td> + <td class='mw-input'>" . + Xml::namespaceSelector( $this->namespace, '' ) . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'import-comment' ), 'mw-interwiki-comment' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'log-comment', 50, '', + array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' . + "</td> + </tr> + <tr> + <td> + </td> + <td class='mw-input'>" . + Xml::submitButton( wfMsg( 'import-interwiki-submit' ), array( 'accesskey' => 's' ) ) . + "</td> + </tr>" . + Xml::closeElement( 'table' ). + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) + ); } - $wgOut->addHTML( - Xml::closeElement( 'select' ) . - "</td> - <td>" . - Xml::input( 'frompage', 50, $frompage ) . - "</td> - </tr> - <tr> - <td> - </td> - <td>" . - Xml::checkLabel( wfMsg( 'import-interwiki-history' ), 'interwikiHistory', 'interwikiHistory', $history ) . - "</td> - </tr> - <tr> - <td> - </td> - <td>" . - Xml::label( wfMsg( 'import-interwiki-namespace' ), 'namespace' ) . - Xml::namespaceSelector( $namespace, '' ) . - "</td> - </tr> - <tr> - <td> - </td> - <td>" . - Xml::submitButton( wfMsg( 'import-interwiki-submit' ) ) . - "</td> - </tr>" . - Xml::closeElement( 'table' ). - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) - ); } } @@ -180,16 +249,19 @@ function wfSpecialImport( $page = '' ) { * @ingroup SpecialPage */ class ImportReporter { - function __construct( $importer, $upload, $interwiki ) { + private $reason=false; + + function __construct( $importer, $upload, $interwiki , $reason=false ) { $importer->setPageOutCallback( array( $this, 'reportPage' ) ); $this->mPageCount = 0; $this->mIsUpload = $upload; $this->mInterwiki = $interwiki; + $this->reason = $reason; } function open() { global $wgOut; - $wgOut->addHtml( "<ul>\n" ); + $wgOut->addHTML( "<ul>\n" ); } function reportPage( $title, $origTitle, $revisionCount, $successCount ) { @@ -203,7 +275,7 @@ class ImportReporter { $contentCount = $wgContLang->formatNum( $successCount ); if( $successCount > 0 ) { - $wgOut->addHtml( "<li>" . $skin->makeKnownLinkObj( $title ) . " " . + $wgOut->addHTML( "<li>" . $skin->makeKnownLinkObj( $title ) . " " . wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) . "</li>\n" ); @@ -212,949 +284,43 @@ class ImportReporter { if( $this->mIsUpload ) { $detail = wfMsgExt( 'import-logentry-upload-detail', array( 'content', 'parsemag' ), $contentCount ); + if ( $this->reason ) { + $detail .= wfMsgForContent( 'colon-separator' ) . $this->reason; + } $log->addEntry( 'upload', $title, $detail ); } else { $interwiki = '[[:' . $this->mInterwiki . ':' . $origTitle->getPrefixedText() . ']]'; $detail = wfMsgExt( 'import-logentry-interwiki-detail', array( 'content', 'parsemag' ), $contentCount, $interwiki ); + if ( $this->reason ) { + $detail .= wfMsgForContent( 'colon-separator' ) . $this->reason; + } $log->addEntry( 'interwiki', $title, $detail ); } $comment = $detail; // quick $dbw = wfGetDB( DB_MASTER ); + $latest = $title->getLatestRevID(); $nullRevision = Revision::newNullRevision( $dbw, $title->getArticleId(), $comment, true ); $nullRevision->insertOn( $dbw ); $article = new Article( $title ); # Update page record $article->updateRevisionOn( $dbw, $nullRevision ); - wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, false) ); + wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) ); } else { - $wgOut->addHtml( '<li>' . wfMsgHtml( 'import-nonewrevisions' ) . '</li>' ); + $wgOut->addHTML( '<li>' . wfMsgHtml( 'import-nonewrevisions' ) . '</li>' ); } } function close() { global $wgOut; if( $this->mPageCount == 0 ) { - $wgOut->addHtml( "</ul>\n" ); + $wgOut->addHTML( "</ul>\n" ); return new WikiErrorMsg( "importnopages" ); } - $wgOut->addHtml( "</ul>\n" ); + $wgOut->addHTML( "</ul>\n" ); return $this->mPageCount; } } - -/** - * - * @ingroup SpecialPage - */ -class WikiRevision { - var $title = null; - var $id = 0; - var $timestamp = "20010115000000"; - var $user = 0; - var $user_text = ""; - var $text = ""; - var $comment = ""; - var $minor = false; - - function setTitle( $title ) { - if( is_object( $title ) ) { - $this->title = $title; - } elseif( is_null( $title ) ) { - throw new MWException( "WikiRevision given a null title in import. You may need to adjust \$wgLegalTitleChars." ); - } else { - throw new MWException( "WikiRevision given non-object title in import." ); - } - } - - function setID( $id ) { - $this->id = $id; - } - - function setTimestamp( $ts ) { - # 2003-08-05T18:30:02Z - $this->timestamp = wfTimestamp( TS_MW, $ts ); - } - - function setUsername( $user ) { - $this->user_text = $user; - } - - function setUserIP( $ip ) { - $this->user_text = $ip; - } - - function setText( $text ) { - $this->text = $text; - } - - function setComment( $text ) { - $this->comment = $text; - } - - function setMinor( $minor ) { - $this->minor = (bool)$minor; - } - - function setSrc( $src ) { - $this->src = $src; - } - - function setFilename( $filename ) { - $this->filename = $filename; - } - - function setSize( $size ) { - $this->size = intval( $size ); - } - - function getTitle() { - return $this->title; - } - - function getID() { - return $this->id; - } - - function getTimestamp() { - return $this->timestamp; - } - - function getUser() { - return $this->user_text; - } - - function getText() { - return $this->text; - } - - function getComment() { - return $this->comment; - } - - function getMinor() { - return $this->minor; - } - - function getSrc() { - return $this->src; - } - - function getFilename() { - return $this->filename; - } - - function getSize() { - return $this->size; - } - - function importOldRevision() { - $dbw = wfGetDB( DB_MASTER ); - - # Sneak a single revision into place - $user = User::newFromName( $this->getUser() ); - if( $user ) { - $userId = intval( $user->getId() ); - $userText = $user->getName(); - } else { - $userId = 0; - $userText = $this->getUser(); - } - - // avoid memory leak...? - $linkCache = LinkCache::singleton(); - $linkCache->clear(); - - $article = new Article( $this->title ); - $pageId = $article->getId(); - if( $pageId == 0 ) { - # must create the page... - $pageId = $article->insertOn( $dbw ); - $created = true; - } else { - $created = false; - - $prior = Revision::loadFromTimestamp( $dbw, $this->title, $this->timestamp ); - if( !is_null( $prior ) ) { - // FIXME: this could fail slightly for multiple matches :P - wfDebug( __METHOD__ . ": skipping existing revision for [[" . - $this->title->getPrefixedText() . "]], timestamp " . - $this->timestamp . "\n" ); - return false; - } - } - - # FIXME: Use original rev_id optionally - # FIXME: blah blah blah - - #if( $numrows > 0 ) { - # return wfMsg( "importhistoryconflict" ); - #} - - # Insert the row - $revision = new Revision( array( - 'page' => $pageId, - 'text' => $this->getText(), - 'comment' => $this->getComment(), - 'user' => $userId, - 'user_text' => $userText, - 'timestamp' => $this->timestamp, - 'minor_edit' => $this->minor, - ) ); - $revId = $revision->insertOn( $dbw ); - $changed = $article->updateIfNewerOn( $dbw, $revision ); - - if( $created ) { - wfDebug( __METHOD__ . ": running onArticleCreate\n" ); - Article::onArticleCreate( $this->title ); - - wfDebug( __METHOD__ . ": running create updates\n" ); - $article->createUpdates( $revision ); - - } elseif( $changed ) { - wfDebug( __METHOD__ . ": running onArticleEdit\n" ); - Article::onArticleEdit( $this->title ); - - wfDebug( __METHOD__ . ": running edit updates\n" ); - $article->editUpdates( - $this->getText(), - $this->getComment(), - $this->minor, - $this->timestamp, - $revId ); - } - - return true; - } - - function importUpload() { - wfDebug( __METHOD__ . ": STUB\n" ); - - /** - // from file revert... - $source = $this->file->getArchiveVirtualUrl( $this->oldimage ); - $comment = $wgRequest->getText( 'wpComment' ); - // TODO: Preserve file properties from database instead of reloading from file - $status = $this->file->upload( $source, $comment, $comment ); - if( $status->isGood() ) { - */ - - /** - // from file upload... - $this->mLocalFile = wfLocalFile( $nt ); - $this->mDestName = $this->mLocalFile->getName(); - //.... - $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText, - File::DELETE_SOURCE, $this->mFileProps ); - if ( !$status->isGood() ) { - $resultDetails = array( 'internal' => $status->getWikiText() ); - */ - - // @fixme upload() uses $wgUser, which is wrong here - // it may also create a page without our desire, also wrong potentially. - // and, it will record a *current* upload, but we might want an archive version here - - $file = wfLocalFile( $this->getTitle() ); - if( !$file ) { - var_dump( $file ); - wfDebug( "IMPORT: Bad file. :(\n" ); - return false; - } - - $source = $this->downloadSource(); - if( !$source ) { - wfDebug( "IMPORT: Could not fetch remote file. :(\n" ); - return false; - } - - $status = $file->upload( $source, - $this->getComment(), - $this->getComment(), // Initial page, if none present... - File::DELETE_SOURCE, - false, // props... - $this->getTimestamp() ); - - if( $status->isGood() ) { - // yay? - wfDebug( "IMPORT: is ok?\n" ); - return true; - } - - wfDebug( "IMPORT: is bad? " . $status->getXml() . "\n" ); - return false; - - } - - function downloadSource() { - global $wgEnableUploads; - if( !$wgEnableUploads ) { - return false; - } - - $tempo = tempnam( wfTempDir(), 'download' ); - $f = fopen( $tempo, 'wb' ); - if( !$f ) { - wfDebug( "IMPORT: couldn't write to temp file $tempo\n" ); - return false; - } - - // @fixme! - $src = $this->getSrc(); - $data = Http::get( $src ); - if( !$data ) { - wfDebug( "IMPORT: couldn't fetch source $src\n" ); - fclose( $f ); - unlink( $tempo ); - return false; - } - - fwrite( $f, $data ); - fclose( $f ); - - return $tempo; - } - -} - -/** - * implements Special:Import - * @ingroup SpecialPage - */ -class WikiImporter { - var $mDebug = false; - var $mSource = null; - var $mPageCallback = null; - var $mPageOutCallback = null; - var $mRevisionCallback = null; - var $mUploadCallback = null; - var $mTargetNamespace = null; - var $lastfield; - var $tagStack = array(); - - function __construct( $source ) { - $this->setRevisionCallback( array( $this, "importRevision" ) ); - $this->setUploadCallback( array( $this, "importUpload" ) ); - $this->mSource = $source; - } - - function throwXmlError( $err ) { - $this->debug( "FAILURE: $err" ); - wfDebug( "WikiImporter XML error: $err\n" ); - } - - # -------------- - - function doImport() { - if( empty( $this->mSource ) ) { - return new WikiErrorMsg( "importnotext" ); - } - - $parser = xml_parser_create( "UTF-8" ); - - # case folding violates XML standard, turn it off - xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); - - xml_set_object( $parser, $this ); - xml_set_element_handler( $parser, "in_start", "" ); - - $offset = 0; // for context extraction on error reporting - do { - $chunk = $this->mSource->readChunk(); - if( !xml_parse( $parser, $chunk, $this->mSource->atEnd() ) ) { - wfDebug( "WikiImporter::doImport encountered XML parsing error\n" ); - return new WikiXmlError( $parser, wfMsgHtml( 'import-parse-failure' ), $chunk, $offset ); - } - $offset += strlen( $chunk ); - } while( $chunk !== false && !$this->mSource->atEnd() ); - xml_parser_free( $parser ); - - return true; - } - - function debug( $data ) { - if( $this->mDebug ) { - wfDebug( "IMPORT: $data\n" ); - } - } - - function notice( $data ) { - global $wgCommandLineMode; - if( $wgCommandLineMode ) { - print "$data\n"; - } else { - global $wgOut; - $wgOut->addHTML( "<li>" . htmlspecialchars( $data ) . "</li>\n" ); - } - } - - /** - * Set debug mode... - */ - function setDebug( $debug ) { - $this->mDebug = $debug; - } - - /** - * Sets the action to perform as each new page in the stream is reached. - * @param $callback callback - * @return callback - */ - function setPageCallback( $callback ) { - $previous = $this->mPageCallback; - $this->mPageCallback = $callback; - return $previous; - } - - /** - * Sets the action to perform as each page in the stream is completed. - * Callback accepts the page title (as a Title object), a second object - * with the original title form (in case it's been overridden into a - * local namespace), and a count of revisions. - * - * @param $callback callback - * @return callback - */ - function setPageOutCallback( $callback ) { - $previous = $this->mPageOutCallback; - $this->mPageOutCallback = $callback; - return $previous; - } - - /** - * Sets the action to perform as each page revision is reached. - * @param $callback callback - * @return callback - */ - function setRevisionCallback( $callback ) { - $previous = $this->mRevisionCallback; - $this->mRevisionCallback = $callback; - return $previous; - } - - /** - * Sets the action to perform as each file upload version is reached. - * @param $callback callback - * @return callback - */ - function setUploadCallback( $callback ) { - $previous = $this->mUploadCallback; - $this->mUploadCallback = $callback; - return $previous; - } - - /** - * Set a target namespace to override the defaults - */ - function setTargetNamespace( $namespace ) { - if( is_null( $namespace ) ) { - // Don't override namespaces - $this->mTargetNamespace = null; - } elseif( $namespace >= 0 ) { - // FIXME: Check for validity - $this->mTargetNamespace = intval( $namespace ); - } else { - return false; - } - } - - /** - * Default per-revision callback, performs the import. - * @param $revision WikiRevision - * @private - */ - function importRevision( $revision ) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->deadlockLoop( array( $revision, 'importOldRevision' ) ); - } - - /** - * Dummy for now... - */ - function importUpload( $revision ) { - //$dbw = wfGetDB( DB_MASTER ); - //return $dbw->deadlockLoop( array( $revision, 'importUpload' ) ); - return false; - } - - /** - * Alternate per-revision callback, for debugging. - * @param $revision WikiRevision - * @private - */ - function debugRevisionHandler( &$revision ) { - $this->debug( "Got revision:" ); - if( is_object( $revision->title ) ) { - $this->debug( "-- Title: " . $revision->title->getPrefixedText() ); - } else { - $this->debug( "-- Title: <invalid>" ); - } - $this->debug( "-- User: " . $revision->user_text ); - $this->debug( "-- Timestamp: " . $revision->timestamp ); - $this->debug( "-- Comment: " . $revision->comment ); - $this->debug( "-- Text: " . $revision->text ); - } - - /** - * Notify the callback function when a new <page> is reached. - * @param $title Title - * @private - */ - function pageCallback( $title ) { - if( is_callable( $this->mPageCallback ) ) { - call_user_func( $this->mPageCallback, $title ); - } - } - - /** - * Notify the callback function when a </page> is closed. - * @param $title Title - * @param $origTitle Title - * @param $revisionCount int - * @param $successCount Int: number of revisions for which callback returned true - * @private - */ - function pageOutCallback( $title, $origTitle, $revisionCount, $successCount ) { - if( is_callable( $this->mPageOutCallback ) ) { - call_user_func( $this->mPageOutCallback, $title, $origTitle, - $revisionCount, $successCount ); - } - } - - - # XML parser callbacks from here out -- beware! - function donothing( $parser, $x, $y="" ) { - #$this->debug( "donothing" ); - } - - function in_start( $parser, $name, $attribs ) { - $this->debug( "in_start $name" ); - if( $name != "mediawiki" ) { - return $this->throwXMLerror( "Expected <mediawiki>, got <$name>" ); - } - xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" ); - } - - function in_mediawiki( $parser, $name, $attribs ) { - $this->debug( "in_mediawiki $name" ); - if( $name == 'siteinfo' ) { - xml_set_element_handler( $parser, "in_siteinfo", "out_siteinfo" ); - } elseif( $name == 'page' ) { - $this->push( $name ); - $this->workRevisionCount = 0; - $this->workSuccessCount = 0; - $this->uploadCount = 0; - $this->uploadSuccessCount = 0; - xml_set_element_handler( $parser, "in_page", "out_page" ); - } else { - return $this->throwXMLerror( "Expected <page>, got <$name>" ); - } - } - function out_mediawiki( $parser, $name ) { - $this->debug( "out_mediawiki $name" ); - if( $name != "mediawiki" ) { - return $this->throwXMLerror( "Expected </mediawiki>, got </$name>" ); - } - xml_set_element_handler( $parser, "donothing", "donothing" ); - } - - - function in_siteinfo( $parser, $name, $attribs ) { - // no-ops for now - $this->debug( "in_siteinfo $name" ); - switch( $name ) { - case "sitename": - case "base": - case "generator": - case "case": - case "namespaces": - case "namespace": - break; - default: - return $this->throwXMLerror( "Element <$name> not allowed in <siteinfo>." ); - } - } - - function out_siteinfo( $parser, $name ) { - if( $name == "siteinfo" ) { - xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" ); - } - } - - - function in_page( $parser, $name, $attribs ) { - $this->debug( "in_page $name" ); - switch( $name ) { - case "id": - case "title": - case "restrictions": - $this->appendfield = $name; - $this->appenddata = ""; - xml_set_element_handler( $parser, "in_nothing", "out_append" ); - xml_set_character_data_handler( $parser, "char_append" ); - break; - case "revision": - $this->push( "revision" ); - if( is_object( $this->pageTitle ) ) { - $this->workRevision = new WikiRevision; - $this->workRevision->setTitle( $this->pageTitle ); - $this->workRevisionCount++; - } else { - // Skipping items due to invalid page title - $this->workRevision = null; - } - xml_set_element_handler( $parser, "in_revision", "out_revision" ); - break; - case "upload": - $this->push( "upload" ); - if( is_object( $this->pageTitle ) ) { - $this->workRevision = new WikiRevision; - $this->workRevision->setTitle( $this->pageTitle ); - $this->uploadCount++; - } else { - // Skipping items due to invalid page title - $this->workRevision = null; - } - xml_set_element_handler( $parser, "in_upload", "out_upload" ); - break; - default: - return $this->throwXMLerror( "Element <$name> not allowed in a <page>." ); - } - } - - function out_page( $parser, $name ) { - $this->debug( "out_page $name" ); - $this->pop(); - if( $name != "page" ) { - return $this->throwXMLerror( "Expected </page>, got </$name>" ); - } - xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" ); - - $this->pageOutCallback( $this->pageTitle, $this->origTitle, - $this->workRevisionCount, $this->workSuccessCount ); - - $this->workTitle = null; - $this->workRevision = null; - $this->workRevisionCount = 0; - $this->workSuccessCount = 0; - $this->pageTitle = null; - $this->origTitle = null; - } - - function in_nothing( $parser, $name, $attribs ) { - $this->debug( "in_nothing $name" ); - return $this->throwXMLerror( "No child elements allowed here; got <$name>" ); - } - function char_append( $parser, $data ) { - $this->debug( "char_append '$data'" ); - $this->appenddata .= $data; - } - function out_append( $parser, $name ) { - $this->debug( "out_append $name" ); - if( $name != $this->appendfield ) { - return $this->throwXMLerror( "Expected </{$this->appendfield}>, got </$name>" ); - } - - switch( $this->appendfield ) { - case "title": - $this->workTitle = $this->appenddata; - $this->origTitle = Title::newFromText( $this->workTitle ); - if( !is_null( $this->mTargetNamespace ) && !is_null( $this->origTitle ) ) { - $this->pageTitle = Title::makeTitle( $this->mTargetNamespace, - $this->origTitle->getDBkey() ); - } else { - $this->pageTitle = Title::newFromText( $this->workTitle ); - } - if( is_null( $this->pageTitle ) ) { - // Invalid page title? Ignore the page - $this->notice( "Skipping invalid page title '$this->workTitle'" ); - } else { - $this->pageCallback( $this->workTitle ); - } - break; - case "id": - if ( $this->parentTag() == 'revision' ) { - if( $this->workRevision ) - $this->workRevision->setID( $this->appenddata ); - } - break; - case "text": - if( $this->workRevision ) - $this->workRevision->setText( $this->appenddata ); - break; - case "username": - if( $this->workRevision ) - $this->workRevision->setUsername( $this->appenddata ); - break; - case "ip": - if( $this->workRevision ) - $this->workRevision->setUserIP( $this->appenddata ); - break; - case "timestamp": - if( $this->workRevision ) - $this->workRevision->setTimestamp( $this->appenddata ); - break; - case "comment": - if( $this->workRevision ) - $this->workRevision->setComment( $this->appenddata ); - break; - case "minor": - if( $this->workRevision ) - $this->workRevision->setMinor( true ); - break; - case "filename": - if( $this->workRevision ) - $this->workRevision->setFilename( $this->appenddata ); - break; - case "src": - if( $this->workRevision ) - $this->workRevision->setSrc( $this->appenddata ); - break; - case "size": - if( $this->workRevision ) - $this->workRevision->setSize( intval( $this->appenddata ) ); - break; - default: - $this->debug( "Bad append: {$this->appendfield}" ); - } - $this->appendfield = ""; - $this->appenddata = ""; - - $parent = $this->parentTag(); - xml_set_element_handler( $parser, "in_$parent", "out_$parent" ); - xml_set_character_data_handler( $parser, "donothing" ); - } - - function in_revision( $parser, $name, $attribs ) { - $this->debug( "in_revision $name" ); - switch( $name ) { - case "id": - case "timestamp": - case "comment": - case "minor": - case "text": - $this->appendfield = $name; - xml_set_element_handler( $parser, "in_nothing", "out_append" ); - xml_set_character_data_handler( $parser, "char_append" ); - break; - case "contributor": - $this->push( "contributor" ); - xml_set_element_handler( $parser, "in_contributor", "out_contributor" ); - break; - default: - return $this->throwXMLerror( "Element <$name> not allowed in a <revision>." ); - } - } - - function out_revision( $parser, $name ) { - $this->debug( "out_revision $name" ); - $this->pop(); - if( $name != "revision" ) { - return $this->throwXMLerror( "Expected </revision>, got </$name>" ); - } - xml_set_element_handler( $parser, "in_page", "out_page" ); - - if( $this->workRevision ) { - $ok = call_user_func_array( $this->mRevisionCallback, - array( $this->workRevision, $this ) ); - if( $ok ) { - $this->workSuccessCount++; - } - } - } - - function in_upload( $parser, $name, $attribs ) { - $this->debug( "in_upload $name" ); - switch( $name ) { - case "timestamp": - case "comment": - case "text": - case "filename": - case "src": - case "size": - $this->appendfield = $name; - xml_set_element_handler( $parser, "in_nothing", "out_append" ); - xml_set_character_data_handler( $parser, "char_append" ); - break; - case "contributor": - $this->push( "contributor" ); - xml_set_element_handler( $parser, "in_contributor", "out_contributor" ); - break; - default: - return $this->throwXMLerror( "Element <$name> not allowed in an <upload>." ); - } - } - - function out_upload( $parser, $name ) { - $this->debug( "out_revision $name" ); - $this->pop(); - if( $name != "upload" ) { - return $this->throwXMLerror( "Expected </upload>, got </$name>" ); - } - xml_set_element_handler( $parser, "in_page", "out_page" ); - - if( $this->workRevision ) { - $ok = call_user_func_array( $this->mUploadCallback, - array( $this->workRevision, $this ) ); - if( $ok ) { - $this->workUploadSuccessCount++; - } - } - } - - function in_contributor( $parser, $name, $attribs ) { - $this->debug( "in_contributor $name" ); - switch( $name ) { - case "username": - case "ip": - case "id": - $this->appendfield = $name; - xml_set_element_handler( $parser, "in_nothing", "out_append" ); - xml_set_character_data_handler( $parser, "char_append" ); - break; - default: - $this->throwXMLerror( "Invalid tag <$name> in <contributor>" ); - } - } - - function out_contributor( $parser, $name ) { - $this->debug( "out_contributor $name" ); - $this->pop(); - if( $name != "contributor" ) { - return $this->throwXMLerror( "Expected </contributor>, got </$name>" ); - } - $parent = $this->parentTag(); - xml_set_element_handler( $parser, "in_$parent", "out_$parent" ); - } - - private function push( $name ) { - array_push( $this->tagStack, $name ); - $this->debug( "PUSH $name" ); - } - - private function pop() { - $name = array_pop( $this->tagStack ); - $this->debug( "POP $name" ); - return $name; - } - - private function parentTag() { - $name = $this->tagStack[count( $this->tagStack ) - 1]; - $this->debug( "PARENT $name" ); - return $name; - } - -} - -/** - * @todo document (e.g. one-sentence class description). - * @ingroup SpecialPage - */ -class ImportStringSource { - function __construct( $string ) { - $this->mString = $string; - $this->mRead = false; - } - - function atEnd() { - return $this->mRead; - } - - function readChunk() { - if( $this->atEnd() ) { - return false; - } else { - $this->mRead = true; - return $this->mString; - } - } -} - -/** - * @todo document (e.g. one-sentence class description). - * @ingroup SpecialPage - */ -class ImportStreamSource { - function __construct( $handle ) { - $this->mHandle = $handle; - } - - function atEnd() { - return feof( $this->mHandle ); - } - - function readChunk() { - return fread( $this->mHandle, 32768 ); - } - - static function newFromFile( $filename ) { - $file = @fopen( $filename, 'rt' ); - if( !$file ) { - return new WikiErrorMsg( "importcantopen" ); - } - return new ImportStreamSource( $file ); - } - - static function newFromUpload( $fieldname = "xmlimport" ) { - $upload =& $_FILES[$fieldname]; - - if( !isset( $upload ) || !$upload['name'] ) { - return new WikiErrorMsg( 'importnofile' ); - } - if( !empty( $upload['error'] ) ) { - switch($upload['error']){ - case 1: # The uploaded file exceeds the upload_max_filesize directive in php.ini. - return new WikiErrorMsg( 'importuploaderrorsize' ); - case 2: # The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form. - return new WikiErrorMsg( 'importuploaderrorsize' ); - case 3: # The uploaded file was only partially uploaded - return new WikiErrorMsg( 'importuploaderrorpartial' ); - case 6: #Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3. - return new WikiErrorMsg( 'importuploaderrortemp' ); - # case else: # Currently impossible - } - - } - $fname = $upload['tmp_name']; - if( is_uploaded_file( $fname ) ) { - return ImportStreamSource::newFromFile( $fname ); - } else { - return new WikiErrorMsg( 'importnofile' ); - } - } - - static function newFromURL( $url, $method = 'GET' ) { - wfDebug( __METHOD__ . ": opening $url\n" ); - # Use the standard HTTP fetch function; it times out - # quicker and sorts out user-agent problems which might - # otherwise prevent importing from large sites, such - # as the Wikimedia cluster, etc. - $data = Http::request( $method, $url ); - if( $data !== false ) { - $file = tmpfile(); - fwrite( $file, $data ); - fflush( $file ); - fseek( $file, 0 ); - return new ImportStreamSource( $file ); - } else { - return new WikiErrorMsg( 'importcantopen' ); - } - } - - public static function newFromInterwiki( $interwiki, $page, $history=false ) { - if( $page == '' ) { - return new WikiErrorMsg( 'import-noarticle' ); - } - $link = Title::newFromText( "$interwiki:Special:Export/$page" ); - if( is_null( $link ) || $link->getInterwiki() == '' ) { - return new WikiErrorMsg( 'importbadinterwiki' ); - } else { - $params = $history ? 'history=1' : ''; - $url = $link->getFullUrl( $params ); - # For interwikis, use POST to avoid redirects. - return ImportStreamSource::newFromURL( $url, "POST" ); - } - } -} diff --git a/includes/specials/SpecialIpblocklist.php b/includes/specials/SpecialIpblocklist.php index 696c7efe..8d573547 100644 --- a/includes/specials/SpecialIpblocklist.php +++ b/includes/specials/SpecialIpblocklist.php @@ -10,7 +10,7 @@ function wfSpecialIpblocklist() { global $wgUser, $wgOut, $wgRequest; - $ip = $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) ); + $ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) ) ); $id = $wgRequest->getVal( 'id' ); $reason = $wgRequest->getText( 'wpUnblockReason' ); $action = $wgRequest->getText( 'action' ); @@ -71,9 +71,13 @@ class IPUnblockForm { var $ip, $reason, $id; function IPUnblockForm( $ip, $id, $reason ) { + global $wgRequest; $this->ip = strtr( $ip, '_', ' ' ); $this->id = $id; $this->reason = $reason; + $this->hideuserblocks = $wgRequest->getBool( 'hideuserblocks' ); + $this->hidetempblocks = $wgRequest->getBool( 'hidetempblocks' ); + $this->hideaddressblocks = $wgRequest->getBool( 'hideaddressblocks' ); } /** @@ -158,8 +162,7 @@ class IPUnblockForm { * @return array array(message key, parameters) on failure, empty array on success */ - static function doUnblock(&$id, &$ip, &$reason, &$range = null) - { + static function doUnblock(&$id, &$ip, &$reason, &$range = null) { if ( $id ) { $block = Block::newFromID( $id ); if ( !$block ) { @@ -241,10 +244,27 @@ class IPUnblockForm { // No extra conditions } elseif ( substr( $this->ip, 0, 1 ) == '#' ) { $conds['ipb_id'] = substr( $this->ip, 1 ); - } elseif ( IP::toUnsigned( $this->ip ) !== false ) { - $conds['ipb_address'] = $this->ip; + // Single IPs + } elseif ( IP::isIPAddress($this->ip) && strpos($this->ip,'/') === false ) { + if( $iaddr = IP::toHex($this->ip) ) { + # Only scan ranges which start in this /16, this improves search speed + # Blocks should not cross a /16 boundary. + $range = substr( $iaddr, 0, 4 ); + // Fixme -- encapsulate this sort of query-building. + $dbr = wfGetDB( DB_SLAVE ); + $encIp = $dbr->addQuotes( IP::sanitizeIP($this->ip) ); + $encRange = $dbr->addQuotes( "$range%" ); + $encAddr = $dbr->addQuotes( $iaddr ); + $conds[] = "(ipb_address = $encIp) OR + (ipb_range_start LIKE $encRange AND + ipb_range_start <= $encAddr + AND ipb_range_end >= $encAddr)"; + } else { + $conds['ipb_address'] = IP::sanitizeIP($this->ip); + } $conds['ipb_auto'] = 0; - } elseif( preg_match( '/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\\/(\\d{1,2})$/', $this->ip, $matches ) ) { + // IP range + } elseif ( IP::isIPAddress($this->ip) ) { $conds['ipb_address'] = Block::normaliseRange( $this->ip ); $conds['ipb_auto'] = 0; } else { @@ -257,6 +277,16 @@ class IPUnblockForm { $conds['ipb_auto'] = 0; } } + // Apply filters + if( $this->hideuserblocks ) { + $conds['ipb_user'] = 0; + } + if( $this->hidetempblocks ) { + $conds['ipb_expiry'] = 'infinity'; + } + if( $this->hideaddressblocks ) { + $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start"; + } $pager = new IPBlocklistPager( $this, $conds ); if ( $pager->getNumRows() ) { @@ -270,12 +300,38 @@ class IPUnblockForm { $wgOut->addHTML( $this->searchForm() ); $wgOut->addWikiMsg( 'ipblocklist-no-results' ); } else { + $wgOut->addHTML( $this->searchForm() ); $wgOut->addWikiMsg( 'ipblocklist-empty' ); } } function searchForm() { global $wgTitle, $wgScript, $wgRequest; + + $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) ); + $nondefaults = array(); + if( $this->hideuserblocks ) { + $nondefaults['hideuserblocks'] = $this->hideuserblocks; + } + if( $this->hidetempblocks ) { + $nondefaults['hidetempblocks'] = $this->hidetempblocks; + } + if( $this->hideaddressblocks ) { + $nondefaults['hideaddressblocks'] = $this->hideaddressblocks; + } + $ubLink = $this->makeOptionsLink( $showhide[1-$this->hideuserblocks], + array( 'hideuserblocks' => 1-$this->hideuserblocks ), $nondefaults); + $tbLink = $this->makeOptionsLink( $showhide[1-$this->hidetempblocks], + array( 'hidetempblocks' => 1-$this->hidetempblocks ), $nondefaults); + $sipbLink = $this->makeOptionsLink( $showhide[1-$this->hideaddressblocks], + array( 'hideaddressblocks' => 1-$this->hideaddressblocks ), $nondefaults); + + $links = array(); + $links[] = wfMsgHtml( 'ipblocklist-sh-userblocks', $ubLink ); + $links[] = wfMsgHtml( 'ipblocklist-sh-tempblocks', $tbLink ); + $links[] = wfMsgHtml( 'ipblocklist-sh-addressblocks', $sipbLink ); + $hl = implode( ' ' . wfMsg( 'pipe-separator' ) . ' ', $links ); + return Xml::tags( 'form', array( 'action' => $wgScript ), Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) . @@ -283,16 +339,32 @@ class IPUnblockForm { Xml::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) . Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) . ' ' . - Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . + Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . '<br />' . + $hl . Xml::closeElement( 'fieldset' ) ); } /** + * Makes change an option link which carries all the other options + * @param $title see Title + * @param $override + * @param $options + */ + function makeOptionsLink( $title, $override, $options, $active = false ) { + global $wgUser; + $sk = $wgUser->getSkin(); + $params = $override + $options; + $ipblocklist = SpecialPage::getTitleFor( 'IPBlockList' ); + return $sk->link( $ipblocklist, htmlspecialchars( $title ), + ( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) ); + } + + /** * Callback function to output a block */ function formatRow( $block ) { - global $wgUser, $wgLang; + global $wgUser, $wgLang, $wgBlockAllowsUTEdit; wfProfileIn( __METHOD__ ); @@ -302,8 +374,8 @@ class IPUnblockForm { $sk = $wgUser->getSkin(); if( is_null( $msg ) ) { $msg = array(); - $keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', - 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock' ); + $keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', 'change-blocklink', + 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock', 'blocklist-nousertalk' ); foreach( $keys as $key ) { $msg[$key] = wfMsgHtml( $key ); } @@ -341,15 +413,33 @@ class IPUnblockForm { if ( $block->mBlockEmail && $block->mUser ) { $properties[] = $msg['emailblock']; } + + if ( !$block->mAllowUsertalk && $wgBlockAllowsUTEdit ) { + $properties[] = $msg['blocklist-nousertalk']; + } $properties = implode( ', ', $properties ); $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) ); $unblocklink = ''; - if ( $wgUser->isAllowed('block') ) { - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $unblocklink = ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')'; + $changeblocklink = ''; + $toolLinks = ''; + if ( $wgUser->isAllowed( 'block' ) ) { + $unblocklink = $sk->link( SpecialPage::getTitleFor( 'Ipblocklist' ), + $msg['unblocklink'], + array(), + array( 'action' => 'unblock', 'id' => $block->mId ), + 'known' ); + + # Create changeblocklink for all blocks with exception of autoblocks + if( !$block->mAuto ) { + $changeblocklink = ' ' . wfMsg( 'pipe-separator' ) . ' ' . + $sk->link( SpecialPage::getTitleFor( 'Blockip', $block->mAddress ), + $msg['change-blocklink'], + array(), array(), 'known' ); + } + $toolLinks = "($unblocklink$changeblocklink)"; } $comment = $sk->commentBlock( $block->mReason ); @@ -359,7 +449,7 @@ class IPUnblockForm { $s = '<span class="history-deleted">' . $s . '</span>'; wfProfileOut( __METHOD__ ); - return "<li>$s $unblocklink</li>\n"; + return "<li>$s $toolLinks</li>\n"; } } diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php new file mode 100644 index 00000000..6b9df58f --- /dev/null +++ b/includes/specials/SpecialLinkSearch.php @@ -0,0 +1,185 @@ +<?php +/** + * @file + * @ingroup SpecialPage + * + * @author Brion Vibber + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ + +/** + * Special:LinkSearch to search the external-links table. + * @ingroup SpecialPage + */ + +function wfSpecialLinkSearch( $par ) { + + list( $limit, $offset ) = wfCheckLimits(); + global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode; + $target = $GLOBALS['wgRequest']->getVal( 'target', $par ); + $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null ); + + $protocols_list[] = ''; + foreach( $wgUrlProtocols as $prot ) { + $protocols_list[] = $prot; + } + + $target2 = $target; + $protocol = ''; + $pr_sl = strpos($target2, '//' ); + $pr_cl = strpos($target2, ':' ); + if ( $pr_sl ) { + // For protocols with '//' + $protocol = substr( $target2, 0 , $pr_sl+2 ); + $target2 = substr( $target2, $pr_sl+2 ); + } elseif ( !$pr_sl && $pr_cl ) { + // For protocols without '//' like 'mailto:' + $protocol = substr( $target2, 0 , $pr_cl+1 ); + $target2 = substr( $target2, $pr_cl+1 ); + } elseif ( $protocol == '' && $target2 != '' ) { + // default + $protocol = 'http://'; + } + if ( !in_array( $protocol, $protocols_list ) ) { + // unsupported protocol, show original search request + $target2 = $target; + $protocol = ''; + } + + $self = Title::makeTitle( NS_SPECIAL, 'Linksearch' ); + + $wgOut->addWikiText( wfMsg( 'linksearch-text', '<nowiki>' . implode( ', ', $wgUrlProtocols) . '</nowiki>' ) ); + $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) . + Xml::hidden( 'title', $self->getPrefixedDbKey() ) . + '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) . + Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' '; + if ( !$wgMiserMode ) { + $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' . + XML::namespaceSelector( $namespace, '' ); + } + $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) . + '</fieldset>' . + Xml::closeElement( 'form' ); + $wgOut->addHTML( $s ); + + if( $target != '' ) { + $searcher = new LinkSearchPage; + $searcher->setParams( array( + 'query' => $target2, + 'namespace' => $namespace, + 'protocol' => $protocol ) ); + $searcher->doQuery( $offset, $limit ); + } +} + +class LinkSearchPage extends QueryPage { + function setParams( $params ) { + $this->mQuery = $params['query']; + $this->mNs = $params['namespace']; + $this->mProt = $params['protocol']; + } + + function getName() { + return 'LinkSearch'; + } + + /** + * Disable RSS/Atom feeds + */ + function isSyndicated() { + return false; + } + + /** + * Return an appropriately formatted LIKE query and the clause + */ + static function mungeQuery( $query , $prot ) { + $field = 'el_index'; + $rv = LinkFilter::makeLike( $query , $prot ); + if ($rv === false) { + //makeLike doesn't handle wildcard in IP, so we'll have to munge here. + if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) { + $rv = $prot . rtrim($query, " \t*") . '%'; + $field = 'el_to'; + } + } + return array( $rv, $field ); + } + + function linkParameters() { + global $wgMiserMode; + $params = array(); + $params['target'] = $this->mProt . $this->mQuery; + if( isset( $this->mNs ) && !$wgMiserMode ) { + $params['namespace'] = $this->mNs; + } + return $params; + } + + function getSQL() { + global $wgMiserMode; + $dbr = wfGetDB( DB_SLAVE ); + $page = $dbr->tableName( 'page' ); + $externallinks = $dbr->tableName( 'externallinks' ); + + /* strip everything past first wildcard, so that index-based-only lookup would be done */ + list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt ); + $stripped = substr($munged,0,strpos($munged,'%')+1); + $encSearch = $dbr->addQuotes( $stripped ); + + $encSQL = ''; + if ( isset ($this->mNs) && !$wgMiserMode ) + $encSQL = 'AND page_namespace=' . $dbr->addQuotes( $this->mNs ); + + $use_index = $dbr->useIndexClause( $clause ); + return + "SELECT + page_namespace AS namespace, + page_title AS title, + el_index AS value, + el_to AS url + FROM + $page, + $externallinks $use_index + WHERE + page_id=el_from + AND $clause LIKE $encSearch + $encSQL"; + } + + function formatResult( $skin, $result ) { + $title = Title::makeTitle( $result->namespace, $result->title ); + $url = $result->url; + $pageLink = $skin->makeKnownLinkObj( $title ); + $urlLink = $skin->makeExternalLink( $url, $url ); + + return wfMsgHtml( 'linksearch-line', $urlLink, $pageLink ); + } + + /** + * Override to check query validity. + */ + function doQuery( $offset, $limit, $shownavigation=true ) { + global $wgOut; + list( $this->mMungedQuery, $clause ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt ); + if( $this->mMungedQuery === false ) { + $wgOut->addWikiText( wfMsg( 'linksearch-error' ) ); + } else { + // For debugging + // Generates invalid xhtml with patterns that contain -- + //$wgOut->addHTML( "\n<!-- " . htmlspecialchars( $this->mMungedQuery ) . " -->\n" ); + parent::doQuery( $offset, $limit, $shownavigation ); + } + } + + /** + * Override to squash the ORDER BY. + * We do a truncated index search, so the optimizer won't trust + * it as good enough for optimizing sort. The implicit ordering + * from the scan will usually do well enough for our needs. + */ + function getOrder() { + return ''; + } +} diff --git a/includes/specials/SpecialListUserRestrictions.php b/includes/specials/SpecialListUserRestrictions.php new file mode 100644 index 00000000..27b24298 --- /dev/null +++ b/includes/specials/SpecialListUserRestrictions.php @@ -0,0 +1,161 @@ +<?php + +function wfSpecialListUserRestrictions() { + global $wgOut, $wgRequest; + + $wgOut->addWikiMsg( 'listuserrestrictions-intro' ); + $f = new SpecialListUserRestrictionsForm(); + $wgOut->addHTML( $f->getHTML() ); + + if( !mt_rand( 0, 10 ) ) + UserRestriction::purgeExpired(); + $pager = new UserRestrictionsPager( $f->getConds() ); + if( $pager->getNumRows() ) + $wgOut->addHTML( $pager->getNavigationBar() . + Xml::tags( 'ul', null, $pager->getBody() ) . + $pager->getNavigationBar() + ); + elseif( $f->getConds() ) + $wgOut->addWikiMsg( 'listuserrestrictions-notfound' ); + else + $wgOut->addWikiMsg( 'listuserrestrictions-empty' ); +} + +class SpecialListUserRestrictionsForm { + public function getHTML() { + global $wgRequest, $wgScript, $wgTitle; + $s = ''; + $s .= Xml::fieldset( wfMsg( 'listuserrestrictions-legend' ) ); + $s .= "<form action=\"{$wgScript}\">"; + $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ); + $s .= Xml::label( wfMsgHtml( 'listuserrestrictions-type' ), 'type' ) . ' ' . + self::typeSelector( 'type', $wgRequest->getVal( 'type' ), 'type' ); + $s .= ' '; + $s .= Xml::inputLabel( wfMsgHtml( 'listuserrestrictions-user' ), 'user', 'user', + false, $wgRequest->getVal( 'user' ) ); + $s .= '<p>'; + $s .= Xml::label( wfMsgHtml( 'listuserrestrictions-namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $wgRequest->getVal( 'namespace' ), '', 'namespace' ); + $s .= ' '; + $s .= Xml::inputLabel( wfMsgHtml( 'listuserrestrictions-page' ), 'page', 'page', + false, $wgRequest->getVal( 'page' ) ); + $s .= Xml::submitButton( wfMsg( 'listuserrestrictions-submit' ) ); + $s .= "</p></form></fieldset>"; + return $s; + } + + public static function typeSelector( $name = 'type', $value = '', $id = false ) { + $s = new XmlSelect( $name, $id, $value ); + $s->addOption( wfMsg( 'userrestrictiontype-none' ), '' ); + $s->addOption( wfMsg( 'userrestrictiontype-page' ), UserRestriction::PAGE ); + $s->addOption( wfMsg( 'userrestrictiontype-namespace' ), UserRestriction::NAMESPACE ); + return $s->getHTML(); + } + + public function getConds() { + global $wgRequest; + $conds = array(); + + $type = $wgRequest->getVal( 'type' ); + if( in_array( $type, array( UserRestriction::PAGE, UserRestriction::NAMESPACE ) ) ) + $conds['ur_type'] = $type; + + $user = $wgRequest->getVal( 'user' ); + if( $user ) + $conds['ur_user_text'] = $user; + + $namespace = $wgRequest->getVal( 'namespace' ); + if( $namespace || $namespace === '0' ) + $conds['ur_namespace'] = $namespace; + + $page = $wgRequest->getVal( 'page' ); + $title = Title::newFromText( $page ); + if( $title ) { + $conds['ur_page_namespace'] = $title->getNamespace(); + $conds['ur_page_title'] = $title->getDBKey(); + } + + return $conds; + } +} + +class UserRestrictionsPager extends ReverseChronologicalPager { + public $mConds; + + public function __construct( $conds = array() ) { + $this->mConds = $conds; + parent::__construct(); + } + + public function getStartBody() { + # Copied from Special:Ipblocklist + wfProfileIn( __METHOD__ ); + # Do a link batch query + $this->mResult->seek( 0 ); + $lb = new LinkBatch; + + # Faster way + # Usernames and titles are in fact related by a simple substitution of space -> underscore + # The last few lines of Title::secureAndSplit() tell the story. + foreach( $this->mResult as $row ) { + $name = str_replace( ' ', '_', $row->ur_by_text ); + $lb->add( NS_USER, $name ); + $lb->add( NS_USER_TALK, $name ); + $name = str_replace( ' ', '_', $row->ur_user_text ); + $lb->add( NS_USER, $name ); + $lb->add( NS_USER_TALK, $name ); + if( $row->ur_type == UserRestriction::PAGE ) + $lb->add( $row->ur_page_namespace, $row->ur_page_title ); + } + $lb->execute(); + wfProfileOut( __METHOD__ ); + return ''; + } + + public function getQueryInfo() { + return array( + 'tables' => 'user_restrictions', + 'fields' => '*', + 'conds' => $this->mConds, + ); + } + + public function formatRow( $row ) { + return self::formatRestriction( UserRestriction::newFromRow( $row ) ); + } + + // Split off for use on Special:RestrictUser + public static function formatRestriction( $r ) { + global $wgUser, $wgLang; + $sk = $wgUser->getSkin(); + $timestamp = $wgLang->timeanddate( $r->getTimestamp(), true ); + $blockerlink = $sk->userLink( $r->getBlockerId(), $r->getBlockerText() ) . + $sk->userToolLinks( $r->getBlockerId(), $r->getBlockerText() ); + $subjlink = $sk->userLink( $r->getSubjectId(), $r->getSubjectText() ) . + $sk->userToolLinks( $r->getSubjectId(), $r->getSubjectText() ); + $expiry = is_numeric( $r->getExpiry() ) ? + wfMsg( 'listuserrestrictions-row-expiry', $wgLang->timeanddate( $r->getExpiry() ) ) : + wfMsg( 'ipbinfinite' ); + $msg = ''; + if( $r->isNamespace() ) { + $msg = wfMsgHtml( 'listuserrestrictions-row-ns', $subjlink, + $wgLang->getDisplayNsText( $r->getNamespace() ), $expiry ); + } + if( $r->isPage() ) { + $pagelink = $sk->link( $r->getPage() ); + $msg = wfMsgHtml( 'listuserrestrictions-row-page', $subjlink, + $pagelink, $expiry ); + } + $reason = $sk->commentBlock( $r->getReason() ); + $removelink = ''; + if( $wgUser->isAllowed( 'restrict' ) ) { + $removelink = '(' . $sk->link( SpecialPage::getTitleFor( 'RemoveRestrictions' ), + wfMsgHtml( 'listuserrestrictions-remove' ), array(), array( 'id' => $r->getId() ) ) . ')'; + } + return "<li>{$timestamp}, {$blockerlink} {$msg} {$reason} {$removelink}</li>\n"; + } + + public function getIndexField() { + return 'ur_timestamp'; + } +} diff --git a/includes/specials/SpecialImagelist.php b/includes/specials/SpecialListfiles.php index 3d449b54..d2178ee0 100644 --- a/includes/specials/SpecialImagelist.php +++ b/includes/specials/SpecialListfiles.php @@ -7,7 +7,7 @@ /** * */ -function wfSpecialImagelist() { +function wfSpecialListfiles() { global $wgOut; $pager = new ImageListPager; @@ -49,13 +49,17 @@ class ImageListPager extends TablePager { function getFieldNames() { if ( !$this->mFieldNames ) { + global $wgMiserMode; $this->mFieldNames = array( - 'img_timestamp' => wfMsg( 'imagelist_date' ), - 'img_name' => wfMsg( 'imagelist_name' ), - 'img_user_text' => wfMsg( 'imagelist_user' ), - 'img_size' => wfMsg( 'imagelist_size' ), - 'img_description' => wfMsg( 'imagelist_description' ), + 'img_timestamp' => wfMsg( 'listfiles_date' ), + 'img_name' => wfMsg( 'listfiles_name' ), + 'img_user_text' => wfMsg( 'listfiles_user' ), + 'img_size' => wfMsg( 'listfiles_size' ), + 'img_description' => wfMsg( 'listfiles_description' ), ); + if( !$wgMiserMode ) { + $this->mFieldNames['COUNT(oi_archive_name)'] = wfMsg( 'listfiles_count' ); + } } return $this->mFieldNames; } @@ -66,13 +70,22 @@ class ImageListPager extends TablePager { } function getQueryInfo() { - $fields = $this->getFieldNames(); - $fields = array_keys( $fields ); + $tables = array( 'image' ); + $fields = array_keys( $this->getFieldNames() ); $fields[] = 'img_user'; + $options = $join_conds = array(); + # Depends on $wgMiserMode + if( isset($this->mFieldNames['COUNT(oi_archive_name)']) ) { + $tables[] = 'oldimage'; + $options = array('GROUP BY' => 'img_name'); + $join_conds = array('oldimage' => array('LEFT JOIN','oi_name = img_name') ); + } return array( - 'tables' => 'image', - 'fields' => $fields, - 'conds' => $this->mQueryConds + 'tables' => $tables, + 'fields' => $fields, + 'conds' => $this->mQueryConds, + 'options' => $options, + 'join_conds' => $join_conds ); } @@ -106,7 +119,7 @@ class ImageListPager extends TablePager { if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' ); $name = $this->mCurrentRow->img_name; - $link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), $value ); + $link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_FILE, $name ), $value ); $image = wfLocalFile( $value ); $url = $image->getURL(); $download = Xml::element('a', array( 'href' => $url ), $imgfile ); @@ -123,6 +136,8 @@ class ImageListPager extends TablePager { return $this->getSkin()->formatSize( $value ); case 'img_description': return $this->getSkin()->commentBlock( $value ); + case 'COUNT(oi_archive_name)': + return intval($value)+1; } } @@ -130,14 +145,14 @@ class ImageListPager extends TablePager { global $wgRequest, $wgMiserMode; $search = $wgRequest->getText( 'ilsearch' ); - $s = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getTitle()->getLocalURL(), 'id' => 'mw-imagelist-form' ) ) . + $s = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getTitle()->getLocalURL(), 'id' => 'mw-listfiles-form' ) ) . Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'imagelist' ) ) . + Xml::element( 'legend', null, wfMsg( 'listfiles' ) ) . Xml::tags( 'label', null, wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) ); if ( !$wgMiserMode ) { $s .= "<br />\n" . - Xml::inputLabel( wfMsg( 'imagelist_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search ); + Xml::inputLabel( wfMsg( 'listfiles_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search ); } $s .= ' ' . Xml::submitButton( wfMsg( 'table_pager_limit_submit' ) ) ."\n" . @@ -148,14 +163,14 @@ class ImageListPager extends TablePager { } function getTableClass() { - return 'imagelist ' . parent::getTableClass(); + return 'listfiles ' . parent::getTableClass(); } function getNavClass() { - return 'imagelist_nav ' . parent::getNavClass(); + return 'listfiles_nav ' . parent::getNavClass(); } function getSortHeaderClass() { - return 'imagelist_sort ' . parent::getSortHeaderClass(); + return 'listfiles_sort ' . parent::getSortHeaderClass(); } } diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php index 131c0606..5c76df8c 100644 --- a/includes/specials/SpecialListgrouprights.php +++ b/includes/specials/SpecialListgrouprights.php @@ -24,7 +24,8 @@ class SpecialListGroupRights extends SpecialPage { * Show the special page */ public function execute( $par ) { - global $wgOut, $wgGroupPermissions, $wgImplicitGroups, $wgMessageCache; + global $wgOut, $wgImplicitGroups, $wgMessageCache; + global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups; $wgMessageCache->loadAllMessages(); $this->setHeaders(); @@ -69,13 +70,16 @@ class SpecialListGroupRights extends SpecialPage { $grouplink = ''; } + $addgroups = isset( $wgAddGroups[$group] ) ? $wgAddGroups[$group] : array(); + $removegroups = isset( $wgRemoveGroups[$group] ) ? $wgRemoveGroups[$group] : array(); + $wgOut->addHTML( '<tr> <td>' . $grouppage . $grouplink . '</td> <td>' . - self::formatPermissions( $permissions ) . + self::formatPermissions( $permissions, $addgroups, $removegroups ) . '</td> </tr>' ); @@ -91,18 +95,29 @@ class SpecialListGroupRights extends SpecialPage { * @param $permissions Array of permission => bool (from $wgGroupPermissions items) * @return string List of all granted permissions, separated by comma separator */ - private static function formatPermissions( $permissions ) { + private static function formatPermissions( $permissions, $add, $remove ) { + global $wgLang; $r = array(); foreach( $permissions as $permission => $granted ) { if ( $granted ) { - $description = wfMsgHTML( 'listgrouprights-right-display', - User::getRightDescription($permission), + $description = wfMsgExt( 'listgrouprights-right-display', array( 'parseinline' ), + User::getRightDescription( $permission ), $permission ); $r[] = $description; } } sort( $r ); + if( $add === true ){ + $r[] = wfMsgExt( 'listgrouprights-addgroup-all', array( 'escape' ) ); + } else if( is_array( $add ) && count( $add ) ) { + $r[] = wfMsgExt( 'listgrouprights-addgroup', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ), count( $add ) ); + } + if( $remove === true ){ + $r[] = wfMsgExt( 'listgrouprights-removegroup-all', array( 'escape' ) ); + } else if( is_array( $remove ) && count( $remove ) ) { + $r[] = wfMsgExt( 'listgrouprights-removegroup', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ), count( $remove ) ); + } if( empty( $r ) ) { return ''; } else { diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php index 808aab14..9555bd16 100644 --- a/includes/specials/SpecialListredirects.php +++ b/includes/specials/SpecialListredirects.php @@ -22,7 +22,8 @@ class ListredirectsPage extends QueryPage { function getSQL() { $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); - $sql = "SELECT 'Listredirects' AS type, page_title AS title, page_namespace AS namespace, 0 AS value FROM $page WHERE page_is_redirect = 1"; + $sql = "SELECT 'Listredirects' AS type, page_title AS title, page_namespace AS namespace, + 0 AS value FROM $page WHERE page_is_redirect = 1"; return( $sql ); } diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php index 7dba44e2..17bec70e 100644 --- a/includes/specials/SpecialListusers.php +++ b/includes/specials/SpecialListusers.php @@ -35,10 +35,25 @@ */ class UsersPager extends AlphabeticPager { - function __construct($group=null) { + function __construct( $par=null ) { global $wgRequest; - $this->requestedGroup = $group != "" ? $group : $wgRequest->getVal( 'group' ); - $un = $wgRequest->getText( 'username' ); + $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) ); + $symsForAll = array( '*', 'user' ); + if ( $parms[0] != '' && ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) ) ) { + $this->requestedGroup = $par; + $un = $wgRequest->getText( 'username' ); + } else if ( count( $parms ) == 2 ) { + $this->requestedGroup = $parms[0]; + $un = $parms[1]; + } else { + $this->requestedGroup = $wgRequest->getVal( 'group' ); + $un = ( $par != '' ) ? $par : $wgRequest->getText( 'username' ); + } + if ( in_array( $this->requestedGroup, $symsForAll ) ) { + $this->requestedGroup = ''; + } + $this->editsOnly = $wgRequest->getBool( 'editsOnly' ); + $this->requestedUser = ''; if ( $un != '' ) { $username = Title::makeTitleSafe( NS_USER, $un ); @@ -56,9 +71,9 @@ class UsersPager extends AlphabeticPager { function getQueryInfo() { $dbr = wfGetDB( DB_SLAVE ); - $conds=array(); - // don't show hidden names - $conds[]='ipb_deleted IS NULL OR ipb_deleted = 0'; + $conds = array(); + // Don't show hidden names + $conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0'; if ($this->requestedGroup != "") { $conds['ug_group'] = $this->requestedGroup; $useIndex = ''; @@ -68,6 +83,9 @@ class UsersPager extends AlphabeticPager { if ($this->requestedUser != "") { $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser ); } + if( $this->editsOnly ) { + $conds[] = 'user_editcount > 0'; + } list ($user,$user_groups,$ipblocks) = $dbr->tableNamesN('user','user_groups','ipblocks'); @@ -76,6 +94,7 @@ class UsersPager extends AlphabeticPager { LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_auto=0 ", 'fields' => array('user_name', 'MAX(user_id) AS user_id', + 'MAX(user_editcount) AS edits', 'COUNT(ug_group) AS numgroups', 'MAX(ug_group) AS singlegroup'), 'options' => array('GROUP BY' => 'user_name'), @@ -87,6 +106,8 @@ class UsersPager extends AlphabeticPager { } function formatRow( $row ) { + global $wgLang; + $userPage = Title::makeTitle( NS_USER, $row->user_name ); $name = $this->getSkin()->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ); @@ -102,18 +123,24 @@ class UsersPager extends AlphabeticPager { } $item = wfSpecialList( $name, $groups ); + + global $wgEdititis; + if ( $wgEdititis ) { + $editCount = $wgLang->formatNum( $row->edits ); + $edits = ' [' . wfMsgExt( 'usereditcount', 'parsemag', $editCount ) . ']'; + } else { + $edits = ''; + } wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) ); - return "<li>{$item}</li>"; + return "<li>{$item}{$edits}</li>"; } function getBody() { - if (!$this->mQueryDone) { + if( !$this->mQueryDone ) { $this->doQuery(); } - $batch = new LinkBatch; - $this->mResult->rewind(); - + $batch = new LinkBatch; while ( $row = $this->mResult->fetchObject() ) { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); } @@ -142,7 +169,9 @@ class UsersPager extends AlphabeticPager { Xml::option( wfMsg( 'group-all' ), '' ); foreach( $this->getAllGroups() as $group => $groupText ) $out .= Xml::option( $groupText, $group, $group == $this->requestedGroup ); - $out .= Xml::closeElement( 'select' ) . ' '; + $out .= Xml::closeElement( 'select' ) . '<br/>'; + $out .= Xml::checkLabel( wfMsg('listusers-editsonly'), 'editsOnly', 'editsOnly', $this->editsOnly ); + $out .= ' '; wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) ); @@ -186,14 +215,8 @@ class UsersPager extends AlphabeticPager { * @return array */ protected static function getGroups( $uid ) { - $dbr = wfGetDB( DB_SLAVE ); - $groups = array(); - $res = $dbr->select( 'user_groups', 'ug_group', array( 'ug_user' => $uid ), __METHOD__ ); - if( $res && $dbr->numRows( $res ) > 0 ) { - while( $row = $dbr->fetchObject( $res ) ) - $groups[] = $row->ug_group; - $dbr->freeResult( $res ); - } + $user = User::newFromId( $uid ); + $groups = array_diff( $user->getEffectiveGroups(), $user->getImplicitGroups() ); return $groups; } @@ -222,7 +245,8 @@ function wfSpecialListusers( $par = null ) { # getBody() first to check, if empty $usersbody = $up->getBody(); - $s = $up->getPageHeader(); + $s = XML::openElement( 'div', array('class' => 'mw-spcontent') ); + $s .= $up->getPageHeader(); if( $usersbody ) { $s .= $up->getNavigationBar(); $s .= '<ul>' . $usersbody . '</ul>'; @@ -230,6 +254,6 @@ function wfSpecialListusers( $par = null ) { } else { $s .= '<p>' . wfMsgHTML('listusers-noresult') . '</p>'; }; - + $s .= XML::closeElement( 'div' ); $wgOut->addHTML( $s ); } diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php index 04019223..5859d5b2 100644 --- a/includes/specials/SpecialLockdb.php +++ b/includes/specials/SpecialLockdb.php @@ -109,7 +109,7 @@ END } fwrite( $fp, $this->reason ); fwrite( $fp, "\n<p>(by " . $wgUser->getName() . " at " . - $wgLang->timeanddate( wfTimestampNow() ) . ")\n" ); + $wgLang->timeanddate( wfTimestampNow() ) . ")</p>\n" ); fclose( $fp ); $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index 3154ed13..492c2608 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -26,10 +26,21 @@ * constructor */ function wfSpecialLog( $par = '' ) { - global $wgRequest, $wgOut, $wgUser; + global $wgRequest, $wgOut, $wgUser, $wgLogTypes; + # Get parameters - $type = $wgRequest->getVal( 'type', $par ); - $user = $wgRequest->getText( 'user' ); + $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) ); + $symsForAll = array( '*', 'all' ); + if ( $parms[0] != '' && ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) ) { + $type = $par; + $user = $wgRequest->getText( 'user' ); + } else if ( count( $parms ) == 2 ) { + $type = $parms[0]; + $user = $parms[1]; + } else { + $type = $wgRequest->getVal( 'type' ); + $user = ( $par != '' ) ? $par : $wgRequest->getText( 'user' ); + } $title = $wgRequest->getText( 'page' ); $pattern = $wgRequest->getBool( 'pattern' ); $y = $wgRequest->getIntOrNull( 'year' ); @@ -40,15 +51,14 @@ function wfSpecialLog( $par = '' ) { $y = ''; $m = ''; } - # Create a LogPager item to get the results and a LogEventsList - # item to format them... + # Create a LogPager item to get the results and a LogEventsList item to format them... $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 ); $pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m ); # Set title and add header $loglist->showHeader( $pager->getType() ); # Show form options $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(), - $pager->getYear(), $pager->getMonth() ); + $pager->getYear(), $pager->getMonth(), $pager->getFilterParams() ); # Insert list $logBody = $pager->getBody(); if( $logBody ) { diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php index 5aafac7d..90da25fd 100644 --- a/includes/specials/SpecialLonelypages.php +++ b/includes/specials/SpecialLonelypages.php @@ -29,7 +29,7 @@ class LonelyPagesPage extends PageQueryPage { function getSQL() { $dbr = wfGetDB( DB_SLAVE ); - list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); + list( $page, $pagelinks, $templatelinks ) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); return "SELECT 'Lonelypages' AS type, @@ -39,9 +39,12 @@ class LonelyPagesPage extends PageQueryPage { FROM $page LEFT JOIN $pagelinks ON page_namespace=pl_namespace AND page_title=pl_title + LEFT JOIN $templatelinks + ON page_namespace=tl_namespace AND page_title=tl_title WHERE pl_namespace IS NULL AND page_namespace=".NS_MAIN." - AND page_is_redirect=0"; + AND page_is_redirect=0 + AND tl_namespace IS NULL"; } } diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php index 82ee4be6..cdfde24e 100644 --- a/includes/specials/SpecialMIMEsearch.php +++ b/includes/specials/SpecialMIMEsearch.php @@ -46,7 +46,7 @@ class MIMEsearchPage extends QueryPage { return "SELECT 'MIMEsearch' AS type, - " . NS_IMAGE . " AS namespace, + " . NS_FILE . " AS namespace, img_name AS title, img_major_mime AS value, diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php index 0460c207..f870406c 100644 --- a/includes/specials/SpecialMergeHistory.php +++ b/includes/specials/SpecialMergeHistory.php @@ -96,8 +96,10 @@ class MergehistoryForm { wfEscapeWikiText( $this->mDestObj->getPrefixedText() ) ); } - - // TODO: warn about target = dest? + + if ( $this->mTargetObj->equals( $this->mDestObj ) ) { + $errors[] = wfMsgExt( 'mergehistory-same-destination', array( 'parse' ) ); + } if ( count( $errors ) ) { $this->showMergeForm(); @@ -113,7 +115,7 @@ class MergehistoryForm { $wgOut->addWikiMsg( 'mergehistory-header' ); - $wgOut->addHtml( + $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . @@ -156,7 +158,7 @@ class MergehistoryForm { $action = $titleObj->getLocalURL( "action=submit" ); # Start the form here $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) ); - $wgOut->addHtml( $top ); + $wgOut->addHTML( $top ); if( $haveRevisions ) { # Format the user-visible controls (comment field, submission button) @@ -188,7 +190,7 @@ class MergehistoryForm { Xml::closeElement( 'table' ) . Xml::closeElement( 'fieldset' ); - $wgOut->addHtml( $table ); + $wgOut->addHTML( $table ); } $wgOut->addHTML( "<h2 id=\"mw-mergehistory\">" . wfMsgHtml( "mergehistory-list" ) . "</h2>\n" ); @@ -215,7 +217,7 @@ class MergehistoryForm { $misc .= Xml::hidden( 'dest', $this->mDest ); $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); $misc .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $misc ); + $wgOut->addHTML( $misc ); return true; } @@ -229,7 +231,7 @@ class MergehistoryForm { $last = $this->message['last']; $ts = wfTimestamp( TS_MW, $row->rev_timestamp ); - $checkBox = wfRadio( "mergepoint", $ts, false ); + $checkBox = Xml::radio( "mergepoint", $ts, false ); $pageLink = $this->sk->makeKnownLinkObj( $rev->getTitle(), htmlspecialchars( $wgLang->timeanddate( $ts ) ), 'oldid=' . $rev->getId() ); @@ -370,7 +372,7 @@ class MergehistoryForm { $log->addEntry( 'merge', $targetTitle, $this->mComment, array($destTitle->getPrefixedText(),$TimestampLimit) ); - $wgOut->addHtml( wfMsgExt( 'mergehistory-success', array('parseinline'), + $wgOut->addHTML( wfMsgExt( 'mergehistory-success', array('parseinline'), $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) ); wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) ); @@ -432,10 +434,10 @@ class MergeHistoryPager extends ReverseChronologicalPager { function getQueryInfo() { $conds = $this->mConds; $conds['rev_page'] = $this->articleID; + $conds[] = 'page_id = rev_page'; $conds[] = "rev_timestamp < {$this->maxTimestamp}"; - return array( - 'tables' => array('revision'), + 'tables' => array('revision','page'), 'fields' => array( 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', 'rev_comment', 'rev_id', 'rev_page', 'rev_text_id', 'rev_len', 'rev_deleted' ), 'conds' => $conds diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php index e6810999..1ba05626 100644 --- a/includes/specials/SpecialMostcategories.php +++ b/includes/specials/SpecialMostcategories.php @@ -39,9 +39,9 @@ class MostcategoriesPage extends QueryPage { function formatResult( $skin, $result ) { global $wgLang; $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if ( !$title instanceof Title ) { throw new MWException('Invalid title in database'); } + $count = wfMsgExt( 'ncategories', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->value ) ); - $link = $skin->makeKnownLinkObj( $title, $title->getText() ); + $link = $skin->link( $title ); return wfSpecialList( $link, $count ); } } diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php index 6cfeb7ad..5cc100ba 100644 --- a/includes/specials/SpecialMostimages.php +++ b/includes/specials/SpecialMostimages.php @@ -25,7 +25,7 @@ class MostimagesPage extends ImageQueryPage { " SELECT 'Mostimages' as type, - " . NS_IMAGE . " as namespace, + " . NS_FILE . " as namespace, il_to as title, COUNT(*) as value FROM $imagelinks diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php index d597a4e0..2d398a38 100644 --- a/includes/specials/SpecialMostlinkedtemplates.php +++ b/includes/specials/SpecialMostlinkedtemplates.php @@ -92,15 +92,12 @@ class SpecialMostlinkedtemplates extends QueryPage { */ public function formatResult( $skin, $result ) { $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if( $title instanceof Title ) { - return wfSpecialList( - $skin->makeLinkObj( $title ), - $this->makeWlhLink( $title, $skin, $result ) - ); - } else { - $tsafe = htmlspecialchars( $result->title ); - return "Invalid title in result set; {$tsafe}"; - } + + $skin->link( $title ); + return wfSpecialList( + $skin->makeLinkObj( $title ), + $this->makeWlhLink( $title, $skin, $result ) + ); } /** @@ -115,8 +112,8 @@ class SpecialMostlinkedtemplates extends QueryPage { global $wgLang; $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->value ) ); - return $skin->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() ); + $wgLang->formatNum( $result->value ) ); + return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) ); } } diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index efd2dcfd..acc27625 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -54,12 +54,13 @@ function wfSpecialMovepage( $par = null ) { * @ingroup SpecialPage */ class MovePageForm { - var $oldTitle, $newTitle, $reason; # Text input - var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects; + var $oldTitle, $newTitle; # Objects + var $reason; # Text input + var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect; # Checks private $watch = false; - function MovePageForm( $oldTitle, $newTitle ) { + function __construct( $oldTitle, $newTitle ) { global $wgRequest; $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); $this->oldTitle = $oldTitle; @@ -68,48 +69,54 @@ class MovePageForm { if ( $wgRequest->wasPosted() ) { $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', false ); $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', false ); + $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', false ); } else { $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', true ); $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', true ); + $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', true ); } $this->moveSubpages = $wgRequest->getBool( 'wpMovesubpages', false ); $this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' ); $this->watch = $wgRequest->getCheck( 'wpWatch' ); } - function showForm( $err, $hookErr = '' ) { - global $wgOut, $wgUser; + /** + * Show the form + * @param mixed $err Error message. May either be a string message name or + * array message name and parameters, like the second argument to + * OutputPage::wrapWikiMsg(). + */ + function showForm( $err ) { + global $wgOut, $wgUser, $wgFixDoubleRedirects; $skin = $wgUser->getSkin(); $oldTitleLink = $skin->makeLinkObj( $this->oldTitle ); - $oldTitle = $this->oldTitle->getPrefixedText(); - $wgOut->setPagetitle( wfMsg( 'move-page', $oldTitle ) ); + $wgOut->setPagetitle( wfMsg( 'move-page', $this->oldTitle->getPrefixedText() ) ); $wgOut->setSubtitle( wfMsg( 'move-page-backlink', $oldTitleLink ) ); - if( $this->newTitle == '' ) { + $newTitle = $this->newTitle; + + if( !$newTitle ) { # Show the current title as a default # when the form is first opened. - $newTitle = $oldTitle; - } else { - if( $err == '' ) { - $nt = Title::newFromURL( $this->newTitle ); - if( $nt ) { - # If a title was supplied, probably from the move log revert - # link, check for validity. We can then show some diagnostic - # information and save a click. - $newerr = $this->oldTitle->isValidMoveOperation( $nt ); - if( is_string( $newerr ) ) { - $err = $newerr; - } + $newTitle = $this->oldTitle; + } + else { + if( empty($err) ) { + # If a title was supplied, probably from the move log revert + # link, check for validity. We can then show some diagnostic + # information and save a click. + $newerr = $this->oldTitle->isValidMoveOperation( $newTitle ); + if( $newerr ) { + $err = $newerr[0]; } } - $newTitle = $this->newTitle; } - if ( $err == 'articleexists' && $wgUser->isAllowed( 'delete' ) ) { - $wgOut->addWikiMsg( 'delete_and_move_text', $newTitle ); + if ( !empty($err) && $err[0] == 'articleexists' && $wgUser->isAllowed( 'delete' ) ) { + $wgOut->addWikiMsg( 'delete_and_move_text', $newTitle->getPrefixedText() ); $movepagebtn = wfMsg( 'delete_and_move' ); $submitVar = 'wpDeleteAndMove'; $confirm = " @@ -131,12 +138,16 @@ class MovePageForm { $considerTalk = ( !$this->oldTitle->isTalkPage() && $oldTalk->exists() ); $dbr = wfGetDB( DB_SLAVE ); - $hasRedirects = $dbr->selectField( 'redirect', '1', - array( - 'rd_namespace' => $this->oldTitle->getNamespace(), - 'rd_title' => $this->oldTitle->getDBkey(), - ) , __METHOD__ ); - + if ( $wgFixDoubleRedirects ) { + $hasRedirects = $dbr->selectField( 'redirect', '1', + array( + 'rd_namespace' => $this->oldTitle->getNamespace(), + 'rd_title' => $this->oldTitle->getDBkey(), + ) , __METHOD__ ); + } else { + $hasRedirects = false; + } + if ( $considerTalk ) { $wgOut->addWikiMsg( 'movepagetalktext' ); } @@ -144,9 +155,10 @@ class MovePageForm { $titleObj = SpecialPage::getTitleFor( 'Movepage' ); $token = htmlspecialchars( $wgUser->editToken() ); - if ( $err != '' ) { + if ( !empty($err) ) { $wgOut->setSubtitle( wfMsg( 'formerror' ) ); - if( $err == 'hookaborted' ) { + if( $err[0] == 'hookaborted' ) { + $hookErr = $err[1]; $errMsg = "<p><strong class=\"error\">$hookErr</strong></p>\n"; $wgOut->addHTML( $errMsg ); } else { @@ -172,8 +184,8 @@ class MovePageForm { Xml::label( wfMsg( 'newtitle' ), 'wpNewTitle' ) . "</td> <td class='mw-input'>" . - Xml::input( 'wpNewTitle', 40, $newTitle, array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) . - Xml::hidden( 'wpOldTitle', $oldTitle ) . + Xml::input( 'wpNewTitle', 40, $newTitle->getPrefixedText(), array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) . + Xml::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) . "</td> </tr> <tr> @@ -197,6 +209,18 @@ class MovePageForm { ); } + if ( $wgUser->isAllowed( 'suppressredirect' ) ) { + $wgOut->addHTML( " + <tr> + <td></td> + <td class='mw-input' >" . + Xml::checkLabel( wfMsg( 'move-leave-redirect' ), 'wpLeaveRedirect', + 'wpLeaveRedirect', $this->leaveRedirect ) . + "</td> + </tr>" + ); + } + if ( $hasRedirects ) { $wgOut->addHTML( " <tr> @@ -205,7 +229,7 @@ class MovePageForm { Xml::checkLabel( wfMsg( 'fix-double-redirects' ), 'wpFixRedirects', 'wpFixRedirects', $this->fixRedirects ) . "</td> - </td>" + </tr>" ); } @@ -215,7 +239,7 @@ class MovePageForm { <tr> <td></td> <td class=\"mw-input\">" . - Xml::checkLabel( wfMsgHtml( + Xml::checkLabel( wfMsg( $this->oldTitle->hasSubpages() ? 'move-subpages' : 'move-talk-subpages' @@ -259,6 +283,7 @@ class MovePageForm { function doSubmit() { global $wgOut, $wgUser, $wgRequest, $wgMaximumMovedPages, $wgLang; + global $wgFixDoubleRedirects; if ( $wgUser->pingLimiter( 'move' ) ) { $wgOut->rateLimited(); @@ -280,6 +305,12 @@ class MovePageForm { return; } + // Delete an associated image if there is + $file = wfLocalFile( $nt ); + if( $file->exists() ) { + $file->delete( wfMsgForContent( 'delete_and_move_reason' ), false ); + } + // This may output an error message and exit $article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) ); } @@ -290,14 +321,20 @@ class MovePageForm { return; } - $error = $ot->moveTo( $nt, true, $this->reason ); + if ( $wgUser->isAllowed( 'suppressredirect' ) ) { + $createRedirect = $this->leaveRedirect; + } else { + $createRedirect = true; + } + + $error = $ot->moveTo( $nt, true, $this->reason, $createRedirect ); if ( $error !== true ) { - # FIXME: showForm() should handle multiple errors - call_user_func_array(array($this, 'showForm'), $error[0]); + # FIXME: show all the errors in a list, not just the first one + $this->showForm( reset( $error ) ); return; } - if ( $this->fixRedirects ) { + if ( $wgFixDoubleRedirects && $this->fixRedirects ) { DoubleRedirectJob::fixRedirects( 'move', $ot, $nt ); } @@ -312,7 +349,9 @@ class MovePageForm { $oldLink = "<span class='plainlinks'>[$oldUrl $oldText]</span>"; $newLink = "<span class='plainlinks'>[$newUrl $newText]</span>"; + $msgName = $createRedirect ? 'movepage-moved-redirect' : 'movepage-moved-noredirect'; $wgOut->addWikiMsg( 'movepage-moved', $oldLink, $newLink, $oldText, $newText ); + $wgOut->addWikiMsg( $msgName ); # Now we move extra pages we've been asked to move: subpages and talk # pages. First, if the old page or the new page is a talk page, we @@ -364,25 +403,26 @@ class MovePageForm { $conds = null; } - $extrapages = array(); + $extraPages = array(); if( !is_null( $conds ) ) { - $extrapages = $dbr->select( 'page', - array( 'page_id', 'page_namespace', 'page_title' ), - $conds, - __METHOD__ + $extraPages = TitleArray::newFromResult( + $dbr->select( 'page', + array( 'page_id', 'page_namespace', 'page_title' ), + $conds, + __METHOD__ + ) ); } $extraOutput = array(); $skin = $wgUser->getSkin(); $count = 1; - foreach( $extrapages as $row ) { - if( $row->page_id == $ot->getArticleId() ) { + foreach( $extraPages as $oldSubpage ) { + if( $oldSubpage->getArticleId() == $ot->getArticleId() ) { # Already did this one. continue; } - $oldSubpage = Title::newFromRow( $row ); $newPageName = preg_replace( '#^'.preg_quote( $ot->getDBKey(), '#' ).'#', $nt->getDBKey(), @@ -408,7 +448,7 @@ class MovePageForm { $link = $skin->makeKnownLinkObj( $newSubpage ); $extraOutput []= wfMsgHtml( 'movepage-page-exists', $link ); } else { - $success = $oldSubpage->moveTo( $newSubpage, true, $this->reason ); + $success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect ); if( $success === true ) { if ( $this->fixRedirects ) { DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage ); diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index e57f6fc1..575e37a7 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -5,66 +5,56 @@ * FIXME: this code is crap, should use Pager and Database::select(). */ -/** - * - */ function wfSpecialNewimages( $par, $specialPage ) { - global $wgUser, $wgOut, $wgLang, $wgRequest, $wgGroupPermissions, $wgMiserMode; + global $wgUser, $wgOut, $wgLang, $wgRequest, $wgMiserMode; $wpIlMatch = $wgRequest->getText( 'wpIlMatch' ); $dbr = wfGetDB( DB_SLAVE ); $sk = $wgUser->getSkin(); $shownav = !$specialPage->including(); - $hidebots = $wgRequest->getBool('hidebots',1); + $hidebots = $wgRequest->getBool( 'hidebots' , 1 ); $hidebotsql = ''; - if ($hidebots) { - - /** Make a list of group names which have the 'bot' flag - set. - */ - $botconds=array(); - foreach ($wgGroupPermissions as $groupname=>$perms) { - if(array_key_exists('bot',$perms) && $perms['bot']) { - $botconds[]="ug_group='$groupname'"; - } + if ( $hidebots ) { + # Make a list of group names which have the 'bot' flag set. + $botconds = array(); + foreach ( User::getGroupsWithPermission('bot') as $groupname ) { + $botconds[] = 'ug_group = ' . $dbr->addQuotes( $groupname ); } - /* If not bot groups, do not set $hidebotsql */ - if ($botconds) { - $isbotmember=$dbr->makeList($botconds, LIST_OR); - - /** This join, in conjunction with WHERE ug_group - IS NULL, returns only those rows from IMAGE - where the uploading user is not a member of - a group which has the 'bot' permission set. - */ - $ug = $dbr->tableName('user_groups'); - $hidebotsql = " LEFT OUTER JOIN $ug ON img_user=ug_user AND ($isbotmember)"; + # If not bot groups, do not set $hidebotsql + if ( $botconds ) { + $isbotmember = $dbr->makeList( $botconds, LIST_OR ); + + # This join, in conjunction with WHERE ug_group IS NULL, returns + # only those rows from IMAGE where the uploading user is not a mem- + # ber of a group which has the 'bot' permission set. + $ug = $dbr->tableName( 'user_groups' ); + $hidebotsql = " LEFT JOIN $ug ON img_user=ug_user AND ($isbotmember)"; } } - $image = $dbr->tableName('image'); + $image = $dbr->tableName( 'image' ); - $sql="SELECT img_timestamp from $image"; + $sql = "SELECT img_timestamp from $image"; if ($hidebotsql) { $sql .= "$hidebotsql WHERE ug_group IS NULL"; } - $sql.=' ORDER BY img_timestamp DESC LIMIT 1'; - $res = $dbr->query($sql, 'wfSpecialNewImages'); - $row = $dbr->fetchRow($res); - if($row!==false) { - $ts=$row[0]; + $sql .= ' ORDER BY img_timestamp DESC LIMIT 1'; + $res = $dbr->query( $sql, __FUNCTION__ ); + $row = $dbr->fetchRow( $res ); + if( $row !== false ) { + $ts = $row[0]; } else { - $ts=false; + $ts = false; } - $dbr->freeResult($res); - $sql=''; + $dbr->freeResult( $res ); + $sql = ''; - /** If we were clever, we'd use this to cache. */ - $latestTimestamp = wfTimestamp( TS_MW, $ts); + # If we were clever, we'd use this to cache. + $latestTimestamp = wfTimestamp( TS_MW, $ts ); - /** Hardcode this for now. */ + # Hardcode this for now. $limit = 48; if ( $parval = intval( $par ) ) { @@ -77,10 +67,8 @@ function wfSpecialNewimages( $par, $specialPage ) { $searchpar = ''; if ( $wpIlMatch != '' && !$wgMiserMode) { $nt = Title::newFromUrl( $wpIlMatch ); - if($nt ) { - $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); - $m = str_replace( '%', "\\%", $m ); - $m = str_replace( '_', "\\_", $m ); + if( $nt ) { + $m = $dbr->escapeLike( strtolower( $nt->getDBkey() ) ); $where[] = "LOWER(img_name) LIKE '%{$m}%'"; $searchpar = '&wpIlMatch=' . urlencode( $wpIlMatch ); } @@ -97,16 +85,16 @@ function wfSpecialNewimages( $par, $specialPage ) { $sql='SELECT img_size, img_name, img_user, img_user_text,'. "img_description,img_timestamp FROM $image"; - if($hidebotsql) { + if( $hidebotsql ) { $sql .= $hidebotsql; - $where[]='ug_group IS NULL'; + $where[] = 'ug_group IS NULL'; } - if(count($where)) { - $sql.=' WHERE '.$dbr->makeList($where, LIST_AND); + if( count( $where ) ) { + $sql .= ' WHERE ' . $dbr->makeList( $where, LIST_AND ); } $sql.=' ORDER BY img_timestamp '. ( $invertSort ? '' : ' DESC' ); - $sql.=' LIMIT '.($limit+1); - $res = $dbr->query($sql, 'wfSpecialNewImages'); + $sql.=' LIMIT ' . ( $limit + 1 ); + $res = $dbr->query( $sql, __FUNCTION__ ); /** * We have to flip things around to get the last N after a certain date @@ -126,7 +114,8 @@ function wfSpecialNewimages( $par, $specialPage ) { $lastTimestamp = null; $shownImages = 0; foreach( $images as $s ) { - if( ++$shownImages > $limit ) { + $shownImages++; + if( $shownImages > $limit ) { # One extra just to test for whether to show a page link; # don't actually show it. break; @@ -135,7 +124,7 @@ function wfSpecialNewimages( $par, $specialPage ) { $name = $s->img_name; $ut = $s->img_user_text; - $nt = Title::newFromText( $name, NS_IMAGE ); + $nt = Title::newFromText( $name, NS_FILE ); $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut ); $gallery->add( $nt, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" ); @@ -147,33 +136,35 @@ function wfSpecialNewimages( $par, $specialPage ) { $lastTimestamp = $timestamp; } + $titleObj = SpecialPage::getTitleFor( 'Newimages' ); + $action = $titleObj->getLocalURL( $hidebots ? '' : 'hidebots=0' ); + if ( $shownav && !$wgMiserMode ) { + $wgOut->addHTML( + Xml::openElement( 'form', array( 'action' => $action, 'method' => 'post', 'id' => 'imagesearch' ) ) . + Xml::fieldset( wfMsg( 'newimages-legend' ) ) . + Xml::inputLabel( wfMsg( 'newimages-label' ), 'wpIlMatch', 'wpIlMatch', 20, $wpIlMatch ) . ' ' . + Xml::submitButton( wfMsg( 'ilsubmit' ), array( 'name' => 'wpIlSubmit' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); + } + $bydate = wfMsg( 'bydate' ); $lt = $wgLang->formatNum( min( $shownImages, $limit ) ); - if ($shownav) { + if ( $shownav ) { $text = wfMsgExt( 'imagelisttext', array('parse'), $lt, $bydate ); $wgOut->addHTML( $text . "\n" ); } - $sub = wfMsg( 'ilsubmit' ); - $titleObj = SpecialPage::getTitleFor( 'Newimages' ); - $action = $titleObj->escapeLocalURL( $hidebots ? '' : 'hidebots=0' ); - if ($shownav && !$wgMiserMode) { - $wgOut->addHTML( "<form id=\"imagesearch\" method=\"post\" action=\"" . - "{$action}\">" . - Xml::input( 'wpIlMatch', 20, $wpIlMatch ) . ' ' . - Xml::submitButton( $sub, array( 'name' => 'wpIlSubmit' ) ) . - "</form>" ); - } - /** * Paging controls... */ # If we change bot visibility, this needs to be carried along. - if(!$hidebots) { - $botpar='&hidebots=0'; + if( !$hidebots ) { + $botpar = '&hidebots=0'; } else { - $botpar=''; + $botpar = ''; } $now = wfTimestampNow(); $d = $wgLang->date( $now, true ); @@ -186,12 +177,12 @@ function wfSpecialNewimages( $par, $specialPage ) { $opts = array( 'parsemag', 'escapenoentities' ); - $prevLink = wfMsgExt( 'prevn', $opts, $wgLang->formatNum( $limit ) ); + $prevLink = wfMsgExt( 'pager-newer-n', $opts, $wgLang->formatNum( $limit ) ); if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) { $prevLink = $sk->makeKnownLinkObj( $titleObj, $prevLink, 'from=' . $firstTimestamp . $botpar . $searchpar ); } - $nextLink = wfMsgExt( 'nextn', $opts, $wgLang->formatNum( $limit ) ); + $nextLink = wfMsgExt( 'pager-older-n', $opts, $wgLang->formatNum( $limit ) ); if( $shownImages > $limit && $lastTimestamp ) { $nextLink = $sk->makeKnownLinkObj( $titleObj, $nextLink, 'until=' . $lastTimestamp.$botpar.$searchpar ); } diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php index 1a410ae0..08e776d8 100644 --- a/includes/specials/SpecialNewpages.php +++ b/includes/specials/SpecialNewpages.php @@ -12,7 +12,7 @@ class SpecialNewpages extends SpecialPage { // Some internal settings protected $showNavigation = false; - public function __construct(){ + public function __construct() { parent::__construct( 'Newpages' ); $this->includable( true ); } @@ -26,7 +26,8 @@ class SpecialNewpages extends SpecialPage { $opts->add( 'hideliu', false ); $opts->add( 'hidepatrolled', false ); $opts->add( 'hidebots', false ); - $opts->add( 'limit', 50 ); + $opts->add( 'hideredirs', true ); + $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) ); $opts->add( 'offset', '' ); $opts->add( 'namespace', '0' ); $opts->add( 'username', '' ); @@ -58,6 +59,8 @@ class SpecialNewpages extends SpecialPage { $this->opts->setValue( 'hidepatrolled', true ); if ( 'hidebots' == $bit ) $this->opts->setValue( 'hidebots', true ); + if ( 'showredirs' == $bit ) + $this->opts->setValue( 'hideredirs', false ); if ( is_numeric( $bit ) ) $this->opts->setValue( 'limit', intval( $bit ) ); @@ -67,6 +70,8 @@ class SpecialNewpages extends SpecialPage { // PG offsets not just digits! if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) $this->opts->setValue( 'offset', intval($m[1]) ); + if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) + $this->opts->setValue( 'username', $m[1] ); if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { $ns = $wgLang->getNsIndex( $m[1] ); if( $ns !== false ) { @@ -83,7 +88,7 @@ class SpecialNewpages extends SpecialPage { * @return string */ public function execute( $par ) { - global $wgLang, $wgGroupPermissions, $wgUser, $wgOut; + global $wgLang, $wgUser, $wgOut; $this->setHeaders(); $this->outputHeader(); @@ -125,7 +130,8 @@ class SpecialNewpages extends SpecialPage { $filters = array( 'hideliu' => 'rcshowhideliu', 'hidepatrolled' => 'rcshowhidepatr', - 'hidebots' => 'rcshowhidebots' + 'hidebots' => 'rcshowhidebots', + 'hideredirs' => 'whatlinkshere-hideredirs' ); // Disable some if needed @@ -142,8 +148,8 @@ class SpecialNewpages extends SpecialPage { $self = $this->getTitle(); foreach ( $filters as $key => $msg ) { $onoff = 1 - $this->opts->getValue($key); - $link = $this->skin->makeKnownLinkObj( $self, $showhide[$onoff], - wfArrayToCGI( array( $key => $onoff ), $changed ) + $link = $this->skin->link( $self, $showhide[$onoff], array(), + array( $key => $onoff ) + $changed ); $links[$key] = wfMsgHtml( $msg, $link ); } @@ -231,7 +237,7 @@ class SpecialNewpages extends SpecialPage { global $wgLang, $wgContLang, $wgUser; $dm = $wgContLang->getDirMark(); - $title = Title::makeTitleSafe( $result->page_namespace, $result->page_title ); + $title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title ); $time = $wgLang->timeAndDate( $result->rc_timestamp, true ); $plink = $this->skin->makeKnownLinkObj( $title, '', $this->patrollable( $result ) ? 'rcid=' . $result->rc_id : '' ); $hist = $this->skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' ); @@ -261,7 +267,7 @@ class SpecialNewpages extends SpecialPage { * @param string $type */ protected function feed( $type ) { - global $wgFeed, $wgFeedClasses; + global $wgFeed, $wgFeedClasses, $wgFeedLimit; if ( !$wgFeed ) { global $wgOut; @@ -277,16 +283,12 @@ class SpecialNewpages extends SpecialPage { $feed = new $wgFeedClasses[$type]( $this->feedTitle(), - wfMsg( 'tagline' ), + wfMsgExt( 'tagline', 'parsemag' ), $this->getTitle()->getFullUrl() ); $pager = new NewPagesPager( $this, $this->opts ); $limit = $this->opts->getValue( 'limit' ); - global $wgFeedLimit; - if( $limit > $wgFeedLimit ) { - $limit = $wgFeedLimit; - } - $pager->mLimit = $limit; + $pager->mLimit = min( $limit, $wgFeedLimit ); $feed->outHeader(); if( $pager->getNumRows() > 0 ) { @@ -305,7 +307,7 @@ class SpecialNewpages extends SpecialPage { } protected function feedItem( $row ) { - $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title ); + $title = Title::MakeTitle( intval( $row->rc_namespace ), $row->rc_title ); if( $title ) { $date = $row->rc_timestamp; $comments = $title->getTalkPage()->getFullURL(); @@ -322,13 +324,6 @@ class SpecialNewpages extends SpecialPage { } } - /** - * Quickie hack... strip out wikilinks to more legible form from the comment. - */ - protected function stripComment( $text ) { - return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text ); - } - protected function feedItemAuthor( $row ) { return isset( $row->rc_user_text ) ? $row->rc_user_text : ''; } @@ -337,7 +332,7 @@ class SpecialNewpages extends SpecialPage { $revision = Revision::newFromId( $row->rev_id ); if( $revision ) { return '<p>' . htmlspecialchars( $revision->getUserText() ) . ': ' . - htmlspecialchars( $revision->getComment() ) . + htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . "</p>\n<hr />\n<div>" . nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; } @@ -352,15 +347,13 @@ class NewPagesPager extends ReverseChronologicalPager { // Stored opts protected $opts, $mForm; - private $hideliu, $hidepatrolled, $hidebots, $namespace, $user, $spTitle; - function __construct( $form, FormOptions $opts ) { parent::__construct(); $this->mForm = $form; $this->opts = $opts; } - function getTitle(){ + function getTitle() { static $title = null; if ( $title === null ) $title = $this->mForm->getTitle(); @@ -379,13 +372,13 @@ class NewPagesPager extends ReverseChronologicalPager { $user = Title::makeTitleSafe( NS_USER, $username ); if( $namespace !== false ) { - $conds['page_namespace'] = $namespace; + $conds['rc_namespace'] = $namespace; $rcIndexes = array( 'new_name_timestamp' ); } else { $rcIndexes = array( 'rc_timestamp' ); } $conds[] = 'page_id = rc_cur_id'; - $conds['page_is_redirect'] = 0; + # $wgEnableNewpagesUserFilter - temp WMF hack if( $wgEnableNewpagesUserFilter && $user ) { $conds['rc_user_text'] = $user->getText(); @@ -402,9 +395,13 @@ class NewPagesPager extends ReverseChronologicalPager { $conds['rc_bot'] = 0; } + if ( $this->opts->getValue( 'hideredirs' ) ) { + $conds['page_is_redirect'] = 0; + } + return array( 'tables' => array( 'recentchanges', 'page' ), - 'fields' => 'page_namespace,page_title, rc_cur_id, rc_user,rc_user_text,rc_comment, + 'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment, rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id', 'conds' => $conds, 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ) @@ -425,7 +422,7 @@ class NewPagesPager extends ReverseChronologicalPager { while( $row = $this->mResult->fetchObject() ) { $linkBatch->add( NS_USER, $row->rc_user_text ); $linkBatch->add( NS_USER_TALK, $row->rc_user_text ); - $linkBatch->add( $row->page_namespace, $row->page_title ); + $linkBatch->add( $row->rc_namespace, $row->rc_title ); } $linkBatch->execute(); return "<ul>"; diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index b3468a3c..ca2236ee 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -21,11 +21,11 @@ function wfSpecialPreferences() { * @ingroup SpecialPage */ class PreferencesForm { - var $mQuickbar, $mOldpass, $mNewpass, $mRetypePass, $mStubs; + var $mQuickbar, $mStubs; var $mRows, $mCols, $mSkin, $mMath, $mDate, $mUserEmail, $mEmailFlag, $mNick; var $mUserLanguage, $mUserVariant; - var $mSearch, $mRecent, $mRecentDays, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; - var $mReset, $mPosted, $mToggles, $mUseAjaxSearch, $mSearchNs, $mRealName, $mImageSize; + var $mSearch, $mRecent, $mRecentDays, $mTimeZone, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; + var $mReset, $mPosted, $mToggles, $mSearchNs, $mRealName, $mImageSize; var $mUnderline, $mWatchlistEdits; /** @@ -36,13 +36,10 @@ class PreferencesForm { global $wgContLang, $wgUser, $wgAllowRealName; $this->mQuickbar = $request->getVal( 'wpQuickbar' ); - $this->mOldpass = $request->getVal( 'wpOldpass' ); - $this->mNewpass = $request->getVal( 'wpNewpass' ); - $this->mRetypePass =$request->getVal( 'wpRetypePass' ); $this->mStubs = $request->getVal( 'wpStubs' ); $this->mRows = $request->getVal( 'wpRows' ); $this->mCols = $request->getVal( 'wpCols' ); - $this->mSkin = $request->getVal( 'wpSkin' ); + $this->mSkin = Skin::normalizeKey( $request->getVal( 'wpSkin' ) ); $this->mMath = $request->getVal( 'wpMath' ); $this->mDate = $request->getVal( 'wpDate' ); $this->mUserEmail = $request->getVal( 'wpUserEmail' ); @@ -54,6 +51,7 @@ class PreferencesForm { $this->mSearch = $request->getVal( 'wpSearch' ); $this->mRecent = $request->getVal( 'wpRecent' ); $this->mRecentDays = $request->getVal( 'wpRecentDays' ); + $this->mTimeZone = $request->getVal( 'wpTimeZone' ); $this->mHourDiff = $request->getVal( 'wpHourDiff' ); $this->mSearchLines = $request->getVal( 'wpSearchLines' ); $this->mSearchChars = $request->getVal( 'wpSearchChars' ); @@ -66,7 +64,6 @@ class PreferencesForm { $this->mSuccess = $request->getCheck( 'success' ); $this->mWatchlistDays = $request->getVal( 'wpWatchlistDays' ); $this->mWatchlistEdits = $request->getVal( 'wpWatchlistEdits' ); - $this->mUseAjaxSearch = $request->getCheck( 'wpUseAjaxSearch' ); $this->mDisableMWSuggest = $request->getCheck( 'wpDisableMWSuggest' ); $this->mSaveprefs = $request->getCheck( 'wpSaveprefs' ) && @@ -105,10 +102,10 @@ class PreferencesForm { } function execute() { - global $wgUser, $wgOut; + global $wgUser, $wgOut, $wgTitle; if ( $wgUser->isAnon() ) { - $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext' ); + $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext', array($wgTitle->getPrefixedDBkey()) ); return; } if ( wfReadOnly() ) { @@ -174,34 +171,37 @@ class PreferencesForm { /** * Used to validate the user inputed timezone before saving it as - * 'timecorrection', will return '00:00' if fed bogus data. - * Note: It's not a 100% correct implementation timezone-wise, it will - * accept stuff like '14:30', + * 'timecorrection', will return 'System' if fed bogus data. * @access private - * @param string $s the user input + * @param string $tz the user input Zoneinfo timezone + * @param string $s the user input offset string * @return string */ - function validateTimeZone( $s ) { - if ( $s !== '' ) { - if ( strpos( $s, ':' ) ) { - # HH:MM - $array = explode( ':' , $s ); - $hour = intval( $array[0] ); - $minute = intval( $array[1] ); - } else { - $minute = intval( $s * 60 ); - $hour = intval( $minute / 60 ); - $minute = abs( $minute ) % 60; - } - # Max is +14:00 and min is -12:00, see: - # http://en.wikipedia.org/wiki/Timezone - $hour = min( $hour, 14 ); - $hour = max( $hour, -12 ); - $minute = min( $minute, 59 ); - $minute = max( $minute, 0 ); - $s = sprintf( "%02d:%02d", $hour, $minute ); + function validateTimeZone( $tz, $s ) { + $data = explode( '|', $tz, 3 ); + switch ( $data[0] ) { + case 'ZoneInfo': + case 'System': + return $tz; + case 'Offset': + default: + $data = explode( ':', $s, 2 ); + $minDiff = 0; + if( count( $data ) == 2 ) { + $data[0] = intval( $data[0] ); + $data[1] = intval( $data[1] ); + $minDiff = abs( $data[0] ) * 60 + $data[1]; + if ( $data[0] < 0 ) $minDiff = -$minDiff; + } else { + $minDiff = intval( $data[0] ) * 60; + } + + # Max is +14:00 and min is -12:00, see: + # http://en.wikipedia.org/wiki/Timezone + $minDiff = min( $minDiff, 840 ); # 14:00 + $minDiff = max( $minDiff, -720 ); # -12:00 + return 'Offset|'.$minDiff; } - return $s; } /** @@ -213,30 +213,6 @@ class PreferencesForm { global $wgEmailAuthentication, $wgRCMaxAge; global $wgAuth, $wgEmailConfirmToEdit; - - if ( '' != $this->mNewpass && $wgAuth->allowPasswordChange() ) { - if ( $this->mNewpass != $this->mRetypePass ) { - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'badretype' ) ); - $this->mainPrefsForm( 'error', wfMsg( 'badretype' ) ); - return; - } - - if (!$wgUser->checkPassword( $this->mOldpass )) { - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'wrongpassword' ) ); - $this->mainPrefsForm( 'error', wfMsg( 'wrongpassword' ) ); - return; - } - - try { - $wgUser->setPassword( $this->mNewpass ); - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'success' ) ); - $this->mNewpass = $this->mOldpass = $this->mRetypePass = ''; - } catch( PasswordError $e ) { - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'error' ) ); - $this->mainPrefsForm( 'error', $e->getMessage() ); - return; - } - } $wgUser->setRealName( $this->mRealName ); $oldOptions = $wgUser->mOptions; @@ -269,7 +245,10 @@ class PreferencesForm { $wgUser->setOption( 'variant', $this->mUserVariant ); $wgUser->setOption( 'nickname', $this->mNick ); $wgUser->setOption( 'quickbar', $this->mQuickbar ); - $wgUser->setOption( 'skin', $this->mSkin ); + global $wgAllowUserSkin; + if( $wgAllowUserSkin ) { + $wgUser->setOption( 'skin', $this->mSkin ); + } global $wgUseTeX; if( $wgUseTeX ) { $wgUser->setOption( 'math', $this->mMath ); @@ -284,12 +263,11 @@ class PreferencesForm { $wgUser->setOption( 'rows', $this->validateInt( $this->mRows, 4, 1000 ) ); $wgUser->setOption( 'cols', $this->validateInt( $this->mCols, 4, 1000 ) ); $wgUser->setOption( 'stubthreshold', $this->validateIntOrNull( $this->mStubs ) ); - $wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mHourDiff, -12, 14 ) ); + $wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mTimeZone, $this->mHourDiff ) ); $wgUser->setOption( 'imagesize', $this->mImageSize ); $wgUser->setOption( 'thumbsize', $this->mThumbSize ); $wgUser->setOption( 'underline', $this->validateInt($this->mUnderline, 0, 2) ); $wgUser->setOption( 'watchlistdays', $this->validateFloat( $this->mWatchlistDays, 0, 7 ) ); - $wgUser->setOption( 'ajaxsearch', $this->mUseAjaxSearch ); $wgUser->setOption( 'disablesuggest', $this->mDisableMWSuggest ); # Set search namespace options @@ -370,9 +348,8 @@ class PreferencesForm { * @access private */ function resetPrefs() { - global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName; + global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName, $wgLocalTZoffset; - $this->mOldpass = $this->mNewpass = $this->mRetypePass = ''; $this->mUserEmail = $wgUser->getEmail(); $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp(); $this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : ''; @@ -391,7 +368,47 @@ class PreferencesForm { $this->mRows = $wgUser->getOption( 'rows' ); $this->mCols = $wgUser->getOption( 'cols' ); $this->mStubs = $wgUser->getOption( 'stubthreshold' ); - $this->mHourDiff = $wgUser->getOption( 'timecorrection' ); + + $tz = $wgUser->getOption( 'timecorrection' ); + $data = explode( '|', $tz, 3 ); + $minDiff = null; + switch ( $data[0] ) { + case 'ZoneInfo': + $this->mTimeZone = $tz; + # Check if the specified TZ exists, and change to 'Offset' if + # not. + if ( !function_exists('timezone_open') || @timezone_open( $data[2] ) === false ) { + $this->mTimeZone = 'Offset'; + $minDiff = intval( $data[1] ); + } + break; + case '': + case 'System': + $this->mTimeZone = 'System|'.$wgLocalTZoffset; + break; + case 'Offset': + $this->mTimeZone = 'Offset'; + $minDiff = intval( $data[1] ); + break; + default: + $this->mTimeZone = 'Offset'; + $data = explode( ':', $tz, 2 ); + if( count( $data ) == 2 ) { + $data[0] = intval( $data[0] ); + $data[1] = intval( $data[1] ); + $minDiff = abs( $data[0] ) * 60 + $data[1]; + if ( $data[0] < 0 ) $minDiff = -$minDiff; + } else { + $minDiff = intval( $data[0] ) * 60; + } + break; + } + if ( is_null( $minDiff ) ) { + $this->mHourDiff = ''; + } else { + $this->mHourDiff = sprintf( '%+03d:%02d', floor($minDiff/60), abs($minDiff)%60 ); + } + $this->mSearch = $wgUser->getOption( 'searchlimit' ); $this->mSearchLines = $wgUser->getOption( 'contextlines' ); $this->mSearchChars = $wgUser->getOption( 'contextchars' ); @@ -402,7 +419,6 @@ class PreferencesForm { $this->mWatchlistEdits = $wgUser->getOption( 'wllimit' ); $this->mUnderline = $wgUser->getOption( 'underline' ); $this->mWatchlistDays = $wgUser->getOption( 'watchlistdays' ); - $this->mUseAjaxSearch = $wgUser->getBoolOption( 'ajaxsearch' ); $this->mDisableMWSuggest = $wgUser->getBoolOption( 'disablesuggest' ); $togs = User::getToggles(); @@ -511,18 +527,18 @@ class PreferencesForm { * @access private */ function mainPrefsForm( $status , $message = '' ) { - global $wgUser, $wgOut, $wgLang, $wgContLang; + global $wgUser, $wgOut, $wgLang, $wgContLang, $wgAuth; global $wgAllowRealName, $wgImageLimits, $wgThumbLimits; - global $wgDisableLangConversion; + global $wgDisableLangConversion, $wgDisableTitleConversion; global $wgEnotifWatchlist, $wgEnotifUserTalk,$wgEnotifMinorEdits; global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress; global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication; - global $wgContLanguageCode, $wgDefaultSkin, $wgSkipSkins, $wgAuth; - global $wgEmailConfirmToEdit, $wgAjaxSearch, $wgEnableMWSuggest; + global $wgContLanguageCode, $wgDefaultSkin, $wgCookieExpiration; + global $wgEmailConfirmToEdit, $wgEnableMWSuggest, $wgLocalTZoffset; $wgOut->setPageTitle( wfMsg( 'preferences' ) ); $wgOut->setArticleRelated( false ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->addScriptFile( 'prefs.js' ); $wgOut->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc. @@ -536,7 +552,6 @@ class PreferencesForm { } $qbs = $wgLang->getQuickbarSettings(); - $skinNames = $wgLang->getSkinNames(); $mathopts = $wgLang->getMathNames(); $dateopts = $wgLang->getDatePreferences(); $togs = User::getToggles(); @@ -552,6 +567,7 @@ class PreferencesForm { $this->mUsedToggles[ 'enotifrevealaddr' ] = true; $this->mUsedToggles[ 'ccmeonemails' ] = true; $this->mUsedToggles[ 'uselivepreview' ] = true; + $this->mUsedToggles[ 'noconvertlink' ] = true; if ( !$this->mEmailFlag ) { $emfc = 'checked="checked"'; } @@ -560,7 +576,13 @@ class PreferencesForm { if ($wgEmailAuthentication && ($this->mUserEmail != '') ) { if( $wgUser->getEmailAuthenticationTimestamp() ) { - $emailauthenticated = wfMsg('emailauthenticated',$wgLang->timeanddate($wgUser->getEmailAuthenticationTimestamp(), true ) ).'<br />'; + // date and time are separate parameters to facilitate localisation. + // $time is kept for backward compat reasons. + // 'emailauthenticated' is also used in SpecialConfirmemail.php + $time = $wgLang->timeAndDate( $wgUser->getEmailAuthenticationTimestamp(), true ); + $d = $wgLang->date( $wgUser->getEmailAuthenticationTimestamp(), true ); + $t = $wgLang->time( $wgUser->getEmailAuthenticationTimestamp(), true ); + $emailauthenticated = wfMsg('emailauthenticated', $time, $d, $t ).'<br />'; $disableEmailPrefs = false; } else { $disableEmailPrefs = true; @@ -620,26 +642,26 @@ class PreferencesForm { $toolLinks = array(); $toolLinks[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'ListGroupRights' ), wfMsg( 'listgrouprights' ) ); # At the moment one tool link only but be prepared for the future... - # FIXME: Add a link to Special:Userrights for users who are allowed to use it. + # FIXME: Add a link to Special:Userrights for users who are allowed to use it. # $wgUser->isAllowed( 'userrights' ) seems to strict in some cases $userInformationHtml = $this->tableRow( wfMsgHtml( 'username' ), htmlspecialchars( $wgUser->getName() ) ) . - $this->tableRow( wfMsgHtml( 'uid' ), htmlspecialchars( $wgUser->getId() ) ) . + $this->tableRow( wfMsgHtml( 'uid' ), $wgLang->formatNum( htmlspecialchars( $wgUser->getId() ) ) ). $this->tableRow( wfMsgExt( 'prefs-memberingroups', array( 'parseinline' ), count( $userEffectiveGroupsArray ) ), - implode( wfMsg( 'comma-separator' ), $userEffectiveGroupsArray ) . + $wgLang->commaList( $userEffectiveGroupsArray ) . '<br />(' . implode( ' | ', $toolLinks ) . ')' ) . $this->tableRow( wfMsgHtml( 'prefs-edits' ), - $wgLang->formatNum( User::edits( $wgUser->getId() ) ) + $wgLang->formatNum( $wgUser->getEditCount() ) ); if( wfRunHooks( 'PreferencesUserInformationPanel', array( $this, &$userInformationHtml ) ) ) { - $wgOut->addHtml( $userInformationHtml ); + $wgOut->addHTML( $userInformationHtml ); } if ( $wgAllowRealName ) { @@ -724,7 +746,7 @@ class PreferencesForm { } if(count($variantArray) > 1) { - $wgOut->addHtml( + $wgOut->addHTML( $this->tableRow( Xml::label( wfMsg( 'yourvariant' ), 'wpUserVariant' ), Xml::tags( 'select', @@ -734,30 +756,25 @@ class PreferencesForm { ) ); } + + if(count($variantArray) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion) { + $wgOut->addHTML( + Xml::tags( 'tr', null, + Xml::tags( 'td', array( 'colspan' => '2' ), + $this->getToggle( "noconvertlink" ) + ) + ) + ); + } } # Password if( $wgAuth->allowPasswordChange() ) { + $link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'ResetPass' ), wfMsgHtml( 'prefs-resetpass' ), + array() , array('returnto' => SpecialPage::getTitleFor( 'Preferences') ) ); $wgOut->addHTML( $this->tableRow( Xml::element( 'h2', null, wfMsg( 'changepassword' ) ) ) . - $this->tableRow( - Xml::label( wfMsg( 'oldpassword' ), 'wpOldpass' ), - Xml::password( 'wpOldpass', 25, $this->mOldpass, array( 'id' => 'wpOldpass' ) ) - ) . - $this->tableRow( - Xml::label( wfMsg( 'newpassword' ), 'wpNewpass' ), - Xml::password( 'wpNewpass', 25, $this->mNewpass, array( 'id' => 'wpNewpass' ) ) - ) . - $this->tableRow( - Xml::label( wfMsg( 'retypenew' ), 'wpRetypePass' ), - Xml::password( 'wpRetypePass', 25, $this->mRetypePass, array( 'id' => 'wpRetypePass' ) ) - ) . - Xml::tags( 'tr', null, - Xml::tags( 'td', array( 'colspan' => '2' ), - $this->getToggle( "rememberpassword" ) - ) - ) - ); + $this->tableRow( '<ul><li>' . $link . '</li></ul>' ) ); } # <FIXME> @@ -799,48 +816,49 @@ class PreferencesForm { # Quickbar # if ($this->mSkin == 'cologneblue' || $this->mSkin == 'standard') { - $wgOut->addHtml( "<fieldset>\n<legend>" . wfMsg( 'qbsettings' ) . "</legend>\n" ); + $wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg( 'qbsettings' ) . "</legend>\n" ); for ( $i = 0; $i < count( $qbs ); ++$i ) { if ( $i == $this->mQuickbar ) { $checked = ' checked="checked"'; } else { $checked = ""; } $wgOut->addHTML( "<div><label><input type='radio' name='wpQuickbar' value=\"$i\"$checked />{$qbs[$i]}</label></div>\n" ); } - $wgOut->addHtml( "</fieldset>\n\n" ); + $wgOut->addHTML( "</fieldset>\n\n" ); } else { # Need to output a hidden option even if the relevant skin is not in use, # otherwise the preference will get reset to 0 on submit - $wgOut->addHtml( wfHidden( 'wpQuickbar', $this->mQuickbar ) ); + $wgOut->addHTML( Xml::hidden( 'wpQuickbar', $this->mQuickbar ) ); } # Skin # - $wgOut->addHTML( "<fieldset>\n<legend>\n" . wfMsg('skin') . "</legend>\n" ); - $mptitle = Title::newMainPage(); - $previewtext = wfMsg('skinpreview'); - # Only show members of Skin::getSkinNames() rather than - # $skinNames (skins is all skin names from Language.php) - $validSkinNames = Skin::getSkinNames(); - # Sort by UI skin name. First though need to update validSkinNames as sometimes - # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI). - foreach ($validSkinNames as $skinkey => & $skinname ) { - if ( isset( $skinNames[$skinkey] ) ) { - $skinname = $skinNames[$skinkey]; + global $wgAllowUserSkin; + if( $wgAllowUserSkin ) { + $wgOut->addHTML( "<fieldset>\n<legend>\n" . wfMsg( 'skin' ) . "</legend>\n" ); + $mptitle = Title::newMainPage(); + $previewtext = wfMsg( 'skin-preview' ); + # Only show members of Skin::getSkinNames() rather than + # $skinNames (skins is all skin names from Language.php) + $validSkinNames = Skin::getUsableSkins(); + # Sort by UI skin name. First though need to update validSkinNames as sometimes + # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI). + foreach ( $validSkinNames as $skinkey => &$skinname ) { + $msgName = "skinname-{$skinkey}"; + $localisedSkinName = wfMsg( $msgName ); + if ( !wfEmptyMsg( $msgName, $localisedSkinName ) ) { + $skinname = $localisedSkinName; + } } - } - asort($validSkinNames); - foreach ($validSkinNames as $skinkey => $sn ) { - if ( in_array( $skinkey, $wgSkipSkins ) ) { - continue; + asort($validSkinNames); + foreach ($validSkinNames as $skinkey => $sn ) { + $checked = $skinkey == $this->mSkin ? ' checked="checked"' : ''; + $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) ); + $previewlink = "(<a target='_blank' href=\"$mplink\">$previewtext</a>)"; + if( $skinkey == $wgDefaultSkin ) + $sn .= ' (' . wfMsg( 'default' ) . ')'; + $wgOut->addHTML( "<input type='radio' name='wpSkin' id=\"wpSkin$skinkey\" value=\"$skinkey\"$checked /> <label for=\"wpSkin$skinkey\">{$sn}</label> $previewlink<br />\n" ); } - $checked = $skinkey == $this->mSkin ? ' checked="checked"' : ''; - - $mplink = htmlspecialchars($mptitle->getLocalURL("useskin=$skinkey")); - $previewlink = "<a target='_blank' href=\"$mplink\">$previewtext</a>"; - if( $skinkey == $wgDefaultSkin ) - $sn .= ' (' . wfMsg( 'default' ) . ')'; - $wgOut->addHTML( "<input type='radio' name='wpSkin' id=\"wpSkin$skinkey\" value=\"$skinkey\"$checked /> <label for=\"wpSkin$skinkey\">{$sn}</label> $previewlink<br />\n" ); + $wgOut->addHTML( "</fieldset>\n\n" ); } - $wgOut->addHTML( "</fieldset>\n\n" ); # Math # @@ -860,10 +878,6 @@ class PreferencesForm { # Files # - $wgOut->addHTML( - "<fieldset>\n" . Xml::element( 'legend', null, wfMsg( 'files' ) ) . "\n" - ); - $imageLimitOptions = null; foreach ( $wgImageLimits as $index => $limits ) { $selected = ($index == $this->mImageSize); @@ -871,14 +885,6 @@ class PreferencesForm { wfMsg('unit-pixel'), $index, $selected ); } - $imageSizeId = 'wpImageSize'; - $wgOut->addHTML( - "<div>" . Xml::label( wfMsg('imagemaxsize'), $imageSizeId ) . " " . - Xml::openElement( 'select', array( 'name' => $imageSizeId, 'id' => $imageSizeId ) ) . - $imageLimitOptions . - Xml::closeElement( 'select' ) . "</div>\n" - ); - $imageThumbOptions = null; foreach ( $wgThumbLimits as $index => $size ) { $selected = ($index == $this->mThumbSize); @@ -886,16 +892,34 @@ class PreferencesForm { $selected); } + $imageSizeId = 'wpImageSize'; $thumbSizeId = 'wpThumbSize'; $wgOut->addHTML( - "<div>" . Xml::label( wfMsg('thumbsize'), $thumbSizeId ) . " " . - Xml::openElement( 'select', array( 'name' => $thumbSizeId, 'id' => $thumbSizeId ) ) . - $imageThumbOptions . - Xml::closeElement( 'select' ) . "</div>\n" + Xml::fieldset( wfMsg( 'files' ) ) . "\n" . + Xml::openElement( 'table' ) . + '<tr> + <td class="mw-label">' . + Xml::label( wfMsg( 'imagemaxsize' ), $imageSizeId ) . + '</td> + <td class="mw-input">' . + Xml::openElement( 'select', array( 'name' => $imageSizeId, 'id' => $imageSizeId ) ) . + $imageLimitOptions . + Xml::closeElement( 'select' ) . + '</td> + </tr><tr> + <td class="mw-label">' . + Xml::label( wfMsg( 'thumbsize' ), $thumbSizeId ) . + '</td> + <td class="mw-input">' . + Xml::openElement( 'select', array( 'name' => $thumbSizeId, 'id' => $thumbSizeId ) ) . + $imageThumbOptions . + Xml::closeElement( 'select' ) . + '</td> + </tr>' . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ) ); - $wgOut->addHTML( "</fieldset>\n\n" ); - # Date format # # Date/Time @@ -929,18 +953,61 @@ class PreferencesForm { $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . "\n" ); } - $nowlocal = $wgLang->time( $now = wfTimestampNow(), true ); - $nowserver = $wgLang->time( $now, false ); + $nowlocal = Xml::openElement( 'span', array( 'id' => 'wpLocalTime' ) ) . + $wgLang->time( $now = wfTimestampNow(), true ) . + Xml::closeElement( 'span' ); + $nowserver = $wgLang->time( $now, false ) . + Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) ); $wgOut->addHTML( Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'timezonelegend' ) ) . Xml::openElement( 'table' ) . $this->addRow( wfMsg( 'servertime' ), $nowserver ) . - $this->addRow( wfMsg( 'localtime' ), $nowlocal ) . + $this->addRow( wfMsg( 'localtime' ), $nowlocal ) + ); + $opt = Xml::openElement( 'select', array( + 'name' => 'wpTimeZone', + 'id' => 'wpTimeZone', + 'onchange' => 'javascript:updateTimezoneSelection(false)' ) ); + $opt .= Xml::option( wfMsg( 'timezoneuseserverdefault' ), "System|$wgLocalTZoffset", $this->mTimeZone === "System|$wgLocalTZoffset" ); + $opt .= Xml::option( wfMsg( 'timezoneuseoffset' ), 'Offset', $this->mTimeZone === 'Offset' ); + if ( function_exists( 'timezone_identifiers_list' ) ) { + $optgroup = ''; + $tzs = timezone_identifiers_list(); + sort( $tzs ); + $selZone = explode( '|', $this->mTimeZone, 3); + $selZone = ( $selZone[0] == 'ZoneInfo' ) ? $selZone[2] : null; + $now = date_create( 'now' ); + foreach ( $tzs as $tz ) { + $z = explode( '/', $tz, 2 ); + # timezone_identifiers_list() returns a number of + # backwards-compatibility entries. This filters them out of the + # list presented to the user. + if ( count( $z ) != 2 || !in_array( $z[0], array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' ) ) ) continue; + if ( $optgroup != $z[0] ) { + if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' ); + $optgroup = $z[0]; + $opt .= Xml::openElement( 'optgroup', array( 'label' => $z[0] ) ); + } + $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 ); + $opt .= Xml::option( str_replace( '_', ' ', $tz ), "ZoneInfo|$minDiff|$tz", $selZone === $tz, array( 'label' => $z[1] ) ); + } + if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' ); + } + $opt .= Xml::closeElement( 'select' ); + $wgOut->addHTML( + $this->addRow( + Xml::label( wfMsg( 'timezoneselect' ), 'wpTimeZone' ), + $opt ) + ); + $wgOut->addHTML( $this->addRow( Xml::label( wfMsg( 'timezoneoffset' ), 'wpHourDiff' ), - Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array( 'id' => 'wpHourDiff' ) ) ) . + Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array( + 'id' => 'wpHourDiff', + 'onfocus' => 'javascript:updateTimezoneSelection(true)', + 'onblur' => 'javascript:updateTimezoneSelection(false)' ) ) ) . "<tr> <td></td> <td class='mw-submit'>" . @@ -961,12 +1028,11 @@ class PreferencesForm { # Editing # global $wgLivePreview; - $wgOut->addHTML( '<fieldset><legend>' . wfMsg( 'textboxsize' ) . '</legend> - <div>' . - wfInputLabel( wfMsg( 'rows' ), 'wpRows', 'wpRows', 3, $this->mRows ) . - ' ' . - wfInputLabel( wfMsg( 'columns' ), 'wpCols', 'wpCols', 3, $this->mCols ) . - "</div>" . + $wgOut->addHTML( + Xml::fieldset( wfMsg( 'textboxsize' ) ) . + wfMsgHTML( 'prefs-edit-boxsize' ) . ' ' . + Xml::inputLabel( wfMsg( 'rows' ), 'wpRows', 'wpRows', 3, $this->mRows ) . ' ' . + Xml::inputLabel( wfMsg( 'columns' ), 'wpCols', 'wpCols', 3, $this->mCols ) . $this->getToggles( array( 'editsection', 'editsectiononrightclick', @@ -980,62 +1046,76 @@ class PreferencesForm { 'externaldiff', $wgLivePreview ? 'uselivepreview' : false, 'forceeditsummary', - ) ) . '</fieldset>' + ) ) ); - # Recent changes - $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'prefs-rc' ) . '</legend>' ); - - $rc = '<table><tr>'; - $rc .= '<td>' . Xml::label( wfMsg( 'recentchangesdays' ), 'wpRecentDays' ) . '</td>'; - $rc .= '<td>' . Xml::input( 'wpRecentDays', 3, $this->mRecentDays, array( 'id' => 'wpRecentDays' ) ) . '</td>'; - $rc .= '</tr><tr>'; - $rc .= '<td>' . Xml::label( wfMsg( 'recentchangescount' ), 'wpRecent' ) . '</td>'; - $rc .= '<td>' . Xml::input( 'wpRecent', 3, $this->mRecent, array( 'id' => 'wpRecent' ) ) . '</td>'; - $rc .= '</tr></table>'; - $wgOut->addHtml( $rc ); + $wgOut->addHTML( Xml::closeElement( 'fieldset' ) ); - $wgOut->addHtml( '<br />' ); + # Recent changes + global $wgRCMaxAge; + $wgOut->addHTML( + Xml::fieldset( wfMsg( 'prefs-rc' ) ) . + Xml::openElement( 'table' ) . + '<tr> + <td class="mw-label">' . + Xml::label( wfMsg( 'recentchangesdays' ), 'wpRecentDays' ) . + '</td> + <td class="mw-input">' . + Xml::input( 'wpRecentDays', 3, $this->mRecentDays, array( 'id' => 'wpRecentDays' ) ) . ' ' . + wfMsgExt( 'recentchangesdays-max', 'parsemag', + $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600 * 24 ) ) ) ) . + '</td> + </tr><tr> + <td class="mw-label">' . + Xml::label( wfMsg( 'recentchangescount' ), 'wpRecent' ) . + '</td> + <td class="mw-input">' . + Xml::input( 'wpRecent', 3, $this->mRecent, array( 'id' => 'wpRecent' ) ) . + '</td> + </tr>' . + Xml::closeElement( 'table' ) . + '<br />' + ); $toggles[] = 'hideminor'; if( $wgRCShowWatchingUsers ) $toggles[] = 'shownumberswatching'; $toggles[] = 'usenewrc'; - $wgOut->addHtml( $this->getToggles( $toggles ) ); - $wgOut->addHtml( '</fieldset>' ); + $wgOut->addHTML( + $this->getToggles( $toggles ) . + Xml::closeElement( 'fieldset' ) + ); # Watchlist - $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'prefs-watchlist' ) . '</legend>' ); - - $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) ); - $wgOut->addHtml( '<br /><br />' ); - - $wgOut->addHtml( $this->getToggle( 'extendwatchlist' ) ); - $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) ); - $wgOut->addHtml( '<br /><br />' ); + $wgOut->addHTML( + Xml::fieldset( wfMsg( 'prefs-watchlist' ) ) . + Xml::inputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) . ' ' . + wfMsgHTML( 'prefs-watchlist-days-max' ) . + '<br /><br />' . + $this->getToggle( 'extendwatchlist' ) . + Xml::inputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) . ' ' . + wfMsgHTML( 'prefs-watchlist-edits-max' ) . + '<br /><br />' . + $this->getToggles( array( 'watchlisthideminor', 'watchlisthidebots', 'watchlisthideown', 'watchlisthideanons', 'watchlisthideliu' ) ) + ); - $wgOut->addHtml( $this->getToggles( array( 'watchlisthideown', 'watchlisthidebots', 'watchlisthideminor' ) ) ); + if( $wgUser->isAllowed( 'createpage' ) || $wgUser->isAllowed( 'createtalk' ) ) { + $wgOut->addHTML( $this->getToggle( 'watchcreations' ) ); + } - if( $wgUser->isAllowed( 'createpage' ) || $wgUser->isAllowed( 'createtalk' ) ) - $wgOut->addHtml( $this->getToggle( 'watchcreations' ) ); foreach( array( 'edit' => 'watchdefault', 'move' => 'watchmoves', 'delete' => 'watchdeletion' ) as $action => $toggle ) { if( $wgUser->isAllowed( $action ) ) - $wgOut->addHtml( $this->getToggle( $toggle ) ); + $wgOut->addHTML( $this->getToggle( $toggle ) ); } $this->mUsedToggles['watchcreations'] = true; $this->mUsedToggles['watchdefault'] = true; $this->mUsedToggles['watchmoves'] = true; $this->mUsedToggles['watchdeletion'] = true; - $wgOut->addHtml( '</fieldset>' ); + $wgOut->addHTML( Xml::closeElement( 'fieldset' ) ); # Search - $ajaxsearch = $wgAjaxSearch ? - $this->addRow( - Xml::label( wfMsg( 'useajaxsearch' ), 'wpUseAjaxSearch' ), - Xml::check( 'wpUseAjaxSearch', $this->mUseAjaxSearch, array( 'id' => 'wpUseAjaxSearch' ) ) - ) : ''; $mwsuggest = $wgEnableMWSuggest ? $this->addRow( Xml::label( wfMsg( 'mwsuggest-disable' ), 'wpDisableMWSuggest' ), @@ -1049,7 +1129,6 @@ class PreferencesForm { Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'prefs-searchoptions' ) ) . Xml::openElement( 'table' ) . - $ajaxsearch . $this->addRow( Xml::label( wfMsg( 'resultsperpage' ), 'wpSearch' ), Xml::input( 'wpSearch', 4, $this->mSearch, array( 'id' => 'wpSearch' ) ) @@ -1078,8 +1157,8 @@ class PreferencesForm { # Misc # $wgOut->addHTML('<fieldset><legend>' . wfMsg('prefs-misc') . '</legend>'); - $wgOut->addHtml( '<label for="wpStubs">' . wfMsg( 'stub-threshold' ) . '</label> ' ); - $wgOut->addHtml( Xml::input( 'wpStubs', 6, $this->mStubs, array( 'id' => 'wpStubs' ) ) ); + $wgOut->addHTML( '<label for="wpStubs">' . wfMsg( 'stub-threshold' ) . '</label> ' ); + $wgOut->addHTML( Xml::input( 'wpStubs', 6, $this->mStubs, array( 'id' => 'wpStubs' ) ) ); $msgUnderline = htmlspecialchars( wfMsg ( 'tog-underline' ) ); $msgUnderlinenever = htmlspecialchars( wfMsg ( 'underline-never' ) ); $msgUnderlinealways = htmlspecialchars( wfMsg ( 'underline-always' ) ); @@ -1098,9 +1177,13 @@ class PreferencesForm { foreach ( $togs as $tname ) { if( !array_key_exists( $tname, $this->mUsedToggles ) ) { - $wgOut->addHTML( $this->getToggle( $tname ) ); + if( $tname == 'norollbackdiff' && $wgUser->isAllowed( 'rollback' ) ) + $wgOut->addHTML( $this->getToggle( $tname ) ); + else + $wgOut->addHTML( $this->getToggle( $tname ) ); } } + $wgOut->addHTML( '</fieldset>' ); wfRunHooks( 'RenderPreferencesForm', array( $this, $wgOut ) ); @@ -1119,7 +1202,7 @@ class PreferencesForm { <input type='hidden' name='wpEditToken' value=\"{$token}\" /> </div></form>\n" ); - $wgOut->addHtml( Xml::tags( 'div', array( 'class' => "prefcache" ), + $wgOut->addHTML( Xml::tags( 'div', array( 'class' => "prefcache" ), wfMsgExt( 'clearyourcache', 'parseinline' ) ) ); } diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php index 9c880349..ea0c1135 100644 --- a/includes/specials/SpecialPrefixindex.php +++ b/includes/specials/SpecialPrefixindex.php @@ -1,40 +1,4 @@ <?php -/** - * @file - * @ingroup SpecialPage - */ - -/** - * Entry point : initialise variables and call subfunctions. - * @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default NULL) - * @param $specialPage SpecialPage object. - */ -function wfSpecialPrefixIndex( $par=NULL, $specialPage ) { - global $wgRequest, $wgOut, $wgContLang; - - # GET values - $from = $wgRequest->getVal( 'from' ); - $prefix = $wgRequest->getVal( 'prefix' ); - $namespace = $wgRequest->getInt( 'namespace' ); - $namespaces = $wgContLang->getNamespaces(); - - $indexPage = new SpecialPrefixIndex(); - - $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) - ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) - : wfMsg( 'allarticles' ) - ); - - if ( isset($par) ) { - $indexPage->showChunk( $namespace, $par, $specialPage->including(), $from ); - } elseif ( isset($prefix) ) { - $indexPage->showChunk( $namespace, $prefix, $specialPage->including(), $from ); - } elseif ( isset($from) ) { - $indexPage->showChunk( $namespace, $from, $specialPage->including(), $from ); - } else { - $wgOut->addHtml($indexPage->namespaceForm ( $namespace, null )); - } -} /** * implements Special:Prefixindex @@ -44,18 +8,90 @@ class SpecialPrefixindex extends SpecialAllpages { // Inherit $maxPerPage // Define other properties - protected $name = 'Prefixindex'; protected $nsfromMsg = 'allpagesprefix'; + + function __construct(){ + parent::__construct( 'Prefixindex' ); + } + + /** + * Entry point : initialise variables and call subfunctions. + * @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default null) + */ + function execute( $par ) { + global $wgRequest, $wgOut, $wgContLang; + + $this->setHeaders(); + $this->outputHeader(); + + # GET values + $from = $wgRequest->getVal( 'from' ); + $prefix = $wgRequest->getVal( 'prefix' ); + $namespace = $wgRequest->getInt( 'namespace' ); + $namespaces = $wgContLang->getNamespaces(); + + $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) + ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) + : wfMsg( 'prefixindex' ) + ); + + if( isset( $par ) ){ + $this->showPrefixChunk( $namespace, $par, $from ); + } elseif( isset( $prefix ) ){ + $this->showPrefixChunk( $namespace, $prefix, $from ); + } elseif( isset( $from ) ){ + $this->showPrefixChunk( $namespace, $from, $from ); + } else { + $wgOut->addHTML( $this->namespacePrefixForm( $namespace, null ) ); + } + } + + /** + * HTML for the top form + * @param integer $namespace A namespace constant (default NS_MAIN). + * @param string $from dbKey we are starting listing at. + */ + function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) { + global $wgScript; + $t = $this->getTitle(); + + $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); + $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); + $out .= Xml::hidden( 'title', $t->getPrefixedText() ); + $out .= Xml::openElement( 'fieldset' ); + $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) ); + $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); + $out .= "<tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . + "</td> + <td class='mw-input'>" . + Xml::namespaceSelector( $namespace, null ) . ' ' . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . + "</td> + </tr>"; + $out .= Xml::closeElement( 'table' ); + $out .= Xml::closeElement( 'fieldset' ); + $out .= Xml::closeElement( 'form' ); + $out .= Xml::closeElement( 'div' ); + return $out; + } /** * @param integer $namespace (Default NS_MAIN) * @param string $from list all pages from this name (default FALSE) */ - function showChunk( $namespace = NS_MAIN, $prefix, $including = false, $from = null ) { + function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) { global $wgOut, $wgUser, $wgContLang; - $fname = 'indexShowChunk'; - $sk = $wgUser->getSkin(); if (!isset($from)) $from = $prefix; @@ -86,7 +122,7 @@ class SpecialPrefixindex extends SpecialAllpages { 'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'', 'page_title >= ' . $dbr->addQuotes( $fromKey ), ), - $fname, + __METHOD__, array( 'ORDER BY' => 'page_title', 'LIMIT' => $this->maxPerPage + 1, @@ -100,7 +136,7 @@ class SpecialPrefixindex extends SpecialAllpages { if( $res->numRows() > 0 ) { $out = '<table style="background: inherit;" border="0" width="100%">'; - while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { + while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $t = Title::makeTitle( $s->page_namespace, $s->page_title ); if( $t ) { $link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . @@ -127,26 +163,27 @@ class SpecialPrefixindex extends SpecialAllpages { } } - if ( $including ) { + if ( $this->including() ) { $out2 = ''; } else { - $nsForm = $this->namespaceForm ( $namespace, $prefix ); + $nsForm = $this->namespacePrefixForm( $namespace, $prefix ); + $self = $this->getTitle(); $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; $out2 .= '<tr valign="top"><td>' . $nsForm; $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . - $sk->makeKnownLink( $wgContLang->specialPage( $this->name ), + $sk->makeKnownLinkObj( $self, wfMsg ( 'allpages' ) ); - if ( isset($dbr) && $dbr && ($n == $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { + if( isset( $res ) && $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $namespaceparam = $namespace ? "&namespace=$namespace" : ""; - $out2 .= " | " . $sk->makeKnownLink( - $wgContLang->specialPage( $this->name ), + $out2 .= " | " . $sk->makeKnownLinkObj( + $self, wfMsgHtml( 'nextpage', htmlspecialchars( $s->page_title ) ), - "from=" . wfUrlEncode ( $s->page_title ) . - "&prefix=" . wfUrlEncode ( $prefix ) . $namespaceparam ); + "from=" . wfUrlEncode( $s->page_title ) . + "&prefix=" . wfUrlEncode( $prefix ) . $namespaceparam ); } $out2 .= "</td></tr></table><hr />"; } - $wgOut->addHtml( $out2 . $out ); + $wgOut->addHTML( $out2 . $out ); } } diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php index 3025c055..4e56ca42 100644 --- a/includes/specials/SpecialProtectedpages.php +++ b/includes/specials/SpecialProtectedpages.php @@ -16,7 +16,6 @@ class ProtectedPagesForm { public function showList( $msg = '' ) { global $wgOut, $wgRequest; - $wgOut->setPagetitle( wfMsg( "protectedpages" ) ); if ( "" != $msg ) { $wgOut->setSubtitle( $msg ); } @@ -32,10 +31,11 @@ class ProtectedPagesForm { $size = $wgRequest->getIntOrNull( 'size' ); $NS = $wgRequest->getIntOrNull( 'namespace' ); $indefOnly = $wgRequest->getBool( 'indefonly' ) ? 1 : 0; + $cascadeOnly = $wgRequest->getBool('cascadeonly') ? 1 : 0; - $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly ); + $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly, $cascadeOnly ); - $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly ) ); + $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly, $cascadeOnly ) ); if ( $pager->getNumRows() ) { $s = $pager->getNavigationBar(); @@ -83,7 +83,7 @@ class ProtectedPagesForm { if ( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { $expiry = Block::decodeExpiry( $row->pr_expiry ); - $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); + $expiry_description = wfMsg( 'protect-expiring' , $wgLang->timeanddate( $expiry ) , $wgLang->date( $expiry ) , $wgLang->time( $expiry ) ); $description_items[] = $expiry_description; } @@ -111,21 +111,24 @@ class ProtectedPagesForm { * @param $level string * @param $minsize int * @param $indefOnly bool + * @param $cascadeOnly bool * @return string Input form * @private */ - protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly ) { + protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) { global $wgScript; $title = SpecialPage::getTitleFor( 'ProtectedPages' ); return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', array(), wfMsg( 'protectedpages' ) ) . - Xml::hidden( 'title', $title->getPrefixedDBkey() ) . " \n" . + Xml::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" . $this->getNamespaceMenu( $namespace ) . " \n" . $this->getTypeMenu( $type ) . " \n" . $this->getLevelMenu( $level ) . " \n" . - "<br /><span style='white-space: nowrap'> " . + "<br/><span style='white-space: nowrap'>" . $this->getExpiryCheck( $indefOnly ) . " \n" . + $this->getCascadeCheck( $cascadeOnly ) . " \n" . + "</span><br/><span style='white-space: nowrap'>" . $this->getSizeLimit( $sizetype, $size ) . " \n" . "</span>" . " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . @@ -153,6 +156,14 @@ class ProtectedPagesForm { return Xml::checkLabel( wfMsg('protectedpages-indef'), 'indefonly', 'indefonly', $indefOnly ) . "\n"; } + + /** + * @return string Formatted HTML + */ + protected function getCascadeCheck( $cascadeOnly ) { + return + Xml::checkLabel( wfMsg('protectedpages-cascade'), 'cascadeonly', 'cascadeonly', $cascadeOnly ) . "\n"; + } /** * @return string Formatted HTML @@ -237,7 +248,8 @@ class ProtectedPagesPager extends AlphabeticPager { public $mForm, $mConds; private $type, $level, $namespace, $sizetype, $size, $indefonly; - function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0, $indefonly=false ) { + function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', + $size=0, $indefonly = false, $cascadeonly = false ) { $this->mForm = $form; $this->mConds = $conds; $this->type = ( $type ) ? $type : 'edit'; @@ -246,6 +258,7 @@ class ProtectedPagesPager extends AlphabeticPager { $this->sizetype = $sizetype; $this->size = intval($size); $this->indefonly = (bool)$indefonly; + $this->cascadeonly = (bool)$cascadeonly; parent::__construct(); } @@ -281,6 +294,9 @@ class ProtectedPagesPager extends AlphabeticPager { if( $this->indefonly ) { $conds[] = "pr_expiry = 'infinity' OR pr_expiry IS NULL"; } + if ( $this->cascadeonly ) { + $conds[] = "pr_cascade = '1'"; + } if( $this->level ) $conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level ); diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php index 2ec68a66..7e8126d9 100644 --- a/includes/specials/SpecialProtectedtitles.php +++ b/includes/specials/SpecialProtectedtitles.php @@ -16,7 +16,6 @@ class ProtectedTitlesForm { function showList( $msg = '' ) { global $wgOut, $wgRequest; - $wgOut->setPagetitle( wfMsg( "protectedtitles" ) ); if ( "" != $msg ) { $wgOut->setSubtitle( $msg ); } @@ -75,7 +74,7 @@ class ProtectedTitlesForm { if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) { $expiry = Block::decodeExpiry( $row->pt_expiry ); - $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); + $expiry_description = wfMsg( 'protect-expiring', $wgLang->timeanddate( $expiry ) , $wgLang->date( $expiry ) , $wgLang->time( $expiry ) ); $description_items[] = $expiry_description; } @@ -102,7 +101,7 @@ class ProtectedTitlesForm { Xml::element( 'legend', array(), wfMsg( 'protectedtitles' ) ) . Xml::hidden( 'title', $special ) . " \n" . $this->getNamespaceMenu( $namespace ) . " \n" . - // $this->getLevelMenu( $level ) . "<br/>\n" . + $this->getLevelMenu( $level ) . " \n" . " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . "</fieldset></form>"; } @@ -137,7 +136,10 @@ class ProtectedTitlesForm { $m[$text] = $type; } } - + // Is there only one level (aside from "all")? + if( count($m) <= 2 ) { + return ''; + } // Third pass generates sorted XHTML content foreach( $m as $text => $type ) { $selected = ($type == $pr_level ); @@ -190,7 +192,8 @@ class ProtectedtitlesPager extends AlphabeticPager { function getQueryInfo() { $conds = $this->mConds; $conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - + if( $this->level ) + $conds['pt_create_perm'] = $this->level; if( !is_null($this->namespace) ) $conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace ); return array( diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php index 0e7ada1d..f4bff30b 100644 --- a/includes/specials/SpecialRandompage.php +++ b/includes/specials/SpecialRandompage.php @@ -8,19 +8,23 @@ * @license GNU General Public Licence 2.0 or later */ class RandomPage extends SpecialPage { - private $namespace = NS_MAIN; // namespace to select pages from + private $namespaces; // namespaces to select pages from function __construct( $name = 'Randompage' ){ + global $wgContentNamespaces; + + $this->namespaces = $wgContentNamespaces; + parent::__construct( $name ); } - public function getNamespace() { - return $this->namespace; + public function getNamespaces() { + return $this->namespaces; } public function setNamespace ( $ns ) { if( $ns < NS_MAIN ) $ns = NS_MAIN; - $this->namespace = $ns; + $this->namespaces = array( $ns ); } // select redirects instead of normal pages? @@ -39,7 +43,7 @@ class RandomPage extends SpecialPage { if( is_null( $title ) ) { $this->setHeaders(); - $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages' ); + $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages', $wgContLang->getNsText( $this->namespace ) ); return; } @@ -67,7 +71,7 @@ class RandomPage extends SpecialPage { $row = $this->selectRandomPageFromDB( "0" ); if( $row ) - return Title::makeTitleSafe( $this->namespace, $row->page_title ); + return Title::makeTitleSafe( $row->page_namespace, $row->page_title ); else return null; } @@ -81,13 +85,13 @@ class RandomPage extends SpecialPage { $use_index = $dbr->useIndexClause( 'page_random' ); $page = $dbr->tableName( 'page' ); - $ns = (int) $this->namespace; + $ns = implode( ",", $this->namespaces ); $redirect = $this->isRedirect() ? 1 : 0; $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : ""; - $sql = "SELECT page_title + $sql = "SELECT page_title, page_namespace FROM $page $use_index - WHERE page_namespace = $ns + WHERE page_namespace IN ( $ns ) AND page_is_redirect = $redirect AND page_random >= $randstr $extra diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index cb718bdc..8c14e1fc 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -6,7 +6,7 @@ */ class SpecialRecentChanges extends SpecialPage { public function __construct() { - SpecialPage::SpecialPage( 'Recentchanges' ); + parent::__construct( 'Recentchanges' ); $this->includable( true ); } @@ -16,13 +16,14 @@ class SpecialRecentChanges extends SpecialPage { * @return FormOptions */ public function getDefaultOptions() { + global $wgUser; $opts = new FormOptions(); - $opts->add( 'days', (int)User::getDefaultOption( 'rcdays' ) ); - $opts->add( 'limit', (int)User::getDefaultOption( 'rclimit' ) ); + $opts->add( 'days', (int)$wgUser->getOption( 'rcdays' ) ); + $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) ); $opts->add( 'from', '' ); - $opts->add( 'hideminor', false ); + $opts->add( 'hideminor', (bool)$wgUser->getOption( 'hideminor' ) ); $opts->add( 'hidebots', true ); $opts->add( 'hideanons', false ); $opts->add( 'hideliu', false ); @@ -34,7 +35,6 @@ class SpecialRecentChanges extends SpecialPage { $opts->add( 'categories', '' ); $opts->add( 'categories_any', false ); - return $opts; } @@ -44,16 +44,13 @@ class SpecialRecentChanges extends SpecialPage { * @return FormOptions */ public function setup( $parameters ) { - global $wgUser, $wgRequest; + global $wgRequest; $opts = $this->getDefaultOptions(); - $opts['days'] = (int)$wgUser->getOption( 'rcdays', $opts['days'] ); - $opts['limit'] = (int)$wgUser->getOption( 'rclimit', $opts['limit'] ); - $opts['hideminor'] = $wgUser->getOption( 'hideminor', $opts['hideminor'] ); $opts->fetchValuesFromRequest( $wgRequest ); // Give precedence to subpage syntax - if ( $parameters !== null ) { + if( $parameters !== null ) { $this->parseParameters( $parameters, $opts ); } @@ -85,9 +82,9 @@ class SpecialRecentChanges extends SpecialPage { # 10 seconds server-side caching max $wgOut->setSquidMaxage( 10 ); - + # Check if the client has a cached version $lastmod = $this->checkLastModified( $feedFormat ); - if( $lastmod === false ){ + if( $lastmod === false ) { return; } @@ -97,33 +94,32 @@ class SpecialRecentChanges extends SpecialPage { // Fetch results, prepare a batch link existence check query $rows = array(); - $batch = new LinkBatch; $conds = $this->buildMainQueryConds( $opts ); - $res = $this->doMainQuery( $conds, $opts ); - if( $res === false ){ - $this->doHeader( $opts ); + $rows = $this->doMainQuery( $conds, $opts ); + if( $rows === false ){ + if( !$this->including() ) { + $this->doHeader( $opts ); + } return; } - $dbr = wfGetDB( DB_SLAVE ); - while( $row = $dbr->fetchObject( $res ) ){ - $rows[] = $row; - if ( !$feedFormat ) { - // User page and talk links + + if( !$feedFormat ) { + $batch = new LinkBatch; + foreach( $rows as $row ) { $batch->add( NS_USER, $row->rc_user_text ); $batch->add( NS_USER_TALK, $row->rc_user_text ); } - + $batch->execute(); } - $dbr->freeResult( $res ); - if ( $feedFormat ) { + if( $feedFormat ) { list( $feed, $feedObj ) = $this->getFeedObject( $feedFormat ); $feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod ); } else { - $batch->execute(); $this->webOutput( $rows, $opts ); } - + + $rows->free(); } /** @@ -149,21 +145,21 @@ class SpecialRecentChanges extends SpecialPage { */ public function parseParameters( $par, FormOptions $opts ) { $bits = preg_split( '/\s*,\s*/', trim( $par ) ); - foreach ( $bits as $bit ) { - if ( 'hidebots' === $bit ) $opts['hidebots'] = true; - if ( 'bots' === $bit ) $opts['hidebots'] = false; - if ( 'hideminor' === $bit ) $opts['hideminor'] = true; - if ( 'minor' === $bit ) $opts['hideminor'] = false; - if ( 'hideliu' === $bit ) $opts['hideliu'] = true; - if ( 'hidepatrolled' === $bit ) $opts['hidepatrolled'] = true; - if ( 'hideanons' === $bit ) $opts['hideanons'] = true; - if ( 'hidemyself' === $bit ) $opts['hidemyself'] = true; - - if ( is_numeric( $bit ) ) $opts['limit'] = $bit; + foreach( $bits as $bit ) { + if( 'hidebots' === $bit ) $opts['hidebots'] = true; + if( 'bots' === $bit ) $opts['hidebots'] = false; + if( 'hideminor' === $bit ) $opts['hideminor'] = true; + if( 'minor' === $bit ) $opts['hideminor'] = false; + if( 'hideliu' === $bit ) $opts['hideliu'] = true; + if( 'hidepatrolled' === $bit ) $opts['hidepatrolled'] = true; + if( 'hideanons' === $bit ) $opts['hideanons'] = true; + if( 'hidemyself' === $bit ) $opts['hidemyself'] = true; + + if( is_numeric( $bit ) ) $opts['limit'] = $bit; $m = array(); - if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) $opts['limit'] = $m[1]; - if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) $opts['days'] = $m[1]; + if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) $opts['limit'] = $m[1]; + if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) $opts['days'] = $m[1]; } } @@ -173,14 +169,14 @@ class SpecialRecentChanges extends SpecialPage { * update the timestamp * * @param $feedFormat String - * @return int or false + * @return string or false */ public function checkLastModified( $feedFormat ) { global $wgUseRCPatrol, $wgOut; $dbr = wfGetDB( DB_SLAVE ); $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __FUNCTION__ ); - if ( $feedFormat || !$wgUseRCPatrol ) { - if( $lastmod && $wgOut->checkLastModified( $lastmod ) ){ + if( $feedFormat || !$wgUseRCPatrol ) { + if( $lastmod && $wgOut->checkLastModified( $lastmod ) ) { # Client cache fresh and headers sent, nothing more to do. return false; } @@ -232,12 +228,12 @@ class SpecialRecentChanges extends SpecialPage { $hideLoggedInUsers = $opts['hideliu'] && !$forcebot; $hideAnonymousUsers = $opts['hideanons'] && !$forcebot; - if ( $opts['hideminor'] ) $conds['rc_minor'] = 0; - if ( $opts['hidebots'] ) $conds['rc_bot'] = 0; - if ( $hidePatrol ) $conds['rc_patrolled'] = 0; - if ( $forcebot ) $conds['rc_bot'] = 1; - if ( $hideLoggedInUsers ) $conds[] = 'rc_user = 0'; - if ( $hideAnonymousUsers ) $conds[] = 'rc_user != 0'; + if( $opts['hideminor'] ) $conds['rc_minor'] = 0; + if( $opts['hidebots'] ) $conds['rc_bot'] = 0; + if( $hidePatrol ) $conds['rc_patrolled'] = 0; + if( $forcebot ) $conds['rc_bot'] = 1; + if( $hideLoggedInUsers ) $conds[] = 'rc_user = 0'; + if( $hideAnonymousUsers ) $conds[] = 'rc_user != 0'; if( $opts['hidemyself'] ) { if( $wgUser->getId() ) { @@ -248,8 +244,8 @@ class SpecialRecentChanges extends SpecialPage { } # Namespace filtering - if ( $opts['namespace'] !== '' ) { - if ( !$opts['invert'] ) { + if( $opts['namespace'] !== '' ) { + if( !$opts['invert'] ) { $conds[] = 'rc_namespace = ' . $dbr->addQuotes( $opts['namespace'] ); } else { $conds[] = 'rc_namespace != ' . $dbr->addQuotes( $opts['namespace'] ); @@ -281,7 +277,8 @@ class SpecialRecentChanges extends SpecialPage { // JOIN on watchlist for users if( $uid ) { $tables[] = 'watchlist'; - $join_conds = array( 'watchlist' => array('LEFT JOIN',"wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace") ); + $join_conds = array( 'watchlist' => array('LEFT JOIN', + "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace") ); } wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) ); @@ -329,7 +326,7 @@ class SpecialRecentChanges extends SpecialPage { $limit = $opts['limit']; - if ( !$this->including() ) { + if( !$this->including() ) { // Output options box $this->doHeader( $opts ); } @@ -337,55 +334,46 @@ class SpecialRecentChanges extends SpecialPage { // And now for the content $wgOut->setSyndicated( true ); - $list = ChangesList::newFromUser( $wgUser ); - - if ( $wgAllowCategorizedRecentChanges ) { + if( $wgAllowCategorizedRecentChanges ) { $this->filterByCategories( $rows, $opts ); } - $s = $list->beginRecentChangesList(); - $counter = 1; - $showWatcherCount = $wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' ); $watcherCache = array(); $dbr = wfGetDB( DB_SLAVE ); - foreach( $rows as $obj ){ - if( $limit == 0) { - break; - } - - if ( ! ( $opts['hideminor'] && $obj->rc_minor ) && - ! ( $opts['hidepatrolled'] && $obj->rc_patrolled ) ) { - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - - if ($wgShowUpdatedMarker - && !empty( $obj->wl_notificationtimestamp ) - && ($obj->rc_timestamp >= $obj->wl_notificationtimestamp)) { - $rc->notificationtimestamp = true; - } else { - $rc->notificationtimestamp = false; - } + $counter = 1; + $list = ChangesList::newFromUser( $wgUser ); - $rc->numberofWatchingusers = 0; // Default - if ($showWatcherCount && $obj->rc_namespace >= 0) { - if (!isset($watcherCache[$obj->rc_namespace][$obj->rc_title])) { - $watcherCache[$obj->rc_namespace][$obj->rc_title] = - $dbr->selectField( 'watchlist', - 'COUNT(*)', - array( - 'wl_namespace' => $obj->rc_namespace, - 'wl_title' => $obj->rc_title, - ), - __METHOD__ . '-watchers' ); - } - $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; + $s = $list->beginRecentChangesList(); + foreach( $rows as $obj ) { + if( $limit == 0 ) break; + $rc = RecentChange::newFromRow( $obj ); + $rc->counter = $counter++; + # Check if the page has been updated since the last visit + if( $wgShowUpdatedMarker && !empty($obj->wl_notificationtimestamp) ) { + $rc->notificationtimestamp = ($obj->rc_timestamp >= $obj->wl_notificationtimestamp); + } else { + $rc->notificationtimestamp = false; // Default + } + # Check the number of users watching the page + $rc->numberofWatchingusers = 0; // Default + if( $showWatcherCount && $obj->rc_namespace >= 0 ) { + if( !isset($watcherCache[$obj->rc_namespace][$obj->rc_title]) ) { + $watcherCache[$obj->rc_namespace][$obj->rc_title] = + $dbr->selectField( 'watchlist', + 'COUNT(*)', + array( + 'wl_namespace' => $obj->rc_namespace, + 'wl_title' => $obj->rc_title, + ), + __METHOD__ . '-watchers' ); } - $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ) ); - --$limit; + $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } + $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ) ); + --$limit; } $s .= $list->endRecentChangesList(); $wgOut->addHTML( $s ); @@ -411,22 +399,29 @@ class SpecialRecentChanges extends SpecialPage { $panel[] = '<hr />'; $extraOpts = $this->getExtraOptions( $opts ); + $extraOptsCount = count( $extraOpts ); + $count = 0; + $submit = ' ' . Xml::submitbutton( wfMsg( 'allpagessubmit' ) ); + + $out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) ); + foreach( $extraOpts as $optionRow ) { + # Add submit button to the last row only + ++$count; + $addSubmit = $count === $extraOptsCount ? $submit : ''; - $out = Xml::openElement( 'table' ); - foreach ( $extraOpts as $optionRow ) { $out .= Xml::openElement( 'tr' ); - if ( is_array($optionRow) ) { - $out .= Xml::tags( 'td', null, $optionRow[0] ); - $out .= Xml::tags( 'td', null, $optionRow[1] ); + if( is_array( $optionRow ) ) { + $out .= Xml::tags( 'td', array( 'class' => 'mw-label' ), $optionRow[0] ); + $out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit ); } else { - $out .= Xml::tags( 'td', array( 'colspan' => 2 ), $optionRow ); + $out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit ); } $out .= Xml::closeElement( 'tr' ); } $out .= Xml::closeElement( 'table' ); $unconsumed = $opts->getUnconsumedValues(); - foreach ( $unconsumed as $key => $value ) { + foreach( $unconsumed as $key => $value ) { $out .= Xml::hidden( $key, $value ); } @@ -437,7 +432,7 @@ class SpecialRecentChanges extends SpecialPage { $panelString = implode( "\n", $panel ); $wgOut->addHTML( - Xml::fieldset( wfMsg( strtolower( $this->mName ) ), $panelString, array( 'class' => 'rcoptions' ) ) + Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) ) ); $this->setBottomText( $wgOut, $opts ); @@ -454,12 +449,11 @@ class SpecialRecentChanges extends SpecialPage { $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); global $wgAllowCategorizedRecentChanges; - if ( $wgAllowCategorizedRecentChanges ) { + if( $wgAllowCategorizedRecentChanges ) { $extraOpts['category'] = $this->categoryFilterForm( $opts ); } wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); - $extraOpts['submit'] = Xml::submitbutton( wfMsg('allpagessubmit') ); return $extraOpts; } @@ -469,7 +463,7 @@ class SpecialRecentChanges extends SpecialPage { * @param $out OutputPage * @param $opts FormOptions */ - function setTopText( &$out, $opts ){ + function setTopText( OutputPage $out, FormOptions $opts ){ $out->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) ); } @@ -480,7 +474,7 @@ class SpecialRecentChanges extends SpecialPage { * @param $out OutputPage * @param $opts FormOptions */ - function setBottomText( &$out, $opts ){} + function setBottomText( OutputPage $out, FormOptions $opts ){} /** * Creates the choose namespace selection @@ -489,7 +483,7 @@ class SpecialRecentChanges extends SpecialPage { * @return string */ protected function namespaceFilterForm( FormOptions $opts ) { - $nsSelect = HTMLnamespaceselector( $opts['namespace'], '' ); + $nsSelect = Xml::namespaceSelector( $opts['namespace'], '' ); $nsLabel = Xml::label( wfMsg('namespace'), 'namespace' ); $invert = Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $opts['invert'] ); return array( $nsLabel, "$nsSelect $invert" ); @@ -526,30 +520,30 @@ class SpecialRecentChanges extends SpecialPage { # Filter categories $cats = array(); - foreach ( $categories as $cat ) { + foreach( $categories as $cat ) { $cat = trim( $cat ); - if ( $cat == "" ) continue; + if( $cat == "" ) continue; $cats[] = $cat; } # Filter articles $articles = array(); $a2r = array(); - foreach ( $rows AS $k => $r ) { + foreach( $rows AS $k => $r ) { $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); $id = $nt->getArticleID(); - if ( $id == 0 ) continue; # Page might have been deleted... - if ( !in_array($id, $articles) ) { + if( $id == 0 ) continue; # Page might have been deleted... + if( !in_array($id, $articles) ) { $articles[] = $id; } - if ( !isset($a2r[$id]) ) { + if( !isset($a2r[$id]) ) { $a2r[$id] = array(); } $a2r[$id][] = $k; } # Shortcut? - if ( !count($articles) || !count($cats) ) + if( !count($articles) || !count($cats) ) return ; # Look up @@ -559,8 +553,8 @@ class SpecialRecentChanges extends SpecialPage { # Filter $newrows = array(); - foreach ( $match AS $id ) { - foreach ( $a2r[$id] AS $rev ) { + foreach( $match AS $id ) { + foreach( $a2r[$id] AS $rev ) { $k = $rev; $newrows[$k] = $rows[$k]; } @@ -577,8 +571,9 @@ class SpecialRecentChanges extends SpecialPage { function makeOptionsLink( $title, $override, $options, $active = false ) { global $wgUser; $sk = $wgUser->getSkin(); - return $sk->makeKnownLinkObj( $this->getTitle(), htmlspecialchars( $title ), - wfArrayToCGI( $override, $options ), '', '', $active ? 'style="font-weight: bold;"' : '' ); + $params = $override + $options; + return $sk->link( $this->getTitle(), htmlspecialchars( $title ), + ( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) ); } /** @@ -591,43 +586,41 @@ class SpecialRecentChanges extends SpecialPage { $options = $nondefaults + $defaults; - if( $options['from'] ) - $note = wfMsgExt( 'rcnotefrom', array( 'parseinline' ), + $note = ''; + if( $options['from'] ) { + $note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ), $wgLang->formatNum( $options['limit'] ), - $wgLang->timeanddate( $options['from'], true ) ); - else - $note = wfMsgExt( 'rcnote', array( 'parseinline' ), - $wgLang->formatNum( $options['limit'] ), - $wgLang->formatNum( $options['days'] ), - $wgLang->timeAndDate( wfTimestampNow(), true ), - $wgLang->date( wfTimestampNow(), true ), - $wgLang->time( wfTimestampNow(), true ) ); + $wgLang->timeanddate( $options['from'], true ) ) . '<br />'; + } + if( !wfEmptyMsg( 'rclegend', wfMsg('rclegend') ) ) { + $note .= wfMsgExt( 'rclegend', array('parseinline') ) . '<br />'; + } # Sort data for display and make sure it's unique after we've added user data. $wgRCLinkLimits[] = $options['limit']; $wgRCLinkDays[] = $options['days']; - sort($wgRCLinkLimits); - sort($wgRCLinkDays); - $wgRCLinkLimits = array_unique($wgRCLinkLimits); - $wgRCLinkDays = array_unique($wgRCLinkDays); + sort( $wgRCLinkLimits ); + sort( $wgRCLinkDays ); + $wgRCLinkLimits = array_unique( $wgRCLinkLimits ); + $wgRCLinkDays = array_unique( $wgRCLinkDays ); // limit links foreach( $wgRCLinkLimits as $value ) { $cl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ), array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ; } - $cl = implode( ' | ', $cl); + $cl = implode( ' | ', $cl ); // day links, reset 'from' to none foreach( $wgRCLinkDays as $value ) { $dl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ), array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ; } - $dl = implode( ' | ', $dl); + $dl = implode( ' | ', $dl ); // show/hide links - $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' )); + $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) ); $minorLink = $this->makeOptionsLink( $showhide[1-$options['hideminor']], array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults); $botLink = $this->makeOptionsLink( $showhide[1-$options['hidebots']], @@ -652,11 +645,11 @@ class SpecialRecentChanges extends SpecialPage { // show from this onward link $now = $wgLang->timeanddate( wfTimestampNow(), true ); - $tl = $this->makeOptionsLink( $now, array( 'from' => wfTimestampNow()), $nondefaults ); + $tl = $this->makeOptionsLink( $now, array( 'from' => wfTimestampNow() ), $nondefaults ); - $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter'), + $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter' ), $cl, $dl, $hl ); - $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter'), $tl ); - return "$note<br />$rclinks<br />$rclistfrom"; + $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter' ), $tl ); + return "{$note}$rclinks<br />$rclistfrom"; } } diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php index d773fb77..c0734354 100644 --- a/includes/specials/SpecialRecentchangeslinked.php +++ b/includes/specials/SpecialRecentchangeslinked.php @@ -7,7 +7,8 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { function __construct(){ - SpecialPage::SpecialPage( 'Recentchangeslinked' ); + SpecialPage::SpecialPage( 'Recentchangeslinked' ); + $this->includable( true ); } public function getDefaultOptions() { @@ -92,10 +93,13 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { } else { // for now, always join on these tables; really should be configurable as in whatlinkshere $link_tables = array( 'pagelinks', 'templatelinks' ); - // imagelinks only contains links to pages in NS_IMAGE - if( $ns == NS_IMAGE || !$showlinkedto ) $link_tables[] = 'imagelinks'; + // imagelinks only contains links to pages in NS_FILE + if( $ns == NS_FILE || !$showlinkedto ) $link_tables[] = 'imagelinks'; } + if( $id == 0 && !$showlinkedto ) + return false; // nonexistent pages can't link to any pages + // field name prefixes for all the various tables we might want to join with $prefix = array( 'pagelinks' => 'pl', 'templatelinks' => 'tl', 'categorylinks' => 'cl', 'imagelinks' => 'il' ); @@ -105,7 +109,7 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $pfx = $prefix[$link_table]; // imagelinks and categorylinks tables have no xx_namespace field, and have xx_to instead of xx_title - if( $link_table == 'imagelinks' ) $link_ns = NS_IMAGE; + if( $link_table == 'imagelinks' ) $link_ns = NS_FILE; else if( $link_table == 'categorylinks' ) $link_ns = NS_CATEGORY; else $link_ns = 0; @@ -145,7 +149,7 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $res = $dbr->query( $sql, __METHOD__ ); - if( $dbr->numRows( $res ) == 0 ) + if( $res->numRows() == 0 ) $this->mResultEmpty = true; return $res; @@ -159,17 +163,21 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { Xml::input( 'target', 40, str_replace('_',' ',$opts['target']) ) . Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' . Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) ); - $extraOpts['submit'] = Xml::submitbutton( wfMsg('allpagessubmit') ); return $extraOpts; } - - function setTopText( &$out, $opts ){} - - function setBottomText( &$out, $opts ){ + + function setTopText( OutputPage $out, FormOptions $opts ) { + global $wgUser; + $skin = $wgUser->getSkin(); + if( isset( $this->mTargetTitle ) && is_object( $this->mTargetTitle ) ) + $out->setSubtitle( wfMsg( 'recentchangeslinked-backlink', $skin->link( $this->mTargetTitle, + $this->mTargetTitle->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) ); + } + + function setBottomText( OutputPage $out, FormOptions $opts ){ if( isset( $this->mTargetTitle ) && is_object( $this->mTargetTitle ) ){ global $wgUser; $out->setFeedAppendQuery( "target=" . urlencode( $this->mTargetTitle->getPrefixedDBkey() ) ); - $out->addHTML("< ".$wgUser->getSkin()->makeLinkObj( $this->mTargetTitle, "", "redirect=no" )."<hr />\n"); } if( isset( $this->mResultEmpty ) && $this->mResultEmpty ){ $out->addWikiMsg( 'recentchangeslinked-noresult' ); diff --git a/includes/specials/SpecialRemoveRestrictions.php b/includes/specials/SpecialRemoveRestrictions.php new file mode 100644 index 00000000..ded6cbe3 --- /dev/null +++ b/includes/specials/SpecialRemoveRestrictions.php @@ -0,0 +1,60 @@ +<?php + +function wfSpecialRemoveRestrictions() { + global $wgOut, $wgRequest, $wgUser, $wgLang, $wgTitle; + $sk = $wgUser->getSkin(); + + $id = $wgRequest->getVal( 'id' ); + if( !is_numeric( $id ) ) { + $wgOut->addWikiMsg( 'removerestrictions-noid' ); + return; + } + + UserRestriction::purgeExpired(); + $r = UserRestriction::newFromId( $id, true ); + if( !$r ) { + $wgOut->addWikiMsg( 'removerestrictions-wrongid' ); + return; + } + + $form = array(); + $form['removerestrictions-user'] = $sk->userLink( $r->getSubjectId(), $r->getSubjectText() ) . + $sk->userToolLinks( $r->getSubjectId(), $r->getSubjectText() ); + $form['removerestrictions-type'] = UserRestriction::formatType( $r->getType() ); + if( $r->isPage() ) + $form['removerestrictions-page'] = $sk->link( $r->getPage() ); + if( $r->isNamespace() ) + $form['removerestrictions-namespace'] = $wgLang->getDisplayNsText( $r->getNamespace() ); + $form['removerestrictions-reason'] = Xml::input( 'reason' ); + + $result = null; + if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) + $result = wfSpecialRemoveRestrictionsProcess( $r ); + + $wgOut->addWikiMsg( 'removerestrictions-intro' ); + $wgOut->addHTML( Xml::fieldset( wfMsgHtml( 'removerestrictions-legend' ) ) ); + if( $result ) + $wgOut->addHTML( '<strong class="success">' . wfMsgExt( 'removerestrictions-success', + 'parseinline', $r->getSubjectText() ) . '</strong>' ); + $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl( array( 'id' => $id ) ), + 'method' => 'post' ) ) ); + $wgOut->addHTML( Xml::buildForm( $form, 'removerestrictions-submit' ) ); + $wgOut->addHTML( Xml::hidden( 'id', $r->getId() ) ); + $wgOut->addHTML( Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) ); + $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) ); + $wgOut->addHTML( "</form></fieldset>" ); +} + +function wfSpecialRemoveRestrictionsProcess( $r ) { + global $wgUser, $wgRequest; + $reason = $wgRequest->getVal( 'reason' ); + $result = $r->delete(); + $log = new LogPage( 'restrict' ); + $params = array( $r->getType() ); + if( $r->isPage() ) + $params[] = $r->getPage()->getPrefixedDbKey(); + if( $r->isNamespace() ) + $params[] = $r->getNamespace(); + $log->addEntry( 'remove', Title::makeTitle( NS_USER, $r->getSubjectText() ), $reason, $params ); + return $result; +} diff --git a/includes/specials/SpecialResetpass.php b/includes/specials/SpecialResetpass.php index 707b941d..059f8dbd 100644 --- a/includes/specials/SpecialResetpass.php +++ b/includes/specials/SpecialResetpass.php @@ -4,26 +4,13 @@ * @ingroup SpecialPage */ -/** Constructor */ -function wfSpecialResetpass( $par ) { - $form = new PasswordResetForm(); - $form->execute( $par ); -} - /** * Let users recover their password. * @ingroup SpecialPage */ -class PasswordResetForm extends SpecialPage { - function __construct( $name=null, $reset=null ) { - if( $name !== null ) { - $this->mName = $name; - $this->mTemporaryPassword = $reset; - } else { - global $wgRequest; - $this->mName = $wgRequest->getVal( 'wpName' ); - $this->mTemporaryPassword = $wgRequest->getVal( 'wpPassword' ); - } +class SpecialResetpass extends SpecialPage { + public function __construct() { + parent::__construct( 'Resetpass' ); } /** @@ -32,36 +19,46 @@ class PasswordResetForm extends SpecialPage { function execute( $par ) { global $wgUser, $wgAuth, $wgOut, $wgRequest; + $this->mUserName = $wgRequest->getVal( 'wpName' ); + $this->mOldpass = $wgRequest->getVal( 'wpPassword' ); + $this->mNewpass = $wgRequest->getVal( 'wpNewPassword' ); + $this->mRetype = $wgRequest->getVal( 'wpRetype' ); + + $this->setHeaders(); + $this->outputHeader(); + if( !$wgAuth->allowPasswordChange() ) { $this->error( wfMsg( 'resetpass_forbidden' ) ); return; } - if( $this->mName === null && !$wgRequest->wasPosted() ) { - $this->error( wfMsg( 'resetpass_missing' ) ); + if( !$wgRequest->wasPosted() && !$wgUser->isLoggedIn() ) { + $this->error( wfMsg( 'resetpass-no-info' ) ); return; } - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) { - $newpass = $wgRequest->getVal( 'wpNewPassword' ); - $retype = $wgRequest->getVal( 'wpRetype' ); + if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal('token') ) ) { try { - $this->attemptReset( $newpass, $retype ); + $this->attemptReset( $this->mNewpass, $this->mRetype ); $wgOut->addWikiMsg( 'resetpass_success' ); - - $data = array( - 'action' => 'submitlogin', - 'wpName' => $this->mName, - 'wpPassword' => $newpass, - 'returnto' => $wgRequest->getVal( 'returnto' ), - ); - if( $wgRequest->getCheck( 'wpRemember' ) ) { - $data['wpRemember'] = 1; + if( !$wgUser->isLoggedIn() ) { + $data = array( + 'action' => 'submitlogin', + 'wpName' => $this->mUserName, + 'wpPassword' => $this->mNewpass, + 'returnto' => $wgRequest->getVal( 'returnto' ), + ); + if( $wgRequest->getCheck( 'wpRemember' ) ) { + $data['wpRemember'] = 1; + } + $login = new LoginForm( new FauxRequest( $data, true ) ); + $login->execute(); } - $login = new LoginForm( new FauxRequest( $data, true ) ); - $login->execute(); - - return; + $titleObj = Title::newFromText( $wgRequest->getVal( 'returnto' ) ); + if ( !$titleObj instanceof Title ) { + $titleObj = Title::newMainPage(); + } + $wgOut->redirect( $titleObj->getFullURL() ); } catch( PasswordError $e ) { $this->error( $e->getMessage() ); } @@ -71,9 +68,7 @@ class PasswordResetForm extends SpecialPage { function error( $msg ) { global $wgOut; - $wgOut->addHtml( '<div class="errorbox">' . - htmlspecialchars( $msg ) . - '</div>' ); + $wgOut->addHTML( Xml::element('p', array( 'class' => 'error' ), $msg ) ); } function showForm() { @@ -82,44 +77,54 @@ class PasswordResetForm extends SpecialPage { $wgOut->disallowUserJs(); $self = SpecialPage::getTitleFor( 'Resetpass' ); - $form = - '<div id="userloginForm">' . - wfOpenElement( 'form', + if ( !$this->mUserName ) { + $this->mUserName = $wgUser->getName(); + } + $rememberMe = ''; + if ( !$wgUser->isLoggedIn() ) { + $rememberMe = '<tr>' . + '<td></td>' . + '<td class="mw-input">' . + Xml::checkLabel( wfMsg( 'remembermypassword' ), + 'wpRemember', 'wpRemember', + $wgRequest->getCheck( 'wpRemember' ) ) . + '</td>' . + '</tr>'; + $submitMsg = 'resetpass_submit'; + $oldpassMsg = 'resetpass-temp-password'; + } else { + $oldpassMsg = 'oldpassword'; + $submitMsg = 'resetpass-submit-loggedin'; + } + $wgOut->addHTML( + Xml::fieldset( wfMsg( 'resetpass_header' ) ) . + Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $self->getLocalUrl() ) ) . - '<h2>' . wfMsgHtml( 'resetpass_header' ) . '</h2>' . - '<div id="userloginprompt">' . + 'action' => $self->getLocalUrl(), + 'id' => 'mw-resetpass-form' ) ) . + Xml::hidden( 'token', $wgUser->editToken() ) . + Xml::hidden( 'wpName', $this->mUserName ) . + Xml::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . wfMsgExt( 'resetpass_text', array( 'parse' ) ) . - '</div>' . - '<table>' . - wfHidden( 'token', $wgUser->editToken() ) . - wfHidden( 'wpName', $this->mName ) . - wfHidden( 'wpPassword', $this->mTemporaryPassword ) . - wfHidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . + Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . $this->pretty( array( - array( 'wpName', 'username', 'text', $this->mName ), + array( 'wpName', 'username', 'text', $this->mUserName ), + array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ), array( 'wpNewPassword', 'newpassword', 'password', '' ), - array( 'wpRetype', 'yourpasswordagain', 'password', '' ), + array( 'wpRetype', 'retypenew', 'password', '' ), ) ) . + $rememberMe . '<tr>' . '<td></td>' . - '<td>' . - Xml::checkLabel( wfMsg( 'remembermypassword' ), - 'wpRemember', 'wpRemember', - $wgRequest->getCheck( 'wpRemember' ) ) . - '</td>' . - '</tr>' . - '<tr>' . - '<td></td>' . - '<td>' . - wfSubmitButton( wfMsgHtml( 'resetpass_submit' ) ) . + '<td class="mw-input">' . + Xml::submitButton( wfMsg( $submitMsg ) ) . '</td>' . '</tr>' . - '</table>' . - wfCloseElement( 'form' ) . - '</div>'; - $wgOut->addHtml( $form ); + Xml::closeElement( 'table' ) . + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) + ); } function pretty( $fields ) { @@ -127,16 +132,19 @@ class PasswordResetForm extends SpecialPage { foreach( $fields as $list ) { list( $name, $label, $type, $value ) = $list; if( $type == 'text' ) { - $field = '<tt>' . htmlspecialchars( $value ) . '</tt>'; + $field = htmlspecialchars( $value ); } else { $field = Xml::input( $name, 20, $value, array( 'id' => $name, 'type' => $type ) ); } $out .= '<tr>'; - $out .= '<td align="right">'; - $out .= Xml::label( wfMsg( $label ), $name ); + $out .= "<td class='mw-label'>"; + if ( $type != 'text' ) + $out .= Xml::label( wfMsg( $label ), $name ); + else + $out .= wfMsg( $label ); $out .= '</td>'; - $out .= '<td>'; + $out .= "<td class='mw-input'>"; $out .= $field; $out .= '</td>'; $out .= '</tr>'; @@ -147,21 +155,33 @@ class PasswordResetForm extends SpecialPage { /** * @throws PasswordError when cannot set the new password because requirements not met. */ - function attemptReset( $newpass, $retype ) { - $user = User::newFromName( $this->mName ); - if( $user->isAnon() ) { + protected function attemptReset( $newpass, $retype ) { + $user = User::newFromName( $this->mUserName ); + if( !$user || $user->isAnon() ) { throw new PasswordError( 'no such user' ); } - - if( !$user->checkTemporaryPassword( $this->mTemporaryPassword ) ) { - throw new PasswordError( wfMsg( 'resetpass_bad_temporary' ) ); - } - + if( $newpass !== $retype ) { + wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) ); throw new PasswordError( wfMsg( 'badretype' ) ); } - $user->setPassword( $newpass ); + if( !$user->checkTemporaryPassword($this->mOldpass) && !$user->checkPassword($this->mOldpass) ) { + wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) ); + throw new PasswordError( wfMsg( 'resetpass-wrong-oldpass' ) ); + } + + try { + $user->setPassword( $this->mNewpass ); + wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) ); + $this->mNewpass = $this->mOldpass = $this->mRetypePass = ''; + } catch( PasswordError $e ) { + wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) ); + throw new PasswordError( $e->getMessage() ); + return; + } + + $user->setCookies(); $user->saveSettings(); } } diff --git a/includes/specials/SpecialRestrictUser.php b/includes/specials/SpecialRestrictUser.php new file mode 100644 index 00000000..761e0cd6 --- /dev/null +++ b/includes/specials/SpecialRestrictUser.php @@ -0,0 +1,189 @@ +<?php + +function wfSpecialRestrictUser( $par = null ) { + global $wgOut, $wgRequest; + $user = $userOrig = null; + if( $par ) { + $userOrig = $par; + } elseif( $wgRequest->getVal( 'user' ) ) { + $userOrig = $wgRequest->getVal( 'user' ); + } else { + $wgOut->addHTML( RestrictUserForm::selectUserForm() ); + return; + } + $isIP = User::isIP( $userOrig ); + $user = $isIP ? $userOrig : User::getCanonicalName( $userOrig ); + $uid = User::idFromName( $user ); + if( !$uid && !$isIP ) { + $err = '<strong class="error">' . wfMsgHtml( 'restrictuser-notfound' ) . '</strong>'; + $wgOut->addHTML( RestrictUserForm::selectUserForm( $userOrig, $err ) ); + return; + } + $wgOut->addHTML( RestrictUserForm::selectUserForm( $user ) ); + + UserRestriction::purgeExpired(); + $old = UserRestriction::fetchForUser( $user, true ); + + RestrictUserForm::pageRestrictionForm( $uid, $user, $old ); + RestrictUserForm::namespaceRestrictionForm( $uid, $user, $old ); + + // Renew it after possible changes in previous two functions + $old = UserRestriction::fetchForUser( $user, true ); + if( $old ) { + $wgOut->addHTML( RestrictUserForm::existingRestrictions( $old ) ); + } +} + +class RestrictUserForm { + public static function selectUserForm( $val = null, $error = null ) { + global $wgScript, $wgTitle; + $s = Xml::fieldset( wfMsg( 'restrictuser-userselect' ) ) . "<form action=\"{$wgScript}\">"; + if( $error ) + $s .= '<p>' . $error . '</p>'; + $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ); + $form = array( 'restrictuser-user' => Xml::input( 'user', false, $val ) ); + $s .= Xml::buildForm( $form, 'restrictuser-go' ); + $s .= "</form></fieldset>"; + return $s; + } + + public static function existingRestrictions( $restrictions ) { + //TODO: autoload? + require_once( dirname( __FILE__ ) . '/SpecialListUserRestrictions.php' ); + $s = Xml::fieldset( wfMsg( 'restrictuser-existing' ) ) . '<ul>'; + foreach( $restrictions as $r ) + $s .= UserRestrictionsPager::formatRestriction( $r ); + $s .= "</ul></fieldset>"; + return $s; + } + + public static function pageRestrictionForm( $uid, $user, $oldRestrictions ) { + global $wgOut, $wgTitle, $wgRequest, $wgUser; + $error = ''; + $success = false; + if( $wgRequest->wasPosted() && $wgRequest->getVal( 'type' ) == UserRestriction::PAGE && + $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) { + + $title = Title::newFromText( $wgRequest->getVal( 'page' ) ); + if( !$title ) { + $error = array( 'restrictuser-badtitle', $wgRequest->getVal( 'page' ) ); + } elseif( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) === false ) { + $error = array( 'restrictuser-badexpiry', $wgRequest->getVal( 'expiry' ) ); + } else { + foreach( $oldRestrictions as $r ) { + if( $r->isPage() && $r->getPage()->equals( $title ) ) + $error = array( 'restrictuser-duptitle' ); + } + } + if( !$error ) { + self::doPageRestriction( $uid, $user ); + $success = array('restrictuser-success', $user); + } + } + $useRequestValues = $wgRequest->getVal( 'type' ) == UserRestriction::PAGE; + $wgOut->addHTML( Xml::fieldset( wfMsg( 'restrictuser-legend-page' ) ) ); + + self::printSuccessError( $success, $error ); + + $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl(), + 'method' => 'post' ) ) ); + $wgOut->addHTML( Xml::hidden( 'type', UserRestriction::PAGE ) ); + $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) ); + $wgOut->addHTML( Xml::hidden( 'user', $user ) ); + $form = array(); + $form['restrictuser-title'] = Xml::input( 'page', false, + $useRequestValues ? $wgRequest->getVal( 'page' ) : false ); + $form['restrictuser-expiry'] = Xml::input( 'expiry', false, + $useRequestValues ? $wgRequest->getVal( 'expiry' ) : false ); + $form['restrictuser-reason'] = Xml::input( 'reason', false, + $useRequestValues ? $wgRequest->getVal( 'reason' ) : false ); + $wgOut->addHTML( Xml::buildForm( $form, 'restrictuser-submit' ) ); + $wgOut->addHTML( "</form></fieldset>" ); + } + + public static function printSuccessError( $success, $error ) { + global $wgOut; + if ( $error ) + $wgOut->wrapWikiMsg( '<strong class="error">$1</strong>', $error ); + if ( $success ) + $wgOut->wrapWikiMsg( '<strong class="success">$1</strong>', $success ); + } + + public static function doPageRestriction( $uid, $user ) { + global $wgUser, $wgRequest; + $r = new UserRestriction(); + $r->setType( UserRestriction::PAGE ); + $r->setPage( Title::newFromText( $wgRequest->getVal( 'page' ) ) ); + $r->setSubjectId( $uid ); + $r->setSubjectText( $user ); + $r->setBlockerId( $wgUser->getId() ); + $r->setBlockerText( $wgUser->getName() ); + $r->setReason( $wgRequest->getVal( 'reason' ) ); + $r->setExpiry( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) ); + $r->setTimestamp( wfTimestampNow( TS_MW ) ); + $r->commit(); + $logExpiry = $wgRequest->getVal( 'expiry' ) ? $wgRequest->getVal( 'expiry' ) : Block::infinity(); + $l = new LogPage( 'restrict' ); + $l->addEntry( 'restrict', Title::makeTitle( NS_USER, $user ), $r->getReason(), + array( $r->getType(), $r->getPage()->getFullText(), $logExpiry) ); + } + + public static function namespaceRestrictionForm( $uid, $user, $oldRestrictions ) { + global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgContLang; + $error = ''; + $success = false; + if( $wgRequest->wasPosted() && $wgRequest->getVal( 'type' ) == UserRestriction::NAMESPACE && + $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) { + $ns = $wgRequest->getVal( 'namespace' ); + if( $wgContLang->getNsText( $ns ) === false ) + $error = wfMsgExt( 'restrictuser-badnamespace', 'parseinline' ); + elseif( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) === false ) + $error = wfMsgExt( 'restrictuser-badexpiry', 'parseinline', $wgRequest->getVal( 'expiry' ) ); + else + foreach( $oldRestrictions as $r ) + if( $r->isNamespace() && $r->getNamespace() == $ns ) + $error = wfMsgExt( 'restrictuser-dupnamespace', 'parse' ); + if( !$error ) { + self::doNamespaceRestriction( $uid, $user ); + $success = array('restrictuser-success', $user); + } + } + $useRequestValues = $wgRequest->getVal( 'type' ) == UserRestriction::NAMESPACE; + $wgOut->addHTML( Xml::fieldset( wfMsg( 'restrictuser-legend-namespace' ) ) ); + + self::printSuccessError( $success, $error ); + + $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl(), + 'method' => 'post' ) ) ); + $wgOut->addHTML( Xml::hidden( 'type', UserRestriction::NAMESPACE ) ); + $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) ); + $wgOut->addHTML( Xml::hidden( 'user', $user ) ); + $form = array(); + $form['restrictuser-namespace'] = Xml::namespaceSelector( $wgRequest->getVal( 'namespace' ) ); + $form['restrictuser-expiry'] = Xml::input( 'expiry', false, + $useRequestValues ? $wgRequest->getVal( 'expiry' ) : false ); + $form['restrictuser-reason'] = Xml::input( 'reason', false, + $useRequestValues ? $wgRequest->getVal( 'reason' ) : false ); + $wgOut->addHTML( Xml::buildForm( $form, 'restrictuser-submit' ) ); + $wgOut->addHTML( "</form></fieldset>" ); + } + + public static function doNamespaceRestriction( $uid, $user ) { + global $wgUser, $wgRequest; + $r = new UserRestriction(); + $r->setType( UserRestriction::NAMESPACE ); + $r->setNamespace( $wgRequest->getVal( 'namespace' ) ); + $r->setSubjectId( $uid ); + $r->setSubjectText( $user ); + $r->setBlockerId( $wgUser->getId() ); + $r->setBlockerText( $wgUser->getName() ); + $r->setReason( $wgRequest->getVal( 'reason' ) ); + $r->setExpiry( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) ); + $r->setTimestamp( wfTimestampNow( TS_MW ) ); + $r->commit(); + $logExpiry = $wgRequest->getVal( 'expiry' ) ? $wgRequest->getVal( 'expiry' ) : Block::infinity(); + $l = new LogPage( 'restrict' ); + $l->addEntry( 'restrict', Title::makeTitle( NS_USER, $user ), $r->getReason(), + array( $r->getType(), $r->getNamespace(), $logExpiry ) ); + } +} diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php index e94fc222..74b118e2 100644 --- a/includes/specials/SpecialRevisiondelete.php +++ b/includes/specials/SpecialRevisiondelete.php @@ -171,7 +171,7 @@ class RevisionDeleteForm { $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(), $count ); $bitfields = 0; - $wgOut->addHtml( "<ul>" ); + $wgOut->addHTML( "<ul>" ); $where = $revObjs = array(); $dbr = wfGetDB( DB_SLAVE ); @@ -204,7 +204,7 @@ class RevisionDeleteForm { $UserAllowed = false; } $revisions++; - $wgOut->addHtml( $this->historyLine( $revObjs[$revid] ) ); + $wgOut->addHTML( $this->historyLine( $revObjs[$revid] ) ); $bitfields |= $revObjs[$revid]->mDeleted; } // The archives... @@ -245,7 +245,7 @@ class RevisionDeleteForm { $UserAllowed = false; } $revisions++; - $wgOut->addHtml( $this->historyLine( $revObjs[$timestamp] ) ); + $wgOut->addHTML( $this->historyLine( $revObjs[$timestamp] ) ); $bitfields |= $revObjs[$timestamp]->mDeleted; } } @@ -254,7 +254,7 @@ class RevisionDeleteForm { return; } - $wgOut->addHtml( "</ul>" ); + $wgOut->addHTML( "</ul>" ); $wgOut->addWikiMsg( 'revdelete-text' ); @@ -278,7 +278,7 @@ class RevisionDeleteForm { $hidden[] = Xml::hidden( 'artimestamp[]', $rev->getTimestamp() ); } $special = SpecialPage::getTitleFor( 'Revisiondelete' ); - $wgOut->addHtml( + $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), 'id' => 'mw-revdel-form-revisions' ) ) . Xml::openElement( 'fieldset' ) . @@ -287,15 +287,15 @@ class RevisionDeleteForm { // FIXME: all items checked for just one rev are checked, even if not set for the others foreach( $this->checks as $item ) { list( $message, $name, $field ) = $item; - $wgOut->addHtml( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); + $wgOut->addHTML( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); } foreach( $items as $item ) { - $wgOut->addHtml( Xml::tags( 'p', null, $item ) ); + $wgOut->addHTML( Xml::tags( 'p', null, $item ) ); } foreach( $hidden as $item ) { - $wgOut->addHtml( $item ); + $wgOut->addHTML( $item ); } - $wgOut->addHtml( + $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n" ); @@ -317,7 +317,7 @@ class RevisionDeleteForm { $wgLang->formatNum($count) ); $bitfields = 0; - $wgOut->addHtml( "<ul>" ); + $wgOut->addHTML( "<ul>" ); $where = $filesObjs = array(); $dbr = wfGetDB( DB_SLAVE ); @@ -326,11 +326,11 @@ class RevisionDeleteForm { if( $this->deleteKey=='oldimage' ) { // Run through and pull all our data in one query foreach( $this->ofiles as $timestamp ) { - $where[] = $dbr->addQuotes( $timestamp.'!'.$this->page->getDbKey() ); + $where[] = $dbr->addQuotes( $timestamp.'!'.$this->page->getDBKey() ); } $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; $result = $dbr->select( 'oldimage', '*', - array( 'oi_name' => $this->page->getDbKey(), + array( 'oi_name' => $this->page->getDBKey(), $whereClause ), __METHOD__ ); while( $row = $dbr->fetchObject( $result ) ) { @@ -340,7 +340,7 @@ class RevisionDeleteForm { } // Check through our images foreach( $this->ofiles as $timestamp ) { - $archivename = $timestamp.'!'.$this->page->getDbKey(); + $archivename = $timestamp.'!'.$this->page->getDBKey(); if( !isset($filesObjs[$archivename]) ) { continue; } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) { @@ -353,7 +353,7 @@ class RevisionDeleteForm { } $revisions++; // Inject history info - $wgOut->addHtml( $this->fileLine( $filesObjs[$archivename] ) ); + $wgOut->addHTML( $this->fileLine( $filesObjs[$archivename] ) ); $bitfields |= $filesObjs[$archivename]->deleted; } // Archived files... @@ -364,7 +364,7 @@ class RevisionDeleteForm { } $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; $result = $dbr->select( 'filearchive', '*', - array( 'fa_name' => $this->page->getDbKey(), + array( 'fa_name' => $this->page->getDBKey(), $whereClause ), __METHOD__ ); while( $row = $dbr->fetchObject( $result ) ) { @@ -384,7 +384,7 @@ class RevisionDeleteForm { } $revisions++; // Inject history info - $wgOut->addHtml( $this->archivedfileLine( $filesObjs[$fileid] ) ); + $wgOut->addHTML( $this->archivedfileLine( $filesObjs[$fileid] ) ); $bitfields |= $filesObjs[$fileid]->deleted; } } @@ -393,7 +393,7 @@ class RevisionDeleteForm { return; } - $wgOut->addHtml( "</ul>" ); + $wgOut->addHTML( "</ul>" ); $wgOut->addWikiMsg('revdelete-text' ); //Normal sysops can always see what they did, but can't always change it @@ -416,7 +416,7 @@ class RevisionDeleteForm { $hidden[] = Xml::hidden( 'fileid[]', $fileid ); } $special = SpecialPage::getTitleFor( 'Revisiondelete' ); - $wgOut->addHtml( + $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), 'id' => 'mw-revdel-form-filerevisions' ) ) . Xml::fieldset( wfMsg( 'revdelete-legend' ) ) @@ -424,16 +424,16 @@ class RevisionDeleteForm { // FIXME: all items checked for just one file are checked, even if not set for the others foreach( $this->checks as $item ) { list( $message, $name, $field ) = $item; - $wgOut->addHtml( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); + $wgOut->addHTML( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); } foreach( $items as $item ) { - $wgOut->addHtml( "<p>$item</p>" ); + $wgOut->addHTML( "<p>$item</p>" ); } foreach( $hidden as $item ) { - $wgOut->addHtml( $item ); + $wgOut->addHTML( $item ); } - $wgOut->addHtml( + $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n" ); @@ -449,7 +449,7 @@ class RevisionDeleteForm { $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->events) ) ); $bitfields = 0; - $wgOut->addHtml( "<ul>" ); + $wgOut->addHTML( "<ul>" ); $where = $logRows = array(); $dbr = wfGetDB( DB_SLAVE ); @@ -480,7 +480,7 @@ class RevisionDeleteForm { $UserAllowed = false; } $logItems++; - $wgOut->addHtml( $this->logLine( $logRows[$logid] ) ); + $wgOut->addHTML( $this->logLine( $logRows[$logid] ) ); $bitfields |= $logRows[$logid]->log_deleted; } if( !$logItems ) { @@ -488,7 +488,7 @@ class RevisionDeleteForm { return; } - $wgOut->addHtml( "</ul>" ); + $wgOut->addHTML( "</ul>" ); $wgOut->addWikiMsg( 'revdelete-text' ); // Normal sysops can always see what they did, but can't always change it @@ -506,7 +506,7 @@ class RevisionDeleteForm { } $special = SpecialPage::getTitleFor( 'Revisiondelete' ); - $wgOut->addHtml( + $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), 'id' => 'mw-revdel-form-logs' ) ) . Xml::fieldset( wfMsg( 'revdelete-legend' ) ) @@ -514,16 +514,16 @@ class RevisionDeleteForm { // FIXME: all items checked for just on event are checked, even if not set for the others foreach( $this->checks as $item ) { list( $message, $name, $field ) = $item; - $wgOut->addHtml( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); + $wgOut->addHTML( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); } foreach( $items as $item ) { - $wgOut->addHtml( "<p>$item</p>" ); + $wgOut->addHTML( "<p>$item</p>" ); } foreach( $hidden as $item ) { - $wgOut->addHtml( $item ); + $wgOut->addHTML( $item ); } - $wgOut->addHtml( + $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n" ); @@ -606,7 +606,7 @@ class RevisionDeleteForm { * @returns string */ private function archivedfileLine( $file ) { - global $wgLang, $wgTitle; + global $wgLang; $target = $this->page->getPrefixedText(); $date = $wgLang->timeanddate( $file->getTimestamp(), true ); @@ -939,11 +939,11 @@ class RevisionDeleter { $set = array(); // Run through and pull all our data in one query foreach( $items as $timestamp ) { - $where[] = $this->dbw->addQuotes( $timestamp.'!'.$title->getDbKey() ); + $where[] = $this->dbw->addQuotes( $timestamp.'!'.$title->getDBKey() ); } $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; $result = $this->dbw->select( 'oldimage', '*', - array( 'oi_name' => $title->getDbKey(), + array( 'oi_name' => $title->getDBKey(), $whereClause ), __METHOD__ ); while( $row = $this->dbw->fetchObject( $result ) ) { @@ -953,7 +953,7 @@ class RevisionDeleter { } // To work! foreach( $items as $timestamp ) { - $archivename = $timestamp.'!'.$title->getDbKey(); + $archivename = $timestamp.'!'.$title->getDBKey(); if( !isset($filesObjs[$archivename]) ) { $success = false; continue; // Must exist @@ -1036,7 +1036,7 @@ class RevisionDeleter { } $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; $result = $this->dbw->select( 'filearchive', '*', - array( 'fa_name' => $title->getDbKey(), + array( 'fa_name' => $title->getDBKey(), $whereClause ), __METHOD__ ); while( $row = $this->dbw->fetchObject( $result ) ) { @@ -1344,7 +1344,7 @@ class RevisionDeleter { function updatePage( $title ) { $title->invalidateCache(); $title->purgeSquid(); - + $title->touchLinks(); // Extensions that require referencing previous revisions may need this wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$title ) ); } diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index f13c1676..f3117242 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -29,13 +29,18 @@ * @param $par String: (default '') */ function wfSpecialSearch( $par = '' ) { - global $wgRequest, $wgUser; - - $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $par ) ); - $searchPage = new SpecialSearch( $wgRequest, $wgUser ); + global $wgRequest, $wgUser, $wgUseOldSearchUI; + // Strip underscores from title parameter; most of the time we'll want + // text form here. But don't strip underscores from actual text params! + $titleParam = str_replace( '_', ' ', $par ); + // Fetch the search term + $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) ); + $class = $wgUseOldSearchUI ? 'SpecialSearchOld' : 'SpecialSearch'; + $searchPage = new $class( $wgRequest, $wgUser ); if( $wgRequest->getVal( 'fulltext' ) || !is_null( $wgRequest->getVal( 'offset' )) - || !is_null( $wgRequest->getVal( 'searchx' ))) { + || !is_null( $wgRequest->getVal( 'searchx' )) ) + { $searchPage->showResults( $search, 'search' ); } else { $searchPage->goResult( $search ); @@ -56,9 +61,806 @@ class SpecialSearch { * @param User $user * @public */ - function SpecialSearch( &$request, &$user ) { + function __construct( &$request, &$user ) { list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); + $this->mPrefix = $request->getVal('prefix', ''); + # Extract requested namespaces + $this->namespaces = $this->powerSearch( $request ); + if( empty( $this->namespaces ) ) { + $this->namespaces = SearchEngine::userNamespaces( $user ); + } + $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; + $this->searchAdvanced = $request->getVal( 'advanced' ); + $this->active = 'advanced'; + $this->sk = $user->getSkin(); + $this->didYouMeanHtml = ''; # html of did you mean... link + } + + /** + * If an exact title match can be found, jump straight ahead to it. + * @param string $term + */ + public function goResult( $term ) { + global $wgOut; + $this->setupPage( $term ); + # Try to go to page as entered. + $t = Title::newFromText( $term ); + # If the string cannot be used to create a title + if( is_null( $t ) ) { + return $this->showResults( $term ); + } + # If there's an exact or very near match, jump right there. + $t = SearchEngine::getNearMatch( $term ); + if( !is_null( $t ) ) { + $wgOut->redirect( $t->getFullURL() ); + return; + } + # No match, generate an edit URL + $t = Title::newFromText( $term ); + if( !is_null( $t ) ) { + global $wgGoToEdit; + wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) ); + # If the feature is enabled, go straight to the edit page + if( $wgGoToEdit ) { + $wgOut->redirect( $t->getFullURL( 'action=edit' ) ); + return; + } + } + return $this->showResults( $term ); + } + + /** + * @param string $term + */ + public function showResults( $term ) { + global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang; + wfProfileIn( __METHOD__ ); + + $sk = $wgUser->getSkin(); + + $this->searchEngine = SearchEngine::create(); + $search =& $this->searchEngine; + $search->setLimitOffset( $this->limit, $this->offset ); + $search->setNamespaces( $this->namespaces ); + $search->showRedirects = $this->searchRedirects; + $search->prefix = $this->mPrefix; + $term = $search->transformSearchTerm($term); + + $this->setupPage( $term ); + + if( $wgDisableTextSearch ) { + global $wgSearchForwardUrl; + if( $wgSearchForwardUrl ) { + $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl ); + $wgOut->redirect( $url ); + wfProfileOut( __METHOD__ ); + return; + } + global $wgInputEncoding; + $wgOut->addHTML( + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'search-external' ) ) . + Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), wfMsg( 'searchdisabled' ) ) . + wfMsg( 'googlesearch', + htmlspecialchars( $term ), + htmlspecialchars( $wgInputEncoding ), + htmlspecialchars( wfMsg( 'searchbutton' ) ) + ) . + Xml::closeElement( 'fieldset' ) + ); + wfProfileOut( __METHOD__ ); + return; + } + + $t = Title::newFromText( $term ); + + // fetch search results + $rewritten = $search->replacePrefixes($term); + + $titleMatches = $search->searchTitle( $rewritten ); + if( !($titleMatches instanceof SearchResultTooMany)) + $textMatches = $search->searchText( $rewritten ); + + // did you mean... suggestions + if( $textMatches && $textMatches->hasSuggestion() ) { + $st = SpecialPage::getTitleFor( 'Search' ); + $stParams = wfArrayToCGI( + array( 'search' => $textMatches->getSuggestionQuery(), 'fulltext' => wfMsg('search') ), + $this->powerSearchOptions() + ); + $suggestLink = $sk->makeKnownLinkObj( $st, + $textMatches->getSuggestionSnippet(), + $stParams ); + + $this->didYouMeanHtml = '<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>'; + } + + // start rendering the page + $wgOut->addHtml( + Xml::openElement( 'table', array( 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) . + Xml::openElement( 'tr' ) . + Xml::openElement( 'td' ) . "\n" . + ( $this->searchAdvanced ? $this->powerSearchBox( $term ) : $this->shortDialog( $term ) ) . + Xml::closeElement('td') . + Xml::closeElement('tr') . + Xml::closeElement('table') + ); + + // Sometimes the search engine knows there are too many hits + if( $titleMatches instanceof SearchResultTooMany ) { + $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" ); + wfProfileOut( __METHOD__ ); + return; + } + + $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':'; + if( '' === trim( $term ) || $filePrefix === trim( $term ) ) { + $wgOut->addHTML( $this->searchAdvanced ? $this->powerSearchFocus() : $this->searchFocus() ); + // Empty query -- straight view of search form + wfProfileOut( __METHOD__ ); + return; + } + + // show direct page/create link + if( !is_null($t) ) { + if( !$t->exists() ) { + $wgOut->addWikiMsg( 'searchmenu-new', wfEscapeWikiText( $t->getPrefixedText() ) ); + } else { + $wgOut->addWikiMsg( 'searchmenu-exists', wfEscapeWikiText( $t->getPrefixedText() ) ); + } + } + + // Get number of results + $titleMatchesSQL = $titleMatches ? $titleMatches->numRows() : 0; + $textMatchesSQL = $textMatches ? $textMatches->numRows() : 0; + // Total initial query matches (possible false positives) + $numSQL = $titleMatchesSQL + $textMatchesSQL; + // Get total actual results (after second filtering, if any) + $numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ? + $titleMatches->getTotalHits() : $titleMatchesSQL; + $numTextMatches = $textMatches && !is_null( $textMatches->getTotalHits() ) ? + $textMatches->getTotalHits() : $textMatchesSQL; + $totalRes = $numTitleMatches + $numTextMatches; + + // show number of results and current offset + if( $numSQL > 0 ) { + if( $numSQL > 0 ) { + $top = wfMsgExt('showingresultstotal', array( 'parseinline' ), + $this->offset+1, $this->offset+$numSQL, $totalRes, $numSQL ); + } elseif( $numSQL >= $this->limit ) { + $top = wfShowingResults( $this->offset, $this->limit ); + } else { + $top = wfShowingResultsNum( $this->offset, $this->limit, $numSQL ); + } + $wgOut->addHTML( "<p class='mw-search-numberresults'>{$top}</p>\n" ); + } + + // prev/next links + if( $numSQL || $this->offset ) { + $prevnext = wfViewPrevNext( $this->offset, $this->limit, + SpecialPage::getTitleFor( 'Search' ), + wfArrayToCGI( $this->powerSearchOptions(), array( 'search' => $term ) ), + max( $titleMatchesSQL, $textMatchesSQL ) < $this->limit + ); + $wgOut->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" ); + wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) ); + } else { + wfRunHooks( 'SpecialSearchNoResults', array( $term ) ); + } + + $wgOut->addHtml( "<div class='searchresults'>" ); + if( $titleMatches ) { + if( $numTitleMatches > 0 ) { + $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' ); + $wgOut->addHTML( $this->showMatches( $titleMatches ) ); + } + $titleMatches->free(); + } + if( $textMatches ) { + // output appropriate heading + if( $numTextMatches > 0 && $numTitleMatches > 0 ) { + // if no title matches the heading is redundant + $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' ); + } elseif( $totalRes == 0 ) { + # Don't show the 'no text matches' if we received title matches + $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' ); + } + // show interwiki results if any + if( $textMatches->hasInterwikiResults() ) { + $wgOut->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) ); + } + // show results + if( $numTextMatches > 0 ) { + $wgOut->addHTML( $this->showMatches( $textMatches ) ); + } + + $textMatches->free(); + } + if( $totalRes === 0 ) { + $wgOut->addWikiMsg( 'search-nonefound' ); + } + $wgOut->addHtml( "</div>" ); + if( $totalRes === 0 ) { + $wgOut->addHTML( $this->searchAdvanced ? $this->powerSearchFocus() : $this->searchFocus() ); + } + + if( $numSQL || $this->offset ) { + $wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" ); + } + wfProfileOut( __METHOD__ ); + } + + /** + * + */ + protected function setupPage( $term ) { + global $wgOut; + // Figure out the active search profile header + $nsAllSet = array_keys( SearchEngine::searchableNamespaces() ); + if( $this->searchAdvanced ) + $this->active = 'advanced'; + else if( $this->namespaces === NS_FILE || $this->startsWithImage( $term ) ) + $this->active = 'images'; + elseif( $this->namespaces === $nsAllSet ) + $this->active = 'all'; + elseif( $this->namespaces === SearchEngine::defaultNamespaces() ) + $this->active = 'default'; + elseif( $this->namespaces === SearchEngine::projectNamespaces() ) + $this->active = 'project'; + else + $this->active = 'advanced'; + # Should advanced UI be used? + $this->searchAdvanced = ($this->active === 'advanced'); + if( !empty( $term ) ) { + $wgOut->setPageTitle( wfMsg( 'searchresults') ); + $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term ) ) ); + } + $wgOut->setArticleRelated( false ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + } + + /** + * Extract "power search" namespace settings from the request object, + * returning a list of index numbers to search. + * + * @param WebRequest $request + * @return array + */ + protected function powerSearch( &$request ) { + $arr = array(); + foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { + if( $request->getCheck( 'ns' . $ns ) ) { + $arr[] = $ns; + } + } + return $arr; + } + /** + * Reconstruct the 'power search' options for links + * @return array + */ + protected function powerSearchOptions() { + $opt = array(); + foreach( $this->namespaces as $n ) { + $opt['ns' . $n] = 1; + } + $opt['redirs'] = $this->searchRedirects ? 1 : 0; + if( $this->searchAdvanced ) { + $opt['advanced'] = $this->searchAdvanced; + } + return $opt; + } + + /** + * Show whole set of results + * + * @param SearchResultSet $matches + */ + protected function showMatches( &$matches ) { + global $wgContLang; + wfProfileIn( __METHOD__ ); + + $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); + + $out = ""; + $infoLine = $matches->getInfo(); + if( !is_null($infoLine) ) { + $out .= "\n<!-- {$infoLine} -->\n"; + } + $off = $this->offset + 1; + $out .= "<ul class='mw-search-results'>\n"; + while( $result = $matches->next() ) { + $out .= $this->showHit( $result, $terms ); + } + $out .= "</ul>\n"; + + // convert the whole thing to desired language variant + $out = $wgContLang->convert( $out ); + wfProfileOut( __METHOD__ ); + return $out; + } + + /** + * Format a single hit result + * @param SearchResult $result + * @param array $terms terms to highlight + */ + protected function showHit( $result, $terms ) { + global $wgContLang, $wgLang, $wgUser; + wfProfileIn( __METHOD__ ); + + if( $result->isBrokenTitle() ) { + wfProfileOut( __METHOD__ ); + return "<!-- Broken link in search result -->\n"; + } + + $sk = $wgUser->getSkin(); + $t = $result->getTitle(); + + $link = $this->sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms)); + + //If page content is not readable, just return the title. + //This is not quite safe, but better than showing excerpts from non-readable pages + //Note that hiding the entry entirely would screw up paging. + if( !$t->userCanRead() ) { + wfProfileOut( __METHOD__ ); + return "<li>{$link}</li>\n"; + } + + // If the page doesn't *exist*... our search index is out of date. + // The least confusing at this point is to drop the result. + // You may get less results, but... oh well. :P + if( $result->isMissingRevision() ) { + wfProfileOut( __METHOD__ ); + return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n"; + } + + // format redirects / relevant sections + $redirectTitle = $result->getRedirectTitle(); + $redirectText = $result->getRedirectSnippet($terms); + $sectionTitle = $result->getSectionTitle(); + $sectionText = $result->getSectionSnippet($terms); + $redirect = ''; + if( !is_null($redirectTitle) ) + $redirect = "<span class='searchalttitle'>" + .wfMsg('search-redirect',$this->sk->makeKnownLinkObj( $redirectTitle, $redirectText)) + ."</span>"; + $section = ''; + if( !is_null($sectionTitle) ) + $section = "<span class='searchalttitle'>" + .wfMsg('search-section', $this->sk->makeKnownLinkObj( $sectionTitle, $sectionText)) + ."</span>"; + + // format text extract + $extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>"; + + // format score + if( is_null( $result->getScore() ) ) { + // Search engine doesn't report scoring info + $score = ''; + } else { + $percent = sprintf( '%2.1f', $result->getScore() * 100 ); + $score = wfMsg( 'search-result-score', $wgLang->formatNum( $percent ) ) + . ' - '; + } + + // format description + $byteSize = $result->getByteSize(); + $wordCount = $result->getWordCount(); + $timestamp = $result->getTimestamp(); + $size = wfMsgExt( 'search-result-size', array( 'parsemag', 'escape' ), + $this->sk->formatSize( $byteSize ), $wordCount ); + $date = $wgLang->timeanddate( $timestamp ); + + // link to related articles if supported + $related = ''; + if( $result->hasRelated() ) { + $st = SpecialPage::getTitleFor( 'Search' ); + $stParams = wfArrayToCGI( $this->powerSearchOptions(), + array('search' => wfMsgForContent('searchrelated').':'.$t->getPrefixedText(), + 'fulltext' => wfMsg('search') )); + + $related = ' -- ' . $sk->makeKnownLinkObj( $st, + wfMsg('search-relatedarticle'), $stParams ); + } + + // Include a thumbnail for media files... + if( $t->getNamespace() == NS_FILE ) { + $img = wfFindFile( $t ); + if( $img ) { + $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); + if( $thumb ) { + $desc = $img->getShortDesc(); + wfProfileOut( __METHOD__ ); + // Float doesn't seem to interact well with the bullets. + // Table messes up vertical alignment of the bullets. + // Bullets are therefore disabled (didn't look great anyway). + return "<li>" . + '<table class="searchResultImage">' . + '<tr>' . + '<td width="120" align="center" valign="top">' . + $thumb->toHtml( array( 'desc-link' => true ) ) . + '</td>' . + '<td valign="top">' . + $link . + $extract . + "<div class='mw-search-result-data'>{$score}{$desc} - {$date}{$related}</div>" . + '</td>' . + '</tr>' . + '</table>' . + "</li>\n"; + } + } + } + + wfProfileOut( __METHOD__ ); + return "<li>{$link} {$redirect} {$section} {$extract}\n" . + "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" . + "</li>\n"; + + } + + /** + * Show results from other wikis + * + * @param SearchResultSet $matches + */ + protected function showInterwiki( &$matches, $query ) { + global $wgContLang; + wfProfileIn( __METHOD__ ); + $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); + + $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>". + wfMsg('search-interwiki-caption')."</div>\n"; + $off = $this->offset + 1; + $out .= "<ul class='mw-search-iwresults'>\n"; + + // work out custom project captions + $customCaptions = array(); + $customLines = explode("\n",wfMsg('search-interwiki-custom')); // format per line <iwprefix>:<caption> + foreach($customLines as $line) { + $parts = explode(":",$line,2); + if(count($parts) == 2) // validate line + $customCaptions[$parts[0]] = $parts[1]; + } + + $prev = null; + while( $result = $matches->next() ) { + $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions ); + $prev = $result->getInterwikiPrefix(); + } + // FIXME: should support paging in a non-confusing way (not sure how though, maybe via ajax).. + $out .= "</ul></div>\n"; + + // convert the whole thing to desired language variant + $out = $wgContLang->convert( $out ); + wfProfileOut( __METHOD__ ); + return $out; + } + + /** + * Show single interwiki link + * + * @param SearchResult $result + * @param string $lastInterwiki + * @param array $terms + * @param string $query + * @param array $customCaptions iw prefix -> caption + */ + protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) { + wfProfileIn( __METHOD__ ); + global $wgContLang, $wgLang; + + if( $result->isBrokenTitle() ) { + wfProfileOut( __METHOD__ ); + return "<!-- Broken link in search result -->\n"; + } + + $t = $result->getTitle(); + + $link = $this->sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms)); + + // format redirect if any + $redirectTitle = $result->getRedirectTitle(); + $redirectText = $result->getRedirectSnippet($terms); + $redirect = ''; + if( !is_null($redirectTitle) ) + $redirect = "<span class='searchalttitle'>" + .wfMsg('search-redirect',$this->sk->makeKnownLinkObj( $redirectTitle, $redirectText)) + ."</span>"; + + $out = ""; + // display project name + if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) { + if( key_exists($t->getInterwiki(),$customCaptions) ) + // captions from 'search-interwiki-custom' + $caption = $customCaptions[$t->getInterwiki()]; + else{ + // default is to show the hostname of the other wiki which might suck + // if there are many wikis on one hostname + $parsed = parse_url($t->getFullURL()); + $caption = wfMsg('search-interwiki-default', $parsed['host']); + } + // "more results" link (special page stuff could be localized, but we might not know target lang) + $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search"); + $searchLink = $this->sk->makeKnownLinkObj( $searchTitle, wfMsg('search-interwiki-more'), + wfArrayToCGI(array('search' => $query, 'fulltext' => 'Search'))); + $out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'> + {$searchLink}</span>{$caption}</div>\n<ul>"; + } + + $out .= "<li>{$link} {$redirect}</li>\n"; + wfProfileOut( __METHOD__ ); + return $out; + } + + + /** + * Generates the power search box at bottom of [[Special:Search]] + * @param $term string: search term + * @return $out string: HTML form + */ + protected function powerSearchBox( $term ) { + global $wgScript; + + $namespaces = SearchEngine::searchableNamespaces(); + + $tables = $this->namespaceTables( $namespaces ); + + $redirect = Xml::check( 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' ) ); + $redirectLabel = Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' ); + $searchField = Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'powerSearchText' ) ); + $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' )) . "\n"; + $searchTitle = SpecialPage::getTitleFor( 'Search' ); + + $redirectText = ''; + // show redirects check only if backend supports it + if( $this->searchEngine->acceptListRedirects() ) { + $redirectText = "<p>". $redirect . " " . $redirectLabel ."</p>"; + } + + $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) . + Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n" . + "<p>" . + wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) . + "</p>\n" . + '<input type="hidden" name="advanced" value="'.$this->searchAdvanced."\"/>\n". + $tables . + "<hr style=\"clear: both;\" />\n". + $redirectText ."\n". + "<div style=\"padding-top:2px;padding-bottom:2px;\">". + wfMsgExt( 'powersearch-field', array( 'parseinline' ) ) . + " " . + $searchField . + " " . + $searchButton . + "</div>". + "</form>"; + $t = Title::newFromText( $term ); + /* if( $t != null && count($this->namespaces) === 1 ) { + $out .= wfMsgExt( 'searchmenu-prefix', array('parseinline'), $term ); + } */ + return Xml::openElement( 'fieldset', array('id' => 'mw-searchoptions','style' => 'margin:0em;') ) . + Xml::element( 'legend', null, wfMsg('powersearch-legend') ) . + $this->formHeader($term) . $out . $this->didYouMeanHtml . + Xml::closeElement( 'fieldset' ); + } + + protected function searchFocus() { + global $wgJsMimeType; + return "<script type=\"$wgJsMimeType\">" . + "hookEvent(\"load\", function() {" . + "document.getElementById('searchText').focus();" . + "});" . + "</script>"; + } + + protected function powerSearchFocus() { + global $wgJsMimeType; + return "<script type=\"$wgJsMimeType\">" . + "hookEvent(\"load\", function() {" . + "document.getElementById('powerSearchText').focus();" . + "});" . + "</script>"; + } + + protected function formHeader( $term ) { + global $wgContLang, $wgCanonicalNamespaceNames; + + $sep = ' '; + $out = Xml::openElement('div', array( 'style' => 'padding-bottom:0.5em;' ) ); + + $bareterm = $term; + if( $this->startsWithImage( $term ) ) + $bareterm = substr( $term, strpos( $term, ':' ) + 1 ); // delete all/image prefix + + $nsAllSet = array_keys( SearchEngine::searchableNamespaces() ); + + // search profiles headers + $m = wfMsg( 'searchprofile-articles' ); + $tt = wfMsg( 'searchprofile-articles-tooltip', + implode( ', ', SearchEngine::namespacesAsText( SearchEngine::defaultNamespaces() ) ) ); + if( $this->active == 'default' ) { + $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); + } else { + $out .= $this->makeSearchLink( $bareterm, SearchEngine::defaultNamespaces(), $m, $tt ); + } + $out .= $sep; + + $m = wfMsg( 'searchprofile-images' ); + $tt = wfMsg( 'searchprofile-images-tooltip' ); + if( $this->active == 'images' ) { + $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); + } else { + $imageTextForm = $wgContLang->getFormattedNsText(NS_FILE).':'.$bareterm; + $out .= $this->makeSearchLink( $imageTextForm, array( NS_FILE ) , $m, $tt ); + } + $out .= $sep; + + /* + $m = wfMsg( 'searchprofile-articles-and-proj' ); + $tt = wfMsg( 'searchprofile-project-tooltip', + implode( ', ', SearchEngine::namespacesAsText( SearchEngine::defaultAndProjectNamespaces() ) ) ); + if( $this->active == 'withproject' ) { + $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); + } else { + $out .= $this->makeSearchLink( $bareterm, SearchEngine::defaultAndProjectNamespaces(), $m, $tt ); + } + $out .= $sep; + */ + + $m = wfMsg( 'searchprofile-project' ); + $tt = wfMsg( 'searchprofile-project-tooltip', + implode( ', ', SearchEngine::namespacesAsText( SearchEngine::projectNamespaces() ) ) ); + if( $this->active == 'project' ) { + $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); + } else { + $out .= $this->makeSearchLink( $bareterm, SearchEngine::projectNamespaces(), $m, $tt ); + } + $out .= $sep; + + $m = wfMsg( 'searchprofile-everything' ); + $tt = wfMsg( 'searchprofile-everything-tooltip' ); + if( $this->active == 'all' ) { + $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); + } else { + $out .= $this->makeSearchLink( $bareterm, $nsAllSet, $m, $tt ); + } + $out .= $sep; + + $m = wfMsg( 'searchprofile-advanced' ); + $tt = wfMsg( 'searchprofile-advanced-tooltip' ); + if( $this->active == 'advanced' ) { + $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); + } else { + $out .= $this->makeSearchLink( $bareterm, $this->namespaces, $m, $tt, array( 'advanced' => '1' ) ); + } + $out .= Xml::closeElement('div') ; + + return $out; + } + + protected function shortDialog( $term ) { + global $wgScript; + $searchTitle = SpecialPage::getTitleFor( 'Search' ); + $searchable = SearchEngine::searchableNamespaces(); + $out = Xml::openElement( 'form', array( 'id' => 'search', 'method' => 'get', 'action' => $wgScript ) ); + $out .= Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n"; + // show namespaces only for advanced search + if( $this->active == 'advanced' ) { + $active = array(); + foreach( $this->namespaces as $ns ) { + $active[$ns] = $searchable[$ns]; + } + $out .= wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) . "<br/>\n"; + $out .= $this->namespaceTables( $active, 1 )."<br/>\n"; + // Still keep namespace settings otherwise, but don't show them + } else { + foreach( $this->namespaces as $ns ) { + $out .= Xml::hidden( "ns{$ns}", '1' ); + } + } + // Keep redirect setting + $out .= Xml::hidden( "redirs", (int)$this->searchRedirects ); + // Term box + $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . "\n"; + $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) ); + $out .= ' (' . wfMsgExt('searchmenu-help',array('parseinline') ) . ')'; + $out .= Xml::closeElement( 'form' ); + // Add prefix link for single-namespace searches + $t = Title::newFromText( $term ); + /*if( $t != null && count($this->namespaces) === 1 ) { + $out .= wfMsgExt( 'searchmenu-prefix', array('parseinline'), $term ); + }*/ + return Xml::openElement( 'fieldset', array('id' => 'mw-searchoptions','style' => 'margin:0em;') ) . + Xml::element( 'legend', null, wfMsg('searchmenu-legend') ) . + $this->formHeader($term) . $out . $this->didYouMeanHtml . + Xml::closeElement( 'fieldset' ); + } + + /** Make a search link with some target namespaces */ + protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) { + $opt = $params; + foreach( $namespaces as $n ) { + $opt['ns' . $n] = 1; + } + $opt['redirs'] = $this->searchRedirects ? 1 : 0; + + $st = SpecialPage::getTitleFor( 'Search' ); + $stParams = wfArrayToCGI( array( 'search' => $term, 'fulltext' => wfMsg( 'search' ) ), $opt ); + + return Xml::element( 'a', + array( 'href'=> $st->getLocalURL( $stParams ), 'title' => $tooltip ), + $label ); + } + + /** Check if query starts with image: prefix */ + protected function startsWithImage( $term ) { + global $wgContLang; + + $p = explode( ':', $term ); + if( count( $p ) > 1 ) { + return $wgContLang->getNsIndex( $p[0] ) == NS_FILE; + } + return false; + } + + protected function namespaceTables( $namespaces, $rowsPerTable = 3 ) { + global $wgContLang; + // Group namespaces into rows according to subject. + // Try not to make too many assumptions about namespace numbering. + $rows = array(); + $tables = ""; + foreach( $namespaces as $ns => $name ) { + $subj = MWNamespace::getSubject( $ns ); + if( !array_key_exists( $subj, $rows ) ) { + $rows[$subj] = ""; + } + $name = str_replace( '_', ' ', $name ); + if( '' == $name ) { + $name = wfMsg( 'blanknamespace' ); + } + $rows[$subj] .= Xml::openElement( 'td', array( 'style' => 'white-space: nowrap' ) ) . + Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $this->namespaces ) ) . + Xml::closeElement( 'td' ) . "\n"; + } + $rows = array_values( $rows ); + $numRows = count( $rows ); + // Lay out namespaces in multiple floating two-column tables so they'll + // be arranged nicely while still accommodating different screen widths + // Float to the right on RTL wikis + $tableStyle = $wgContLang->isRTL() ? + 'float: right; margin: 0 0 0em 1em' : 'float: left; margin: 0 1em 0em 0'; + // Build the final HTML table... + for( $i = 0; $i < $numRows; $i += $rowsPerTable ) { + $tables .= Xml::openElement( 'table', array( 'style' => $tableStyle ) ); + for( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) { + $tables .= "<tr>\n" . $rows[$j] . "</tr>"; + } + $tables .= Xml::closeElement( 'table' ) . "\n"; + } + return $tables; + } +} + +/** + * implements Special:Search - Run text & title search and display the output + * @ingroup SpecialPage + */ +class SpecialSearchOld { + + /** + * Set up basic search parameters from the request and user settings. + * Typically you'll pass $wgRequest and $wgUser. + * + * @param WebRequest $request + * @param User $user + * @public + */ + function __construct( &$request, &$user ) { + list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); + $this->mPrefix = $request->getVal('prefix', ''); $this->namespaces = $this->powerSearch( $request ); if( empty( $this->namespaces ) ) { $this->namespaces = SearchEngine::userNamespaces( $user ); @@ -119,13 +921,38 @@ class SpecialSearch { * @public */ function showResults( $term ) { - $fname = 'SpecialSearch::showResults'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); global $wgOut, $wgUser; $sk = $wgUser->getSkin(); + $search = SearchEngine::create(); + $search->setLimitOffset( $this->limit, $this->offset ); + $search->setNamespaces( $this->namespaces ); + $search->showRedirects = $this->searchRedirects; + $search->prefix = $this->mPrefix; + $term = $search->transformSearchTerm($term); + $this->setupPage( $term ); + $rewritten = $search->replacePrefixes($term); + $titleMatches = $search->searchTitle( $rewritten ); + $textMatches = $search->searchText( $rewritten ); + + // did you mean... suggestions + if($textMatches && $textMatches->hasSuggestion()){ + $st = SpecialPage::getTitleFor( 'Search' ); + $stParams = wfArrayToCGI( array( + 'search' => $textMatches->getSuggestionQuery(), + 'fulltext' => wfMsg('search')), + $this->powerSearchOptions()); + + $suggestLink = $sk->makeKnownLinkObj( $st, + $textMatches->getSuggestionSnippet(), + $stParams ); + + $wgOut->addHTML('<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>'); + } + $wgOut->addWikiMsg( 'searchresulttext' ); if( '' === trim( $term ) ) { @@ -133,7 +960,7 @@ class SpecialSearch { $wgOut->setSubtitle( '' ); $wgOut->addHTML( $this->powerSearchBox( $term ) ); $wgOut->addHTML( $this->powerSearchFocus() ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } @@ -143,6 +970,7 @@ class SpecialSearch { if( $wgSearchForwardUrl ) { $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl ); $wgOut->redirect( $url ); + wfProfileOut( __METHOD__ ); return; } global $wgInputEncoding; @@ -157,45 +985,21 @@ class SpecialSearch { ) . Xml::closeElement( 'fieldset' ) ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } - $wgOut->addHTML( $this->shortDialog( $term ) ); - - $search = SearchEngine::create(); - $search->setLimitOffset( $this->limit, $this->offset ); - $search->setNamespaces( $this->namespaces ); - $search->showRedirects = $this->searchRedirects; - $rewritten = $search->replacePrefixes($term); - - $titleMatches = $search->searchTitle( $rewritten ); + $wgOut->addHTML( $this->shortDialog( $term ) ); // Sometimes the search engine knows there are too many hits if ($titleMatches instanceof SearchResultTooMany) { $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" ); $wgOut->addHTML( $this->powerSearchBox( $term ) ); $wgOut->addHTML( $this->powerSearchFocus() ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } - $textMatches = $search->searchText( $rewritten ); - - // did you mean... suggestions - if($textMatches && $textMatches->hasSuggestion()){ - $st = SpecialPage::getTitleFor( 'Search' ); - $stParams = wfArrayToCGI( array( - 'search' => $textMatches->getSuggestionQuery(), - 'fulltext' => wfMsg('search')), - $this->powerSearchOptions()); - - $suggestLink = '<a href="'.$st->escapeLocalURL($stParams).'">'. - $textMatches->getSuggestionSnippet().'</a>'; - - $wgOut->addHTML('<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>'); - } - // show number of results $num = ( $titleMatches ? $titleMatches->numRows() : 0 ) + ( $textMatches ? $textMatches->numRows() : 0); @@ -207,7 +1011,7 @@ class SpecialSearch { if ( $num > 0 ) { if ( $totalNum > 0 ){ $top = wfMsgExt('showingresultstotal', array( 'parseinline' ), - $this->offset+1, $this->offset+$num, $totalNum ); + $this->offset+1, $this->offset+$num, $totalNum, $num ); } elseif ( $num >= $this->limit ) { $top = wfShowingResults( $this->offset, $this->limit ); } else { @@ -251,7 +1055,7 @@ class SpecialSearch { } // show interwiki results if any if( $textMatches->hasInterwikiResults() ) - $wgOut->addHtml( $this->showInterwiki( $textMatches->getInterwikiResults(), $term )); + $wgOut->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term )); // show results if( $textMatches->numRows() ) $wgOut->addHTML( $this->showMatches( $textMatches ) ); @@ -266,7 +1070,7 @@ class SpecialSearch { $wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" ); } $wgOut->addHTML( $this->powerSearchBox( $term ) ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } #------------------------------------------------------------------ @@ -277,12 +1081,14 @@ class SpecialSearch { */ function setupPage( $term ) { global $wgOut; - if( !empty( $term ) ) - $wgOut->setPageTitle( wfMsg( 'searchresults' ) ); + if( !empty( $term ) ){ + $wgOut->setPageTitle( wfMsg( 'searchresults') ); + $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term) ) ); + } $subtitlemsg = ( Title::newFromText( $term ) ? 'searchsubtitle' : 'searchsubtitleinvalid' ); $wgOut->setSubtitle( $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) ) ); $wgOut->setArticleRelated( false ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); } /** @@ -323,8 +1129,7 @@ class SpecialSearch { * @param SearchResultSet $matches */ function showMatches( &$matches ) { - $fname = 'SpecialSearch::showMatches'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); global $wgContLang; $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); @@ -347,7 +1152,7 @@ class SpecialSearch { // convert the whole thing to desired language variant global $wgContLang; $out = $wgContLang->convert( $out ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $out; } @@ -357,12 +1162,11 @@ class SpecialSearch { * @param array $terms terms to highlight */ function showHit( $result, $terms ) { - $fname = 'SpecialSearch::showHit'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); global $wgUser, $wgContLang, $wgLang; if( $result->isBrokenTitle() ) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return "<!-- Broken link in search result -->\n"; } @@ -375,7 +1179,7 @@ class SpecialSearch { //This is not quite safe, but better than showing excerpts from non-readable pages //Note that hiding the entry entirely would screw up paging. if (!$t->userCanRead()) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return "<li>{$link}</li>\n"; } @@ -383,7 +1187,7 @@ class SpecialSearch { // The least confusing at this point is to drop the result. // You may get less results, but... oh well. :P if( $result->isMissingRevision() ) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n"; } @@ -434,18 +1238,18 @@ class SpecialSearch { array('search' => wfMsgForContent('searchrelated').':'.$t->getPrefixedText(), 'fulltext' => wfMsg('search') )); - $related = ' -- <a href="'.$st->escapeLocalURL($stParams).'">'. - wfMsg('search-relatedarticle').'</a>'; + $related = ' -- ' . $sk->makeKnownLinkObj( $st, + wfMsg('search-relatedarticle'), $stParams ); } // Include a thumbnail for media files... - if( $t->getNamespace() == NS_IMAGE ) { + if( $t->getNamespace() == NS_FILE ) { $img = wfFindFile( $t ); if( $img ) { - $thumb = $img->getThumbnail( 120, 120 ); + $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); if( $thumb ) { $desc = $img->getShortDesc(); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); // Ugly table. :D // Float doesn't seem to interact well with the bullets. // Table messes up vertical alignment of the bullet, but I'm @@ -468,7 +1272,7 @@ class SpecialSearch { } } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return "<li>{$link} {$redirect} {$section} {$extract}\n" . "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" . "</li>\n"; @@ -481,8 +1285,7 @@ class SpecialSearch { * @param SearchResultSet $matches */ function showInterwiki( &$matches, $query ) { - $fname = 'SpecialSearch::showInterwiki'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); global $wgContLang; $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); @@ -512,7 +1315,7 @@ class SpecialSearch { // convert the whole thing to desired language variant global $wgContLang; $out = $wgContLang->convert( $out ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $out; } @@ -525,13 +1328,12 @@ class SpecialSearch { * @param string $query * @param array $customCaptions iw prefix -> caption */ - function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions){ - $fname = 'SpecialSearch::showInterwikiHit'; - wfProfileIn( $fname ); + function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) { + wfProfileIn( __METHOD__ ); global $wgUser, $wgContLang, $wgLang; if( $result->isBrokenTitle() ) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return "<!-- Broken link in search result -->\n"; } @@ -569,7 +1371,7 @@ class SpecialSearch { } $out .= "<li>{$link} {$redirect}</li>\n"; - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $out; } @@ -580,35 +1382,64 @@ class SpecialSearch { * @return $out string: HTML form */ function powerSearchBox( $term ) { - global $wgScript; + global $wgScript, $wgContLang; - $namespaces = ''; - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { + $namespaces = SearchEngine::searchableNamespaces(); + + // group namespaces into rows according to subject; try not to make too + // many assumptions about namespace numbering + $rows = array(); + foreach( $namespaces as $ns => $name ) { + $subj = MWNamespace::getSubject( $ns ); + if( !array_key_exists( $subj, $rows ) ) { + $rows[$subj] = ""; + } $name = str_replace( '_', ' ', $name ); if( '' == $name ) { $name = wfMsg( 'blanknamespace' ); } - $namespaces .= Xml::openElement( 'span', array( 'style' => 'white-space: nowrap' ) ) . + $rows[$subj] .= Xml::openElement( 'td', array( 'style' => 'white-space: nowrap' ) ) . Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $this->namespaces ) ) . - Xml::closeElement( 'span' ) . "\n"; + Xml::closeElement( 'td' ) . "\n"; + } + $rows = array_values( $rows ); + $numRows = count( $rows ); + + // lay out namespaces in multiple floating two-column tables so they'll + // be arranged nicely while still accommodating different screen widths + $rowsPerTable = 3; // seems to look nice + + // float to the right on RTL wikis + $tableStyle = ( $wgContLang->isRTL() ? + 'float: right; margin: 0 0 1em 1em' : + 'float: left; margin: 0 1em 1em 0' ); + + $tables = ""; + for( $i = 0; $i < $numRows; $i += $rowsPerTable ) { + $tables .= Xml::openElement( 'table', array( 'style' => $tableStyle ) ); + for( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) { + $tables .= "<tr>\n" . $rows[$j] . "</tr>"; + } + $tables .= Xml::closeElement( 'table' ) . "\n"; } $redirect = Xml::check( 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' ) ); $redirectLabel = Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' ); $searchField = Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'powerSearchText' ) ); $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' ) ) . "\n"; - + $searchTitle = SpecialPage::getTitleFor( 'Search' ); + $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) . Xml::fieldset( wfMsg( 'powersearch-legend' ), - Xml::hidden( 'title', 'Special:Search' ) . + Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n" . "<p>" . wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) . - "<br />" . - $namespaces . - "</p>" . + "</p>\n" . + $tables . + "<hr style=\"clear: both\" />\n" . "<p>" . $redirect . " " . $redirectLabel . - "</p>" . + "</p>\n" . wfMsgExt( 'powersearch-field', array( 'parseinline' ) ) . " " . $searchField . @@ -636,7 +1467,8 @@ class SpecialSearch { 'method' => 'get', 'action' => $wgScript )); - $out .= Xml::hidden( 'title', 'Special:Search' ); + $searchTitle = SpecialPage::getTitleFor( 'Search' ); + $out .= Xml::hidden( 'title', $searchTitle->getPrefixedText() ); $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . ' '; foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { if( in_array( $ns, $this->namespaces ) ) { diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php index ca91ad51..560ba445 100644 --- a/includes/specials/SpecialSpecialpages.php +++ b/includes/specials/SpecialSpecialpages.php @@ -12,7 +12,7 @@ function wfSpecialSpecialpages() { $wgMessageCache->loadAllMessages(); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Is this really needed? + $wgOut->setRobotPolicy( 'noindex,nofollow' ); # Is this really needed? $sk = $wgUser->getSkin(); $pages = SpecialPage::getUsablePages(); diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php index 570a21c6..109c5c30 100644 --- a/includes/specials/SpecialStatistics.php +++ b/includes/specials/SpecialStatistics.php @@ -13,50 +13,214 @@ * * @param mixed $par (not used) */ -function wfSpecialStatistics( $par = '' ) { - global $wgOut, $wgLang, $wgRequest; - $dbr = wfGetDB( DB_SLAVE ); +class SpecialStatistics extends SpecialPage { + + private $views, $edits, $good, $images, $total, $users, + $activeUsers, $admins, $numJobs = 0; + + public function __construct() { + parent::__construct( 'Statistics' ); + } + + public function execute( $par ) { + global $wgOut, $wgRequest, $wgMessageCache; + global $wgDisableCounters, $wgMiserMode; + $wgMessageCache->loadAllMessages(); + + $this->setHeaders(); + + $this->views = SiteStats::views(); + $this->edits = SiteStats::edits(); + $this->good = SiteStats::articles(); + $this->images = SiteStats::images(); + $this->total = SiteStats::pages(); + $this->users = SiteStats::users(); + $this->activeUsers = SiteStats::activeUsers(); + $this->admins = SiteStats::numberingroup('sysop'); + $this->numJobs = SiteStats::jobs(); + + # Staticic - views + $viewsStats = ''; + if( !$wgDisableCounters ) { + $viewsStats = $this->getViewsStats(); + } + + # Set active user count + if( !$wgMiserMode ) { + $dbw = wfGetDB( DB_MASTER ); + SiteStatsUpdate::cacheUpdate( $dbw ); + } + + # Do raw output + if( $wgRequest->getVal( 'action' ) == 'raw' ) { + $this->doRawOutput(); + } - $views = SiteStats::views(); - $edits = SiteStats::edits(); - $good = SiteStats::articles(); - $images = SiteStats::images(); - $total = SiteStats::pages(); - $users = SiteStats::users(); - $admins = SiteStats::admins(); - $numJobs = SiteStats::jobs(); + $text = Xml::openElement( 'table', array( 'class' => 'mw-statistics-table' ) ); - if( $wgRequest->getVal( 'action' ) == 'raw' ) { - $wgOut->disable(); - header( 'Pragma: nocache' ); - echo "total=$total;good=$good;views=$views;edits=$edits;users=$users;admins=$admins;images=$images;jobs=$numJobs\n"; - return; - } else { - $text = "__NOTOC__\n"; - $text .= '==' . wfMsgNoTrans( 'sitestats' ) . "==\n"; - $text .= wfMsgExt( 'sitestatstext', array( 'parsemag' ), - $wgLang->formatNum( $total ), - $wgLang->formatNum( $good ), - $wgLang->formatNum( $views ), - $wgLang->formatNum( $edits ), - $wgLang->formatNum( sprintf( '%.2f', $total ? $edits / $total : 0 ) ), - $wgLang->formatNum( sprintf( '%.2f', $edits ? $views / $edits : 0 ) ), - $wgLang->formatNum( $numJobs ), - $wgLang->formatNum( $images ) - )."\n"; + # Statistic - pages + $text .= $this->getPageStats(); + + # Statistic - edits + $text .= $this->getEditStats(); - $text .= "==" . wfMsgNoTrans( 'userstats' ) . "==\n"; - $text .= wfMsgExt( 'userstatstext', array ( 'parsemag' ), - $wgLang->formatNum( $users ), - $wgLang->formatNum( $admins ), - '[[' . wfMsgForContent( 'grouppage-sysop' ) . ']]', # TODO somehow remove, kept for backwards compatibility - $wgLang->formatNum( @sprintf( '%.2f', $admins / $users * 100 ) ), - User::makeGroupLinkWiki( 'sysop' ) - )."\n"; + # Statistic - users + $text .= $this->getUserStats(); - global $wgDisableCounters, $wgMiserMode, $wgUser, $wgLang, $wgContLang; + # Statistic - usergroups + $text .= $this->getGroupStats(); + $text .= $viewsStats; + + # Statistic - popular pages if( !$wgDisableCounters && !$wgMiserMode ) { - $res = $dbr->select( + $text .= $this->getMostViewedPages(); + } + + $text .= Xml::closeElement( 'table' ); + + # Customizable footer + $footer = wfMsgExt( 'statistics-footer', array('parseinline') ); + if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) { + $text .= "\n" . $footer; + } + + $wgOut->addHTML( $text ); + } + + /** + * Format a row + * @param string $text description of the row + * @param float $number a number + * @param array $trExtraParams + * @param string $descMsg + * @param string $descMsgParam + * @return string table row in HTML format + */ + private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) { + global $wgStylePath; + if( $descMsg ) { + $descriptionText = wfMsgExt( $descMsg, array( 'parseinline' ), $descMsgParam ); + if ( !wfEmptyMsg( $descMsg, $descriptionText ) ) { + $descriptionText = " ($descriptionText)"; + $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc'), + $descriptionText ); + } + } + return Xml::openElement( 'tr', $trExtraParams ) . + Xml::openElement( 'td' ) . $text . Xml::closeElement( 'td' ) . + Xml::openElement( 'td', array( 'class' => 'mw-statistics-numbers' ) ) . $number . Xml::closeElement( 'td' ) . + Xml::closeElement( 'tr' ); + } + + /** + * Each of these methods is pretty self-explanatory, get a particular + * row for the table of statistics + * @return string + */ + private function getPageStats() { + global $wgLang; + return Xml::openElement( 'tr' ) . + Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-pages', array( 'parseinline' ) ) ) . + Xml::closeElement( 'tr' ) . + $this->formatRow( wfMsgExt( 'statistics-articles', array( 'parseinline' ) ), + $wgLang->formatNum( $this->good ), + array( 'class' => 'mw-statistics-articles' ) ) . + $this->formatRow( wfMsgExt( 'statistics-pages', array( 'parseinline' ) ), + $wgLang->formatNum( $this->total ), + array( 'class' => 'mw-statistics-pages' ), + 'statistics-pages-desc' ) . + $this->formatRow( wfMsgExt( 'statistics-files', array( 'parseinline' ) ), + $wgLang->formatNum( $this->images ), + array( 'class' => 'mw-statistics-files' ) ); + } + private function getEditStats() { + global $wgLang; + return Xml::openElement( 'tr' ) . + Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-edits', array( 'parseinline' ) ) ) . + Xml::closeElement( 'tr' ) . + $this->formatRow( wfMsgExt( 'statistics-edits', array( 'parseinline' ) ), + $wgLang->formatNum( $this->edits ), + array( 'class' => 'mw-statistics-edits' ) ) . + $this->formatRow( wfMsgExt( 'statistics-edits-average', array( 'parseinline' ) ), + $wgLang->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ), + array( 'class' => 'mw-statistics-edits-average' ) ) . + $this->formatRow( wfMsgExt( 'statistics-jobqueue', array( 'parseinline' ) ), + $wgLang->formatNum( $this->numJobs ), + array( 'class' => 'mw-statistics-jobqueue' ) ); + } + private function getUserStats() { + global $wgLang, $wgRCMaxAge; + return Xml::openElement( 'tr' ) . + Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-users', array( 'parseinline' ) ) ) . + Xml::closeElement( 'tr' ) . + $this->formatRow( wfMsgExt( 'statistics-users', array( 'parseinline' ) ), + $wgLang->formatNum( $this->users ), + array( 'class' => 'mw-statistics-users' ) ) . + $this->formatRow( wfMsgExt( 'statistics-users-active', array( 'parseinline' ) ), + $wgLang->formatNum( $this->activeUsers ), + array( 'class' => 'mw-statistics-users-active' ), + 'statistics-users-active-desc', + $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600 * 24 ) ) ) ); + } + private function getGroupStats() { + global $wgGroupPermissions, $wgImplicitGroups, $wgLang, $wgUser; + $sk = $wgUser->getSkin(); + $text = ''; + foreach( $wgGroupPermissions as $group => $permissions ) { + # Skip generic * and implicit groups + if ( in_array( $group, $wgImplicitGroups ) || $group == '*' ) { + continue; + } + $groupname = htmlspecialchars( $group ); + $msg = wfMsg( 'group-' . $groupname ); + if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) { + $groupnameLocalized = $groupname; + } else { + $groupnameLocalized = $msg; + } + $msg = wfMsgForContent( 'grouppage-' . $groupname ); + if ( wfEmptyMsg( 'grouppage-' . $groupname, $msg ) || $msg == '' ) { + $grouppageLocalized = MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname; + } else { + $grouppageLocalized = $msg; + } + $grouppage = $sk->makeLink( $grouppageLocalized, htmlspecialchars( $groupnameLocalized ) ); + $grouplink = $sk->link( SpecialPage::getTitleFor( 'Listusers' ), + wfMsgHtml( 'listgrouprights-members' ), + array(), + array( 'group' => $group ), + 'known' ); + # Add a class when a usergroup contains no members to allow hiding these rows + $classZero = ''; + $countUsers = SiteStats::numberingroup( $groupname ); + if( $countUsers == 0 ) { + $classZero = ' statistics-group-zero'; + } + $text .= $this->formatRow( $grouppage . ' ' . $grouplink, + $wgLang->formatNum( $countUsers ), + array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) ); + } + return $text; + } + private function getViewsStats() { + global $wgLang; + return Xml::openElement( 'tr' ) . + Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-views', array( 'parseinline' ) ) ) . + Xml::closeElement( 'tr' ) . + $this->formatRow( wfMsgExt( 'statistics-views-total', array( 'parseinline' ) ), + $wgLang->formatNum( $this->views ), + array ( 'class' => 'mw-statistics-views-total' ) ) . + $this->formatRow( wfMsgExt( 'statistics-views-peredit', array( 'parseinline' ) ), + $wgLang->formatNum( sprintf( '%.2f', $this->edits ? + $this->views / $this->edits : 0 ) ), + array ( 'class' => 'mw-statistics-views-peredit' ) ); + } + private function getMostViewedPages() { + global $wgLang, $wgUser; + $text = ''; + $dbr = wfGetDB( DB_SLAVE ); + $sk = $wgUser->getSkin(); + $res = $dbr->select( 'page', array( 'page_namespace', @@ -74,20 +238,33 @@ function wfSpecialStatistics( $par = '' ) { ) ); if( $res->numRows() > 0 ) { - $text .= "==" . wfMsgNoTrans( 'statistics-mostpopular' ) . "==\n"; + $text .= Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-mostpopular', array( 'parseinline' ) ) ); while( $row = $res->fetchObject() ) { $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - if( $title instanceof Title ) - $text .= '* [[:' . $title->getPrefixedText() . ']] (' . $wgLang->formatNum( $row->page_counter ) . ")\n"; + if( $title instanceof Title ) { + $text .= $this->formatRow( $sk->link( $title ), + $wgLang->formatNum( $row->page_counter ) ); + + } } $res->free(); } - } - - $footer = wfMsgNoTrans( 'statistics-footer' ); - if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) - $text .= "\n" . $footer; - - $wgOut->addWikiText( $text ); + return $text; + } + + /** + * Do the action=raw output for this page. Legacy, but we support + * it for backwards compatibility + * http://lists.wikimedia.org/pipermail/wikitech-l/2008-August/039202.html + */ + private function doRawOutput() { + global $wgOut; + $wgOut->disable(); + header( 'Pragma: nocache' ); + echo "total=" . $this->total . ";good=" . $this->good . ";views=" . + $this->views . ";edits=" . $this->edits . ";users=" . $this->users . ";"; + echo "activeusers=" . $this->activeUsers . ";admins=" . $this->admins . + ";images=" . $this->images . ";jobs=" . $this->numJobs . "\n"; + return; } -} +}
\ No newline at end of file diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php index 986ec967..25310081 100644 --- a/includes/specials/SpecialUncategorizedimages.php +++ b/includes/specials/SpecialUncategorizedimages.php @@ -31,7 +31,7 @@ class UncategorizedImagesPage extends ImageQueryPage { function getSQL() { $dbr = wfGetDB( DB_SLAVE ); list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $ns = NS_IMAGE; + $ns = NS_FILE; return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace, page_title AS title, page_title AS value diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index d862ebb3..a9fb4ef1 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -119,7 +119,7 @@ class PageArchive { * @todo Does this belong in Image for fuller encapsulation? */ function listFiles() { - if( $this->title->getNamespace() == NS_IMAGE ) { + if( $this->title->getNamespace() == NS_FILE ) { $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'filearchive', array( @@ -336,7 +336,7 @@ class PageArchive { $restoreText = $restoreAll || !empty( $timestamps ); $restoreFiles = $restoreAll || !empty( $fileVersions ); - if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) { + if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) { $img = wfLocalFile( $this->title ); $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); $filesRestored = $this->fileStatus->successCount; @@ -412,7 +412,7 @@ class PageArchive { # we'll update the latest revision field in the record. $newid = 0; $pageId = $page->page_id; - $previousRevId = $page->page_latest; + $previousRevId = $page->page_latest; # Get the time span of this page $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp', array( 'rev_id' => $previousRevId ), @@ -461,25 +461,10 @@ class PageArchive { 'ar_title' => $this->title->getDBkey(), $oldones ), __METHOD__, - /* options */ array( - 'ORDER BY' => 'ar_timestamp' ) + /* options */ array( 'ORDER BY' => 'ar_timestamp' ) ); $ret = $dbw->resultObject( $result ); - $rev_count = $dbw->numRows( $result ); - if( $rev_count ) { - # We need to seek around as just using DESC in the ORDER BY - # would leave the revisions inserted in the wrong order - $first = $ret->fetchObject(); - $ret->seek( $rev_count - 1 ); - $last = $ret->fetchObject(); - // We don't handle well changing the top revision's settings - if( !$unsuppress && $last->ar_deleted && $last->ar_timestamp > $previousTimestamp ) { - wfDebug( __METHOD__.": restoration would result in a deleted top revision\n" ); - return false; - } - $ret->seek( 0 ); - } if( $makepage ) { $newid = $article->insertOn( $dbw ); @@ -502,6 +487,12 @@ class PageArchive { // a new text table entry will be created for it. $revText = Revision::getRevisionText( $row, 'ar_' ); } + // Check for key dupes due to shitty archive integrity. + if( $row->ar_rev_id ) { + $exists = $dbw->selectField( 'revision', '1', array('rev_id' => $row->ar_rev_id), __METHOD__ ); + if( $exists ) continue; // don't throw DB errors + } + $revision = new Revision( array( 'page' => $pageId, 'id' => $row->ar_rev_id, @@ -520,17 +511,32 @@ class PageArchive { wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) ); } + # Now that it's safely stored, take it out of the archive + $dbw->delete( 'archive', + /* WHERE */ array( + 'ar_namespace' => $this->title->getNamespace(), + 'ar_title' => $this->title->getDBkey(), + $oldones ), + __METHOD__ ); + // Was anything restored at all? - if($restored == 0) + if( $restored == 0 ) return 0; if( $revision ) { // Attach the latest revision to the page... $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId ); - if( $newid || $wasnew ) { // Update site stats, link tables, etc $article->createUpdates( $revision ); + // We don't handle well with top revision deleted + if( $revision->getVisibility() ) { + $dbw->update( 'revision', + array( 'rev_deleted' => 0 ), + array( 'rev_id' => $revision->getId() ), + __METHOD__ + ); + } } if( $newid ) { @@ -541,7 +547,7 @@ class PageArchive { Article::onArticleEdit( $this->title ); } - if( $this->title->getNamespace() == NS_IMAGE ) { + if( $this->title->getNamespace() == NS_FILE ) { $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); $update->doUpdate(); } @@ -550,14 +556,6 @@ class PageArchive { return self::UNDELETE_UNKNOWNERR; } - # Now that it's safely stored, take it out of the archive - $dbw->delete( 'archive', - /* WHERE */ array( - 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - $oldones ), - __METHOD__ ); - return $restored; } @@ -570,7 +568,7 @@ class PageArchive { * @ingroup SpecialPage */ class UndeleteForm { - var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj; + var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj; var $mTargetTimestamp, $mAllowed, $mComment, $mToken; function UndeleteForm( $request, $par = "" ) { @@ -585,6 +583,7 @@ class UndeleteForm { $posted = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); $this->mRestore = $request->getCheck( 'restore' ) && $posted; + $this->mInvert = $request->getCheck( 'invert' ) && $posted; $this->mPreview = $request->getCheck( 'preview' ) && $posted; $this->mDiff = $request->getCheck( 'diff' ); $this->mComment = $request->getText( 'wpComment' ); @@ -606,7 +605,7 @@ class UndeleteForm { } else { $this->mTargetObj = NULL; } - if( $this->mRestore ) { + if( $this->mRestore || $this->mInvert ) { $timestamps = array(); $this->mFileVersions = array(); foreach( $_REQUEST as $key => $val ) { @@ -666,6 +665,9 @@ class UndeleteForm { if( $this->mRestore && $this->mAction == "submit" ) { return $this->undelete(); } + if( $this->mInvert && $this->mAction == "submit" ) { + return $this->showHistory( ); + } return $this->showHistory(); } @@ -673,21 +675,20 @@ class UndeleteForm { global $wgOut, $wgScript; $wgOut->addWikiMsg( 'undelete-header' ); - $wgOut->addHtml( + $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - '<fieldset>' . - Xml::element( 'legend', array(), - wfMsg( 'undelete-search-box' ) ) . + Xml::fieldset( wfMsg( 'undelete-search-box' ) ) . Xml::hidden( 'title', SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) . Xml::inputLabel( wfMsg( 'undelete-search-prefix' ), 'prefix', 'prefix', 20, - $this->mSearchPrefix ) . + $this->mSearchPrefix ) . ' ' . Xml::submitButton( wfMsg( 'undelete-search-submit' ) ) . - '</fieldset>' . - '</form>' ); + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); } // Generic list of deleted pages @@ -699,7 +700,7 @@ class UndeleteForm { return; } - $wgOut->addWikiMsg( "undeletepagetext" ); + $wgOut->addWikiMsg( 'undeletepagetext', $wgLang->formatNum( $result->numRows() ) ); $sk = $wgUser->getSkin(); $undelete = SpecialPage::getTitleFor( 'Undelete' ); @@ -708,11 +709,10 @@ class UndeleteForm { $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); $link = $sk->makeKnownLinkObj( $undelete, htmlspecialchars( $title->getPrefixedText() ), 'target=' . $title->getPrefixedUrl() ); - #$revs = wfMsgHtml( 'undeleterevisions', $wgLang->formatNum( $row->count ) ); $revs = wfMsgExt( 'undeleterevisions', array( 'parseinline' ), $wgLang->formatNum( $row->count ) ); - $wgOut->addHtml( "<li>{$link} ({$revs})</li>\n" ); + $wgOut->addHTML( "<li>{$link} ({$revs})</li>\n" ); } $result->free(); $wgOut->addHTML( "</ul>\n" ); @@ -752,8 +752,6 @@ class UndeleteForm { SpecialPage::getTitleFor( 'Undelete', $this->mTargetObj->getPrefixedDBkey() ), htmlspecialchars( $this->mTargetObj->getPrefixedText() ) ); - $time = htmlspecialchars( $wgLang->timeAndDate( $timestamp, true ) ); - $user = $skin->revUserTools( $rev ); if( $this->mDiff ) { $previousRev = $archive->getPreviousRevision( $timestamp ); @@ -762,59 +760,66 @@ class UndeleteForm { if( $wgUser->getOption( 'diffonly' ) ) { return; } else { - $wgOut->addHtml( '<hr />' ); + $wgOut->addHTML( '<hr />' ); } } else { - $wgOut->addHtml( wfMsgHtml( 'undelete-nodiff' ) ); + $wgOut->addHTML( wfMsgHtml( 'undelete-nodiff' ) ); } } - $wgOut->addHtml( '<p>' . wfMsgHtml( 'undelete-revision', $link, $time, $user ) . '</p>' ); + // date and time are separate parameters to facilitate localisation. + // $time is kept for backward compat reasons. + $time = htmlspecialchars( $wgLang->timeAndDate( $timestamp, true ) ); + $d = htmlspecialchars( $wgLang->date( $timestamp, true ) ); + $t = htmlspecialchars( $wgLang->time( $timestamp, true ) ); + $user = $skin->revUserTools( $rev ); + + $wgOut->addHTML( '<p>' . wfMsgHtml( 'undelete-revision', $link, $time, $user, $d, $t ) . '</p>' ); wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ); if( $this->mPreview ) { - $wgOut->addHtml( "<hr />\n" ); + $wgOut->addHTML( "<hr />\n" ); //Hide [edit]s $popts = $wgOut->parserOptions(); $popts->setEditSection( false ); $wgOut->parserOptions( $popts ); - $wgOut->addWikiTextTitleTidy( $rev->revText(), $this->mTargetObj, true ); + $wgOut->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER ), $this->mTargetObj, true ); } - $wgOut->addHtml( - wfElement( 'textarea', array( + $wgOut->addHTML( + Xml::element( 'textarea', array( 'readonly' => 'readonly', 'cols' => intval( $wgUser->getOption( 'cols' ) ), 'rows' => intval( $wgUser->getOption( 'rows' ) ) ), - $rev->revText() . "\n" ) . - wfOpenElement( 'div' ) . - wfOpenElement( 'form', array( + $rev->getText( Revision::FOR_THIS_USER ) . "\n" ) . + Xml::openElement( 'div' ) . + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalURL( "action=submit" ) ) ) . - wfElement( 'input', array( + Xml::element( 'input', array( 'type' => 'hidden', 'name' => 'target', 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) . - wfElement( 'input', array( + Xml::element( 'input', array( 'type' => 'hidden', 'name' => 'timestamp', 'value' => $timestamp ) ) . - wfElement( 'input', array( + Xml::element( 'input', array( 'type' => 'hidden', 'name' => 'wpEditToken', 'value' => $wgUser->editToken() ) ) . - wfElement( 'input', array( + Xml::element( 'input', array( 'type' => 'submit', 'name' => 'preview', 'value' => wfMsg( 'showpreview' ) ) ) . - wfElement( 'input', array( + Xml::element( 'input', array( 'name' => 'diff', 'type' => 'submit', 'value' => wfMsg( 'showdiff' ) ) ) . - wfCloseElement( 'form' ) . - wfCloseElement( 'div' ) ); + Xml::closeElement( 'form' ) . + Xml::closeElement( 'div' ) ); } /** @@ -829,7 +834,7 @@ class UndeleteForm { $diffEngine = new DifferenceEngine(); $diffEngine->showDiffStyle(); - $wgOut->addHtml( + $wgOut->addHTML( "<div>" . "<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>" . "<col class='diff-marker' />" . @@ -838,11 +843,11 @@ class UndeleteForm { "<col class='diff-content' />" . "<tr>" . "<td colspan='2' width='50%' align='center' class='diff-otitle'>" . - $this->diffHeader( $previousRev ) . - "</td>" . + $this->diffHeader( $previousRev, 'o' ) . + "</td>\n" . "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" . - $this->diffHeader( $currentRev ) . - "</td>" . + $this->diffHeader( $currentRev, 'n' ) . + "</td>\n" . "</tr>" . $diffEngine->generateDiffBody( $previousRev->getText(), $currentRev->getText() ) . @@ -851,7 +856,7 @@ class UndeleteForm { } - private function diffHeader( $rev ) { + private function diffHeader( $rev, $prefix ) { global $wgUser, $wgLang, $wgLang; $sk = $wgUser->getSkin(); $isDeleted = !( $rev->getId() && $rev->getTitle() ); @@ -868,17 +873,17 @@ class UndeleteForm { $targetQuery = 'oldid=' . $rev->getId(); } return - '<div id="mw-diff-otitle1"><strong>' . + '<div id="mw-diff-'.$prefix.'title1"><strong>' . $sk->makeLinkObj( $targetPage, wfMsgHtml( 'revisionasof', $wgLang->timeanddate( $rev->getTimestamp(), true ) ), $targetQuery ) . ( $isDeleted ? ' ' . wfMsgHtml( 'deletedrev' ) : '' ) . '</strong></div>' . - '<div id="mw-diff-otitle2">' . + '<div id="mw-diff-'.$prefix.'title2">' . $sk->revUserTools( $rev ) . '<br/>' . '</div>' . - '<div id="mw-diff-otitle3">' . + '<div id="mw-diff-'.$prefix.'title3">' . $sk->revComment( $rev ) . '<br/>' . '</div>'; } @@ -891,7 +896,8 @@ class UndeleteForm { $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile ); $wgOut->addWikiMsg( 'undelete-show-file-confirm', $this->mTargetObj->getText(), - $wgLang->timeanddate( $file->getTimestamp() ) ); + $wgLang->date( $file->getTimestamp() ), + $wgLang->time( $file->getTimestamp() ) ); $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'POST', @@ -925,7 +931,7 @@ class UndeleteForm { $store->stream( $key ); } - private function showHistory() { + private function showHistory( ) { global $wgLang, $wgUser, $wgOut; $sk = $wgUser->getSkin(); @@ -984,7 +990,7 @@ class UndeleteForm { $action = $titleObj->getLocalURL( "action=submit" ); # Start the form here $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) ); - $wgOut->addHtml( $top ); + $wgOut->addHTML( $top ); } # Show relevant lines from the deletion log: @@ -1007,8 +1013,7 @@ class UndeleteForm { $unsuppressBox = ""; } $table = - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'undelete-fieldset-title' ) ). + Xml::fieldset( wfMsg( 'undelete-fieldset-title' ) ) . Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . "<tr> <td colspan='2'>" . @@ -1026,15 +1031,16 @@ class UndeleteForm { <tr> <td> </td> <td class='mw-submit'>" . - Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . - Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . + Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' . + Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . ' ' . + Xml::submitButton( wfMsg( 'undeleteinvert' ), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) . "</td> </tr>" . $unsuppressBox . Xml::closeElement( 'table' ) . Xml::closeElement( 'fieldset' ); - $wgOut->addHtml( $table ); + $wgOut->addHTML( $table ); } $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" ); @@ -1044,7 +1050,7 @@ class UndeleteForm { $wgOut->addHTML("<ul>"); $target = urlencode( $this->mTarget ); $remaining = $revisions->numRows(); - $earliestLiveTime = $this->getEarliestTime( $this->mTargetObj ); + $earliestLiveTime = $this->mTargetObj->getEarliestRevTime(); while( $row = $revisions->fetchObject() ) { $remaining--; @@ -1057,8 +1063,8 @@ class UndeleteForm { } if( $haveFiles ) { - $wgOut->addHtml( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" ); - $wgOut->addHtml( "<ul>" ); + $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" ); + $wgOut->addHTML( "<ul>" ); while( $row = $files->fetchObject() ) { $wgOut->addHTML( $this->formatFileRow( $row, $sk ) ); } @@ -1071,7 +1077,7 @@ class UndeleteForm { $misc = Xml::hidden( 'target', $this->mTarget ); $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); $misc .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $misc ); + $wgOut->addHTML( $misc ); } return true; @@ -1093,7 +1099,15 @@ class UndeleteForm { $stxt = ''; $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); if( $this->mAllowed ) { - $checkBox = Xml::check( "ts$ts" ); + if( $this->mInvert){ + if( in_array( $ts, $this->mTargetTimestamp ) ) { + $checkBox = Xml::check( "ts$ts"); + } else { + $checkBox = Xml::check( "ts$ts", true ); + } + } else { + $checkBox = Xml::check( "ts$ts" ); + } $titleObj = SpecialPage::getTitleFor( "Undelete" ); $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk ); # Last link @@ -1123,7 +1137,6 @@ class UndeleteForm { // If revision was hidden from sysops $del = wfMsgHtml('rev-delundel'); } else { - $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); $del = $sk->makeKnownLinkObj( $revdel, wfMsgHtml('rev-delundel'), 'target=' . $this->mTargetObj->getPrefixedUrl() . "&artimestamp=$ts" ); @@ -1183,18 +1196,6 @@ class UndeleteForm { return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n"; } - private function getEarliestTime( $title ) { - $dbr = wfGetDB( DB_SLAVE ); - if( $title->exists() ) { - $min = $dbr->selectField( 'revision', - 'MIN(rev_timestamp)', - array( 'rev_page' => $title->getArticleId() ), - __METHOD__ ); - return wfTimestampOrNull( TS_MW, $min ); - } - return null; - } - /** * Fetch revision text link if it's available to all users * @return string @@ -1286,10 +1287,10 @@ class UndeleteForm { $skin = $wgUser->getSkin(); $link = $skin->makeKnownLinkObj( $this->mTargetObj ); - $wgOut->addHtml( wfMsgWikiHtml( 'undeletedpage', $link ) ); + $wgOut->addHTML( wfMsgWikiHtml( 'undeletedpage', $link ) ); } else { $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); - $wgOut->addHtml( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' ); + $wgOut->addHTML( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' ); } // Show file deletion warnings and errors diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php index d71b638f..4adf405d 100644 --- a/includes/specials/SpecialUnusedimages.php +++ b/includes/specials/SpecialUnusedimages.php @@ -33,7 +33,7 @@ class UnusedimagesPage extends ImageQueryPage { FROM ((($page AS I LEFT JOIN $categorylinks AS L ON I.page_id = L.cl_from) LEFT JOIN $imagelinks AS P ON I.page_title = P.il_to) INNER JOIN $image AS G ON I.page_title = G.img_name) - WHERE I.page_namespace = ".NS_IMAGE." AND L.cl_from IS NULL AND P.il_to IS NULL"; + WHERE I.page_namespace = ".NS_FILE." AND L.cl_from IS NULL AND P.il_to IS NULL"; } else { list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' ); diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 3a79e052..450c8728 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -23,7 +23,7 @@ class UploadForm { const BEFORE_PROCESSING = 1; const LARGE_FILE_SERVER = 2; const EMPTY_FILE = 3; - const MIN_LENGHT_PARTNAME = 4; + const MIN_LENGTH_PARTNAME = 4; const ILLEGAL_FILENAME = 5; const PROTECTED_PAGE = 6; const OVERWRITE_EXISTING_FILE = 7; @@ -300,7 +300,7 @@ class UploadForm { $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) ); break; - case self::MIN_LENGHT_PARTNAME: + case self::MIN_LENGTH_PARTNAME: $this->mainUploadForm( wfMsgHtml( 'minlength1' ) ); break; @@ -328,10 +328,7 @@ class UploadForm { wfMsgExt( 'filetype-banned-type', array( 'parseinline' ), htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ), + $wgLang->commaList( $wgFileExtensions ), $wgLang->formatNum( count($wgFileExtensions) ) ) ); @@ -402,7 +399,15 @@ class UploadForm { $basename = $this->mSrcName; } $filtered = wfStripIllegalFilenameChars( $basename ); - + + /* Normalize to title form before we do any further processing */ + $nt = Title::makeTitleSafe( NS_FILE, $filtered ); + if( is_null( $nt ) ) { + $resultDetails = array( 'filtered' => $filtered ); + return self::ILLEGAL_FILENAME; + } + $filtered = $nt->getDBkey(); + /** * We'll want to blacklist against *any* 'extension', and use * only the final one for the whitelist. @@ -423,14 +428,9 @@ class UploadForm { } if( strlen( $partname ) < 1 ) { - return self::MIN_LENGHT_PARTNAME; + return self::MIN_LENGTH_PARTNAME; } - $nt = Title::makeTitleSafe( NS_IMAGE, $filtered ); - if( is_null( $nt ) ) { - $resultDetails = array( 'filtered' => $filtered ); - return self::ILLEGAL_FILENAME; - } $this->mLocalFile = wfLocalFile( $nt ); $this->mDestName = $this->mLocalFile->getName(); @@ -520,10 +520,7 @@ class UploadForm { wfMsgExt( 'filetype-unwanted-type', array( 'parseinline' ), htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ), + $wgLang->commaList( $wgFileExtensions ), $wgLang->formatNum( count($wgFileExtensions) ) ) . '</li>'; } @@ -544,7 +541,7 @@ class UploadForm { $warning .= self::getExistsWarning( $this->mLocalFile ); } - $warning .= $this->getDupeWarning( $this->mTempPath ); + $warning .= $this->getDupeWarning( $this->mTempPath, $finalExt ); if( $warning != '' ) { /** @@ -610,7 +607,7 @@ class UploadForm { // extensions (eg 'jpg' rather than 'JPEG'). // // Check for another file using the normalized form... - $nt_lc = Title::makeTitle( NS_IMAGE, $partname . '.' . $file->getExtension() ); + $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() ); $file_lc = wfLocalFile( $nt_lc ); } else { $file_lc = false; @@ -737,7 +734,7 @@ class UploadForm { public static function ajaxGetLicensePreview( $license ) { global $wgParser, $wgUser; $text = '{{' . $license . '}}'; - $title = Title::makeTitle( NS_IMAGE, 'Sample.jpg' ); + $title = Title::makeTitle( NS_FILE, 'Sample.jpg' ); $options = ParserOptions::newFromUser( $wgUser ); // Expand subst: first, then live templates... @@ -751,9 +748,10 @@ class UploadForm { * Check for duplicate files and throw up a warning before the upload * completes. */ - function getDupeWarning( $tempfile ) { + function getDupeWarning( $tempfile, $extension ) { $hash = File::sha1Base36( $tempfile ); $dupes = RepoGroup::singleton()->findBySha1( $hash ); + $archivedImage = new ArchivedFile( null, 0, $hash.".$extension" ); if( $dupes ) { global $wgOut; $msg = "<gallery>"; @@ -767,6 +765,10 @@ class UploadForm { wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) . $wgOut->parse( $msg ) . "</li>\n"; + } elseif ( $archivedImage->getID() > 0 ) { + global $wgOut; + $name = Title::makeTitle( NS_FILE, $archivedImage->getName() )->getPrefixedText(); + return Xml::tags( 'li', null, wfMsgExt( 'file-deleted-duplicate', array( 'parseinline' ), array( $name ) ) ); } else { return ''; } @@ -961,7 +963,7 @@ wgUploadAutoFill = {$autofill}; } if( $this->mDesiredDestName ) { - $title = Title::makeTitleSafe( NS_IMAGE, $this->mDesiredDestName ); + $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); // Show a subtitle link to deleted revisions (to sysops et al only) if( $title instanceof Title && ( $count = $title->isDeleted() ) > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) { $link = wfMsgExt( @@ -972,7 +974,7 @@ wgUploadAutoFill = {$autofill}; wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count ) ) ); - $wgOut->addHtml( "<div id=\"contentSub2\">{$link}</div>" ); + $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" ); } // Show the relevant lines from deletion log (for still deleted files only) @@ -1005,21 +1007,20 @@ wgUploadAutoFill = {$autofill}; $allowedExtensions = ''; if( $wgCheckFileExtensions ) { - $delim = wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ); if( $wgStrictFileExtensions ) { # Everything not permitted is banned $extensionsList = '<div id="mw-upload-permitted">' . - wfMsgWikiHtml( 'upload-permitted', implode( $wgFileExtensions, $delim ) ) . + wfMsgWikiHtml( 'upload-permitted', $wgLang->commaList( $wgFileExtensions ) ) . "</div>\n"; } else { # We have to list both preferred and prohibited $extensionsList = '<div id="mw-upload-preferred">' . - wfMsgWikiHtml( 'upload-preferred', implode( $wgFileExtensions, $delim ) ) . + wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) . "</div>\n" . '<div id="mw-upload-prohibited">' . - wfMsgWikiHtml( 'upload-prohibited', implode( $wgFileBlacklist, $delim ) ) . + wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) . "</div>\n"; } } else { @@ -1169,7 +1170,7 @@ wgUploadAutoFill = {$autofill}; <tr>" ); if( $useAjaxLicensePreview ) { - $wgOut->addHtml( " + $wgOut->addHTML( " <td></td> <td id=\"mw-license-preview\"></td> </tr> @@ -1205,7 +1206,7 @@ wgUploadAutoFill = {$autofill}; ); } - $wgOut->addHtml( " + $wgOut->addHTML( " <td></td> <td> <input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' /> @@ -1279,7 +1280,7 @@ wgUploadAutoFill = {$autofill}; * * @return array */ - function splitExtensions( $filename ) { + public function splitExtensions( $filename ) { $bits = explode( '.', $filename ); $basename = array_shift( $bits ); return array( $basename, $bits ); @@ -1305,7 +1306,7 @@ wgUploadAutoFill = {$autofill}; * @param array $list * @return bool */ - function checkFileExtensionList( $ext, $list ) { + public function checkFileExtensionList( $ext, $list ) { foreach( $ext as $e ) { if( in_array( strtolower( $e ), $list ) ) { return true; @@ -1754,7 +1755,7 @@ wgUploadAutoFill = {$autofill}; function showError( $description ) { global $wgOut; $wgOut->setPageTitle( wfMsg( "internalerror" ) ); - $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->setRobotPolicy( "noindex,nofollow" ); $wgOut->setArticleRelated( false ); $wgOut->enableClientCache( false ); $wgOut->addWikiText( $description ); @@ -1797,14 +1798,14 @@ wgUploadAutoFill = {$autofill}; $loglist = new LogEventsList( $wgUser->getSkin(), $out ); $pager = new LogPager( $loglist, 'delete', false, $filename ); if( $pager->getNumRows() > 0 ) { - $out->addHtml( '<div id="mw-upload-deleted-warn">' ); + $out->addHTML( '<div class="mw-warning-with-logexcerpt">' ); $out->addWikiMsg( 'upload-wasdeleted' ); $out->addHTML( $loglist->beginLogEventsList() . $pager->getBody() . $loglist->endLogEventsList() ); - $out->addHtml( '</div>' ); + $out->addHTML( '</div>' ); } } } diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 27009eed..6a4da7a4 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -33,6 +33,7 @@ class LoginForm { const RESET_PASS = 7; const ABORTED = 8; const CREATE_BLOCKED = 9; + const THROTTLED = 10; var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; @@ -128,9 +129,10 @@ class LoginForm { $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); wfRunHooks( 'AddNewAccount', array( $u, true ) ); + $u->addNewUserLogEntry(); $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); if( WikiError::isError( $result ) ) { @@ -174,14 +176,16 @@ class LoginForm { # Save settings (including confirmation token) $u->saveSettings(); - # If not logged in, assume the new account as the current one and set session cookies - # then show a "welcome" message or a "need cookies" message as needed + # If not logged in, assume the new account as the current one and set + # session cookies then show a "welcome" message or a "need cookies" + # message as needed if( $wgUser->isAnon() ) { $wgUser = $u; $wgUser->setCookies(); wfRunHooks( 'AddNewAccount', array( $wgUser ) ); + $wgUser->addNewUserLogEntry(); if( $this->hasSessionCookie() ) { - return $this->successfulLogin( 'welcomecreation', $wgUser->getName(), false ); + return $this->successfulCreation(); } else { return $this->cookieRedirectCheck( 'new' ); } @@ -192,9 +196,10 @@ class LoginForm { $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) ); $wgOut->setArticleRelated( false ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->addHtml( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); + $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); $wgOut->returnToMain( false, $self ); wfRunHooks( 'AddNewAccount', array( $u ) ); + $u->addNewUserLogEntry(); return true; } } @@ -215,12 +220,11 @@ class LoginForm { return false; } - // If we are not allowing users to login locally, we should - // be checking to see if the user is actually able to - // authenticate to the authentication server before they - // create an account (otherwise, they can create a local account - // and login as any domain user). We only need to check this for - // domains that aren't local. + // If we are not allowing users to login locally, we should be checking + // to see if the user is actually able to authenticate to the authenti- + // cation server before they create an account (otherwise, they can + // create a local account and login as any domain user). We only need + // to check this for domains that aren't local. if( 'local' != $this->mDomain && '' != $this->mDomain ) { if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) { $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); @@ -280,7 +284,8 @@ class LoginForm { } } - # if you need a confirmed email address to edit, then obviously you need an email address. + # if you need a confirmed email address to edit, then obviously you + # need an email address. if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) { $this->mainLoginForm( wfMsg( 'noemailtitle' ) ); return false; @@ -291,8 +296,8 @@ class LoginForm { return false; } - # Set some additional data so the AbortNewAccount hook can be - # used for more than just username validation + # Set some additional data so the AbortNewAccount hook can be used for + # more than just username validation $u->setEmail( $this->mEmail ); $u->setRealName( $this->mRealName ); @@ -306,14 +311,15 @@ class LoginForm { if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) { $key = wfMemcKey( 'acctcreate', 'ip', $ip ); - $value = $wgMemc->incr( $key ); + $value = $wgMemc->get( $key ); if ( !$value ) { - $wgMemc->set( $key, 1, 86400 ); + $wgMemc->set( $key, 0, 86400 ); } - if ( $value > $wgAccountCreationThrottle ) { + if ( $value >= $wgAccountCreationThrottle ) { $this->throttleHit( $wgAccountCreationThrottle ); return false; } + $wgMemc->incr( $key ); } if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { @@ -372,12 +378,32 @@ class LoginForm { if ( '' == $this->mName ) { return self::NO_NAME; } + + global $wgPasswordAttemptThrottle; + + $throttleCount=0; + if ( is_array($wgPasswordAttemptThrottle) ) { + $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); + $count = $wgPasswordAttemptThrottle['count']; + $period = $wgPasswordAttemptThrottle['seconds']; + + global $wgMemc; + $throttleCount = $wgMemc->get($throttleKey); + if ( !$throttleCount ) { + $wgMemc->add( $throttleKey, 1, $period ); // start counter + } else if ( $throttleCount < $count ) { + $wgMemc->incr($throttleKey); + } else if ( $throttleCount >= $count ) { + return self::THROTTLED; + } + } - // Load $wgUser now, and check to see if we're logging in as the same name. - // This is necessary because loading $wgUser (say by calling getName()) calls - // the UserLoadFromSession hook, which potentially creates the user in the - // database. Until we load $wgUser, checking for user existence using - // User::newFromName($name)->getId() below will effectively be using stale data. + // Load $wgUser now, and check to see if we're logging in as the same + // name. This is necessary because loading $wgUser (say by calling + // getName()) calls the UserLoadFromSession hook, which potentially + // creates the user in the database. Until we load $wgUser, checking + // for user existence using User::newFromName($name)->getId() below + // will effectively be using stale data. if ( $wgUser->getName() === $this->mName ) { wfDebug( __METHOD__.": already logged in as {$this->mName}\n" ); return self::SUCCESS; @@ -407,34 +433,30 @@ class LoginForm { if (!$u->checkPassword( $this->mPassword )) { if( $u->checkTemporaryPassword( $this->mPassword ) ) { - // The e-mailed temporary password should not be used - // for actual logins; that's a very sloppy habit, - // and insecure if an attacker has a few seconds to - // click "search" on someone's open mail reader. + // The e-mailed temporary password should not be used for actu- + // al logins; that's a very sloppy habit, and insecure if an + // attacker has a few seconds to click "search" on someone's o- + // pen mail reader. // - // Allow it to be used only to reset the password - // a single time to a new value, which won't be in - // the user's e-mail archives. + // Allow it to be used only to reset the password a single time + // to a new value, which won't be in the user's e-mail ar- + // chives. // - // For backwards compatibility, we'll still recognize - // it at the login form to minimize surprises for - // people who have been logging in with a temporary - // password for some time. - // - // As a side-effect, we can authenticate the user's - // e-mail address if it's not already done, since - // the temporary password was sent via e-mail. + // For backwards compatibility, we'll still recognize it at the + // login form to minimize surprises for people who have been + // logging in with a temporary password for some time. // + // As a side-effect, we can authenticate the user's e-mail ad- + // dress if it's not already done, since the temporary password + // was sent via e-mail. if( !$u->isEmailConfirmed() ) { $u->confirmEmail(); $u->saveSettings(); } - // At this point we just return an appropriate code - // indicating that the UI should show a password - // reset form; bot interfaces etc will probably just - // fail cleanly here. - // + // At this point we just return an appropriate code/ indicating + // that the UI should show a password reset form; bot inter- + // faces etc will probably just fail cleanly here. $retval = self::RESET_PASS; } else { $retval = '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS; @@ -443,6 +465,11 @@ class LoginForm { $wgAuth->updateUser( $u ); $wgUser = $u; + // Please reset throttle for successful logins, thanks! + if($throttleCount) { + $wgMemc->delete($throttleKey); + } + if ( $isAutoCreated ) { // Must be run after $wgUser is set, for correct new user log wfRunHooks( 'AuthPluginAutoCreate', array( $wgUser ) ); @@ -455,16 +482,16 @@ class LoginForm { } /** - * Attempt to automatically create a user on login. - * Only succeeds if there is an external authentication method which allows it. + * Attempt to automatically create a user on login. Only succeeds if there + * is an external authentication method which allows it. * @return integer Status code */ function attemptAutoCreate( $user ) { global $wgAuth, $wgUser; /** - * If the external authentication plugin allows it, - * automatically create a new account for users that - * are externally defined but have not yet logged in. + * If the external authentication plugin allows it, automatically cre- + * ate a new account for users that are externally defined but have not + * yet logged in. */ if ( !$wgAuth->autoCreate() ) { return self::NOT_EXISTS; @@ -502,14 +529,19 @@ class LoginForm { } $wgUser->setCookies(); + // Reset the throttle + $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); + global $wgMemc; + $wgMemc->delete( $key ); + if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { - /* Replace the language object to provide user interface in correct - * language immediately on this first page load. + /* Replace the language object to provide user interface in + * correct language immediately on this first page load. */ global $wgLang, $wgRequest; $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) ); $wgLang = Language::factory( $code ); - return $this->successfulLogin( 'loginsuccess', $wgUser->getName() ); + return $this->successfulLogin(); } else { return $this->cookieRedirectCheck( 'login' ); } @@ -524,7 +556,7 @@ class LoginForm { break; case self::NOT_EXISTS: if( $wgUser->isAllowed( 'createaccount' ) ){ - $this->mainLoginForm( wfMsg( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); + $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); } else { $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) ); } @@ -541,6 +573,9 @@ class LoginForm { case self::CREATE_BLOCKED: $this->userBlockedMessage(); break; + case self::THROTTLED: + $this->mainLoginForm( wfMsg( 'login-throttled' ) ); + break; default: throw new MWException( "Unhandled case value" ); } @@ -548,8 +583,8 @@ class LoginForm { function resetLoginForm( $error ) { global $wgOut; - $wgOut->addWikiText( "<div class=\"errorbox\">$error</div>" ); - $reset = new PasswordResetForm( $this->mName, $this->mPassword ); + $wgOut->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) ); + $reset = new SpecialResetpass(); $reset->execute( null ); } @@ -587,14 +622,15 @@ class LoginForm { return; } if ( 0 == $u->getID() ) { - $this->mainLoginForm( wfMsg( 'nosuchuser', $u->getName() ) ); + $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $u->getName() ) ) ); return; } # Check against password throttle if ( $u->isPasswordReminderThrottled() ) { global $wgPasswordReminderResendTime; - # Round the time in hours to 3 d.p., in case someone is specifying minutes or seconds. + # Round the time in hours to 3 d.p., in case someone is specifying + # minutes or seconds. $this->mainLoginForm( wfMsgExt( 'throttled-mailpassword', array( 'parsemag' ), round( $wgPasswordReminderResendTime, 3 ) ) ); return; @@ -618,20 +654,22 @@ class LoginForm { * @private */ function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { - global $wgCookiePath, $wgCookieDomain, $wgCookiePrefix, $wgCookieSecure; - global $wgServer, $wgScript; + global $wgServer, $wgScript, $wgUser; if ( '' == $u->getEmail() ) { return new WikiError( wfMsg( 'noemail', $u->getName() ) ); } + $ip = wfGetIP(); + if( !$ip ) { + return new WikiError( wfMsg( 'badipaddress' ) ); + } + + wfRunHooks( 'User::mailPasswordInternal', array(&$wgUser, &$ip, &$u) ); $np = $u->randomPassword(); $u->setNewpassword( $np, $throttle ); $u->saveSettings(); - $ip = wfGetIP(); - if ( '' == $ip ) { $ip = '(Unknown)'; } - $m = wfMsg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript ); $result = $u->sendMail( wfMsg( $emailTitle ), $m ); @@ -640,29 +678,66 @@ class LoginForm { /** - * @param string $msg Message key that will be shown on success - * @param $params String: parameters for the above message - * @param bool $auto Toggle auto-redirect to main page; default true + * Run any hooks registered for logins, then HTTP redirect to + * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a + * nice message here, but that's really not as useful as just being sent to + * wherever you logged in from. It should be clear that the action was + * successful, given the lack of error messages plus the appearance of your + * name in the upper right. + * * @private */ - function successfulLogin( $msg, $params, $auto = true ) { - global $wgUser; - global $wgOut; + function successfulLogin() { + global $wgUser, $wgOut; - # Run any hooks; ignore results + # Run any hooks; display injected HTML if any, else redirect + $injected_html = ''; + wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html)); + if( $injected_html !== '' ) { + $this->displaySuccessfulLogin( 'loginsuccess', $injected_html ); + } else { + $titleObj = Title::newFromText( $this->mReturnTo ); + if ( !$titleObj instanceof Title ) { + $titleObj = Title::newMainPage(); + } + + $wgOut->redirect( $titleObj->getFullURL() ); + } + } + + /** + * Run any hooks registered for logins, then display a message welcoming + * the user. + * + * @private + */ + function successfulCreation() { + global $wgUser, $wgOut; + + # Run any hooks; display injected HTML $injected_html = ''; wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html)); + $this->displaySuccessfulLogin( 'welcomecreation', $injected_html ); + } + + /** + * Display a "login successful" page. + */ + private function displaySuccessfulLogin( $msgname, $injected_html ) { + global $wgOut, $wgUser; + $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); - $wgOut->addWikiMsgArray( $msg, $params ); - $wgOut->addHtml( $injected_html ); + $wgOut->addWikiMsg( $msgname, $wgUser->getName() ); + $wgOut->addHTML( $injected_html ); + if ( !empty( $this->mReturnTo ) ) { - $wgOut->returnToMain( $auto, $this->mReturnTo ); + $wgOut->returnToMain( null, $this->mReturnTo ); } else { - $wgOut->returnToMain( $auto ); + $wgOut->returnToMain( null ); } } @@ -671,11 +746,12 @@ class LoginForm { global $wgOut; $wgOut->setPageTitle( wfMsg( 'permissionserrors' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); $wgOut->addWikitext( $wgOut->formatPermissionsErrorMessage( $errors, 'createaccount' ) ); - // Stuff that might want to be added at the end. For example, instructions if blocked. + // Stuff that might want to be added at the end. For example, instruc- + // tions if blocked. $wgOut->addWikiMsg( 'cantcreateaccount-nonblock-text' ); $wgOut->returnToMain( false ); @@ -694,7 +770,7 @@ class LoginForm { # out. $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); $ip = wfGetIP(); @@ -713,8 +789,8 @@ class LoginForm { */ function mainLoginForm( $msg, $msgtype = 'error' ) { global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail; - global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector; - global $wgAuth, $wgEmailConfirmToEdit; + global $wgCookiePrefix, $wgLoginLanguageSelector; + global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration; $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); @@ -792,6 +868,7 @@ class LoginForm { $template->set( 'useemail', $wgEnableEmail ); $template->set( 'emailrequired', $wgEmailConfirmToEdit ); $template->set( 'canreset', $wgAuth->allowPasswordChange() ); + $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); # Prepare language selection links as needed @@ -810,7 +887,7 @@ class LoginForm { } $wgOut->setPageTitle( wfMsg( 'userlogin' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); $wgOut->disallowUserJs(); // just in case... $wgOut->addTemplate( $template ); @@ -832,9 +909,9 @@ class LoginForm { /** * Check if a session cookie is present. * - * This will not pick up a cookie set during _this_ request, but is - * meant to ensure that the client is returning the cookie which was - * set on a previous pass through the system. + * This will not pick up a cookie set during _this_ request, but is meant + * to ensure that the client is returning the cookie which was set on a + * previous pass through the system. * * @private */ @@ -850,7 +927,9 @@ class LoginForm { global $wgOut; $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - $check = $titleObj->getFullURL( 'wpCookieCheck='.$type ); + $query = array( 'wpCookieCheck' => $type ); + if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo; + $check = $titleObj->getFullURL( $query ); return $wgOut->redirect( $check ); } @@ -871,7 +950,7 @@ class LoginForm { return $this->mainLoginForm( wfMsg( 'error' ) ); } } else { - return $this->successfulLogin( 'loginsuccess', $wgUser->getName() ); + return $this->successfulLogin(); } } @@ -879,9 +958,7 @@ class LoginForm { * @private */ function throttleHit( $limit ) { - global $wgOut; - - $wgOut->addWikiMsg( 'acct_creation_throttle_hit', $limit ); + $this->mainLoginForm( wfMsgExt( 'acct_creation_throttle_hit', array( 'parseinline' ), $limit ) ); } /** diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php index 137eadb4..3d497bd7 100644 --- a/includes/specials/SpecialUserlogout.php +++ b/includes/specials/SpecialUserlogout.php @@ -12,7 +12,7 @@ function wfSpecialUserlogout() { $oldName = $wgUser->getName(); $wgUser->logout(); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); // Hook. $injected_html = ''; diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php index fd3c690b..ce0097b2 100644 --- a/includes/specials/SpecialUserrights.php +++ b/includes/specials/SpecialUserrights.php @@ -26,10 +26,14 @@ class UserrightsPage extends SpecialPage { } public function userCanExecute( $user ) { + return $this->userCanChangeRights( $user, false ); + } + + public function userCanChangeRights( $user, $checkIfSelf = true ) { $available = $this->changeableGroups(); return !empty( $available['add'] ) or !empty( $available['remove'] ) - or ($this->isself and + or ( ( $this->isself || !$checkIfSelf ) and (!empty( $available['add-self'] ) or !empty( $available['remove-self'] ))); } @@ -65,7 +69,7 @@ class UserrightsPage extends SpecialPage { if ($this->mTarget == $wgUser->getName()) $this->isself = true; - if( !$this->userCanExecute( $wgUser ) ) { + if( !$this->userCanChangeRights( $wgUser, true ) ) { // fixme... there may be intermediate groups we can mention. global $wgOut; $wgOut->showPermissionsErrorPage( array( @@ -141,13 +145,8 @@ class UserrightsPage extends SpecialPage { // Validate input set... $changeable = $this->changeableGroups(); - if ($wgUser->getId() != 0 && $wgUser->getId() == $user->getId()) { - $addable = array_merge($changeable['add'], $wgGroupsAddToSelf); - $removable = array_merge($changeable['remove'], $wgGroupsRemoveFromSelf); - } else { - $addable = $changeable['add']; - $removable = $changeable['remove']; - } + $addable = array_merge( $changeable['add'], $this->isself ? $changeable['add-self'] : array() ); + $removable = array_merge( $changeable['remove'], $this->isself ? $changeable['remove-self'] : array() ); $removegroup = array_unique( array_intersect( (array)$removegroup, $removable ) ); @@ -289,7 +288,7 @@ class UserrightsPage extends SpecialPage { function makeGroupNameList( $ids ) { if( empty( $ids ) ) { - return wfMsg( 'rightsnone' ); + return wfMsgForContent( 'rightsnone' ); } else { return implode( ', ', $ids ); } @@ -329,14 +328,13 @@ class UserrightsPage extends SpecialPage { * @return Array: Tuple of addable, then removable groups */ protected function splitGroups( $groups ) { - global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - list($addable, $removable) = array_values( $this->changeableGroups() ); + list($addable, $removable, $addself, $removeself) = array_values( $this->changeableGroups() ); $removable = array_intersect( - array_merge($this->isself ? $wgGroupsRemoveFromSelf : array(), $removable), + array_merge( $this->isself ? $removeself : array(), $removable ), $groups ); // Can't remove groups the user doesn't have $addable = array_diff( - array_merge($this->isself ? $wgGroupsAddToSelf : array(), $addable), + array_merge( $this->isself ? $addself : array(), $addable ), $groups ); // Can't add groups the user does have return array( $addable, $removable ); @@ -351,10 +349,8 @@ class UserrightsPage extends SpecialPage { protected function showEditUserGroupsForm( $user, $groups ) { global $wgOut, $wgUser, $wgLang; - list( $addable, $removable ) = $this->splitGroups( $groups ); - $list = array(); - foreach( $user->getGroups() as $group ) + foreach( $groups as $group ) $list[] = self::buildGroupLink( $group ); $grouplist = ''; @@ -384,7 +380,7 @@ class UserrightsPage extends SpecialPage { <tr> <td></td> <td class='mw-submit'>" . - Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups' ) ) . + Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups', 'accesskey' => 's' ) ) . "</td> </tr>" . Xml::closeElement( 'table' ) . "\n" . @@ -510,10 +506,10 @@ class UserrightsPage extends SpecialPage { /** * Returns an array of the groups that the user can add/remove. * - * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) ) + * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) ) */ function changeableGroups() { - global $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + global $wgUser; if( $wgUser->isAllowed( 'userrights' ) ) { // This group gives the right to modify everything (reverse- @@ -533,8 +529,8 @@ class UserrightsPage extends SpecialPage { $groups = array( 'add' => array(), 'remove' => array(), - 'add-self' => $wgGroupsAddToSelf, - 'remove-self' => $wgGroupsRemoveFromSelf); + 'add-self' => array(), + 'remove-self' => array() ); $addergroups = $wgUser->getEffectiveGroups(); foreach ($addergroups as $addergroup) { @@ -543,7 +539,13 @@ class UserrightsPage extends SpecialPage { ); $groups['add'] = array_unique( $groups['add'] ); $groups['remove'] = array_unique( $groups['remove'] ); + $groups['add-self'] = array_unique( $groups['add-self'] ); + $groups['remove-self'] = array_unique( $groups['remove-self'] ); } + + // Run a hook because we can + wfRunHooks( 'UserrightsChangeableGroups', array( $this, $wgUser, $addergroups, &$groups ) ); + return $groups; } @@ -551,12 +553,12 @@ class UserrightsPage extends SpecialPage { * Returns an array of the groups that a particular group can add/remove. * * @param $group String: the group to check for whether it can add/remove - * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) ) + * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) ) */ private function changeableByGroup( $group ) { - global $wgAddGroups, $wgRemoveGroups; + global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - $groups = array( 'add' => array(), 'remove' => array() ); + $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() ); if( empty($wgAddGroups[$group]) ) { // Don't add anything to $groups } elseif( $wgAddGroups[$group] === true ) { @@ -573,6 +575,40 @@ class UserrightsPage extends SpecialPage { } elseif( is_array($wgRemoveGroups[$group]) ) { $groups['remove'] = $wgRemoveGroups[$group]; } + + // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility + if( empty($wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) { + foreach($wgGroupsAddToSelf as $key => $value) { + if( is_int($key) ) { + $wgGroupsAddToSelf['user'][] = $value; + } + } + } + + if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) { + foreach($wgGroupsRemoveFromSelf as $key => $value) { + if( is_int($key) ) { + $wgGroupsRemoveFromSelf['user'][] = $value; + } + } + } + + // Now figure out what groups the user can add to him/herself + if( empty($wgGroupsAddToSelf[$group]) ) { + } elseif( $wgGroupsAddToSelf[$group] === true ) { + // No idea WHY this would be used, but it's there + $groups['add-self'] = User::getAllGroups(); + } elseif( is_array($wgGroupsAddToSelf[$group]) ) { + $groups['add-self'] = $wgGroupsAddToSelf[$group]; + } + + if( empty($wgGroupsRemoveFromSelf[$group]) ) { + } elseif( $wgGroupsRemoveFromSelf[$group] === true ) { + $groups['remove-self'] = User::getAllGroups(); + } elseif( is_array($wgGroupsRemoveFromSelf[$group]) ) { + $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group]; + } + return $groups; } @@ -583,7 +619,7 @@ class UserrightsPage extends SpecialPage { * @param $output OutputPage to use */ protected function showLogFragment( $user, $output ) { - $output->addHtml( Xml::element( 'h2', null, LogPage::logName( 'rights' ) . "\n" ) ); + $output->addHTML( Xml::element( 'h2', null, LogPage::logName( 'rights' ) . "\n" ) ); LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage()->getPrefixedText() ); } } diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 8c8e386d..29f527f2 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -1,42 +1,37 @@ <?php -/**#@+ + +/** * Give information about the version of MediaWiki, PHP, the DB and extensions * - * @file * @ingroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ - -/** - * constructor - */ -function wfSpecialVersion() { - $version = new SpecialVersion; - $version->execute(); -} - -/** - * @ingroup SpecialPage - */ -class SpecialVersion { +class SpecialVersion extends SpecialPage { private $firstExtOpened = true; + function __construct(){ + parent::__construct( 'Version' ); + } + /** * main() */ - function execute() { + function execute( $par ) { global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks; $wgMessageCache->loadAllMessages(); + $this->setHeaders(); + $this->outputHeader(); + $wgOut->addHTML( '<div dir="ltr">' ); $text = $this->MediaWikiCredits() . $this->softwareInformation() . $this->extensionCredits(); - if ( $wgSpecialVersionShowHooks ) { + if ( $wgSpecialVersionShowHooks ) { $text .= $this->wgHooks(); } $wgOut->addWikiText( $text ); @@ -162,15 +157,21 @@ class SpecialVersion { usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); foreach ( $wgExtensionCredits[$type] as $extension ) { + $version = null; + $subVersion = ''; if ( isset( $extension['version'] ) ) { $version = $extension['version']; - } elseif ( isset( $extension['svn-revision'] ) && + } + if ( isset( $extension['svn-revision'] ) && preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/', - $extension['svn-revision'], $m ) ) - { - $version = 'r' . $m[1]; - } else { - $version = null; + $extension['svn-revision'], $m ) ) { + $subVersion = 'r' . $m[1]; + } + + if( $version && $subVersion ) { + $version = $version . ' [' . $subVersion . ']'; + } elseif ( !$version && $subVersion ) { + $version = $subVersion; } $out .= $this->formatCredits( @@ -287,8 +288,6 @@ class SpecialVersion { } /** - * @static - * * @return string */ function IPInfo() { @@ -306,35 +305,34 @@ class SpecialVersion { if ( $cnt == 1 ) { // Enforce always returning a string - return (string)$this->arrayToString( $list[0] ); + return (string)self::arrayToString( $list[0] ); } elseif ( $cnt == 0 ) { return ''; } else { + global $wgLang; sort( $list ); - $t = array_slice( $list, 0, $cnt - 1 ); - $one = array_map( array( &$this, 'arrayToString' ), $t ); - $two = $this->arrayToString( $list[$cnt - 1] ); - $and = wfMsg( 'and' ); - - return implode( ', ', $one ) . " $and $two"; + return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) ); } } /** - * @static - * * @param mixed $list Will convert an array to string if given and return * the paramater unaltered otherwise * @return mixed */ - function arrayToString( $list ) { + static function arrayToString( $list ) { + if( is_array( $list ) && count( $list ) == 1 ) + $list = $list[0]; if( is_object( $list ) ) { $class = get_class( $list ); return "($class)"; - } elseif ( ! is_array( $list ) ) { + } elseif ( !is_array( $list ) ) { return $list; } else { - $class = get_class( $list[0] ); + if( is_object( $list[0] ) ) + $class = get_class( $list[0] ); + else + $class = $list[0]; return "($class, {$list[1]})"; } } @@ -387,5 +385,3 @@ class SpecialVersion { /**#@-*/ } - -/**#@-*/ diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php new file mode 100644 index 00000000..c2731fa9 --- /dev/null +++ b/includes/specials/SpecialWantedfiles.php @@ -0,0 +1,90 @@ +<?php +/* + * @file + * @ingroup SpecialPage + */ + +/** + * Querypage that lists the most wanted files - implements Special:Wantedfiles + * + * @ingroup SpecialPage + * + * @author Soxred93 <soxred93@gmail.com> + * @copyright Copyright © 2008, Soxred93 + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class WantedFilesPage extends QueryPage { + + function getName() { + return 'Wantedfiles'; + } + + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $imagelinks, $page ) = $dbr->tableNamesN( 'imagelinks', 'page' ); + $name = $dbr->addQuotes( $this->getName() ); + return + " + SELECT + $name as type, + " . NS_FILE . " as namespace, + il_to as title, + COUNT(*) as value + FROM $imagelinks + LEFT JOIN $page ON il_to = page_title AND page_namespace = ". NS_FILE ." + WHERE page_title IS NULL + GROUP BY il_to + "; + } + + function sortDescending() { return true; } + + /** + * Fetch user page links and cache their existence + */ + function preprocessResults( $db, $res ) { + $batch = new LinkBatch; + while ( $row = $db->fetchObject( $res ) ) + $batch->add( $row->namespace, $row->title ); + $batch->execute(); + + // Back to start for display + if ( $db->numRows( $res ) > 0 ) + // If there are no rows we get an error seeking. + $db->dataSeek( $res, 0 ); + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + + $nt = Title::makeTitle( $result->namespace, $result->title ); + $text = $wgContLang->convert( $nt->getText() ); + + $plink = $this->isCached() ? + $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) : + $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) ); + + $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ); + return wfSpecialList($plink, $nlinks); + } +} + +/** + * constructor + */ +function wfSpecialWantedFiles() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new WantedFilesPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php new file mode 100644 index 00000000..43b5cf8f --- /dev/null +++ b/includes/specials/SpecialWantedtemplates.php @@ -0,0 +1,110 @@ +<?php +/** + * @file + * @ingroup SpecialPage + */ + +/** + * A querypage to list the most wanted templates - implements Special:Wantedtemplates + * based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com> + * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com> + * + * @ingroup SpecialPage + * + * @author Danny B. + * @copyright Copyright © 2008, Danny B. + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class WantedTemplatesPage extends QueryPage { + + function getName() { + return 'Wantedtemplates'; + } + + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $templatelinks, $page ) = $dbr->tableNamesN( 'templatelinks', 'page' ); + $name = $dbr->addQuotes( $this->getName() ); + return + " + SELECT $name as type, + tl_namespace as namespace, + tl_title as title, + COUNT(*) as value + FROM $templatelinks LEFT JOIN + $page ON tl_title = page_title AND tl_namespace = page_namespace + WHERE page_title IS NULL AND tl_namespace = ". NS_TEMPLATE ." + GROUP BY tl_title + "; + } + + function sortDescending() { return true; } + + /** + * Fetch user page links and cache their existence + */ + function preprocessResults( $db, $res ) { + $batch = new LinkBatch; + while ( $row = $db->fetchObject( $res ) ) + $batch->add( $row->namespace, $row->title ); + $batch->execute(); + + // Back to start for display + if ( $db->numRows( $res ) > 0 ) + // If there are no rows we get an error seeking. + $db->dataSeek( $res, 0 ); + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + + $nt = Title::makeTitle( $result->namespace, $result->title ); + $text = $wgContLang->convert( $nt->getText() ); + + $plink = $this->isCached() ? + $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) : + $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) ); + + $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ); + return wfSpecialList( + $plink, + $this->makeWlhLink( $nt, $skin, $result ) + ); + } + + /** + * Make a "what links here" link for a given title + * + * @param Title $title Title to make the link for + * @param Skin $skin Skin to use + * @param object $result Result row + * @return string + */ + private function makeWlhLink( $title, $skin, $result ) { + global $wgLang; + $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); + $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $result->value ) ); + return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) ); + } +} + +/** + * constructor + */ +function wfSpecialWantedTemplates() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new WantedTemplatesPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index db7cd423..61dd6b3e 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -13,7 +13,6 @@ function wfSpecialWatchlist( $par ) { global $wgUser, $wgOut, $wgLang, $wgRequest; global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; global $wgEnotifWatchlist; - $fname = 'wfSpecialWatchlist'; $skin = $wgUser->getSkin(); $specialTitle = SpecialPage::getTitleFor( 'Watchlist' ); @@ -22,8 +21,9 @@ function wfSpecialWatchlist( $par ) { # Anons don't get a watchlist if( $wgUser->isAnon() ) { $wgOut->setPageTitle( wfMsg( 'watchnologin' ) ); - $llink = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Userlogin' ), wfMsgHtml( 'loginreqlink' ), 'returnto=' . $specialTitle->getPrefixedUrl() ); - $wgOut->addHtml( wfMsgWikiHtml( 'watchlistanontext', $llink ) ); + $llink = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Userlogin' ), + wfMsgHtml( 'loginreqlink' ), 'returnto=' . $specialTitle->getPrefixedUrl() ); + $wgOut->addHTML( wfMsgWikiHtml( 'watchlistanontext', $llink ) ); return; } @@ -40,40 +40,56 @@ function wfSpecialWatchlist( $par ) { } $uid = $wgUser->getId(); - if( ($wgEnotifWatchlist || $wgShowUpdatedMarker) && $wgRequest->getVal( 'reset' ) && $wgRequest->wasPosted() ) { + if( ($wgEnotifWatchlist || $wgShowUpdatedMarker) && $wgRequest->getVal( 'reset' ) && + $wgRequest->wasPosted() ) + { $wgUser->clearAllNotifications( $uid ); $wgOut->redirect( $specialTitle->getFullUrl() ); return; } $defaults = array( - /* float */ 'days' => floatval( $wgUser->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */ - /* bool */ 'hideOwn' => (int)$wgUser->getBoolOption( 'watchlisthideown' ), - /* bool */ 'hideBots' => (int)$wgUser->getBoolOption( 'watchlisthidebots' ), - /* bool */ 'hideMinor' => (int)$wgUser->getBoolOption( 'watchlisthideminor' ), + /* float */ 'days' => floatval( $wgUser->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */ + /* bool */ 'hideMinor' => (int)$wgUser->getBoolOption( 'watchlisthideminor' ), + /* bool */ 'hideBots' => (int)$wgUser->getBoolOption( 'watchlisthidebots' ), + /* bool */ 'hideAnons' => (int)$wgUser->getBoolOption( 'watchlisthideanons' ), + /* bool */ 'hideLiu' => (int)$wgUser->getBoolOption( 'watchlisthideliu' ), + /* bool */ 'hidePatrolled' => (int)$wgUser->getBoolOption( 'watchlisthidepatrolled' ), // TODO + /* bool */ 'hideOwn' => (int)$wgUser->getBoolOption( 'watchlisthideown' ), /* ? */ 'namespace' => 'all', + /* ? */ 'invert' => false, ); extract($defaults); # Extract variables from the request, falling back to user preferences or # other default values if these don't exist - $prefs['days' ] = floatval( $wgUser->getOption( 'watchlistdays' ) ); - $prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' ); - $prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' ); + $prefs['days'] = floatval( $wgUser->getOption( 'watchlistdays' ) ); $prefs['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' ); + $prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' ); + $prefs['hideanons'] = $wgUser->getBoolOption( 'watchlisthideanon' ); + $prefs['hideliu'] = $wgUser->getBoolOption( 'watchlisthideliu' ); + $prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' ); + $prefs['hidepatrolled' ] = $wgUser->getBoolOption( 'watchlisthidepatrolled' ); # Get query variables - $days = $wgRequest->getVal( 'days', $prefs['days'] ); - $hideOwn = $wgRequest->getBool( 'hideOwn', $prefs['hideown'] ); - $hideBots = $wgRequest->getBool( 'hideBots', $prefs['hidebots'] ); + $days = $wgRequest->getVal( 'days' , $prefs['days'] ); $hideMinor = $wgRequest->getBool( 'hideMinor', $prefs['hideminor'] ); + $hideBots = $wgRequest->getBool( 'hideBots' , $prefs['hidebots'] ); + $hideAnons = $wgRequest->getBool( 'hideAnons', $prefs['hideanons'] ); + $hideLiu = $wgRequest->getBool( 'hideLiu' , $prefs['hideliu'] ); + $hideOwn = $wgRequest->getBool( 'hideOwn' , $prefs['hideown'] ); + $hidePatrolled = $wgRequest->getBool( 'hidePatrolled' , $prefs['hidepatrolled'] ); # Get namespace value, if supplied, and prepare a WHERE fragment $nameSpace = $wgRequest->getIntOrNull( 'namespace' ); + $invert = $wgRequest->getIntOrNull( 'invert' ); if( !is_null( $nameSpace ) ) { $nameSpace = intval( $nameSpace ); - $nameSpaceClause = " AND rc_namespace = $nameSpace"; + if( $invert && $nameSpace !== 'all' ) + $nameSpaceClause = "rc_namespace != $nameSpace"; + else + $nameSpaceClause = "rc_namespace = $nameSpace"; } else { $nameSpace = ''; $nameSpaceClause = ''; @@ -103,32 +119,24 @@ function wfSpecialWatchlist( $par ) { // Dump everything here $nondefaults = array(); - wfAppendToArrayIfNotDefault('days' , $days , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault('hideOwn' , (int)$hideOwn , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault('hideBots' , (int)$hideBots, $defaults, $nondefaults); + wfAppendToArrayIfNotDefault( 'days' , $days , $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'hideMinor', (int)$hideMinor, $defaults, $nondefaults ); - wfAppendToArrayIfNotDefault('namespace', $nameSpace , $defaults, $nondefaults); - - $hookSql = ""; - if( ! wfRunHooks('BeforeWatchlist', array($nondefaults, $wgUser, &$hookSql)) ) { - return; - } - - if($nitems == 0) { + wfAppendToArrayIfNotDefault( 'hideBots' , (int)$hideBots , $defaults, $nondefaults); + wfAppendToArrayIfNotDefault( 'hideAnons', (int)$hideAnons, $defaults, $nondefaults ); + wfAppendToArrayIfNotDefault( 'hideLiu' , (int)$hideLiu , $defaults, $nondefaults ); + wfAppendToArrayIfNotDefault( 'hideOwn' , (int)$hideOwn , $defaults, $nondefaults); + wfAppendToArrayIfNotDefault( 'namespace', $nameSpace , $defaults, $nondefaults); + wfAppendToArrayIfNotDefault( 'hidePatrolled', (int)$hidePatrolled, $defaults, $nondefaults ); + + if( $nitems == 0 ) { $wgOut->addWikiMsg( 'nowatchlist' ); return; } - if ( $days <= 0 ) { + if( $days <= 0 ) { $andcutoff = ''; } else { - $andcutoff = "AND rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'"; - /* - $sql = "SELECT COUNT(*) AS n FROM $page, $revision WHERE rev_timestamp>'$cutoff' AND page_id=rev_page"; - $res = $dbr->query( $sql, $fname ); - $s = $dbr->fetchObject( $res ); - $npages = $s->n; - */ + $andcutoff = "rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'"; } # If the watchlist is relatively short, it's simplest to zip @@ -140,128 +148,158 @@ function wfSpecialWatchlist( $par ) { # Up estimate of watched items by 15% to compensate for talk pages... # Toggles - $andHideOwn = $hideOwn ? "AND (rc_user <> $uid)" : ''; - $andHideBots = $hideBots ? "AND (rc_bot = 0)" : ''; - $andHideMinor = $hideMinor ? 'AND rc_minor = 0' : ''; - - # Show watchlist header - $header = ''; - if( $wgUser->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) { - $header .= wfMsg( 'wlheader-enotif' ) . "\n"; - } - if ( $wgShowUpdatedMarker ) { - $header .= wfMsg( 'wlheader-showupdated' ) . "\n"; - } - - # Toggle watchlist content (all recent edits or just the latest) + $andHideOwn = $hideOwn ? "rc_user != $uid" : ''; + $andHideBots = $hideBots ? "rc_bot = 0" : ''; + $andHideMinor = $hideMinor ? "rc_minor = 0" : ''; + $andHideLiu = $hideLiu ? "rc_user = 0" : ''; + $andHideAnons = $hideAnons ? "rc_user != 0" : ''; + $andHidePatrolled = $wgUser->useRCPatrol() && $hidePatrolled ? "rc_patrolled != 1" : ''; + + # Toggle watchlist content (all recent edits or just the latest) if( $wgUser->getOption( 'extendwatchlist' )) { $andLatest=''; - $limitWatchlist = 'LIMIT ' . intval( $wgUser->getOption( 'wllimit' ) ); + $limitWatchlist = intval( $wgUser->getOption( 'wllimit' ) ); } else { # Top log Ids for a page are not stored - $andLatest = 'AND (rc_this_oldid=page_latest OR rc_type=' . RC_LOG . ') '; - $limitWatchlist = ''; + $andLatest = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; + $limitWatchlist = 0; } - $header .= wfMsgExt( 'watchlist-details', array( 'parsemag' ), $wgLang->formatNum( $nitems ) ); - $wgOut->addWikiText( $header ); - # Show a message about slave lag, if applicable if( ( $lag = $dbr->getLag() ) > 0 ) $wgOut->showLagWarning( $lag ); - if ( $wgShowUpdatedMarker ) { - $wgOut->addHTML( '<form action="' . - $specialTitle->escapeLocalUrl() . - '" method="post"><input type="submit" name="dummy" value="' . - htmlspecialchars( wfMsg( 'enotif_reset' ) ) . - '" /><input type="hidden" name="reset" value="all" /></form>' . - "\n\n" ); + # Create output form + $form = Xml::fieldset( wfMsg( 'watchlist-options' ), false, array( 'id' => 'mw-watchlist-options' ) ); + + # Show watchlist header + $form .= wfMsgExt( 'watchlist-details', array( 'parseinline' ), $wgLang->formatNum( $nitems ) ); + + if( $wgUser->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) { + $form .= wfMsgExt( 'wlheader-enotif', 'parse' ) . "\n"; } - if ( $wgShowUpdatedMarker ) { - $wltsfield = ", ${watchlist}.wl_notificationtimestamp "; - } else { - $wltsfield = ''; + if( $wgShowUpdatedMarker ) { + $form .= Xml::openElement( 'form', array( 'method' => 'post', + 'action' => $specialTitle->getLocalUrl(), + 'id' => 'mw-watchlist-resetbutton' ) ) . + wfMsgExt( 'wlheader-showupdated', array( 'parseinline' ) ) . ' ' . + Xml::submitButton( wfMsg( 'enotif_reset' ), array( 'name' => 'dummy' ) ) . + Xml::hidden( 'reset', 'all' ) . + Xml::closeElement( 'form' ); + } + $form .= '<hr />'; + + $tables = array( 'recentchanges', 'watchlist', 'page' ); + $fields = array( "{$recentchanges}.*" ); + $conds = array(); + $join_conds = array( + 'watchlist' => array('INNER JOIN',"wl_user='{$uid}' AND wl_namespace=rc_namespace AND wl_title=rc_title"), + 'page' => array('LEFT JOIN','rc_cur_id=page_id') + ); + $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); + if( $wgShowUpdatedMarker ) { + $fields[] = 'wl_notificationtimestamp'; + } + if( $limitWatchlist ) { + $options['LIMIT'] = $limitWatchlist; } - $sql = "SELECT ${recentchanges}.* ${wltsfield} - FROM $watchlist,$recentchanges - LEFT JOIN $page ON rc_cur_id=page_id - WHERE wl_user=$uid - AND wl_namespace=rc_namespace - AND wl_title=rc_title - $andcutoff - $andLatest - $andHideOwn - $andHideBots - $andHideMinor - $nameSpaceClause - $hookSql - ORDER BY rc_timestamp DESC - $limitWatchlist"; - - $res = $dbr->query( $sql, $fname ); + if( $andcutoff ) $conds[] = $andcutoff; + if( $andLatest ) $conds[] = $andLatest; + if( $andHideOwn ) $conds[] = $andHideOwn; + if( $andHideBots ) $conds[] = $andHideBots; + if( $andHideMinor ) $conds[] = $andHideMinor; + if( $andHideLiu ) $conds[] = $andHideLiu; + if( $andHideAnons ) $conds[] = $andHideAnons; + if( $andHidePatrolled ) $conds[] = $andHidePatrolled; + if( $nameSpaceClause ) $conds[] = $nameSpaceClause; + + wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) ); + + $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds ); $numRows = $dbr->numRows( $res ); /* Start bottom header */ - $wgOut->addHTML( "<hr />\n" ); - if($days >= 1) { - $wgOut->addHTML( - wfMsgExt( 'rcnote', 'parseinline', + $wlInfo = ''; + if( $days >= 1 ) { + $wlInfo = wfMsgExt( 'rcnote', 'parseinline', $wgLang->formatNum( $numRows ), $wgLang->formatNum( $days ), $wgLang->timeAndDate( wfTimestampNow(), true ), $wgLang->date( wfTimestampNow(), true ), $wgLang->time( wfTimestampNow(), true ) - ) . '<br />' - ); - } elseif($days > 0) { - $wgOut->addHtml( - wfMsgExt( 'wlnote', 'parseinline', + ) . '<br />'; + } elseif( $days > 0 ) { + $wlInfo = wfMsgExt( 'wlnote', 'parseinline', $wgLang->formatNum( $numRows ), $wgLang->formatNum( round($days*24) ) - ) . '<br />' - ); + ) . '<br />'; } - $wgOut->addHTML( "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n" ); + $cutofflinks = "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n"; # Spit out some control panel links $thisTitle = SpecialPage::getTitleFor( 'Watchlist' ); $skin = $wgUser->getSkin(); + $showLinktext = wfMsgHtml( 'show' ); + $hideLinktext = wfMsgHtml( 'hide' ); + # Hide/show minor edits + $label = $hideMinor ? $showLinktext : $hideLinktext; + $linkBits = wfArrayToCGI( array( 'hideMinor' => 1 - (int)$hideMinor ), $nondefaults ); + $links[] = wfMsgHtml( 'rcshowhideminor', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) ); + # Hide/show bot edits - $label = $hideBots ? wfMsgHtml( 'watchlist-show-bots' ) : wfMsgHtml( 'watchlist-hide-bots' ); + $label = $hideBots ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI( array( 'hideBots' => 1 - (int)$hideBots ), $nondefaults ); - $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); + $links[] = wfMsgHtml( 'rcshowhidebots', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) ); + + # Hide/show anonymous edits + $label = $hideAnons ? $showLinktext : $hideLinktext; + $linkBits = wfArrayToCGI( array( 'hideAnons' => 1 - (int)$hideAnons ), $nondefaults ); + $links[] = wfMsgHtml( 'rcshowhideanons', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) ); + + # Hide/show logged in edits + $label = $hideLiu ? $showLinktext : $hideLinktext; + $linkBits = wfArrayToCGI( array( 'hideLiu' => 1 - (int)$hideLiu ), $nondefaults ); + $links[] = wfMsgHtml( 'rcshowhideliu', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) ); # Hide/show own edits - $label = $hideOwn ? wfMsgHtml( 'watchlist-show-own' ) : wfMsgHtml( 'watchlist-hide-own' ); + $label = $hideOwn ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI( array( 'hideOwn' => 1 - (int)$hideOwn ), $nondefaults ); - $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); + $links[] = wfMsgHtml( 'rcshowhidemine', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) ); - # Hide/show minor edits - $label = $hideMinor ? wfMsgHtml( 'watchlist-show-minor' ) : wfMsgHtml( 'watchlist-hide-minor' ); - $linkBits = wfArrayToCGI( array( 'hideMinor' => 1 - (int)$hideMinor ), $nondefaults ); - $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); - - $wgOut->addHTML( implode( ' | ', $links ) ); + # Hide/show patrolled edits + if( $wgUser->useRCPatrol() ) { + $label = $hidePatrolled ? $showLinktext : $hideLinktext; + $linkBits = wfArrayToCGI( array( 'hidePatrolled' => 1 - (int)$hidePatrolled ), $nondefaults ); + $links[] = wfMsgHtml( 'rcshowhidepatr', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) ); + } - # Form for namespace filtering - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) ); - $form .= '<p>'; + # Namespace filter and put the whole form together. + $form .= $wlInfo; + $form .= $cutofflinks; + $form .= implode( ' | ', $links ); + $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) ); + $form .= '<hr /><p>'; $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; + $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . ' '; $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>'; $form .= Xml::hidden( 'days', $days ); - if( $hideOwn ) - $form .= Xml::hidden( 'hideOwn', 1 ); - if( $hideBots ) - $form .= Xml::hidden( 'hideBots', 1 ); if( $hideMinor ) $form .= Xml::hidden( 'hideMinor', 1 ); + if( $hideBots ) + $form .= Xml::hidden( 'hideBots', 1 ); + if( $hideAnons ) + $form .= Xml::hidden( 'hideAnons', 1 ); + if( $hideLiu ) + $form .= Xml::hidden( 'hideLiu', 1 ); + if( $hideOwn ) + $form .= Xml::hidden( 'hideOwn', 1 ); $form .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $form ); + $form .= Xml::closeElement( 'fieldset' ); + $wgOut->addHTML( $form ); # If there's nothing to show, stop here if( $numRows == 0 ) { @@ -316,7 +354,6 @@ function wfSpecialWatchlist( $par ) { $dbr->freeResult( $res ); $wgOut->addHTML( $s ); - } function wlHoursLink( $h, $page, $options = array() ) { @@ -370,7 +407,8 @@ function wlCountItems( &$user, $talk = true ) { $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); # Fetch the raw count - $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->mId ), 'wlCountItems' ); + $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', + array( 'wl_user' => $user->mId ), 'wlCountItems' ); $row = $dbr->fetchObject( $res ); $count = $row->count; $dbr->freeResult( $res ); diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php index 3502e33c..d91b4960 100644 --- a/includes/specials/SpecialWhatlinkshere.php +++ b/includes/specials/SpecialWhatlinkshere.php @@ -74,9 +74,7 @@ class WhatLinksHerePage { $this->selfTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->target->getPrefixedDBkey() ); $wgOut->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) ); - $wgOut->setSubtitle( wfMsgHtml( 'linklistsub' ) ); - - $wgOut->addHTML( wfMsgExt( 'whatlinkshere-barrow', array( 'escapenoentities') ) . ' ' .$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."<br />\n"); + $wgOut->setSubtitle( wfMsg( 'whatlinkshere-backlink', $this->skin->link( $this->target, $this->target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) ); $this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ), $opts->getValue( 'from' ), $opts->getValue( 'back' ) ); @@ -98,7 +96,7 @@ class WhatLinksHerePage { $hidelinks = $this->opts->getValue( 'hidelinks' ); $hideredirs = $this->opts->getValue( 'hideredirs' ); $hidetrans = $this->opts->getValue( 'hidetrans' ); - $hideimages = $target->getNamespace() != NS_IMAGE || $this->opts->getValue( 'hideimages' ); + $hideimages = $target->getNamespace() != NS_FILE || $this->opts->getValue( 'hideimages' ); $fetchlinks = (!$hidelinks || !$hideredirs); @@ -169,11 +167,13 @@ class WhatLinksHerePage { if( ( !$fetchlinks || !$dbr->numRows($plRes) ) && ( $hidetrans || !$dbr->numRows($tlRes) ) && ( $hideimages || !$dbr->numRows($ilRes) ) ) { if ( 0 == $level ) { $wgOut->addHTML( $this->whatlinkshereForm() ); - $errMsg = is_int($namespace) ? 'nolinkshere-ns' : 'nolinkshere'; - $wgOut->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); + // Show filters only if there are links if( $hidelinks || $hidetrans || $hideredirs || $hideimages ) $wgOut->addHTML( $this->getFilterPanel() ); + + $errMsg = is_int($namespace) ? 'nolinkshere-ns' : 'nolinkshere'; + $wgOut->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); } return; } @@ -256,7 +256,7 @@ class WhatLinksHerePage { } protected function listStart() { - return Xml::openElement( 'ul' ); + return Xml::openElement( 'ul', array ( 'id' => 'mw-whatlinkshere-list' ) ); } protected function listItem( $row, $nt, $notClose = false ) { @@ -267,7 +267,7 @@ class WhatLinksHerePage { 'whatlinkshere-links', 'isimage' ); $msgcache = array(); foreach ( $msgs as $msg ) { - $msgcache[$msg] = wfMsgHtml( $msg ); + $msgcache[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) ); } } @@ -377,6 +377,8 @@ class WhatLinksHerePage { $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . Xml::namespaceSelector( $namespace, '' ); + $f .= ' '; + # Submit $f .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); @@ -395,7 +397,7 @@ class WhatLinksHerePage { $links = array(); $types = array( 'hidetrans', 'hidelinks', 'hideredirs' ); - if( $this->target->getNamespace() == NS_IMAGE ) + if( $this->target->getNamespace() == NS_FILE ) $types[] = 'hideimages'; foreach( $types as $type ) { $chosen = $this->opts->getValue( $type ); |