From: Brion Vibber Date: Thu, 19 Jun 2008 21:12:45 +0000 (+0000) Subject: Rename all the special page class files back to their proper names. X-Git-Tag: 1.31.0-rc.0~46935 X-Git-Url: http://git.cyclocoop.org/%7B%24admin_url%7Dmes_infos.php?a=commitdiff_plain;h=660a5eb7aa6112a5138c7c66721d667d88c2df0f;p=lhc%2Fweb%2Fwiklou.git Rename all the special page class files back to their proper names. 1) This keeps the filename the same as the classname, which is always nice 2) This avoids duplicate filenames (such as includes/Export.php and includes/specials/Export.php) So I've at least got a chance of figuring out what file is what still... --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 2f0726a402..92ef75327e 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -377,73 +377,73 @@ class AutoLoader { 'StripState' => 'includes/parser/Parser.php', # includes/specials - 'AncientPagesPage' => 'includes/specials/Ancientpages.php', - 'BrokenRedirectsPage' => 'includes/specials/BrokenRedirects.php', - 'ContribsPager' => 'includes/specials/Contributions.php', - 'DBLockForm' => 'includes/specials/Lockdb.php', - 'DBUnlockForm' => 'includes/specials/Unlockdb.php', - 'DeadendPagesPage' => 'includes/specials/Deadendpages.php', - 'DisambiguationsPage' => 'includes/specials/Disambiguations.php', - 'DoubleRedirectsPage' => 'includes/specials/DoubleRedirects.php', - 'EmailConfirmation' => 'includes/specials/Confirmemail.php', - 'EmailInvalidation' => 'includes/specials/Confirmemail.php', - 'EmailUserForm' => 'includes/specials/Emailuser.php', - 'FewestrevisionsPage' => 'includes/specials/Fewestrevisions.php', - 'FileDuplicateSearchPage' => 'includes/specials/FileDuplicateSearch.php', - 'IPBlockForm' => 'includes/specials/Blockip.php', - 'IPBlocklistPager' => 'includes/specials/Ipblocklist.php', - 'IPUnblockForm' => 'includes/specials/Ipblocklist.php', - 'ImportReporter' => 'includes/specials/Import.php', - 'ImportStreamSource' => 'includes/specials/Import.php', - 'ImportStringSource' => 'includes/specials/Import.php', - 'ListredirectsPage' => 'includes/specials/Listredirects.php', - 'LoginForm' => 'includes/specials/Userlogin.php', - 'LonelyPagesPage' => 'includes/specials/Lonelypages.php', - 'LongPagesPage' => 'includes/specials/Longpages.php', - 'MIMEsearchPage' => 'includes/specials/MIMEsearch.php', - 'MostcategoriesPage' => 'includes/specials/Mostcategories.php', - 'MostimagesPage' => 'includes/specials/Mostimages.php', - 'MostlinkedCategoriesPage' => 'includes/specials/Mostlinkedcategories.php', - 'MostlinkedPage' => 'includes/specials/Mostlinked.php', - 'MostrevisionsPage' => 'includes/specials/Mostrevisions.php', - 'MovePageForm' => 'includes/specials/Movepage.php', - 'SpecialNewpages' => 'includes/specials/Newpages.php', - 'NewPagesPager' => 'includes/specials/Newpages.php', - 'PageArchive' => 'includes/specials/Undelete.php', - 'PasswordResetForm' => 'includes/specials/Resetpass.php', - 'PopularPagesPage' => 'includes/specials/Popularpages.php', - 'PreferencesForm' => 'includes/specials/Preferences.php', - 'RandomPage' => 'includes/specials/Randompage.php', - 'RevisionDeleteForm' => 'includes/specials/Revisiondelete.php', - 'RevisionDeleter' => 'includes/specials/Revisiondelete.php', - 'ShortPagesPage' => 'includes/specials/Shortpages.php', - 'SpecialAllpages' => 'includes/specials/Allpages.php', - 'SpecialBookSources' => 'includes/specials/Booksources.php', - 'SpecialListGroupRights' => 'includes/specials/Listgrouprights.php', - 'SpecialMostlinkedtemplates' => 'includes/specials/Mostlinkedtemplates.php', - 'SpecialPrefixindex' => 'includes/specials/Prefixindex.php', - 'SpecialRandomredirect' => 'includes/specials/Randomredirect.php', - 'SpecialRecentChanges' => 'includes/specials/Recentchanges.php', - 'SpecialSearch' => 'includes/specials/Search.php', - 'SpecialVersion' => 'includes/specials/Version.php', - 'UncategorizedCategoriesPage' => 'includes/specials/Uncategorizedcategories.php', - 'UncategorizedPagesPage' => 'includes/specials/Uncategorizedpages.php', - 'UncategorizedTemplatesPage' => 'includes/specials/Uncategorizedtemplates.php', - 'UndeleteForm' => 'includes/specials/Undelete.php', - 'UnusedCategoriesPage' => 'includes/specials/Unusedcategories.php', - 'UnusedimagesPage' => 'includes/specials/Unusedimages.php', - 'UnusedtemplatesPage' => 'includes/specials/Unusedtemplates.php', - 'UnwatchedpagesPage' => 'includes/specials/Unwatchedpages.php', - 'UploadForm' => 'includes/specials/Upload.php', - 'UploadFormMogile' => 'includes/specials/UploadMogile.php', - 'UserrightsPage' => 'includes/specials/Userrights.php', - 'UsersPager' => 'includes/specials/Listusers.php', - 'WantedCategoriesPage' => 'includes/specials/Wantedcategories.php', - 'WantedPagesPage' => 'includes/specials/Wantedpages.php', - 'WhatLinksHerePage' => 'includes/specials/Whatlinkshere.php', - 'WikiImporter' => 'includes/specials/Import.php', - 'WikiRevision' => 'includes/specials/Import.php', - 'WithoutInterwikiPage' => 'includes/specials/Withoutinterwiki.php', + 'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php', + 'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php', + 'ContribsPager' => 'includes/specials/SpecialContributions.php', + 'DBLockForm' => 'includes/specials/SpecialLockdb.php', + 'DBUnlockForm' => 'includes/specials/SpecialUnlockdb.php', + 'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php', + 'DisambiguationsPage' => 'includes/specials/SpecialDisambiguations.php', + 'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php', + 'EmailConfirmation' => 'includes/specials/SpecialConfirmemail.php', + 'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php', + 'EmailUserForm' => 'includes/specials/SpecialEmailuser.php', + 'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php', + 'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php', + 'IPBlockForm' => 'includes/specials/SpecialBlockip.php', + 'IPBlocklistPager' => 'includes/specials/SpecialIpblocklist.php', + 'IPUnblockForm' => 'includes/specials/SpecialIpblocklist.php', + 'ImportReporter' => 'includes/specials/SpecialImport.php', + 'ImportStreamSource' => 'includes/specials/SpecialImport.php', + 'ImportStringSource' => 'includes/specials/SpecialImport.php', + 'ListredirectsPage' => 'includes/specials/SpecialListredirects.php', + 'LoginForm' => 'includes/specials/SpecialUserlogin.php', + 'LonelyPagesPage' => 'includes/specials/SpecialLonelypages.php', + 'LongPagesPage' => 'includes/specials/SpecialLongpages.php', + 'MIMEsearchPage' => 'includes/specials/SpecialMIMEsearch.php', + 'MostcategoriesPage' => 'includes/specials/SpecialMostcategories.php', + 'MostimagesPage' => 'includes/specials/SpecialMostimages.php', + 'MostlinkedCategoriesPage' => 'includes/specials/SpecialMostlinkedcategories.php', + 'MostlinkedPage' => 'includes/specials/SpecialMostlinked.php', + 'MostrevisionsPage' => 'includes/specials/SpecialMostrevisions.php', + 'MovePageForm' => 'includes/specials/SpecialMovepage.php', + 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php', + 'NewPagesPager' => 'includes/specials/SpecialNewpages.php', + 'PageArchive' => 'includes/specials/SpecialUndelete.php', + 'PasswordResetForm' => 'includes/specials/SpecialResetpass.php', + 'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php', + 'PreferencesForm' => 'includes/specials/SpecialPreferences.php', + 'RandomPage' => 'includes/specials/SpecialRandompage.php', + 'RevisionDeleteForm' => 'includes/specials/SpecialRevisiondelete.php', + 'RevisionDeleter' => 'includes/specials/SpecialRevisiondelete.php', + 'ShortPagesPage' => 'includes/specials/SpecialShortpages.php', + 'SpecialAllpages' => 'includes/specials/SpecialAllpages.php', + 'SpecialBookSources' => 'includes/specials/SpecialBooksources.php', + 'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php', + 'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php', + 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php', + 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php', + 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php', + 'SpecialSearch' => 'includes/specials/SpecialSearch.php', + 'SpecialVersion' => 'includes/specials/SpecialVersion.php', + 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php', + 'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php', + 'UncategorizedTemplatesPage' => 'includes/specials/SpecialUncategorizedtemplates.php', + 'UndeleteForm' => 'includes/specials/SpecialUndelete.php', + 'UnusedCategoriesPage' => 'includes/specials/SpecialUnusedcategories.php', + 'UnusedimagesPage' => 'includes/specials/SpecialUnusedimages.php', + 'UnusedtemplatesPage' => 'includes/specials/SpecialUnusedtemplates.php', + 'UnwatchedpagesPage' => 'includes/specials/SpecialUnwatchedpages.php', + 'UploadForm' => 'includes/specials/SpecialUpload.php', + 'UploadFormMogile' => 'includes/specials/SpecialUploadMogile.php', + 'UserrightsPage' => 'includes/specials/SpecialUserrights.php', + 'UsersPager' => 'includes/specials/SpecialListusers.php', + 'WantedCategoriesPage' => 'includes/specials/SpecialWantedcategories.php', + 'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php', + 'WhatLinksHerePage' => 'includes/specials/SpecialWhatlinkshere.php', + 'WikiImporter' => 'includes/specials/SpecialImport.php', + 'WikiRevision' => 'includes/specials/SpecialImport.php', + 'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php', # includes/templates 'UsercreateTemplate' => 'includes/templates/Userlogin.php', diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php index f9211e9587..e092980c4f 100644 --- a/includes/SpecialPage.php +++ b/includes/SpecialPage.php @@ -642,7 +642,7 @@ class SpecialPage $this->mFunction = $function; } if ( $file === 'default' ) { - $this->mFile = dirname(__FILE__) . "/specials/$name.php"; + $this->mFile = dirname(__FILE__) . "/specials/Special$name.php"; } else { $this->mFile = $file; } diff --git a/includes/specials/Allmessages.php b/includes/specials/Allmessages.php deleted file mode 100644 index c2a8de4ef0..0000000000 --- a/includes/specials/Allmessages.php +++ /dev/null @@ -1,217 +0,0 @@ -addWikiMsg( 'allmessagesnotsupportedDB' ); - return; - } - - wfProfileIn( __METHOD__ ); - - wfProfileIn( __METHOD__ . '-setup' ); - $ot = $wgRequest->getText( 'ot' ); - - $navText = wfMsg( 'allmessagestext' ); - - # Make sure all extension messages are available - - $wgMessageCache->loadAllMessages(); - - $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) ); - ksort( $sortedArray ); - $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]['msg'] = wfMsgNoTrans( $key ); - } - - wfProfileOut( __METHOD__ . '-setup' ); - - wfProfileIn( __METHOD__ . '-output' ); - $wgOut->addScriptFile( 'allmessages.js' ); - if ( $ot == 'php' ) { - $navText .= wfAllMessagesMakePhp( $messages ); - $wgOut->addHTML( 'PHP | HTML | ' . - 'XML' . - '
' . htmlspecialchars( $navText ) . '
' ); - } else if ( $ot == 'xml' ) { - $wgOut->disable(); - header( 'Content-type: text/xml' ); - echo wfAllMessagesMakeXml( $messages ); - } else { - $wgOut->addHTML( 'PHP | ' . - 'HTML | XML' ); - $wgOut->addWikiText( $navText ); - $wgOut->addHTML( wfAllMessagesMakeHTMLText( $messages ) ); - } - wfProfileOut( __METHOD__ . '-output' ); - - wfProfileOut( __METHOD__ ); -} - -function wfAllMessagesMakeXml( $messages ) { - global $wgLang; - $lang = $wgLang->getCode(); - $txt = "\n"; - $txt .= "\n"; - foreach( $messages as $key => $m ) { - $txt .= "\t" . Xml::element( 'message', array( 'name' => $key ), $m['msg'] ) . "\n"; - } - $txt .= ""; - return $txt; -} - -/** - * Create the messages array, formatted in PHP to copy to language files. - * @param $messages Messages array. - * @return The PHP messages array. - * @todo Make suitable for language files. - */ -function wfAllMessagesMakePhp( $messages ) { - global $wgLang; - $txt = "\n\n\$messages = array(\n"; - foreach( $messages as $key => $m ) { - if( $wgLang->getCode() != 'en' && $m['msg'] == $m['enmsg'] ) { - continue; - } else if ( wfEmptyMsg( $key, $m['msg'] ) ) { - $m['msg'] = ''; - $comment = ' #empty'; - } else { - $comment = ''; - } - $txt .= "'$key' => '" . preg_replace( '/(?getSkin(); - $talk = wfMsg( 'talkpagelinktext' ); - - $input = Xml::element( 'input', array( - 'type' => 'text', - 'id' => 'allmessagesinput', - 'onkeyup' => 'allmessagesfilter()' - ), '' ); - $checkbox = Xml::element( 'input', array( - 'type' => 'button', - 'value' => wfMsgHtml( 'allmessagesmodified' ), - 'id' => 'allmessagescheckbox', - 'onclick' => 'allmessagesmodified()' - ), '' ); - - $txt = ''; - - $txt .= ' - - - - - - - - '; - - wfProfileIn( __METHOD__ . "-check" ); - - # This is a nasty hack to avoid doing independent existence checks - # without sending the links and table through the slow wiki parser. - $pageExists = array( - NS_MEDIAWIKI => array(), - 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 ); - while( $s = $dbr->fetchObject( $res ) ) { - $pageExists[$s->page_namespace][$s->page_title] = true; - } - $dbr->freeResult( $res ); - wfProfileOut( __METHOD__ . "-check" ); - - wfProfileIn( __METHOD__ . "-output" ); - - $i = 0; - - foreach( $messages as $key => $m ) { - $title = $wgLang->ucfirst( $key ); - if( $wgLang->getCode() != $wgContLang->getCode() ) { - $title .= '/' . $wgLang->getCode(); - } - - $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, "" . htmlspecialchars( $key ) . '' ); - } else { - $pageLink = $sk->makeBrokenLinkObj( $titleObj, "" . htmlspecialchars( $key ) . '' ); - } - if( isset( $pageExists[NS_MEDIAWIKI_TALK][$title] ) ) { - $talkLink = $sk->makeKnownLinkObj( $talkPage, htmlspecialchars( $talk ) ); - } else { - $talkLink = $sk->makeBrokenLinkObj( $talkPage, htmlspecialchars( $talk ) ); - } - - $anchor = 'msg_' . htmlspecialchars( strtolower( $title ) ); - $anchor = ""; - - if( $changed ) { - $txt .= " - - - - - "; - } else { - $txt .= " - - - "; - } - $i++; - } - $txt .= '
' . wfMsgHtml( 'allmessagesname' ) . '' . wfMsgHtml( 'allmessagesdefault' ) . '
' . wfMsgHtml( 'allmessagescurrent' ) . '
- $anchor$pageLink
$talkLink -
-$message -
-$mw -
- $anchor$pageLink
$talkLink -
-$mw -
'; - wfProfileOut( __METHOD__ . '-output' ); - - wfProfileOut( __METHOD__ ); - return $txt; -} diff --git a/includes/specials/Allpages.php b/includes/specials/Allpages.php deleted file mode 100644 index 7223e31747..0000000000 --- a/includes/specials/Allpages.php +++ /dev/null @@ -1,404 +0,0 @@ -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 { - /** - * Maximum number of pages to show on single subpage. - */ - protected $maxPerPage = 960; - - /** - * Name of this special page. Used to make title objects that reference back - * to this page. - */ - protected $name = 'Allpages'; - - /** - * 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 .= " - " . - Xml::label( wfMsg( $this->nsfromMsg ), 'nsfrom' ) . - " - " . - Xml::input( 'from', 20, $from, array( 'id' => 'nsfrom' ) ) . - " - - - " . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . - " - " . - Xml::namespaceSelector( $namespace, null ) . ' ' . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - " - "; - $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, $including = false ) { - 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 ); - - global $wgMemc; - $key = wfMemcKey( 'allpages', 'ns', $namespace ); - $lines = $wgMemc->get( $key ); - - if( !is_array( $lines ) ) { - $options = array( 'LIMIT' => 1 ); - if ( ! $dbr->implicitOrderby() ) { - $options['ORDER BY'] = 'page_title'; - } - $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; - } - if( $s = $dbr->fetchObject( $res ) ) { - 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; - } - $dbr->freeResult( $res ); - } - $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 .= ""; - while ( count ( $lines ) > 0 ) { - $inpoint = array_shift ( $lines ); - $outpoint = array_shift ( $lines ); - $out .= $this->showline ( $inpoint, $outpoint, $namespace, false ); - } - $out .= '
'; - $nsForm = $this->namespaceForm( $namespace, '', false ); - - # Is there more? - if ( $including ) { - $out2 = ''; - } else { - $morelinks = ''; - if ( $morelinks != '' ) { - $out2 = ''; - $out2 .= '
' . $nsForm; - $out2 .= ''; - $out2 .= $morelinks . '

'; - } else { - $out2 = $nsForm . '
'; - } - } - - $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', - "$inpointf", - "$outpointf" - ); - return ''.$out.''; -} - -/** - * @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; - - $sk = $wgUser->getSkin(); - - $fromList = $this->getNamespaceKeyAndText($namespace, $from); - $namespaces = $wgContLang->getNamespaces(); - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - $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; - - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'page', - array( 'page_namespace', 'page_title', 'page_is_redirect' ), - array( - 'page_namespace' => $namespace, - 'page_title >= ' . $dbr->addQuotes( $fromKey ) - ), - __METHOD__, - array( - 'ORDER BY' => 'page_title', - 'LIMIT' => $this->maxPerPage + 1, - 'USE INDEX' => 'name_title', - ) - ); - - if( $res->numRows() > 0 ) { - $out = ''; - - while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $t = Title::makeTitle( $s->page_namespace, $s->page_title ); - if( $t ) { - $link = ($s->page_is_redirect ? '
' : '' ) . - $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . - ($s->page_is_redirect ? '
' : '' ); - } else { - $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; - } - if( $n % 3 == 0 ) { - $out .= ''; - } - $out .= ""; - $n++; - if( $n % 3 == 0 ) { - $out .= ''; - } - } - if( ($n % 3) != 0 ) { - $out .= ''; - } - $out .= '
$link
'; - } else { - $out = ''; - } - } - - if ( $including ) { - $out2 = ''; - } else { - if( $from == '' ) { - // First chunk; no previous link. - $prevTitle = null; - } 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 ); - } 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 ); - } else { - $prevTitle = null; - } - } - } - - $nsForm = $this->namespaceForm( $namespace, $from ); - $out2 = ''; - $out2 .= '
' . $nsForm; - $out2 .= '' . - $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; - } - - 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 .= "

"; - } - - $wgOut->addHtml( $out2 . $out ); - if( isset($prevLink) or isset($nextLink) ) { - $wgOut->addHtml( '

' ); - if( isset( $prevLink ) ) { - $wgOut->addHTML( $prevLink ); - } - if( isset( $prevLink ) && isset( $nextLink ) ) { - $wgOut->addHTML( ' | ' ); - } - if( isset( $nextLink ) ) { - $wgOut->addHTML( $nextLink ); - } - $wgOut->addHTML( '

' ); - - } - -} - -/** - * @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/Ancientpages.php b/includes/specials/Ancientpages.php deleted file mode 100644 index 724d34b152..0000000000 --- a/includes/specials/Ancientpages.php +++ /dev/null @@ -1,61 +0,0 @@ -tableName( 'page' ); - $revision = $db->tableName( 'revision' ); - #$use_index = $db->useIndexClause( 'cur_timestamp' ); # FIXME! this is gone - $epoch = $wgDBtype == 'mysql' ? 'UNIX_TIMESTAMP(rev_timestamp)' : - 'EXTRACT(epoch FROM rev_timestamp)'; - return - "SELECT 'Ancientpages' as type, - page_namespace as namespace, - page_title as title, - $epoch as value - FROM $page, $revision - WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0 - AND page_latest=rev_id"; - } - - function sortDescending() { - return false; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $result->value ), true ); - $title = Title::makeTitle( $result->namespace, $result->title ); - $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); - return wfSpecialList($link, $d); - } -} - -function wfSpecialAncientpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $app = new AncientPagesPage(); - - $app->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Blockip.php b/includes/specials/Blockip.php deleted file mode 100644 index 5ea25ca46a..0000000000 --- a/includes/specials/Blockip.php +++ /dev/null @@ -1,494 +0,0 @@ -readOnlyPage(); - return; - } - - # Permission check - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - - $ipb = new IPBlockForm( $par ); - - $action = $wgRequest->getVal( 'action' ); - if ( 'success' == $action ) { - $ipb->showSuccess(); - } else if ( $wgRequest->wasPosted() && 'submit' == $action && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $ipb->doSubmit(); - } else { - $ipb->showForm( '' ); - } -} - -/** - * Form object for the Special:Blockip page. - * - * @ingroup SpecialPage - */ -class IPBlockForm { - var $BlockAddress, $BlockExpiry, $BlockReason; -# var $BlockEmail; - - function IPBlockForm( $par ) { - global $wgRequest, $wgUser; - - $this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) ); - $this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' ); - $this->BlockReason = $wgRequest->getText( 'wpBlockReason' ); - $this->BlockReasonList = $wgRequest->getText( 'wpBlockReasonList' ); - $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') ); - $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' ); - - # Unchecked checkboxes are not included in the form data at all, so having one - # that is true by default is a bit tricky - $byDefault = !$wgRequest->wasPosted(); - $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault ); - $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault ); - $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault ); - $this->BlockEmail = $wgRequest->getBool( 'wpEmailBan', false ); - $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; - } - - function showForm( $err ) { - global $wgOut, $wgUser, $wgSysopUserBans; - - $wgOut->setPagetitle( wfMsg( 'blockip' ) ); - $wgOut->addWikiMsg( 'blockiptext' ); - - if($wgSysopUserBans) { - $mIpaddress = Xml::label( wfMsg( 'ipadressorusername' ), 'mw-bi-target' ); - } else { - $mIpaddress = Xml::label( wfMsg( 'ipaddress' ), 'mw-bi-target' ); - } - $mIpbexpiry = Xml::label( wfMsg( 'ipbexpiry' ), 'wpBlockExpiry' ); - $mIpbother = Xml::label( wfMsg( 'ipbother' ), 'mw-bi-other' ); - $mIpbreasonother = Xml::label( wfMsg( 'ipbreason' ), 'wpBlockReasonList' ); - $mIpbreason = Xml::label( wfMsg( 'ipbotherreason' ), 'mw-bi-reason' ); - - $titleObj = SpecialPage::getTitleFor( 'Blockip' ); - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) ); - $wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $err ) ); - } - - $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' ); - - $showblockoptions = $scBlockExpiryOptions != '-'; - if (!$showblockoptions) - $mIpbother = $mIpbexpiry; - - $blockExpiryFormOptions = Xml::option( wfMsg( 'ipbotheroption' ), 'other' ); - foreach (explode(',', $scBlockExpiryOptions) as $option) { - if ( strpos($option, ":") === false ) $option = "$option:$option"; - list($show, $value) = explode(":", $option); - $show = htmlspecialchars($show); - $value = htmlspecialchars($value); - $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ? true : false ) . "\n"; - } - - $reasonDropDown = Xml::listDropDown( 'wpBlockReasonList', - wfMsgForContent( 'ipbreason-dropdown' ), - wfMsgForContent( 'ipbreasonotherlist' ), '', 'wpBlockDropDown', 4 ); - - global $wgStylePath, $wgStyleVersion; - $wgOut->addHTML( - Xml::tags( 'script', array( 'type' => 'text/javascript', 'src' => "$wgStylePath/common/block.js?$wgStyleVersion" ), '' ) . - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( "action=submit" ), 'id' => 'blockip' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'blockip-legend' ) ) . - Xml::openElement( 'table', array ( 'border' => '0', 'id' => 'mw-blockip-table' ) ) . - " - - {$mIpaddress} - - " . - Xml::input( 'wpBlockAddress', 45, $this->BlockAddress, - array( - 'tabindex' => '1', - 'id' => 'mw-bi-target', - 'onchange' => 'updateBlockOptions()' ) ). " - - - " - ); - if ( $showblockoptions ) { - $wgOut->addHTML(" - - {$mIpbexpiry} - - " . - Xml::tags( 'select', - array( - 'id' => 'wpBlockExpiry', - 'name' => 'wpBlockExpiry', - 'onchange' => 'considerChangingExpiryFocus()', - 'tabindex' => '2' ), - $blockExpiryFormOptions ) . - "" - ); - } - $wgOut->addHTML(" - - - - {$mIpbother} - - " . - Xml::input( 'wpBlockOther', 45, $this->BlockOther, - array( 'tabindex' => '3', 'id' => 'mw-bi-other' ) ) . " - - - - - {$mIpbreasonother} - - - {$reasonDropDown} - - - - - {$mIpbreason} - - " . - Xml::input( 'wpBlockReason', 45, $this->BlockReason, - array( 'tabindex' => '5', 'id' => 'mw-bi-reason', 'maxlength'=> '200' ) ) . " - - - -   - " . - Xml::checkLabel( wfMsg( 'ipbanononly' ), - 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly, - array( 'tabindex' => '6' ) ) . " - - - -   - " . - Xml::checkLabel( wfMsg( 'ipbcreateaccount' ), - 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount, - array( 'tabindex' => '7' ) ) . " - - - -   - " . - Xml::checkLabel( wfMsg( 'ipbenableautoblock' ), - 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, - array( 'tabindex' => '8' ) ) . " - - " - ); - - global $wgSysopEmailBans; - if ( $wgSysopEmailBans && $wgUser->isAllowed( 'blockemail' ) ) { - $wgOut->addHTML(" - -   - " . - Xml::checkLabel( wfMsg( 'ipbemailban' ), - 'wpEmailBan', 'wpEmailBan', $this->BlockEmail, - array( 'tabindex' => '9' )) . " - - " - ); - } - - // Allow some users to hide name from block log, blocklist and listusers - if ( $wgUser->isAllowed( 'hideuser' ) ) { - $wgOut->addHTML(" - -   - " . - Xml::checkLabel( wfMsg( 'ipbhidename' ), - 'wpHideName', 'wpHideName', $this->BlockHideName, - array( 'tabindex' => '10' ) ) . " - - " - ); - } - - # Watchlist their user page? - $wgOut->addHTML(" - -   - " . - Xml::checkLabel( wfMsg( 'ipbwatchuser' ), - 'wpWatchUser', 'wpWatchUser', $this->BlockWatchUser, - array( 'tabindex' => '11' ) ) . " - - " - ); - - $wgOut->addHTML(" - -   - " . - Xml::submitButton( wfMsg( 'ipbsubmit' ), - array( 'name' => 'wpBlock', 'tabindex' => '12' ) ) . " - - " . - Xml::closeElement( 'table' ) . - Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . - Xml::tags( 'script', array( 'type' => 'text/javascript' ), 'updateBlockOptions()' ) . "\n" - ); - - $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 ) ) { - $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } elseif( preg_match( '/^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/', $this->BlockAddress ) ) { - $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } - } - - /** - * Backend block code. - * $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; - - $userId = 0; - # Expand valid IPv6 addresses, usernames are left as is - $this->BlockAddress = IP::sanitizeIP( $this->BlockAddress ); - # isIPv4() and IPv6() are used for final validation - $rxIP4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; - $rxIP6 = '\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}'; - $rxIP = "($rxIP4|$rxIP6)"; - - # Check for invalid specifications - if ( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) { - $matches = array(); - if ( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { - # IPv4 - if ( $wgSysopRangeBans ) { - if ( !IP::isIPv4( $this->BlockAddress ) || $matches[2] < 16 || $matches[2] > 32 ) { - return array('ip_range_invalid'); - } - $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); - } else { - # Range block illegal - return array('range_block_disabled'); - } - } else if ( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) { - # IPv6 - if ( $wgSysopRangeBans ) { - if ( !IP::isIPv6( $this->BlockAddress ) || $matches[2] < 64 || $matches[2] > 128 ) { - return array('ip_range_invalid'); - } - $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); - } else { - # Range block illegal - return array('range_block_disabled'); - } - } else { - # Username block - if ( $wgSysopUserBans ) { - $user = User::newFromName( $this->BlockAddress ); - if( !is_null( $user ) && $user->getId() ) { - # Use canonical name - $userId = $user->getId(); - $this->BlockAddress = $user->getName(); - } else { - return array('nosuchusershort', htmlspecialchars( $user ? $user->getName() : $this->BlockAddress ) ); - } - } else { - return array('badipaddress'); - } - } - } - - $reasonstr = $this->BlockReasonList; - if ( $reasonstr != 'other' && $this->BlockReason != '') { - // Entry from drop down menu + additional comment - $reasonstr .= ': ' . $this->BlockReason; - } elseif ( $reasonstr == 'other' ) { - $reasonstr = $this->BlockReason; - } - - $expirestr = $this->BlockExpiry; - if( $expirestr == 'other' ) - $expirestr = $this->BlockOther; - - if (strlen($expirestr) == 0) { - return array('ipb_expiry_invalid'); - } - - if ( false === ($expiry = Block::parseExpiryInput( $expirestr )) ) { - // Bad expiry. - return array('ipb_expiry_invalid'); - } - - if( $this->BlockHideName && $expiry != 'infinity' ) { - // Bad expiry. - return array('ipb_expiry_temp'); - } - - # Create block - # Note: for a user block, ipb_address is only for display purposes - $block = new Block( $this->BlockAddress, $userId, $wgUser->getId(), - $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, - $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, - $this->BlockEmail ); - - if ( wfRunHooks('BlockIp', array(&$block, &$wgUser)) ) { - - if ( !$block->insert() ) { - return array('ipb_already_blocked', htmlspecialchars($this->BlockAddress)); - } - - wfRunHooks('BlockIpComplete', array($block, $wgUser)); - - if ( $this->BlockWatchUser ) { - $wgUser->addWatch ( Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } - - # Prepare log parameters - $logParams = array(); - $logParams[] = $expirestr; - $logParams[] = $this->blockLogFlags(); - - # 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 ), - $reasonstr, $logParams ); - - # Report to the user - return array(); - } - else - return array('hookaborted'); - } - - /** - * UI entry point for blocking - * Wraps around doBlock() - */ - function doSubmit() - { - global $wgOut; - $retval = $this->doBlock(); - if(empty($retval)) { - $titleObj = SpecialPage::getTitleFor( 'Blockip' ); - $wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' . - urlencode( $this->BlockAddress ) ) ); - return; - } - $key = array_shift($retval); - $this->showForm(wfMsgReal($key, $retval)); - } - - function showSuccess() { - global $wgOut; - - $wgOut->setPagetitle( wfMsg( 'blockip' ) ); - $wgOut->setSubtitle( wfMsg( 'blockipsuccesssub' ) ); - $text = wfMsgExt( 'blockipsuccesstext', array( 'parse' ), $this->BlockAddress ); - $wgOut->addHtml( $text ); - } - - function showLogFragment( $out, $title ) { - $out->addHtml( Xml::element( 'h2', NULL, LogPage::logName( 'block' ) ) ); - LogEventsList::showLogExtract( $out, 'block', $title->getPrefixedText() ); - } - - /** - * Return a comma-delimited list of "flags" to be passed to the log - * reader for this block, to provide more information in the logs - * - * @return array - */ - private function blockLogFlags() { - $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 - $flags[] = 'anononly'; - if( $this->BlockCreateAccount ) - $flags[] = 'nocreate'; - if( !$this->BlockEnableAutoblock ) - $flags[] = 'noautoblock'; - if ( $this->BlockEmail ) - $flags[] = 'noemail'; - return implode( ',', $flags ); - } - - /** - * Builds unblock and block list links - * - * @return string - */ - private function getConvenienceLinks() { - global $wgUser; - $skin = $wgUser->getSkin(); - $links[] = $skin->makeLink ( 'MediaWiki:Ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) ); - $links[] = $this->getUnblockLink( $skin ); - $links[] = $this->getBlockListLink( $skin ); - return ''; - } - - /** - * Build a convenient link to unblock the given username or IP - * address, if available; otherwise link to a blank unblock - * form - * - * @param $skin Skin to use - * @return string - */ - private function getUnblockLink( $skin ) { - $list = SpecialPage::getTitleFor( 'Ipblocklist' ); - if( $this->BlockAddress ) { - $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock-addr', $addr ), - 'action=unblock&ip=' . urlencode( $this->BlockAddress ) ); - } else { - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock' ), 'action=unblock' ); - } - } - - /** - * Build a convenience link to the block list - * - * @param $skin Skin to use - * @return string - */ - private function getBlockListLink( $skin ) { - $list = SpecialPage::getTitleFor( 'Ipblocklist' ); - if( $this->BlockAddress ) { - $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist-addr', $addr ), - 'ip=' . urlencode( $this->BlockAddress ) ); - } else { - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist' ) ); - } - } -} diff --git a/includes/specials/Blockme.php b/includes/specials/Blockme.php deleted file mode 100644 index f222e3c63e..0000000000 --- a/includes/specials/Blockme.php +++ /dev/null @@ -1,37 +0,0 @@ -getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) { - $wgOut->addWikiMsg( 'proxyblocker-disabled' ); - return; - } - - $blockerName = wfMsg( "proxyblocker" ); - $reason = wfMsg( "proxyblockreason" ); - - $u = User::newFromName( $blockerName ); - $id = $u->idForName(); - if ( !$id ) { - $u = User::newFromName( $blockerName ); - $u->addToDatabase(); - $u->setPassword( bin2hex( mt_rand(0, 0x7fffffff ) ) ); - $u->saveSettings(); - $id = $u->getID(); - } - - $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() ); - $block->insert(); - - $wgOut->addWikiMsg( "proxyblocksuccess" ); -} diff --git a/includes/specials/Booksources.php b/includes/specials/Booksources.php deleted file mode 100644 index 0690c5c05d..0000000000 --- a/includes/specials/Booksources.php +++ /dev/null @@ -1,110 +0,0 @@ - - * @todo Validate ISBNs using the standard check-digit method - * @ingroup SpecialPages - */ -class SpecialBookSources extends SpecialPage { - - /** - * ISBN passed to the page, if any - */ - private $isbn = ''; - - /** - * Constructor - */ - public function __construct() { - parent::__construct( 'Booksources' ); - } - - /** - * Show the special page - * - * @param $isbn ISBN passed as a subpage parameter - */ - public function execute( $isbn ) { - global $wgOut, $wgRequest; - $this->setHeaders(); - $this->isbn = $this->cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); - $wgOut->addWikiMsg( 'booksources-summary' ); - $wgOut->addHtml( $this->makeForm() ); - if( strlen( $this->isbn ) > 0 ) - $this->showList(); - } - - /** - * Trim ISBN and remove characters which aren't required - * - * @param $isbn Unclean ISBN - * @return string - */ - private function cleanIsbn( $isbn ) { - return trim( preg_replace( '![^0-9X]!', '', $isbn ) ); - } - - /** - * Generate a form to allow users to enter an ISBN - * - * @return string - */ - private function makeForm() { - global $wgScript; - $title = self::getTitleFor( 'Booksources' ); - $form = '
' . wfMsgHtml( 'booksources-search-legend' ) . ''; - $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $form .= Xml::hidden( 'title', $title->getPrefixedText() ); - $form .= '

' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn ); - $form .= ' ' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '

'; - $form .= Xml::closeElement( 'form' ); - $form .= '
'; - return $form; - } - - /** - * Determine where to get the list of book sources from, - * format and output them - * - * @return string - */ - private function showList() { - global $wgOut, $wgContLang; - - # Hook to allow extensions to insert additional HTML, - # e.g. for API-interacting plugins and so on - wfRunHooks( 'BookInformation', array( $this->isbn, &$wgOut ) ); - - # Check for a local page such as Project:Book_sources and use that if available - $title = Title::makeTitleSafe( NS_PROJECT, wfMsgForContent( 'booksources' ) ); # Show list in content language - if( is_object( $title ) && $title->exists() ) { - $rev = Revision::newFromTitle( $title ); - $wgOut->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); - return true; - } - - # Fall back to the defaults given in the language file - $wgOut->addWikiMsg( 'booksources-text' ); - $wgOut->addHtml( '' ); - return true; - } - - /** - * Format a book source list item - * - * @param $label Book source label - * @param $url Book source URL - * @return string - */ - private function makeListItem( $label, $url ) { - $url = str_replace( '$1', $this->isbn, $url ); - return '
  • ' . htmlspecialchars( $label ) . '
  • '; - } -} diff --git a/includes/specials/BrokenRedirects.php b/includes/specials/BrokenRedirects.php deleted file mode 100644 index 0a16e6de32..0000000000 --- a/includes/specials/BrokenRedirects.php +++ /dev/null @@ -1,93 +0,0 @@ -tableNamesN( 'page', 'redirect' ); - - $sql = "SELECT 'BrokenRedirects' AS type, - p1.page_namespace AS namespace, - p1.page_title AS title, - rd_namespace, - rd_title - FROM $redirect AS rd - JOIN $page p1 ON (rd.rd_from=p1.page_id) - LEFT JOIN $page AS p2 ON (rd_namespace=p2.page_namespace AND rd_title=p2.page_title ) - WHERE rd_namespace >= 0 - AND p2.page_namespace IS NULL"; - return $sql; - } - - function getOrder() { - return ''; - } - - function formatResult( $skin, $result ) { - global $wgUser, $wgContLang; - - $fromObj = Title::makeTitle( $result->namespace, $result->title ); - if ( isset( $result->rd_title ) ) { - $toObj = Title::makeTitle( $result->rd_namespace, $result->rd_title ); - } else { - $blinks = $fromObj->getBrokenLinksFrom(); # TODO: check for redirect, not for links - if ( $blinks ) { - $toObj = $blinks[0]; - } else { - $toObj = false; - } - } - - // $toObj may very easily be false if the $result list is cached - if ( !is_object( $toObj ) ) { - return '' . $skin->makeLinkObj( $fromObj ) . ''; - } - - $from = $skin->makeKnownLinkObj( $fromObj ,'', 'redirect=no' ); - $edit = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-edit' ), 'action=edit' ); - $to = $skin->makeBrokenLinkObj( $toObj ); - $arr = $wgContLang->getArrow(); - - $out = "{$from} {$edit}"; - - if( $wgUser->isAllowed( 'delete' ) ) { - $delete = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-delete' ), 'action=delete' ); - $out .= " {$delete}"; - } - - $out .= " {$arr} {$to}"; - return $out; - } -} - -/** - * constructor - */ -function wfSpecialBrokenRedirects() { - list( $limit, $offset ) = wfCheckLimits(); - - $sbr = new BrokenRedirectsPage(); - - return $sbr->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Categories.php b/includes/specials/Categories.php deleted file mode 100644 index 951c2228e4..0000000000 --- a/includes/specials/Categories.php +++ /dev/null @@ -1,112 +0,0 @@ -getText( 'from' ); - } else { - $from = $par; - } - $cap = new CategoryPager( $from ); - $wgOut->addHTML( - wfMsgExt( 'categoriespagetext', array( 'parse' ) ) . - $cap->getStartForm( $from ) . - $cap->getNavigationBar() . - '' . - $cap->getNavigationBar() - ); -} - -/** - * TODO: Allow sorting by count. We need to have a unique index to do this - * properly. - * - * @ingroup SpecialPage Pager - */ -class CategoryPager extends AlphabeticPager { - function __construct( $from ) { - parent::__construct(); - $from = str_replace( ' ', '_', $from ); - if( $from !== '' ) { - global $wgCapitalLinks, $wgContLang; - if( $wgCapitalLinks ) { - $from = $wgContLang->ucfirst( $from ); - } - $this->mOffset = $from; - } - } - - function getQueryInfo() { - global $wgRequest; - return array( - 'tables' => array( 'category' ), - 'fields' => array( 'cat_title','cat_pages' ), - 'conds' => array( 'cat_pages > 0' ), - 'options' => array( 'USE INDEX' => 'cat_title' ), - ); - } - - function getIndexField() { -# return array( 'abc' => 'cat_title', 'count' => 'cat_pages' ); - return 'cat_title'; - } - - function getDefaultQuery() { - parent::getDefaultQuery(); - unset( $this->mDefaultQuery['from'] ); - } -# protected function getOrderTypeMessages() { -# return array( 'abc' => 'special-categories-sort-abc', -# 'count' => 'special-categories-sort-count' ); -# } - - protected function getDefaultDirections() { -# return array( 'abc' => false, 'count' => true ); - return false; - } - - /* Override getBody to apply LinksBatch on resultset before actually outputting anything. */ - public function getBody() { - if (!$this->mQueryDone) { - $this->doQuery(); - } - $batch = new LinkBatch; - - $this->mResult->rewind(); - - while ( $row = $this->mResult->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_CATEGORY, $row->cat_title ) ); - } - $batch->execute(); - $this->mResult->rewind(); - return parent::getBody(); - } - - function formatRow($result) { - global $wgLang; - $title = Title::makeTitle( NS_CATEGORY, $result->cat_title ); - $titleText = $this->getSkin()->makeLinkObj( $title, htmlspecialchars( $title->getText() ) ); - $count = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->cat_pages ) ); - return Xml::tags('li', null, "$titleText ($count)" ) . "\n"; - } - - public function getStartForm( $from ) { - global $wgScript; - $t = SpecialPage::getTitleFor( 'Categories' ); - - return - Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ), - Xml::hidden( 'title', $t->getPrefixedText() ) . - Xml::fieldset( wfMsg( 'categories' ), - Xml::inputLabel( wfMsg( 'categoriesfrom' ), - 'from', 'from', 20, $from ) . - ' ' . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) ) ); - } -} diff --git a/includes/specials/Confirmemail.php b/includes/specials/Confirmemail.php deleted file mode 100644 index 9075fb9573..0000000000 --- a/includes/specials/Confirmemail.php +++ /dev/null @@ -1,139 +0,0 @@ - - */ -class EmailConfirmation extends UnlistedSpecialPage { - - /** - * Constructor - */ - public function __construct() { - parent::__construct( 'Confirmemail' ); - } - - /** - * Main execution point - * - * @param $code Confirmation code passed to the page - */ - function execute( $code ) { - global $wgUser, $wgOut; - $this->setHeaders(); - if( empty( $code ) ) { - if( $wgUser->isLoggedIn() ) { - if( User::isValidEmailAddr( $wgUser->getEmail() ) ) { - $this->showRequestForm(); - } else { - $wgOut->addWikiMsg( 'confirmemail_noemail' ); - } - } else { - $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 ) ); - } - } else { - $this->attemptConfirm( $code ); - } - } - - /** - * Show a nice form for the user to request a confirmation mail - */ - function showRequestForm() { - global $wgOut, $wgUser, $wgLang, $wgRequest; - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) { - $ok = $wgUser->sendConfirmationMail(); - if ( WikiError::isError( $ok ) ) { - $wgOut->addWikiMsg( 'confirmemail_sendfailed', $ok->toString() ); - } else { - $wgOut->addWikiMsg( 'confirmemail_sent' ); - } - } else { - if( $wgUser->isEmailConfirmed() ) { - $time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true ); - $wgOut->addWikiMsg( 'emailauthenticated', $time ); - } - 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 ); - } - } - - /** - * Attempt to confirm the user's email address and show success or failure - * as needed; if successful, take the user to log in - * - * @param $code Confirmation code - */ - function attemptConfirm( $code ) { - global $wgUser, $wgOut; - $user = User::newFromConfirmationCode( $code ); - if( is_object( $user ) ) { - $user->confirmEmail(); - $user->saveSettings(); - $message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success'; - $wgOut->addWikiMsg( $message ); - if( !$wgUser->isLoggedIn() ) { - $title = SpecialPage::getTitleFor( 'Userlogin' ); - $wgOut->returnToMain( true, $title ); - } - } else { - $wgOut->addWikiMsg( 'confirmemail_invalid' ); - } - } - -} - -/** - * Special page allows users to cancel an email confirmation using the e-mail - * confirmation code - * - * @ingroup SpecialPage - */ -class EmailInvalidation extends UnlistedSpecialPage { - - public function __construct() { - parent::__construct( 'Invalidateemail' ); - } - - function execute( $code ) { - $this->setHeaders(); - $this->attemptInvalidate( $code ); - } - - /** - * Attempt to invalidate the user's email address and show success or failure - * as needed; if successful, link to main page - * - * @param $code Confirmation code - */ - function attemptInvalidate( $code ) { - global $wgUser, $wgOut; - $user = User::newFromConfirmationCode( $code ); - if( is_object( $user ) ) { - $user->invalidateEmail(); - $user->saveSettings(); - $wgOut->addWikiMsg( 'confirmemail_invalidated' ); - if( !$wgUser->isLoggedIn() ) { - $wgOut->returnToMain(); - } - } else { - $wgOut->addWikiMsg( 'confirmemail_invalid' ); - } - } -} diff --git a/includes/specials/Contributions.php b/includes/specials/Contributions.php deleted file mode 100644 index c9cbc180fa..0000000000 --- a/includes/specials/Contributions.php +++ /dev/null @@ -1,465 +0,0 @@ -messages[$msg] = wfMsgExt( $msg, array( 'escape') ); - } - $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->mDb = wfGetDB( DB_SLAVE, 'contributions' ); - } - - 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(); - $conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() ); - return array( - 'tables' => array( 'page', 'revision' ), - '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' => $index ) - ); - } - - function getUserCond() { - $condition = array(); - - if ( $this->target == 'newbies' ) { - $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ ); - $condition[] = 'rev_user >' . (int)($max - $max / 100); - $index = 'user_timestamp'; - } else { - $condition['rev_user_text'] = $this->target; - $index = 'usertext_timestamp'; - } - return array( $index, $condition ); - } - - function getNamespaceCond() { - 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'; - } - - function getStartBody() { - return "\n"; - } - - /** - * 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 roll- - * back 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, $wgContLang; - - $sk = $this->getSkin(); - $rev = new Revision( $row ); - - $page = Title::makeTitle( $row->page_namespace, $row->page_title ); - $link = $sk->makeKnownLinkObj( $page ); - $difftext = $topmarktext = ''; - if( $row->rev_id == $row->page_latest ) { - $topmarktext .= '' . $this->messages['uctop'] . ''; - if( !$row->page_is_new ) { - $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')'; - } else { - $difftext .= $this->messages['newarticle']; - } - - if( !$page->getUserPermissionsErrors( 'rollback', $wgUser ) - && !$page->getUserPermissionsErrors( 'edit', $wgUser ) ) { - $topmarktext .= ' '.$sk->generateRollback( $rev ); - } - - } - # Is there a visible previous revision? - if( $rev->userCan(Revision::DELETED_TEXT) ) { - $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; - } else { - $difftext = '(' . $this->messages['diff'] . ')'; - } - $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 ); - - if( $this->target == 'newbies' ) { - $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text ); - $userlink .= ' (' . $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) . ') '; - } else { - $userlink = ''; - } - - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $d = '' . $d . ''; - } - - if( $rev->getParentId() === 0 ) { - $nflag = '' . $this->messages['newpageletter'] . ''; - } else { - $nflag = ''; - } - - if( $row->rev_minor_edit ) { - $mflag = '' . $this->messages['minoreditletter'] . ' '; - } else { - $mflag = ''; - } - - $ret = "{$d} {$histlink} {$difftext} {$nflag}{$mflag} {$link}{$userlink}{$comment} {$topmarktext}"; - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $ret .= ' ' . wfMsgHtml( 'deletedrev' ); - } - $ret = "
  • $ret
  • \n"; - wfProfileOut( __METHOD__ ); - return $ret; - } - - /** - * Get the Database object in use - * - * @return Database - */ - public function getDatabase() { - return $this->mDb; - } - -} - -/** - * 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( - '

    ' . $pager->getNavigationBar() . '

    ' . - $pager->getBody() . - '

    ' . $pager->getNavigationBar() . '

    ' ); - - # 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( '' ); - } - } -} - -/** - * 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 .= '
    ' . - Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . - Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), 'contribs' , 'newbie' , 'newbie', $options['contribs'] == 'newbie' ? true : false ) . '
    ' . - Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ), 'contribs' , 'user', 'user', $options['contribs'] == 'user' ? true : false ) . ' ' . - Xml::input( 'target', 20, $options['target']) . ' '. - '' . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . - Xml::namespaceSelector( $options['namespace'], '' ) . - '' . - Xml::openElement( 'p' ) . - '' . - Xml::label( wfMsg( 'year' ), 'year' ) . ' '. - Xml::input( 'year', 4, $options['year'], array('id' => 'year', 'maxlength' => 4) ) . - '' . - ' '. - '' . - Xml::label( wfMsg( 'month' ), 'month' ) . ' '. - Xml::monthSelector( $options['month'], -1 ) . ' '. - '' . - Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . - Xml::closeElement( 'p' ); - - $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); - if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) - $f .= "

    {$explain}

    "; - - $f .= '
    ' . - Xml::closeElement( 'form' ); - return $f; -} diff --git a/includes/specials/Deadendpages.php b/includes/specials/Deadendpages.php deleted file mode 100644 index a8416c9741..0000000000 --- a/includes/specials/Deadendpages.php +++ /dev/null @@ -1,62 +0,0 @@ -tableNamesN( 'page', 'pagelinks' ); - return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " . - "FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " . - "WHERE pl_from IS NULL " . - "AND page_namespace = 0 " . - "AND page_is_redirect = 0"; - } -} - -/** - * Constructor - */ -function wfSpecialDeadendpages() { - - list( $limit, $offset ) = wfCheckLimits(); - - $depp = new DeadendPagesPage(); - - return $depp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Disambiguations.php b/includes/specials/Disambiguations.php deleted file mode 100644 index 34045660ff..0000000000 --- a/includes/specials/Disambiguations.php +++ /dev/null @@ -1,108 +0,0 @@ -getNamespace() != NS_TEMPLATE) { - # FIXME we assume the disambiguation message is a template but - # the page can potentially be from another namespace :/ - wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); - } - $linkBatch->addObj( $dp ); - } else { - # Get all the templates linked from the Mediawiki:Disambiguationspage - $disPageObj = Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage' ); - $res = $dbr->select( - array('pagelinks', 'page'), - 'pl_title', - array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, - 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), - __METHOD__ ); - - while ( $row = $dbr->fetchObject( $res ) ) { - $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); - } - - $dbr->freeResult( $res ); - } - - $set = $linkBatch->constructSet( 'lb.tl', $dbr ); - if( $set === false ) { - # We must always return a valid sql query, but this way DB will always quicly return an empty result - $set = 'FALSE'; - wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n"); - } - - list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); - - $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," - ." pb.page_title AS title, la.pl_from AS value" - ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" - ." WHERE $set" # disambiguation template(s) - .' AND pa.page_id = la.pl_from' - .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace - .' AND pb.page_id = lb.tl_from' - .' AND pb.page_namespace = la.pl_namespace' - .' AND pb.page_title = la.pl_title' - .' ORDER BY lb.tl_namespace, lb.tl_title'; - - return $sql; - } - - function getOrder() { - return ''; - } - - function formatResult( $skin, $result ) { - global $wgContLang; - $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' ); - $arr = $wgContLang->getArrow(); - $to = $skin->makeKnownLinkObj( $dp, '' ); - - return "$from $edit $arr $to"; - } -} - -/** - * Constructor - */ -function wfSpecialDisambiguations() { - list( $limit, $offset ) = wfCheckLimits(); - - $sd = new DisambiguationsPage(); - - return $sd->doQuery( $offset, $limit ); -} diff --git a/includes/specials/DoubleRedirects.php b/includes/specials/DoubleRedirects.php deleted file mode 100644 index b1bad0c311..0000000000 --- a/includes/specials/DoubleRedirects.php +++ /dev/null @@ -1,103 +0,0 @@ -tableNamesN( 'page', 'redirect' ); - - $limitToTitle = !( $namespace === null && $title === null ); - $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ; - $sql .= - " pa.page_namespace as namespace, pa.page_title as title," . - " pb.page_namespace as nsb, pb.page_title as tb," . - " pc.page_namespace as nsc, pc.page_title as tc" . - " FROM $redirect AS ra, $redirect AS rb, $page AS pa, $page AS pb, $page AS pc" . - " WHERE ra.rd_from=pa.page_id" . - " AND ra.rd_namespace=pb.page_namespace" . - " AND ra.rd_title=pb.page_title" . - " AND rb.rd_from=pb.page_id" . - " AND rb.rd_namespace=pc.page_namespace" . - " AND rb.rd_title=pc.page_title"; - - if( $limitToTitle ) { - $encTitle = $dbr->addQuotes( $title ); - $sql .= " AND pa.page_namespace=$namespace" . - " AND pa.page_title=$encTitle"; - } - - return $sql; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - return $this->getSQLText( $dbr ); - } - - function getOrder() { - return ''; - } - - function formatResult( $skin, $result ) { - global $wgContLang; - - $fname = 'DoubleRedirectsPage::formatResult'; - $titleA = Title::makeTitle( $result->namespace, $result->title ); - - if ( $result && !isset( $result->nsb ) ) { - $dbr = wfGetDB( DB_SLAVE ); - $sql = $this->getSQLText( $dbr, $result->namespace, $result->title ); - $res = $dbr->query( $sql, $fname ); - if ( $res ) { - $result = $dbr->fetchObject( $res ); - $dbr->freeResult( $res ); - } - } - if ( !$result ) { - return '' . $skin->makeLinkObj( $titleA, '', 'redirect=no' ) . ''; - } - - $titleB = Title::makeTitle( $result->nsb, $result->tb ); - $titleC = Title::makeTitle( $result->nsc, $result->tc ); - - $linkA = $skin->makeKnownLinkObj( $titleA, '', 'redirect=no' ); - $edit = $skin->makeBrokenLinkObj( $titleA, "(".wfMsg("qbedit").")" , 'redirect=no'); - $linkB = $skin->makeKnownLinkObj( $titleB, '', 'redirect=no' ); - $linkC = $skin->makeKnownLinkObj( $titleC ); - $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); - - return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" ); - } -} - -/** - * constructor - */ -function wfSpecialDoubleRedirects() { - list( $limit, $offset ) = wfCheckLimits(); - - $sdr = new DoubleRedirectsPage(); - - return $sdr->doQuery( $offset, $limit ); - -} diff --git a/includes/specials/Emailuser.php b/includes/specials/Emailuser.php deleted file mode 100644 index 596f16bab6..0000000000 --- a/includes/specials/Emailuser.php +++ /dev/null @@ -1,291 +0,0 @@ -getVal( 'action' ); - $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); - $targetUser = EmailUserForm::validateEmailTarget( $target ); - - if ( !( $targetUser instanceof User ) ) { - $wgOut->showErrorPage( $targetUser[0], $targetUser[1] ); - return; - } - - $form = new EmailUserForm( $targetUser, - $wgRequest->getText( 'wpText' ), - $wgRequest->getText( 'wpSubject' ), - $wgRequest->getBool( 'wpCCMe' ) ); - if ( $action == 'success' ) { - $form->showSuccess(); - return; - } - - $error = EmailUserForm::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) ); - if ( $error ) { - switch ( $error[0] ) { - case 'blockedemailuser': - $wgOut->blockedPage(); - return; - case 'actionthrottledtext': - $wgOut->rateLimited(); - return; - case 'sessionfailure': - $form->showForm(); - return; - default: - $wgOut->showErrorPage( $error[0], $error[1] ); - return; - } - } - - - if ( "submit" == $action && $wgRequest->wasPosted() ) { - $result = $form->doSubmit(); - - if ( !is_null( $result ) ) { - $wgOut->addHTML( wfMsg( "usermailererror" ) . - ' ' . htmlspecialchars( $result->getMessage() ) ); - } else { - $titleObj = SpecialPage::getTitleFor( "Emailuser" ); - $encTarget = wfUrlencode( $form->getTarget()->getName() ); - $wgOut->redirect( $titleObj->getFullURL( "target={$encTarget}&action=success" ) ); - } - } else { - $form->showForm(); - } -} - -/** - * Implements the Special:Emailuser web interface, and invokes userMailer for sending the email message. - * @ingroup SpecialPage - */ -class EmailUserForm { - - var $target; - var $text, $subject; - var $cc_me; // Whether user requested to be sent a separate copy of their email. - - /** - * @param User $target - */ - function EmailUserForm( $target, $text, $subject, $cc_me ) { - $this->target = $target; - $this->text = $text; - $this->subject = $subject; - $this->cc_me = $cc_me; - } - - function showForm() { - global $wgOut, $wgUser; - $skin = $wgUser->getSkin(); - - $wgOut->setPagetitle( wfMsg( "emailpage" ) ); - $wgOut->addWikiMsg( "emailpagetext" ); - - if ( $this->subject === "" ) { - $this->subject = wfMsgForContent( "defemailsubject" ); - } - - $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=" . - urlencode( $this->target->getName() ) . "&action=submit" ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( " -
    - - - - - - - - - - -
    {$emf}:{$senderLink}
    {$emt}:{$recipientLink}
    {$emr}: - -
    -
    - -" . wfCheckLabel( $emc, 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) . "
    - - -
    \n" ); - - } - - /* - * Really send a mail. Permissions should have been checked using - * EmailUserForm::getPermissionsError. It is probably also a good idea to - * check the edit token and ping limiter in advance. - */ - function doSubmit() { - global $wgUser, $wgUserEmailUseReplyTo, $wgSiteName; - - $to = new MailAddress( $this->target ); - $from = new MailAddress( $wgUser ); - $subject = $this->subject; - - $prefsTitle = Title::newFromText( 'Preferences', NS_SPECIAL ); - - // Add a standard footer - $footerArgs[0] = $from->name; - $footerArgs[1] = $to->name; - $footerArgs[2] = $prefsTitle->getFullURL(); - $footerArgs[3] = wfMsg ('allowemail'); - $this->text = $this->text . "\n" . wfMsgExt( 'emailuserfooter', 'parsemag', $footerArgs ); - - if( wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$this->text ) ) ) { - - if( $wgUserEmailUseReplyTo ) { - // Put the generic wiki autogenerated address in the From: - // header and reserve the user for Reply-To. - // - // This is a bit ugly, but will serve to differentiate - // wiki-borne mails from direct mails and protects against - // SPF and bounce problems with some mailers (see below). - global $wgPasswordSender; - $mailFrom = new MailAddress( $wgPasswordSender ); - $replyTo = $from; - } else { - // Put the sending user's e-mail address in the From: header. - // - // This is clean-looking and convenient, but has issues. - // One is that it doesn't as clearly differentiate the wiki mail - // from "directly" sent mails. - // - // Another is that some mailers (like sSMTP) will use the From - // address as the envelope sender as well. For open sites this - // can cause mails to be flunked for SPF violations (since the - // wiki server isn't an authorized sender for various users' - // domains) as well as creating a privacy issue as bounces - // containing the recipient's e-mail address may get sent to - // the sending user. - $mailFrom = $from; - $replyTo = null; - } - - $mailResult = UserMailer::send( $to, $mailFrom, $subject, $this->text, $replyTo ); - - if( WikiError::isError( $mailResult ) ) { - return $mailResult; - - } else { - - // if the user requested a copy of this mail, do this now, - // unless they are emailing themselves, in which case one copy of the message is sufficient. - if ($this->cc_me && $to != $from) { - $cc_subject = wfMsg('emailccsubject', $this->target->getName(), $subject); - if( wfRunHooks( 'EmailUser', array( &$from, &$from, &$cc_subject, &$this->text ) ) ) { - $ccResult = UserMailer::send( $from, $from, $cc_subject, $this->text ); - if( WikiError::isError( $ccResult ) ) { - // At this stage, the user's CC mail has failed, but their - // original mail has succeeded. It's unlikely, but still, what to do? - // We can either show them an error, or we can say everything was fine, - // or we can say we sort of failed AND sort of succeeded. Of these options, - // simply saying there was an error is probably best. - return $ccResult; - } - } - } - - wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $this->text ) ); - return; - } - } - } - - function showSuccess( &$user = null ) { - global $wgOut; - - if ( is_null($user) ) - $user = $this->target; - - $wgOut->setPagetitle( wfMsg( "emailsent" ) ); - $wgOut->addHTML( wfMsg( "emailsenttext" ) ); - - $wgOut->returnToMain( false, $user->getUserPage() ); - } - - function getTarget() { - return $this->target; - } - - static function validateEmailTarget ( $target ) { - global $wgEnableEmail, $wgEnableUserEmail; - - if( !( $wgEnableEmail && $wgEnableUserEmail ) ) - return array( "nosuchspecialpage", "nospecialpagetext" ); - - if ( "" == $target ) { - wfDebug( "Target is empty.\n" ); - return array( "notargettitle", "notargettext" ); - } - - $nt = Title::newFromURL( $target ); - if ( is_null( $nt ) ) { - wfDebug( "Target is invalid title.\n" ); - return array( "notargettitle", "notargettext" ); - } - - $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" ); - } - - return $nu; - } - static function getPermissionsError ( $user, $editToken ) { - if( !$user->canSendEmail() ) { - wfDebug( "User can't send.\n" ); - return array( "mailnologin", "mailnologintext" ); - } - - if( $user->isBlockedFromEmailuser() ) { - wfDebug( "User is blocked from sending e-mail.\n" ); - return array( "blockedemailuser", "" ); - } - - if( $user->pingLimiter( 'emailuser' ) ) { - wfDebug( "Ping limiter triggered.\n" ); - return array( 'actionthrottledtext', '' ); - } - - if( !$user->matchEditToken( $editToken ) ) { - wfDebug( "Matching edit token failed.\n" ); - return array( 'sessionfailure', '' ); - } - - return; - } - - static function newFromURL( $target, $text, $subject, $cc_me ) - { - $nt = Title::newFromURL( $target ); - $nu = User::newFromName( $nt->getText() ); - return new EmailUserForm( $nu, $text, $subject, $cc_me ); - } -} diff --git a/includes/specials/Export.php b/includes/specials/Export.php deleted file mode 100644 index 38bfc83e83..0000000000 --- a/includes/specials/Export.php +++ /dev/null @@ -1,284 +0,0 @@ - -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html -/** - * @file - * @ingroup SpecialPage - */ - -function wfExportGetPagesFromCategory( $title ) { - global $wgContLang; - - $name = $title->getDBkey(); - - $dbr = wfGetDB( DB_SLAVE ); - - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $sql = "SELECT page_namespace, page_title FROM $page " . - "JOIN $categorylinks ON cl_from = page_id " . - "WHERE cl_to = " . $dbr->addQuotes( $name ); - - $pages = array(); - $res = $dbr->query( $sql, 'wfExportGetPagesFromCategory' ); - while ( $row = $dbr->fetchObject( $res ) ) { - $n = $row->page_title; - if ($row->page_namespace) { - $ns = $wgContLang->getNsText( $row->page_namespace ); - $n = $ns . ':' . $n; - } - - $pages[] = $n; - } - $dbr->freeResult($res); - - return $pages; -} - -/** - * Expand a list of pages to include templates used in those pages. - * @param $inputPages array, list of titles to look up - * @param $pageSet array, associative array indexed by titles for output - * @return array associative array index by titles - */ -function wfExportGetTemplates( $inputPages, $pageSet ) { - return wfExportGetLinks( $inputPages, $pageSet, - 'templatelinks', - array( 'tl_namespace AS namespace', 'tl_title AS title' ), - array( 'page_id=tl_from' ) ); -} - -/** - * Expand a list of pages to include images used in those pages. - * @param $inputPages array, list of titles to look up - * @param $pageSet array, associative array indexed by titles for output - * @return array associative array index by titles - */ -function wfExportGetImages( $inputPages, $pageSet ) { - return wfExportGetLinks( $inputPages, $pageSet, - 'imagelinks', - array( NS_IMAGE . ' AS namespace', 'il_to AS title' ), - array( 'page_id=il_from' ) ); -} - -/** - * Expand a list of pages to include items used in those pages. - * @private - */ -function wfExportGetLinks( $inputPages, $pageSet, $table, $fields, $join ) { - $dbr = wfGetDB( DB_SLAVE ); - foreach( $inputPages as $page ) { - $title = Title::newFromText( $page ); - if( $title ) { - $pageSet[$title->getPrefixedText()] = true; - /// @fixme May or may not be more efficient to batch these - /// by namespace when given multiple input pages. - $result = $dbr->select( - array( 'page', $table ), - $fields, - array_merge( $join, - array( - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDbKey() ) ), - __METHOD__ ); - foreach( $result as $row ) { - $template = Title::makeTitle( $row->namespace, $row->title ); - $pageSet[$template->getPrefixedText()] = true; - } - } - } - return $pageSet; -} - -/** - * Callback function to remove empty strings from the pages array. - */ -function wfFilterPage( $page ) { - return $page !== '' && $page !== null; -} - -/** - * - */ -function wfSpecialExport( $page = '' ) { - global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors; - global $wgExportAllowHistory, $wgExportMaxHistory; - - $curonly = true; - $doexport = false; - - if ( $wgRequest->getCheck( 'addcat' ) ) { - $page = $wgRequest->getText( 'pages' ); - $catname = $wgRequest->getText( 'catname' ); - - if ( $catname !== '' && $catname !== NULL && $catname !== false ) { - $t = Title::makeTitleSafe( NS_CATEGORY, $catname ); - if ( $t ) { - /** - * @fixme This can lead to hitting memory limit for very large - * categories. Ideally we would do the lookup synchronously - * during the export in a single query. - */ - $catpages = wfExportGetPagesFromCategory( $t ); - if ( $catpages ) $page .= "\n" . implode( "\n", $catpages ); - } - } - } - else if( $wgRequest->wasPosted() && $page == '' ) { - $page = $wgRequest->getText( 'pages' ); - $curonly = $wgRequest->getCheck( 'curonly' ); - $rawOffset = $wgRequest->getVal( 'offset' ); - if( $rawOffset ) { - $offset = wfTimestamp( TS_MW, $rawOffset ); - } else { - $offset = null; - } - $limit = $wgRequest->getInt( 'limit' ); - $dir = $wgRequest->getVal( 'dir' ); - $history = array( - 'dir' => 'asc', - 'offset' => false, - 'limit' => $wgExportMaxHistory, - ); - $historyCheck = $wgRequest->getCheck( 'history' ); - if ( $curonly ) { - $history = WikiExporter::CURRENT; - } elseif ( !$historyCheck ) { - if ( $limit > 0 && $limit < $wgExportMaxHistory ) { - $history['limit'] = $limit; - } - if ( !is_null( $offset ) ) { - $history['offset'] = $offset; - } - if ( strtolower( $dir ) == 'desc' ) { - $history['dir'] = 'desc'; - } - } - - if( $page != '' ) $doexport = true; - } else { - // Default to current-only for GET requests - $page = $wgRequest->getText( 'pages', $page ); - $historyCheck = $wgRequest->getCheck( 'history' ); - if( $historyCheck ) { - $history = WikiExporter::FULL; - } else { - $history = WikiExporter::CURRENT; - } - - if( $page != '' ) $doexport = true; - } - - if( !$wgExportAllowHistory ) { - // Override - $history = WikiExporter::CURRENT; - } - - $list_authors = $wgRequest->getCheck( 'listauthors' ); - if ( !$curonly || !$wgExportAllowListContributors ) $list_authors = false ; - - if ( $doexport ) { - $wgOut->disable(); - - // Cancel output buffering and gzipping if set - // This should provide safer streaming for pages with history - wfResetOutputBuffers(); - header( "Content-type: application/xml; charset=utf-8" ); - if( $wgRequest->getCheck( 'wpDownload' ) ) { - // Provide a sane filename suggestion - $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); - $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" ); - } - - /* Split up the input and look up linked pages */ - $inputPages = array_filter( explode( "\n", $page ), 'wfFilterPage' ); - $pageSet = array_flip( $inputPages ); - - if( $wgRequest->getCheck( 'templates' ) ) { - $pageSet = wfExportGetTemplates( $inputPages, $pageSet ); - } - - /* - // Enable this when we can do something useful exporting/importing image information. :) - if( $wgRequest->getCheck( 'images' ) ) { - $pageSet = wfExportGetImages( $inputPages, $pageSet ); - } - */ - - $pages = array_keys( $pageSet ); - - /* Ok, let's get to it... */ - - $db = wfGetDB( DB_SLAVE ); - $exporter = new WikiExporter( $db, $history ); - $exporter->list_authors = $list_authors ; - $exporter->openStream(); - - foreach( $pages as $page ) { - /* - if( $wgExportMaxHistory && !$curonly ) { - $title = Title::newFromText( $page ); - if( $title ) { - $count = Revision::countByTitle( $db, $title ); - if( $count > $wgExportMaxHistory ) { - wfDebug( __FUNCTION__ . - ": Skipped $page, $count revisions too big\n" ); - continue; - } - } - }*/ - - #Bug 8824: Only export pages the user can read - $title = Title::newFromText( $page ); - if( is_null( $title ) ) continue; #TODO: perhaps output an tag or something. - if( !$title->userCanRead() ) continue; #TODO: perhaps output an tag or something. - - $exporter->pageByTitle( $title ); - } - - $exporter->closeStream(); - return; - } - - $self = SpecialPage::getTitleFor( 'Export' ); - $wgOut->addHtml( wfMsgExt( 'exporttext', 'parse' ) ); - - $form = Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $self->getLocalUrl( 'action=submit' ) ) ); - - $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . ' '; - $form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '
    '; - - $form .= Xml::openElement( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ) ); - $form .= htmlspecialchars( $page ); - $form .= Xml::closeElement( 'textarea' ); - $form .= '
    '; - - if( $wgExportAllowHistory ) { - $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '
    '; - } else { - $wgOut->addHtml( wfMsgExt( 'exportnohistory', 'parse' ) ); - } - $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '
    '; - // Enable this when we can do something useful exporting/importing image information. :) - //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '
    '; - $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '
    '; - - $form .= Xml::submitButton( wfMsg( 'export-submit' ) ); - $form .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $form ); -} diff --git a/includes/specials/Fewestrevisions.php b/includes/specials/Fewestrevisions.php deleted file mode 100644 index 5ad8136939..0000000000 --- a/includes/specials/Fewestrevisions.php +++ /dev/null @@ -1,66 +0,0 @@ -tableNamesN( 'revision', 'page' ); - - return "SELECT 'Fewestrevisions' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $revision - JOIN $page ON page_id = rev_page - WHERE page_namespace = " . NS_MAIN . " - GROUP BY 1,2,3 - HAVING COUNT(*) > 1"; - } - - function sortDescending() { - return false; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitleSafe( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->makeKnownLinkObj( $nt, $text ); - - $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ); - - return wfSpecialList( $plink, $nlink ); - } -} - -function wfSpecialFewestrevisions() { - list( $limit, $offset ) = wfCheckLimits(); - $frp = new FewestrevisionsPage(); - $frp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/FileDuplicateSearch.php b/includes/specials/FileDuplicateSearch.php deleted file mode 100644 index 5236ca2519..0000000000 --- a/includes/specials/FileDuplicateSearch.php +++ /dev/null @@ -1,135 +0,0 @@ -hash = $hash; - $this->filename = $filename; - } - - function getName() { return 'FileDuplicateSearch'; } - function isExpensive() { return false; } - function isSyndicated() { return false; } - - function linkParameters() { - return array( 'filename' => $this->filename ); - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $hash = $dbr->addQuotes( $this->hash ); - - return "SELECT 'FileDuplicateSearch' AS type, - img_name AS title, - img_sha1 AS value, - img_user_text, - img_timestamp - FROM $image - WHERE img_sha1 = $hash - "; - } - - function formatResult( $skin, $result ) { - global $wgContLang, $wgLang; - - $nt = Title::makeTitle( NS_IMAGE, $result->title ); - $text = $wgContLang->convert( $nt->getText() ); - $plink = $skin->makeLink( $nt->getPrefixedText(), $text ); - - $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text ); - $time = $wgLang->timeanddate( $result->img_timestamp ); - - return "$plink . . $user . . $time"; - } -} - -/** - * Output the HTML search form, and constructs the FileDuplicateSearch object. - */ -function wfSpecialFileDuplicateSearch( $par = null ) { - global $wgRequest, $wgTitle, $wgOut, $wgLang, $wgContLang; - - $hash = ''; - $filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' ); - - $title = Title::newFromText( $filename ); - if( $title && $title->getText() != '' ) { - $dbr = wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDbKey() ) ); - $sql = "SELECT img_sha1 from $image where img_name = $encFilename"; - $res = $dbr->query( $sql ); - $row = $dbr->fetchRow( $res ); - if( $row !== false ) { - $hash = $row[0]; - } - $dbr->freeResult( $res ); - } - - # Create the input form - $wgOut->addHTML( - Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgTitle->getLocalUrl() ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) . - Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $filename ) . ' ' . - Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - - if( $hash != '' ) { - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - # Show a thumbnail of the file - $img = wfFindFile( $title ); - if ( $img ) { - $thumb = $img->getThumbnail( 120, 120 ); - if( $thumb ) { - $wgOut->addHTML( '
    ' . - $thumb->toHtml( array( 'desc-link' => false ) ) . '
    ' . - wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ), - $wgLang->formatNum( $img->getWidth() ), - $wgLang->formatNum( $img->getHeight() ), - $wgLang->formatSize( $img->getSize() ), - $img->getMimeType() - ) . - '
    ' ); - } - } - - # Do the query - $wpp = new FileDuplicateSearchPage( $hash, $filename ); - list( $limit, $offset ) = wfCheckLimits(); - $count = $wpp->doQuery( $offset, $limit ); - - # Show a short summary - if( $count == 1 ) { - $wgOut->addHTML( '

    ' . - wfMsgHtml( 'fileduplicatesearch-result-1', $filename ) . - '

    ' - ); - } elseif ( $count > 1 ) { - $wgOut->addHTML( '

    ' . - wfMsgExt( 'fileduplicatesearch-result-n', array( 'parseinline' ), $filename, $wgLang->formatNum( $count - 1 ) ) . - '

    ' - ); - } - } -} diff --git a/includes/specials/Filepath.php b/includes/specials/Filepath.php deleted file mode 100644 index a2ba3e57a1..0000000000 --- a/includes/specials/Filepath.php +++ /dev/null @@ -1,53 +0,0 @@ -getText( 'file' ); - - $title = Title::newFromText( $file, NS_IMAGE ); - - if ( ! $title instanceof Title || $title->getNamespace() != NS_IMAGE ) { - $cform = new FilepathForm( $title ); - $cform->execute(); - } else { - $file = wfFindFile( $title ); - if ( $file && $file->exists() ) { - $wgOut->redirect( $file->getURL() ); - } else { - $wgOut->setStatusCode( 404 ); - $cform = new FilepathForm( $title ); - $cform->execute(); - } - } -} - -/** - * @ingroup SpecialPage - */ -class FilepathForm { - var $mTitle; - - function FilepathForm( &$title ) { - $this->mTitle =& $title; - } - - function execute() { - global $wgOut, $wgTitle, $wgScript; - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'filepath' ) ) . - Xml::hidden( 'title', $wgTitle->getPrefixedText() ) . - Xml::inputLabel( wfMsg( 'filepath-page' ), 'file', 'file', 25, is_object( $this->mTitle ) ? $this->mTitle->getText() : '' ) . ' ' . - Xml::submitButton( wfMsg( 'filepath-submit' ) ) . "\n" . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - } -} diff --git a/includes/specials/Imagelist.php b/includes/specials/Imagelist.php deleted file mode 100644 index 3d449b5484..0000000000 --- a/includes/specials/Imagelist.php +++ /dev/null @@ -1,161 +0,0 @@ -getForm(); - $body = $pager->getBody(); - $nav = $pager->getNavigationBar(); - $wgOut->addHTML( "$limit
    \n$body
    \n$nav" ); -} - -/** - * @ingroup SpecialPage Pager - */ -class ImageListPager extends TablePager { - var $mFieldNames = null; - var $mQueryConds = array(); - - function __construct() { - global $wgRequest, $wgMiserMode; - if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) { - $this->mDefaultDirection = true; - } else { - $this->mDefaultDirection = false; - } - $search = $wgRequest->getText( 'ilsearch' ); - if ( $search != '' && !$wgMiserMode ) { - $nt = Title::newFromUrl( $search ); - if( $nt ) { - $dbr = wfGetDB( DB_SLAVE ); - $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); - $m = str_replace( "%", "\\%", $m ); - $m = str_replace( "_", "\\_", $m ); - $this->mQueryConds = array( "LOWER(img_name) LIKE '%{$m}%'" ); - } - } - - parent::__construct(); - } - - function getFieldNames() { - if ( !$this->mFieldNames ) { - $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' ), - ); - } - return $this->mFieldNames; - } - - function isFieldSortable( $field ) { - static $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); - return in_array( $field, $sortable ); - } - - function getQueryInfo() { - $fields = $this->getFieldNames(); - $fields = array_keys( $fields ); - $fields[] = 'img_user'; - return array( - 'tables' => 'image', - 'fields' => $fields, - 'conds' => $this->mQueryConds - ); - } - - function getDefaultSort() { - return 'img_timestamp'; - } - - function getStartBody() { - # Do a link batch query for user pages - if ( $this->mResult->numRows() ) { - $lb = new LinkBatch; - $this->mResult->seek( 0 ); - while ( $row = $this->mResult->fetchObject() ) { - if ( $row->img_user ) { - $lb->add( NS_USER, str_replace( ' ', '_', $row->img_user_text ) ); - } - } - $lb->execute(); - } - - return parent::getStartBody(); - } - - function formatValue( $field, $value ) { - global $wgLang; - switch ( $field ) { - case 'img_timestamp': - return $wgLang->timeanddate( $value, true ); - case 'img_name': - static $imgfile = null; - if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' ); - - $name = $this->mCurrentRow->img_name; - $link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), $value ); - $image = wfLocalFile( $value ); - $url = $image->getURL(); - $download = Xml::element('a', array( 'href' => $url ), $imgfile ); - return "$link ($download)"; - case 'img_user_text': - if ( $this->mCurrentRow->img_user ) { - $link = $this->getSkin()->makeLinkObj( Title::makeTitle( NS_USER, $value ), - htmlspecialchars( $value ) ); - } else { - $link = htmlspecialchars( $value ); - } - return $link; - case 'img_size': - return $this->getSkin()->formatSize( $value ); - case 'img_description': - return $this->getSkin()->commentBlock( $value ); - } - } - - function getForm() { - global $wgRequest, $wgMiserMode; - $search = $wgRequest->getText( 'ilsearch' ); - - $s = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getTitle()->getLocalURL(), 'id' => 'mw-imagelist-form' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'imagelist' ) ) . - Xml::tags( 'label', null, wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) ); - - if ( !$wgMiserMode ) { - $s .= "
    \n" . - Xml::inputLabel( wfMsg( 'imagelist_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search ); - } - $s .= ' ' . - Xml::submitButton( wfMsg( 'table_pager_limit_submit' ) ) ."\n" . - $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n"; - return $s; - } - - function getTableClass() { - return 'imagelist ' . parent::getTableClass(); - } - - function getNavClass() { - return 'imagelist_nav ' . parent::getNavClass(); - } - - function getSortHeaderClass() { - return 'imagelist_sort ' . parent::getSortHeaderClass(); - } -} diff --git a/includes/specials/Import.php b/includes/specials/Import.php deleted file mode 100644 index 4c37f1f9ea..0000000000 --- a/includes/specials/Import.php +++ /dev/null @@ -1,1154 +0,0 @@ - - * http://www.mediawiki.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @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; - } - - if( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit') { - $isUpload = false; - $namespace = $wgRequest->getIntOrNull( 'namespace' ); - - switch( $wgRequest->getVal( "source" ) ) { - case "upload": - $isUpload = true; - if( $wgUser->isAllowed( 'importupload' ) ) { - $source = ImportStreamSource::newFromUpload( "xmlimport" ); - } else { - return $wgOut->permissionRequired( 'importupload' ); - } - break; - case "interwiki": - $interwiki = $wgRequest->getVal( 'interwiki' ); - $history = $wgRequest->getCheck( 'interwikiHistory' ); - $frompage = $wgRequest->getText( "frompage" ); - $source = ImportStreamSource::newFromInterwiki( - $interwiki, - $frompage, - $history ); - break; - default: - $source = new WikiErrorMsg( "importunknownsource" ); - } - - if( WikiError::isError( $source ) ) { - $wgOut->wrapWikiMsg( '

    $1

    ', array( 'importfailed', $source->getMessage() ) ); - } else { - $wgOut->addWikiMsg( "importstart" ); - - $importer = new WikiImporter( $source ); - if( !is_null( $namespace ) ) { - $importer->setTargetNamespace( $namespace ); - } - $reporter = new ImportReporter( $importer, $isUpload, $interwiki ); - - $reporter->open(); - $result = $importer->doImport(); - $resultCount = $reporter->close(); - - if( WikiError::isError( $result ) ) { - # No source or XML parse error - $wgOut->wrapWikiMsg( '

    $1

    ', array( 'importfailed', $result->getMessage() ) ); - } elseif( WikiError::isError( $resultCount ) ) { - # Zero revisions - $wgOut->wrapWikiMsg( '

    $1

    ', array( 'importfailed', $resultCount->getMessage() ) ); - } else { - # Success! - $wgOut->addWikiMsg( 'importsuccess' ); - } - $wgOut->addWikiText( '
    ' ); - } - } - - $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::submitButton( wfMsg( 'uploadbtn' ) ) . - 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::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . - " - " . - Xml::openElement( 'select', array( 'name' => 'interwiki' ) ) - ); - foreach( $wgImportSources as $prefix ) { - $selected = ( $interwiki === $prefix ) ? ' selected="selected"' : ''; - $wgOut->addHTML( Xml::option( $prefix, $prefix, $selected ) ); - } - $wgOut->addHTML( - Xml::closeElement( 'select' ) . - " - " . - Xml::input( 'frompage', 50, $frompage ) . - " - - - - - " . - Xml::checkLabel( wfMsg( 'import-interwiki-history' ), 'interwikiHistory', 'interwikiHistory', $history ) . - " - - - - - " . - Xml::label( wfMsg( 'import-interwiki-namespace' ), 'namespace' ) . - Xml::namespaceSelector( $namespace, '' ) . - " - - - - - " . - Xml::submitButton( wfMsg( 'import-interwiki-submit' ) ) . - " - " . - Xml::closeElement( 'table' ). - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) - ); - } -} - -/** - * Reporting callback - * @ingroup SpecialPage - */ -class ImportReporter { - function __construct( $importer, $upload, $interwiki ) { - $importer->setPageOutCallback( array( $this, 'reportPage' ) ); - $this->mPageCount = 0; - $this->mIsUpload = $upload; - $this->mInterwiki = $interwiki; - } - - function open() { - global $wgOut; - $wgOut->addHtml( "
      \n" ); - } - - function reportPage( $title, $origTitle, $revisionCount, $successCount ) { - global $wgOut, $wgUser, $wgLang, $wgContLang; - - $skin = $wgUser->getSkin(); - - $this->mPageCount++; - - $localCount = $wgLang->formatNum( $successCount ); - $contentCount = $wgContLang->formatNum( $successCount ); - - if( $successCount > 0 ) { - $wgOut->addHtml( "
    • " . $skin->makeKnownLinkObj( $title ) . " " . - wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) . - "
    • \n" - ); - - $log = new LogPage( 'import' ); - if( $this->mIsUpload ) { - $detail = wfMsgExt( 'import-logentry-upload-detail', array( 'content', 'parsemag' ), - $contentCount ); - $log->addEntry( 'upload', $title, $detail ); - } else { - $interwiki = '[[:' . $this->mInterwiki . ':' . - $origTitle->getPrefixedText() . ']]'; - $detail = wfMsgExt( 'import-logentry-interwiki-detail', array( 'content', 'parsemag' ), - $contentCount, $interwiki ); - $log->addEntry( 'interwiki', $title, $detail ); - } - - $comment = $detail; // quick - $dbw = wfGetDB( DB_MASTER ); - $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) ); - } else { - $wgOut->addHtml( '
    • ' . wfMsgHtml( 'import-nonewrevisions' ) . '
    • ' ); - } - } - - function close() { - global $wgOut; - if( $this->mPageCount == 0 ) { - $wgOut->addHtml( "
    \n" ); - return new WikiErrorMsg( "importnopages" ); - } - $wgOut->addHtml( "\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( "
  • " . htmlspecialchars( $data ) . "
  • \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: " ); - } - $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 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 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 , 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 , got <$name>" ); - } - } - function out_mediawiki( $parser, $name ) { - $this->debug( "out_mediawiki $name" ); - if( $name != "mediawiki" ) { - return $this->throwXMLerror( "Expected , got " ); - } - 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 ." ); - } - } - - 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 ." ); - } - } - - function out_page( $parser, $name ) { - $this->debug( "out_page $name" ); - $this->pop(); - if( $name != "page" ) { - return $this->throwXMLerror( "Expected , got " ); - } - 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 appendfield}>, got " ); - } - - 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 ." ); - } - } - - function out_revision( $parser, $name ) { - $this->debug( "out_revision $name" ); - $this->pop(); - if( $name != "revision" ) { - return $this->throwXMLerror( "Expected , got " ); - } - 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 ." ); - } - } - - function out_upload( $parser, $name ) { - $this->debug( "out_revision $name" ); - $this->pop(); - if( $name != "upload" ) { - return $this->throwXMLerror( "Expected , got " ); - } - 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 " ); - } - } - - function out_contributor( $parser, $name ) { - $this->debug( "out_contributor $name" ); - $this->pop(); - if( $name != "contributor" ) { - return $this->throwXMLerror( "Expected , got " ); - } - $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/Ipblocklist.php b/includes/specials/Ipblocklist.php deleted file mode 100644 index 696c7efe84..0000000000 --- a/includes/specials/Ipblocklist.php +++ /dev/null @@ -1,427 +0,0 @@ -getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) ); - $id = $wgRequest->getVal( 'id' ); - $reason = $wgRequest->getText( 'wpUnblockReason' ); - $action = $wgRequest->getText( 'action' ); - $successip = $wgRequest->getVal( 'successip' ); - - $ipu = new IPUnblockForm( $ip, $id, $reason ); - - if( $action == 'unblock' ) { - # Check permissions - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - # Check for database lock - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Show unblock form - $ipu->showForm( '' ); - } elseif( $action == 'submit' && $wgRequest->wasPosted() - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - # Check permissions - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - # Check for database lock - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Remove blocks and redirect user to success page - $ipu->doSubmit(); - } elseif( $action == 'success' ) { - # Inform the user of a successful unblock - # (No need to check permissions or locks here, - # if something was done, then it's too late!) - if ( substr( $successip, 0, 1) == '#' ) { - // A block ID was unblocked - $ipu->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) ); - } else { - // A username/IP was unblocked - $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) ); - } - } else { - # Just show the block list - $ipu->showList( '' ); - } - -} - -/** - * implements Special:ipblocklist GUI - * @ingroup SpecialPage - */ -class IPUnblockForm { - var $ip, $reason, $id; - - function IPUnblockForm( $ip, $id, $reason ) { - $this->ip = strtr( $ip, '_', ' ' ); - $this->id = $id; - $this->reason = $reason; - } - - /** - * Generates the unblock form - * @param $err string: error message - * @return $out string: HTML form - */ - function showForm( $err ) { - global $wgOut, $wgUser, $wgSysopUserBans; - - $wgOut->setPagetitle( wfMsg( 'unblockip' ) ); - $wgOut->addWikiMsg( 'unblockiptext' ); - - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $action = $titleObj->getLocalURL( "action=submit" ); - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsg( "formerror" ) ); - $wgOut->addWikiText( Xml::tags( 'span', array( 'class' => 'error' ), $err ) . "\n" ); - } - - $addressPart = false; - if ( $this->id ) { - $block = Block::newFromID( $this->id ); - if ( $block ) { - $encName = htmlspecialchars( $block->getRedactedName() ); - $encId = $this->id; - $addressPart = $encName . Xml::hidden( 'id', $encId ); - $ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ); - } - } - if ( !$addressPart ) { - $addressPart = Xml::input( 'wpUnblockAddress', 40, $this->ip, array( 'type' => 'text', 'tabindex' => '1' ) ); - $ipa = Xml::label( wfMsg( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ), 'wpUnblockAddress' ); - } - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'ipb-unblock' ) ) . - Xml::openElement( 'table', array( 'id' => 'mw-unblock-table' ) ). - " - - {$ipa} - - - {$addressPart} - - - - " . - Xml::label( wfMsg( 'ipbreason' ), 'wpUnblockReason' ) . - " - " . - Xml::input( 'wpUnblockReason', 40, $this->reason, array( 'type' => 'text', 'tabindex' => '2' ) ) . - " - - -   - " . - Xml::submitButton( wfMsg( 'ipusubmit' ), array( 'name' => 'wpBlock', 'tabindex' => '3' ) ) . - " - " . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ) . - Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . - Xml::closeElement( 'form' ) . "\n" - ); - - } - - const UNBLOCK_SUCCESS = 0; // Success - const UNBLOCK_NO_SUCH_ID = 1; // No such block ID - const UNBLOCK_USER_NOT_BLOCKED = 2; // IP wasn't blocked - const UNBLOCK_BLOCKED_AS_RANGE = 3; // IP is part of a range block - const UNBLOCK_UNKNOWNERR = 4; // Unknown error - - /** - * Backend code for unblocking. doSubmit() wraps around this. - * $range is only used when UNBLOCK_BLOCKED_AS_RANGE is returned, in which - * case it contains the range $ip is part of. - * @return array array(message key, parameters) on failure, empty array on success - */ - - static function doUnblock(&$id, &$ip, &$reason, &$range = null) - { - if ( $id ) { - $block = Block::newFromID( $id ); - if ( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - $ip = $block->getRedactedName(); - } else { - $block = new Block(); - $ip = trim( $ip ); - if ( substr( $ip, 0, 1 ) == "#" ) { - $id = substr( $ip, 1 ); - $block = Block::newFromID( $id ); - if( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - $ip = $block->getRedactedName(); - } else { - $block = Block::newFromDB( $ip ); - if ( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - if( $block->mRangeStart != $block->mRangeEnd - && !strstr( $ip, "/" ) ) { - /* If the specified IP is a single address, and the block is - * a range block, don't unblock the range. */ - $range = $block->mAddress; - return array('ipb_blocked_as_range', $ip, $range); - } - } - } - // Yes, this is really necessary - $id = $block->mId; - - # Delete block - if ( !$block->delete() ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - - # Make log entry - $log = new LogPage( 'block' ); - $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $ip ), $reason ); - return array(); - } - - function doSubmit() { - global $wgOut; - $retval = self::doUnblock($this->id, $this->ip, $this->reason, $range); - if(!empty($retval)) - { - $key = array_shift($retval); - $this->showForm(wfMsgReal($key, $retval)); - return; - } - # Report to the user - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) ); - $wgOut->redirect( $success ); - } - - function showList( $msg ) { - global $wgOut, $wgUser; - - $wgOut->setPagetitle( wfMsg( "ipblocklist" ) ); - if ( "" != $msg ) { - $wgOut->setSubtitle( $msg ); - } - - // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { - Block::purgeExpired(); - } - - $conds = array(); - $matches = array(); - // Is user allowed to see all the blocks? - if ( !$wgUser->isAllowed( 'suppress' ) ) - $conds['ipb_deleted'] = 0; - if ( $this->ip == '' ) { - // 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; - $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 ) ) { - $conds['ipb_address'] = Block::normaliseRange( $this->ip ); - $conds['ipb_auto'] = 0; - } else { - $user = User::newFromName( $this->ip ); - if ( $user && ( $id = $user->getId() ) != 0 ) { - $conds['ipb_user'] = $id; - } else { - // Uh...? - $conds['ipb_address'] = $this->ip; - $conds['ipb_auto'] = 0; - } - } - - $pager = new IPBlocklistPager( $this, $conds ); - if ( $pager->getNumRows() ) { - $wgOut->addHTML( - $this->searchForm() . - $pager->getNavigationBar() . - Xml::tags( 'ul', null, $pager->getBody() ) . - $pager->getNavigationBar() - ); - } elseif ( $this->ip != '') { - $wgOut->addHTML( $this->searchForm() ); - $wgOut->addWikiMsg( 'ipblocklist-no-results' ); - } else { - $wgOut->addWikiMsg( 'ipblocklist-empty' ); - } - } - - function searchForm() { - global $wgTitle, $wgScript, $wgRequest; - return - Xml::tags( 'form', array( 'action' => $wgScript ), - Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) . - Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) . - ' ' . - Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . - Xml::closeElement( 'fieldset' ) - ); - } - - /** - * Callback function to output a block - */ - function formatRow( $block ) { - global $wgUser, $wgLang; - - wfProfileIn( __METHOD__ ); - - static $sk=null, $msg=null; - - if( is_null( $sk ) ) - $sk = $wgUser->getSkin(); - if( is_null( $msg ) ) { - $msg = array(); - $keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', - 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock' ); - foreach( $keys as $key ) { - $msg[$key] = wfMsgHtml( $key ); - } - $msg['blocklistline'] = wfMsg( 'blocklistline' ); - } - - # Prepare links to the blocker's user and talk pages - $blocker_id = $block->getBy(); - $blocker_name = $block->getByName(); - $blocker = $sk->userLink( $blocker_id, $blocker_name ); - $blocker .= $sk->userToolLinks( $blocker_id, $blocker_name ); - - # Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks) - if( $block->mAuto ) { - $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy - } else { - $target = $sk->userLink( $block->mUser, $block->mAddress ) - . $sk->userToolLinks( $block->mUser, $block->mAddress, false, Linker::TOOL_LINKS_NOBLOCK ); - } - - $formattedTime = $wgLang->timeanddate( $block->mTimestamp, true ); - - $properties = array(); - $properties[] = Block::formatExpiry( $block->mExpiry ); - if ( $block->mAnonOnly ) { - $properties[] = $msg['anononlyblock']; - } - if ( $block->mCreateAccount ) { - $properties[] = $msg['createaccountblock']; - } - if (!$block->mEnableAutoblock && $block->mUser ) { - $properties[] = $msg['noautoblockblock']; - } - - if ( $block->mBlockEmail && $block->mUser ) { - $properties[] = $msg['emailblock']; - } - - $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 ) ) . ')'; - } - - $comment = $sk->commentBlock( $block->mReason ); - - $s = "{$line} $comment"; - if ( $block->mHideName ) - $s = '' . $s . ''; - - wfProfileOut( __METHOD__ ); - return "
  • $s $unblocklink
  • \n"; - } -} - -/** - * @todo document - * @ingroup Pager - */ -class IPBlocklistPager extends ReverseChronologicalPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array() ) { - $this->mForm = $form; - $this->mConds = $conds; - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $lb = new LinkBatch; - - /* - while ( $row = $this->mResult->fetchObject() ) { - $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) ); - }*/ - # 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. - while ( $row = $this->mResult->fetchObject() ) { - $name = str_replace( ' ', '_', $row->ipb_by_text ); - $lb->add( NS_USER, $name ); - $lb->add( NS_USER_TALK, $name ); - $name = str_replace( ' ', '_', $row->ipb_address ); - $lb->add( NS_USER, $name ); - $lb->add( NS_USER_TALK, $name ); - } - $lb->execute(); - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - $block = new Block; - $block->initFromRow( $row ); - return $this->mForm->formatRow( $block ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - return array( - 'tables' => 'ipblocks', - 'fields' => '*', - 'conds' => $conds, - ); - } - - function getIndexField() { - return 'ipb_timestamp'; - } -} diff --git a/includes/specials/Listgrouprights.php b/includes/specials/Listgrouprights.php deleted file mode 100644 index 131c06061a..0000000000 --- a/includes/specials/Listgrouprights.php +++ /dev/null @@ -1,112 +0,0 @@ - - */ -class SpecialListGroupRights extends SpecialPage { - - var $skin; - - /** - * Constructor - */ - function __construct() { - global $wgUser; - parent::__construct( 'Listgrouprights' ); - $this->skin = $wgUser->getSkin(); - } - - /** - * Show the special page - */ - public function execute( $par ) { - global $wgOut, $wgGroupPermissions, $wgImplicitGroups, $wgMessageCache; - $wgMessageCache->loadAllMessages(); - - $this->setHeaders(); - $this->outputHeader(); - - $wgOut->addHTML( - Xml::openElement( 'table', array( 'class' => 'mw-listgrouprights-table' ) ) . - '' . - Xml::element( 'th', null, wfMsg( 'listgrouprights-group' ) ) . - Xml::element( 'th', null, wfMsg( 'listgrouprights-rights' ) ) . - '' - ); - - foreach( $wgGroupPermissions as $group => $permissions ) { - $groupname = ( $group == '*' ) ? 'all' : htmlspecialchars( $group ); // Replace * with a more descriptive groupname - - $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; - } - - if( $group == '*' ) { - // Do not make a link for the generic * group - $grouppage = $groupnameLocalized; - } else { - $grouppage = $this->skin->makeLink( $grouppageLocalized, $groupnameLocalized ); - } - - if ( !in_array( $group, $wgImplicitGroups ) ) { - $grouplink = '
    ' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), 'group=' . $group ); - } else { - // No link to Special:listusers for implicit groups as they are unlistable - $grouplink = ''; - } - - $wgOut->addHTML( - ' - ' . - $grouppage . $grouplink . - ' - ' . - self::formatPermissions( $permissions ) . - ' - ' - ); - } - $wgOut->addHTML( - Xml::closeElement( 'table' ) . "\n" - ); - } - - /** - * Create a user-readable list of permissions from the given array. - * - * @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 ) { - $r = array(); - foreach( $permissions as $permission => $granted ) { - if ( $granted ) { - $description = wfMsgHTML( 'listgrouprights-right-display', - User::getRightDescription($permission), - $permission - ); - $r[] = $description; - } - } - sort( $r ); - if( empty( $r ) ) { - return ''; - } else { - return '
    • ' . implode( "
    • \n
    • ", $r ) . '
    '; - } - } -} diff --git a/includes/specials/Listredirects.php b/includes/specials/Listredirects.php deleted file mode 100644 index 808aab14bb..0000000000 --- a/includes/specials/Listredirects.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * Special:Listredirects - Lists all the redirects on the wiki. - * @ingroup SpecialPage - */ -class ListredirectsPage extends QueryPage { - - function getName() { return( 'Listredirects' ); } - function isExpensive() { return( true ); } - function isSyndicated() { return( false ); } - function sortDescending() { return( false ); } - - 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"; - return( $sql ); - } - - function formatResult( $skin, $result ) { - global $wgContLang; - - # Make a link to the redirect itself - $rd_title = Title::makeTitle( $result->namespace, $result->title ); - $rd_link = $skin->makeLinkObj( $rd_title, '', 'redirect=no' ); - - # Find out where the redirect leads - $revision = Revision::newFromTitle( $rd_title ); - if( $revision ) { - # Make a link to the destination page - $target = Title::newFromRedirect( $revision->getText() ); - if( $target ) { - $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); - $targetLink = $skin->makeLinkObj( $target ); - return "$rd_link $arr $targetLink"; - } else { - return "$rd_link"; - } - } else { - return "$rd_link"; - } - } -} - -function wfSpecialListredirects() { - list( $limit, $offset ) = wfCheckLimits(); - $lrp = new ListredirectsPage(); - $lrp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Listusers.php b/includes/specials/Listusers.php deleted file mode 100644 index 7dba44e23a..0000000000 --- a/includes/specials/Listusers.php +++ /dev/null @@ -1,235 +0,0 @@ - -# -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html -/** - * @file - * @ingroup SpecialPage - */ - -/** - * This class is used to get a list of user. The ones with specials - * rights (sysop, bureaucrat, developer) will have them displayed - * next to their names. - * - * @ingroup SpecialPage - */ -class UsersPager extends AlphabeticPager { - - function __construct($group=null) { - global $wgRequest; - $this->requestedGroup = $group != "" ? $group : $wgRequest->getVal( 'group' ); - $un = $wgRequest->getText( 'username' ); - $this->requestedUser = ''; - if ( $un != '' ) { - $username = Title::makeTitleSafe( NS_USER, $un ); - if( ! is_null( $username ) ) { - $this->requestedUser = $username->getText(); - } - } - parent::__construct(); - } - - - function getIndexField() { - return 'user_name'; - } - - function getQueryInfo() { - $dbr = wfGetDB( DB_SLAVE ); - $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 = ''; - } else { - $useIndex = $dbr->useIndexClause('user_name'); - } - if ($this->requestedUser != "") { - $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser ); - } - - list ($user,$user_groups,$ipblocks) = $dbr->tableNamesN('user','user_groups','ipblocks'); - - $query = array( - 'tables' => " $user $useIndex LEFT JOIN $user_groups ON user_id=ug_user - LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_auto=0 ", - 'fields' => array('user_name', - 'MAX(user_id) AS user_id', - 'COUNT(ug_group) AS numgroups', - 'MAX(ug_group) AS singlegroup'), - 'options' => array('GROUP BY' => 'user_name'), - 'conds' => $conds - ); - - wfRunHooks( 'SpecialListusersQueryInfo', array( $this, &$query ) ); - return $query; - } - - function formatRow( $row ) { - $userPage = Title::makeTitle( NS_USER, $row->user_name ); - $name = $this->getSkin()->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ); - - if( $row->numgroups > 1 || ( $this->requestedGroup && $row->numgroups == 1 ) ) { - $list = array(); - foreach( self::getGroups( $row->user_id ) as $group ) - $list[] = self::buildGroupLink( $group ); - $groups = implode( ', ', $list ); - } elseif( $row->numgroups == 1 ) { - $groups = self::buildGroupLink( $row->singlegroup ); - } else { - $groups = ''; - } - - $item = wfSpecialList( $name, $groups ); - wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) ); - return "
  • {$item}
  • "; - } - - function getBody() { - if (!$this->mQueryDone) { - $this->doQuery(); - } - $batch = new LinkBatch; - - $this->mResult->rewind(); - - while ( $row = $this->mResult->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); - } - $batch->execute(); - $this->mResult->rewind(); - return parent::getBody(); - } - - function getPageHeader( ) { - global $wgScript, $wgRequest; - $self = $this->getTitle(); - - # Form tag - $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - '
    ' . - Xml::element( 'legend', array(), wfMsg( 'listusers' ) ); - $out .= Xml::hidden( 'title', $self->getPrefixedDbKey() ); - - # Username field - $out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' . - Xml::input( 'username', 20, $this->requestedUser, array( 'id' => 'offset' ) ) . ' '; - - # Group drop-down list - $out .= Xml::label( wfMsg( 'group' ), 'group' ) . ' ' . - Xml::openElement('select', array( 'name' => 'group', 'id' => 'group' ) ) . - Xml::option( wfMsg( 'group-all' ), '' ); - foreach( $this->getAllGroups() as $group => $groupText ) - $out .= Xml::option( $groupText, $group, $group == $this->requestedGroup ); - $out .= Xml::closeElement( 'select' ) . ' '; - - wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) ); - - # Submit button and form bottom - if( $this->mLimit ) - $out .= Xml::hidden( 'limit', $this->mLimit ); - $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); - wfRunHooks( 'SpecialListusersHeader', array( $this, &$out ) ); - $out .= '
    ' . - Xml::closeElement( 'form' ); - - return $out; - } - - function getAllGroups() { - $result = array(); - foreach( User::getAllGroups() as $group ) { - $result[$group] = User::getGroupName( $group ); - } - return $result; - } - - /** - * Preserve group and username offset parameters when paging - * @return array - */ - function getDefaultQuery() { - $query = parent::getDefaultQuery(); - if( $this->requestedGroup != '' ) - $query['group'] = $this->requestedGroup; - if( $this->requestedUser != '' ) - $query['username'] = $this->requestedUser; - wfRunHooks( 'SpecialListusersDefaultQuery', array( $this, &$query ) ); - return $query; - } - - /** - * Get a list of groups the specified user belongs to - * - * @param int $uid - * @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 ); - } - return $groups; - } - - /** - * Format a link to a group description page - * - * @param string $group - * @return string - */ - protected static function buildGroupLink( $group ) { - static $cache = array(); - if( !isset( $cache[$group] ) ) - $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) ); - return $cache[$group]; - } -} - -/** - * constructor - * $par string (optional) A group to list users from - */ -function wfSpecialListusers( $par = null ) { - global $wgRequest, $wgOut; - - $up = new UsersPager($par); - - # getBody() first to check, if empty - $usersbody = $up->getBody(); - $s = $up->getPageHeader(); - if( $usersbody ) { - $s .= $up->getNavigationBar(); - $s .= '
      ' . $usersbody . '
    '; - $s .= $up->getNavigationBar() ; - } else { - $s .= '

    ' . wfMsgHTML('listusers-noresult') . '

    '; - }; - - $wgOut->addHTML( $s ); -} diff --git a/includes/specials/Lockdb.php b/includes/specials/Lockdb.php deleted file mode 100644 index 04019223f1..0000000000 --- a/includes/specials/Lockdb.php +++ /dev/null @@ -1,131 +0,0 @@ -isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); - return; - } - - # If the lock file isn't writable, we can do sweet bugger all - global $wgReadOnlyFile; - if( !is_writable( dirname( $wgReadOnlyFile ) ) ) { - DBLockForm::notWritable(); - return; - } - - $action = $wgRequest->getVal( 'action' ); - $f = new DBLockForm(); - - if ( 'success' == $action ) { - $f->showSuccess(); - } else if ( 'submit' == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( '' ); - } -} - -/** - * A form to make the database readonly (eg for maintenance purposes). - * @ingroup SpecialPage - */ -class DBLockForm { - var $reason = ''; - - function DBLockForm() { - global $wgRequest; - $this->reason = $wgRequest->getText( 'wpLockReason' ); - } - - function showForm( $err ) { - global $wgOut, $wgUser; - - $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); - $wgOut->addWikiMsg( 'lockdbtext' ); - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsg( 'formerror' ) ); - $wgOut->addHTML( '

    ' . htmlspecialchars( $err ) . "

    \n" ); - } - $lc = htmlspecialchars( wfMsg( 'lockconfirm' ) ); - $lb = htmlspecialchars( wfMsg( 'lockbtn' ) ); - $elr = htmlspecialchars( wfMsg( 'enterlockreason' ) ); - $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); - $action = $titleObj->escapeLocalURL( 'action=submit' ); - $reason = htmlspecialchars( $this->reason ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( << -{$elr}: - - - - - - - - - - -
    - - {$lc}
      - -
    - - -END -); - - } - - function doSubmit() { - global $wgOut, $wgUser, $wgLang, $wgRequest; - global $wgReadOnlyFile; - - if ( ! $wgRequest->getCheck( 'wpLockConfirm' ) ) { - $this->showForm( wfMsg( 'locknoconfirm' ) ); - return; - } - $fp = @fopen( $wgReadOnlyFile, 'w' ); - - if ( false === $fp ) { - # This used to show a file not found error, but the likeliest reason for fopen() - # to fail at this point is insufficient permission to write to the file...good old - # is_writable() is plain wrong in some cases, it seems... - self::notWritable(); - return; - } - fwrite( $fp, $this->reason ); - fwrite( $fp, "\n

    (by " . $wgUser->getName() . " at " . - $wgLang->timeanddate( wfTimestampNow() ) . ")\n" ); - fclose( $fp ); - - $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); - $wgOut->redirect( $titleObj->getFullURL( 'action=success' ) ); - } - - function showSuccess() { - global $wgOut; - - $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); - $wgOut->setSubtitle( wfMsg( 'lockdbsuccesssub' ) ); - $wgOut->addWikiMsg( 'lockdbsuccesstext' ); - } - - public static function notWritable() { - global $wgOut; - $wgOut->showErrorPage( 'lockdb', 'lockfilenotwritable' ); - } -} diff --git a/includes/specials/Log.php b/includes/specials/Log.php deleted file mode 100644 index 3154ed1335..0000000000 --- a/includes/specials/Log.php +++ /dev/null @@ -1,65 +0,0 @@ -getVal( 'type', $par ); - $user = $wgRequest->getText( 'user' ); - $title = $wgRequest->getText( 'page' ); - $pattern = $wgRequest->getBool( 'pattern' ); - $y = $wgRequest->getIntOrNull( 'year' ); - $m = $wgRequest->getIntOrNull( 'month' ); - # Don't let the user get stuck with a certain date - $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; - if( $skip ) { - $y = ''; - $m = ''; - } - # 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() ); - # Insert list - $logBody = $pager->getBody(); - if( $logBody ) { - $wgOut->addHTML( - $pager->getNavigationBar() . - $loglist->beginLogEventsList() . - $logBody . - $loglist->endLogEventsList() . - $pager->getNavigationBar() - ); - } else { - $wgOut->addWikiMsg( 'logempty' ); - } -} diff --git a/includes/specials/Lonelypages.php b/includes/specials/Lonelypages.php deleted file mode 100644 index 5aafac7dd5..0000000000 --- a/includes/specials/Lonelypages.php +++ /dev/null @@ -1,58 +0,0 @@ -tableNamesN( 'page', 'pagelinks' ); - - return - "SELECT 'Lonelypages' AS type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $pagelinks - ON page_namespace=pl_namespace AND page_title=pl_title - WHERE pl_namespace IS NULL - AND page_namespace=".NS_MAIN." - AND page_is_redirect=0"; - - } -} - -/** - * Constructor - */ -function wfSpecialLonelypages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new LonelyPagesPage(); - - return $lpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Longpages.php b/includes/specials/Longpages.php deleted file mode 100644 index be16a02942..0000000000 --- a/includes/specials/Longpages.php +++ /dev/null @@ -1,31 +0,0 @@ -doQuery( $offset, $limit ); -} diff --git a/includes/specials/MIMEsearch.php b/includes/specials/MIMEsearch.php deleted file mode 100644 index 82ee4be6e1..0000000000 --- a/includes/specials/MIMEsearch.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * Searches the database for files of the requested MIME type, comparing this with the - * 'img_major_mime' and 'img_minor_mime' fields in the image table. - * @ingroup SpecialPage - */ -class MIMEsearchPage extends QueryPage { - var $major, $minor; - - function MIMEsearchPage( $major, $minor ) { - $this->major = $major; - $this->minor = $minor; - } - - function getName() { return 'MIMEsearch'; } - - /** - * Due to this page relying upon extra fields being passed in the SELECT it - * will fail if it's set as expensive and misermode is on - */ - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function linkParameters() { - $arr = array( $this->major, $this->minor ); - $mime = implode( '/', $arr ); - return array( 'mime' => $mime ); - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $major = $dbr->addQuotes( $this->major ); - $minor = $dbr->addQuotes( $this->minor ); - - return - "SELECT 'MIMEsearch' AS type, - " . NS_IMAGE . " AS namespace, - img_name AS title, - img_major_mime AS value, - - img_size, - img_width, - img_height, - img_user_text, - img_timestamp - FROM $image - WHERE img_major_mime = $major AND img_minor_mime = $minor - "; - } - - function formatResult( $skin, $result ) { - global $wgContLang, $wgLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getText() ); - $plink = $skin->makeLink( $nt->getPrefixedText(), $text ); - - $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) ); - $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->img_size ) ); - $dimensions = wfMsgHtml( 'widthheight', $wgLang->formatNum( $result->img_width ), - $wgLang->formatNum( $result->img_height ) ); - $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text ); - $time = $wgLang->timeanddate( $result->img_timestamp ); - - return "($download) $plink . . $dimensions . . $bytes . . $user . . $time"; - } -} - -/** - * Output the HTML search form, and constructs the MIMEsearchPage object. - */ -function wfSpecialMIMEsearch( $par = null ) { - global $wgRequest, $wgTitle, $wgOut; - - $mime = isset( $par ) ? $par : $wgRequest->getText( 'mime' ); - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgTitle->getLocalUrl() ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) . - Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' . - Xml::submitButton( wfMsg( 'ilsubmit' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - - list( $major, $minor ) = wfSpecialMIMEsearchParse( $mime ); - if ( $major == '' or $minor == '' or !wfSpecialMIMEsearchValidType( $major ) ) - return; - $wpp = new MIMEsearchPage( $major, $minor ); - - list( $limit, $offset ) = wfCheckLimits(); - $wpp->doQuery( $offset, $limit ); -} - -function wfSpecialMIMEsearchParse( $str ) { - // searched for an invalid MIME type. - if( strpos( $str, '/' ) === false) { - return array ('', ''); - } - - list( $major, $minor ) = explode( '/', $str, 2 ); - - return array( - ltrim( $major, ' ' ), - rtrim( $minor, ' ' ) - ); -} - -function wfSpecialMIMEsearchValidType( $type ) { - // From maintenance/tables.sql => img_major_mime - $types = array( - 'unknown', - 'application', - 'audio', - 'image', - 'text', - 'video', - 'message', - 'model', - 'multipart' - ); - - return in_array( $type, $types ); -} diff --git a/includes/specials/MergeHistory.php b/includes/specials/MergeHistory.php deleted file mode 100644 index 6183374ee4..0000000000 --- a/includes/specials/MergeHistory.php +++ /dev/null @@ -1,451 +0,0 @@ -execute(); -} - -/** - * The HTML form for Special:MergeHistory, which allows users with the appropriate - * permissions to view and restore deleted content. - * @ingroup SpecialPage - */ -class MergehistoryForm { - var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment; - var $mTargetObj, $mDestObj; - - function MergehistoryForm( $request, $par = "" ) { - global $wgUser; - - $this->mAction = $request->getVal( 'action' ); - $this->mTarget = $request->getVal( 'target' ); - $this->mDest = $request->getVal( 'dest' ); - $this->mSubmitted = $request->getBool( 'submitted' ); - - $this->mTargetID = intval( $request->getVal( 'targetID' ) ); - $this->mDestID = intval( $request->getVal( 'destID' ) ); - $this->mTimestamp = $request->getVal( 'mergepoint' ); - if( !preg_match("/[0-9]{14}/",$this->mTimestamp) ) { - $this->mTimestamp = ''; - } - $this->mComment = $request->getText( 'wpComment' ); - - $this->mMerge = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - // target page - if( $this->mSubmitted ) { - $this->mTargetObj = Title::newFromURL( $this->mTarget ); - $this->mDestObj = Title::newFromURL( $this->mDest ); - } else { - $this->mTargetObj = null; - $this->mDestObj = null; - } - - $this->preCacheMessages(); - } - - /** - * As we use the same small set of messages in various methods and that - * they are called often, we call them once and save them in $this->message - */ - function preCacheMessages() { - // Precache various messages - if( !isset( $this->message ) ) { - $this->message['last'] = wfMsgExt( 'last', array( 'escape') ); - } - } - - function execute() { - global $wgOut, $wgUser; - - $wgOut->setPagetitle( wfMsgHtml( "mergehistory" ) ); - - if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) { - return $this->merge(); - } - - if ( !$this->mSubmitted ) { - $this->showMergeForm(); - return; - } - - $errors = array(); - if ( !$this->mTargetObj instanceof Title ) { - $errors[] = wfMsgExt( 'mergehistory-invalid-source', array( 'parse' ) ); - } elseif( !$this->mTargetObj->exists() ) { - $errors[] = wfMsgExt( 'mergehistory-no-source', array( 'parse' ), - wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) - ); - } - - if ( !$this->mDestObj instanceof Title) { - $errors[] = wfMsgExt( 'mergehistory-invalid-destination', array( 'parse' ) ); - } elseif( !$this->mDestObj->exists() ) { - $errors[] = wfMsgExt( 'mergehistory-no-destination', array( 'parse' ), - wfEscapeWikiText( $this->mDestObj->getPrefixedText() ) - ); - } - - // TODO: warn about target = dest? - - if ( count( $errors ) ) { - $this->showMergeForm(); - $wgOut->addHTML( implode( "\n", $errors ) ); - } else { - $this->showHistory(); - } - - } - - function showMergeForm() { - global $wgOut, $wgScript; - - $wgOut->addWikiMsg( 'mergehistory-header' ); - - $wgOut->addHtml( - Xml::openElement( 'form', array( - 'method' => 'get', - 'action' => $wgScript ) ) . - '

    ' . - Xml::element( 'legend', array(), - wfMsg( 'mergehistory-box' ) ) . - Xml::hidden( 'title', - SpecialPage::getTitleFor( 'Mergehistory' )->getPrefixedDbKey() ) . - Xml::hidden( 'submitted', '1' ) . - Xml::hidden( 'mergepoint', $this->mTimestamp ) . - Xml::openElement( 'table' ) . - " - ".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )." - ".Xml::input( 'target', 30, $this->mTarget, array('id'=>'target') )." - - ".Xml::label( wfMsg( 'mergehistory-into' ), 'dest' )." - ".Xml::input( 'dest', 30, $this->mDest, array('id'=>'dest') )." - " . - Xml::submitButton( wfMsg( 'mergehistory-go' ) ) . - "" . - Xml::closeElement( 'table' ) . - '
    ' . - '' ); - } - - private function showHistory() { - global $wgLang, $wgContLang, $wgUser, $wgOut; - - $this->sk = $wgUser->getSkin(); - - $wgOut->setPagetitle( wfMsg( "mergehistory" ) ); - - $this->showMergeForm(); - - # List all stored revisions - $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj ); - $haveRevisions = $revisions && $revisions->getNumRows() > 0; - - $titleObj = SpecialPage::getTitleFor( "Mergehistory" ); - $action = $titleObj->getLocalURL( "action=submit" ); - # Start the form here - $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) ); - $wgOut->addHtml( $top ); - - if( $haveRevisions ) { - # Format the user-visible controls (comment field, submission button) - # in a nice little table - $align = $wgContLang->isRtl() ? 'left' : 'right'; - $table = - Xml::openElement( 'fieldset' ) . - Xml::openElement( 'table' ) . - " - " . - wfMsgExt( 'mergehistory-merge', array('parseinline'), - $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) . - " - - - " . - Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) . - " - " . - Xml::input( 'wpComment', 50, $this->mComment ) . - " - - -   - " . - Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . - " - " . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ); - - $wgOut->addHtml( $table ); - } - - $wgOut->addHTML( "

    " . wfMsgHtml( "mergehistory-list" ) . "

    \n" ); - - if( $haveRevisions ) { - $wgOut->addHTML( $revisions->getNavigationBar() ); - $wgOut->addHTML( "
      " ); - $wgOut->addHTML( $revisions->getBody() ); - $wgOut->addHTML( "
    " ); - $wgOut->addHTML( $revisions->getNavigationBar() ); - } else { - $wgOut->addWikiMsg( "mergehistory-empty" ); - } - - # Show relevant lines from the deletion log: - $wgOut->addHTML( "

    " . htmlspecialchars( LogPage::logName( 'merge' ) ) . "

    \n" ); - LogEventsList::showLogExtract( $wgOut, 'merge', $this->mTargetObj->getPrefixedText() ); - - # When we submit, go by page ID to avoid some nasty but unlikely collisions. - # Such would happen if a page was renamed after the form loaded, but before submit - $misc = Xml::hidden( 'targetID', $this->mTargetObj->getArticleID() ); - $misc .= Xml::hidden( 'destID', $this->mDestObj->getArticleID() ); - $misc .= Xml::hidden( 'target', $this->mTarget ); - $misc .= Xml::hidden( 'dest', $this->mDest ); - $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); - $misc .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $misc ); - - return true; - } - - function formatRevisionRow( $row ) { - global $wgUser, $wgLang; - - $rev = new Revision( $row ); - - $stxt = ''; - $last = $this->message['last']; - - $ts = wfTimestamp( TS_MW, $row->rev_timestamp ); - $checkBox = wfRadio( "mergepoint", $ts, false ); - - $pageLink = $this->sk->makeKnownLinkObj( $rev->getTitle(), - htmlspecialchars( $wgLang->timeanddate( $ts ) ), 'oldid=' . $rev->getId() ); - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $pageLink = '' . $pageLink . ''; - } - - # Last link - if( !$rev->userCan( Revision::DELETED_TEXT ) ) - $last = $this->message['last']; - else if( isset($this->prevId[$row->rev_id]) ) - $last = $this->sk->makeKnownLinkObj( $rev->getTitle(), $this->message['last'], - "diff=" . $row->rev_id . "&oldid=" . $this->prevId[$row->rev_id] ); - - $userLink = $this->sk->revUserTools( $rev ); - - if(!is_null($size = $row->rev_len)) { - if($size == 0) - $stxt = wfMsgHtml('historyempty'); - else - $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); - } - $comment = $this->sk->revComment( $rev ); - - return "
  • $checkBox ($last) $pageLink . . $userLink $stxt $comment
  • "; - } - - /** - * Fetch revision text link if it's available to all users - * @return string - */ - function getPageLink( $row, $titleObj, $ts, $target ) { - global $wgLang; - - if( !$this->userCan($row, Revision::DELETED_TEXT) ) { - return '' . $wgLang->timeanddate( $ts, true ) . ''; - } else { - $link = $this->sk->makeKnownLinkObj( $titleObj, - $wgLang->timeanddate( $ts, true ), "target=$target×tamp=$ts" ); - if( $this->isDeleted($row, Revision::DELETED_TEXT) ) - $link = '' . $link . ''; - return $link; - } - } - - function merge() { - global $wgOut, $wgUser; - # Get the titles directly from the IDs, in case the target page params - # were spoofed. The queries are done based on the IDs, so it's best to - # keep it consistent... - $targetTitle = Title::newFromID( $this->mTargetID ); - $destTitle = Title::newFromID( $this->mDestID ); - if( is_null($targetTitle) || is_null($destTitle) ) - return false; // validate these - if( $targetTitle->getArticleId() == $destTitle->getArticleId() ) - return false; - # Verify that this timestamp is valid - # Must be older than the destination page - $dbw = wfGetDB( DB_MASTER ); - # Get timestamp into DB format - $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp($this->mTimestamp) : ''; - # Max timestamp should be min of destination page - $maxtimestamp = $dbw->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $this->mDestID ), - __METHOD__ ); - # Destination page must exist with revisions - if( !$maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Get the latest timestamp of the source - $lasttimestamp = $dbw->selectField( array('page','revision'), - 'rev_timestamp', - array('page_id' => $this->mTargetID, 'page_latest = rev_id' ), - __METHOD__ ); - # $this->mTimestamp must be older than $maxtimestamp - if( $this->mTimestamp >= $maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Update the revisions - if( $this->mTimestamp ) { - $timewhere = "rev_timestamp <= {$this->mTimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$this->mTimestamp); - } else { - $timewhere = "rev_timestamp <= {$maxtimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$lasttimestamp); - } - # Do the moving... - $dbw->update( 'revision', - array( 'rev_page' => $this->mDestID ), - array( 'rev_page' => $this->mTargetID, - $timewhere ), - __METHOD__ ); - - $count = $dbw->affectedRows(); - # Make the source page a redirect if no revisions are left - $haveRevisions = $dbw->selectField( 'revision', - 'rev_timestamp', - array( 'rev_page' => $this->mTargetID ), - __METHOD__, - array( 'FOR UPDATE' ) ); - if( !$haveRevisions ) { - if( $this->mComment ) { - $comment = wfMsgForContent( 'mergehistory-comment', $targetTitle->getPrefixedText(), - $destTitle->getPrefixedText(), $this->mComment ); - } else { - $comment = wfMsgForContent( 'mergehistory-autocomment', $targetTitle->getPrefixedText(), - $destTitle->getPrefixedText() ); - } - $mwRedir = MagicWord::get( 'redirect' ); - $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n"; - $redirectArticle = new Article( $targetTitle ); - $redirectRevision = new Revision( array( - 'page' => $this->mTargetID, - 'comment' => $comment, - 'text' => $redirectText ) ); - $redirectRevision->insertOn( $dbw ); - $redirectArticle->updateRevisionOn( $dbw, $redirectRevision ); - - # Now, we record the link from the redirect to the new title. - # It should have no other outgoing links... - $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ ); - $dbw->insert( 'pagelinks', - array( - 'pl_from' => $this->mDestID, - 'pl_namespace' => $destTitle->getNamespace(), - 'pl_title' => $destTitle->getDBkey() ), - __METHOD__ ); - } else { - $targetTitle->invalidateCache(); // update histories - } - $destTitle->invalidateCache(); // update histories - # Check if this did anything - if( !$count ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Update our logs - $log = new LogPage( 'merge' ); - $log->addEntry( 'merge', $targetTitle, $this->mComment, - array($destTitle->getPrefixedText(),$TimestampLimit) ); - - $wgOut->addHtml( wfMsgExt( 'mergehistory-success', array('parseinline'), - $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) ); - - wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) ); - - return true; - } -} - -class MergeHistoryPager extends ReverseChronologicalPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array(), $source, $dest ) { - $this->mForm = $form; - $this->mConds = $conds; - $this->title = $source; - $this->articleID = $source->getArticleID(); - - $dbr = wfGetDB( DB_SLAVE ); - $maxtimestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $dest->getArticleID() ), - __METHOD__ ); - $this->maxTimestamp = $maxtimestamp; - - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $batch = new LinkBatch(); - # Give some pointers to make (last) links - $this->mForm->prevId = array(); - while( $row = $this->mResult->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) ); - - $rev_id = isset($rev_id) ? $rev_id : $row->rev_id; - if( $rev_id > $row->rev_id ) - $this->mForm->prevId[$rev_id] = $row->rev_id; - else if( $rev_id < $row->rev_id ) - $this->mForm->prevId[$row->rev_id] = $rev_id; - - $rev_id = $row->rev_id; - } - - $batch->execute(); - $this->mResult->seek( 0 ); - - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - $block = new Block; - return $this->mForm->formatRevisionRow( $row ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds['rev_page'] = $this->articleID; - $conds[] = "rev_timestamp < {$this->maxTimestamp}"; - - return array( - 'tables' => array('revision'), - '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 - ); - } - - function getIndexField() { - return 'rev_timestamp'; - } -} diff --git a/includes/specials/Mostcategories.php b/includes/specials/Mostcategories.php deleted file mode 100644 index 5df9c861b4..0000000000 --- a/includes/specials/Mostcategories.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * implements Special:Mostcategories - * @ingroup SpecialPage - */ -class MostcategoriesPage extends QueryPage { - - function getName() { return 'Mostcategories'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' ); - return - " - SELECT - 'Mostcategories' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $categorylinks - LEFT JOIN $page ON cl_from = page_id - WHERE page_namespace = " . NS_MAIN . " - GROUP BY 1,2,3 - HAVING COUNT(*) > 1 - "; - } - - 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() ); - return wfSpecialList( $link, $count ); - } -} - -/** - * constructor - */ -function wfSpecialMostcategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostcategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Mostimages.php b/includes/specials/Mostimages.php deleted file mode 100644 index 2fed0bd1ae..0000000000 --- a/includes/specials/Mostimages.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * implements Special:Mostimages - * @ingroup SpecialPage - */ -class MostimagesPage extends ImageQueryPage { - - function getName() { return 'Mostimages'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $imagelinks = $dbr->tableName( 'imagelinks' ); - return - " - SELECT - 'Mostimages' as type, - " . NS_IMAGE . " as namespace, - il_to as title, - COUNT(*) as value - FROM $imagelinks - GROUP BY 1,2,3 - HAVING COUNT(*) > 1 - "; - } - - function getCellHtml( $row ) { - global $wgLang; - return wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $row->value ) ) . '
    '; - } - -} - -/** - * Constructor - */ -function wfSpecialMostimages() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostimagesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Mostlinked.php b/includes/specials/Mostlinked.php deleted file mode 100644 index a56ac26941..0000000000 --- a/includes/specials/Mostlinked.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @author Rob Church - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class MostlinkedPage extends QueryPage { - - function getName() { return 'Mostlinked'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - /** - * Note: Getting page_namespace only works if $this->isCached() is false - */ - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' ); - return - "SELECT 'Mostlinked' AS type, - pl_namespace AS namespace, - pl_title AS title, - COUNT(*) AS value, - page_namespace - FROM $pagelinks - LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title - GROUP BY 1,2,3,5 - HAVING COUNT(*) > 1"; - } - - /** - * Pre-fill the link cache - */ - function preprocessResults( $db, $res ) { - if( $db->numRows( $res ) > 0 ) { - $linkBatch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) - $linkBatch->add( $row->namespace, $row->title ); - $db->dataSeek( $res, 0 ); - $linkBatch->execute(); - } - } - - /** - * Make a link to "what links here" for the specified title - * - * @param $title Title being queried - * @param $skin Skin to use - * @return string - */ - function makeWlhLink( &$title, $caption, &$skin ) { - $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() ); - return $skin->makeKnownLinkObj( $wlh, $caption ); - } - - /** - * Make links to the page corresponding to the item, and the "what links here" page for it - * - * @param $skin Skin to be used - * @param $result Result row - * @return string - */ - function formatResult( $skin, $result ) { - global $wgLang; - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - $link = $skin->makeLinkObj( $title ); - $wlh = $this->makeWlhLink( $title, - wfMsgExt( 'nlinks', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ), $skin ); - return wfSpecialList( $link, $wlh ); - } -} - -/** - * constructor - */ -function wfSpecialMostlinked() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostlinkedPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Mostlinkedcategories.php b/includes/specials/Mostlinkedcategories.php deleted file mode 100644 index 1b66d48403..0000000000 --- a/includes/specials/Mostlinkedcategories.php +++ /dev/null @@ -1,78 +0,0 @@ - - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class MostlinkedCategoriesPage extends QueryPage { - - function getName() { return 'Mostlinkedcategories'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $categorylinks = $dbr->tableName( 'categorylinks' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT - $name as type, - " . NS_CATEGORY . " as namespace, - cl_to as title, - COUNT(*) as value - FROM $categorylinks - GROUP BY 1,2,3 - "; - } - - 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 = $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ); - - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - return wfSpecialList($plink, $nlinks); - } -} - -/** - * constructor - */ -function wfSpecialMostlinkedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostlinkedCategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Mostlinkedtemplates.php b/includes/specials/Mostlinkedtemplates.php deleted file mode 100644 index b8d47e6d22..0000000000 --- a/includes/specials/Mostlinkedtemplates.php +++ /dev/null @@ -1,132 +0,0 @@ - - */ -class SpecialMostlinkedtemplates extends QueryPage { - - /** - * Name of the report - * - * @return string - */ - public function getName() { - return 'Mostlinkedtemplates'; - } - - /** - * Is this report expensive, i.e should it be cached? - * - * @return bool - */ - public function isExpensive() { - return true; - } - - /** - * Is there a feed available? - * - * @return bool - */ - public function isSyndicated() { - return false; - } - - /** - * Sort the results in descending order? - * - * @return bool - */ - public function sortDescending() { - return true; - } - - /** - * Generate SQL for the report - * - * @return string - */ - public function getSql() { - $dbr = wfGetDB( DB_SLAVE ); - $templatelinks = $dbr->tableName( 'templatelinks' ); - $name = $dbr->addQuotes( $this->getName() ); - return "SELECT {$name} AS type, - " . NS_TEMPLATE . " AS namespace, - tl_title AS title, - COUNT(*) AS value - FROM {$templatelinks} - WHERE tl_namespace = " . NS_TEMPLATE . " - GROUP BY 1, 2, 3"; - } - - /** - * Pre-cache page existence to speed up link generation - * - * @param Database $dbr Database connection - * @param int $res Result pointer - */ - public function preprocessResults( $db, $res ) { - $batch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) { - $batch->add( $row->namespace, $row->title ); - } - $batch->execute(); - if( $db->numRows( $res ) > 0 ) - $db->dataSeek( $res, 0 ); - } - - /** - * Format a result row - * - * @param Skin $skin Skin to use for UI elements - * @param object $result Result row - * @return string - */ - 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}"; - } - } - - /** - * 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->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() ); - } -} - -/** - * Execution function - * - * @param mixed $par Parameters passed to the page - */ -function wfSpecialMostlinkedtemplates( $par = false ) { - list( $limit, $offset ) = wfCheckLimits(); - $mlt = new SpecialMostlinkedtemplates(); - $mlt->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Mostrevisions.php b/includes/specials/Mostrevisions.php deleted file mode 100644 index 001a08b1d1..0000000000 --- a/includes/specials/Mostrevisions.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * @ingroup SpecialPage - */ -class MostrevisionsPage extends QueryPage { - - function getName() { return 'Mostrevisions'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); - return - " - SELECT - 'Mostrevisions' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $revision - JOIN $page ON page_id = rev_page - WHERE page_namespace = " . NS_MAIN . " - GROUP BY 1,2,3 - HAVING COUNT(*) > 1 - "; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->makeKnownLinkObj( $nt, $text ); - - $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ); - - return wfSpecialList($plink, $nlink); - } -} - -/** - * constructor - */ -function wfSpecialMostrevisions() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostrevisionsPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Movepage.php b/includes/specials/Movepage.php deleted file mode 100644 index d08fb66bd8..0000000000 --- a/includes/specials/Movepage.php +++ /dev/null @@ -1,428 +0,0 @@ -readOnlyPage(); - return; - } - - $target = isset( $par ) ? $par : $wgRequest->getVal( 'target' ); - $oldTitle = $wgRequest->getText( 'wpOldTitle', $target ); - $newTitle = $wgRequest->getText( 'wpNewTitle' ); - - # Variables beginning with 'o' for old article 'n' for new article - $ot = Title::newFromText( $oldTitle ); - $nt = Title::newFromText( $newTitle ); - - if( is_null( $ot ) ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; - } - if( !$ot->exists() ) { - $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' ); - return; - } - - # Check rights - $permErrors = $ot->getUserPermissionsErrors( 'move', $wgUser ); - if( !empty( $permErrors ) ) { - $wgOut->showPermissionsErrorPage( $permErrors ); - return; - } - - $f = new MovePageForm( $ot, $nt ); - - if ( 'submit' == $action && $wgRequest->wasPosted() - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( '' ); - } -} - -/** - * HTML form for Special:Movepage - * @ingroup SpecialPage - */ -class MovePageForm { - var $oldTitle, $newTitle, $reason; # Text input - var $moveTalk, $deleteAndMove, $moveSubpages; - - private $watch = false; - - function MovePageForm( $oldTitle, $newTitle ) { - global $wgRequest; - $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); - $this->oldTitle = $oldTitle; - $this->newTitle = $newTitle; - $this->reason = $wgRequest->getText( 'wpReason' ); - if ( $wgRequest->wasPosted() ) { - $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', false ); - } else { - $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', 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; - - $ot = $this->oldTitle; - $sk = $wgUser->getSkin(); - - $oldTitleLink = $sk->makeLinkObj( $ot ); - $oldTitle = $ot->getPrefixedText(); - - $wgOut->setPagetitle( wfMsg( 'move-page', $oldTitle ) ); - $wgOut->setSubtitle( wfMsg( 'move-page-backlink', $oldTitleLink ) ); - - if( $this->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 = $ot->isValidMoveOperation( $nt ); - if( is_string( $newerr ) ) { - $err = $newerr; - } - } - } - $newTitle = $this->newTitle; - } - - if ( $err == 'articleexists' && $wgUser->isAllowed( 'delete' ) ) { - $wgOut->addWikiMsg( 'delete_and_move_text', $newTitle ); - $movepagebtn = wfMsg( 'delete_and_move' ); - $submitVar = 'wpDeleteAndMove'; - $confirm = " - - - " . - Xml::checkLabel( wfMsg( 'delete_and_move_confirm' ), 'wpConfirm', 'wpConfirm' ) . - " - "; - $err = ''; - } else { - $wgOut->addWikiMsg( 'movepagetext' ); - $movepagebtn = wfMsg( 'movepagebtn' ); - $submitVar = 'wpMove'; - $confirm = false; - } - - $oldTalk = $ot->getTalkPage(); - $considerTalk = ( !$ot->isTalkPage() && $oldTalk->exists() ); - - if ( $considerTalk ) { - $wgOut->addWikiMsg( 'movepagetalktext' ); - } - - $titleObj = SpecialPage::getTitleFor( 'Movepage' ); - $token = htmlspecialchars( $wgUser->editToken() ); - - if ( $err != '' ) { - $wgOut->setSubtitle( wfMsg( 'formerror' ) ); - $errMsg = ""; - if( $err == 'hookaborted' ) { - $errMsg = "

    $hookErr

    \n"; - } else if (is_array($err)) { - $errMsg = '

    ' . call_user_func_array( 'wfMsgWikiHtml', $err ) . "

    \n"; - } else { - $errMsg = '

    ' . wfMsgWikiHtml( $err ) . "

    \n"; - } - $wgOut->addHTML( $errMsg ); - } - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'move-page-legend' ) ) . - Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-movepage-table' ) ) . - " - " . - wfMsgHtml( 'movearticle' ) . - " - - {$oldTitleLink} - - - - " . - Xml::label( wfMsg( 'newtitle' ), 'wpNewTitle' ) . - " - " . - Xml::input( 'wpNewTitle', 40, $newTitle, array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) . - Xml::hidden( 'wpOldTitle', $oldTitle ) . - " - - - " . - Xml::label( wfMsg( 'movereason' ), 'wpReason' ) . - " - " . - Xml::tags( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2 ), htmlspecialchars( $this->reason ) ) . - " - " - ); - - if( $considerTalk ) { - $wgOut->addHTML( " - - - " . - Xml::checkLabel( wfMsg( 'movetalk' ), 'wpMovetalk', 'wpMovetalk', $this->moveTalk ) . - " - " - ); - } - - if( ($ot->hasSubpages() || $ot->getTalkPage()->hasSubpages()) - && $ot->userCan( 'move-subpages' ) ) { - $wgOut->addHTML( " - - - " . - Xml::checkLabel( wfMsgHtml( - $ot->hasSubpages() - ? 'move-subpages' - : 'move-talk-subpages' - ), - 'wpMovesubpages', 'wpMovesubpages', - # Don't check the box if we only have talk subpages to - # move and we aren't moving the talk page. - $this->moveSubpages && ($ot->hasSubpages() || $this->moveTalk) - ) . - " - " - ); - } - - $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $ot->userIsWatching(); - $wgOut->addHTML( " - - - " . - Xml::checkLabel( wfMsg( 'move-watch' ), 'wpWatch', 'watch', $watchChecked ) . - " - - {$confirm} - -   - " . - Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) . - " - " . - Xml::closeElement( 'table' ) . - Xml::hidden( 'wpEditToken', $token ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . - "\n" - ); - - $this->showLogFragment( $ot, $wgOut ); - - } - - function doSubmit() { - global $wgOut, $wgUser, $wgRequest, $wgMaximumMovedPages, $wgLang; - - if ( $wgUser->pingLimiter( 'move' ) ) { - $wgOut->rateLimited(); - return; - } - - $ot = $this->oldTitle; - $nt = $this->newTitle; - - # Delete to make way if requested - if ( $wgUser->isAllowed( 'delete' ) && $this->deleteAndMove ) { - $article = new Article( $nt ); - - # Disallow deletions of big articles - $bigHistory = $article->isBigDeletion(); - if( $bigHistory && !$nt->userCan( 'bigdelete' ) ) { - global $wgLang, $wgDeleteRevisionsLimit; - $this->showForm( array('delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) ); - return; - } - - // This may output an error message and exit - $article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) ); - } - - # don't allow moving to pages with # in - if ( !$nt || $nt->getFragment() != '' ) { - $this->showForm( 'badtitletext' ); - return; - } - - $error = $ot->moveTo( $nt, true, $this->reason ); - if ( $error !== true ) { - # FIXME: showForm() should handle multiple errors - call_user_func_array(array($this, 'showForm'), $error[0]); - return; - } - - wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ot , &$nt ) ) ; - - $wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) ); - - $oldUrl = $ot->getFullUrl( 'redirect=no' ); - $newUrl = $nt->getFullUrl(); - $oldText = $ot->getPrefixedText(); - $newText = $nt->getPrefixedText(); - $oldLink = "[$oldUrl $oldText]"; - $newLink = "[$newUrl $newText]"; - - $wgOut->addWikiMsg( 'movepage-moved', $oldLink, $newLink, $oldText, $newText ); - - # 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 - # can't move any talk pages: cancel that. - if( $ot->isTalkPage() || $nt->isTalkPage() ) { - $this->moveTalk = false; - } - - if( !$ot->userCan( 'move-subpages' ) ) { - $this->moveSubpages = false; - } - - # Next make a list of id's. This might be marginally less efficient - # than a more direct method, but this is not a highly performance-cri- - # tical code path and readable code is more important here. - # - # Note: this query works nicely on MySQL 5, but the optimizer in MySQL - # 4 might get confused. If so, consider rewriting as a UNION. - # - # If the target namespace doesn't allow subpages, moving with subpages - # would mean that you couldn't move them back in one operation, which - # is bad. FIXME: A specific error message should be given in this - # case. - $dbr = wfGetDB( DB_MASTER ); - if( $this->moveSubpages && ( - MWNamespace::hasSubpages( $nt->getNamespace() ) || ( - $this->moveTalk && - MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) - ) - ) ) { - $conds = array( - 'page_title LIKE '.$dbr->addQuotes( $dbr->escapeLike( $ot->getDBkey() ) . '/%' ) - .' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() ) - ); - $conds['page_namespace'] = array(); - if( MWNamespace::hasSubpages( $nt->getNamespace() ) ) { - $conds['page_namespace'] []= $ot->getNamespace(); - } - if( $this->moveTalk && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) ) { - $conds['page_namespace'] []= $ot->getTalkPage()->getNamespace(); - } - } elseif( $this->moveTalk ) { - $conds = array( - 'page_namespace' => $ot->getTalkPage()->getNamespace(), - 'page_title' => $ot->getDBKey() - ); - } else { - # Skip the query - $conds = null; - } - - $extrapages = array(); - if( !is_null( $conds ) ) { - $extrapages = $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() ) { - # Already did this one. - continue; - } - - $oldPage = Title::newFromRow( $row ); - $newPageName = preg_replace( - '#^'.preg_quote( $ot->getDBKey(), '#' ).'#', - $nt->getDBKey(), - $oldPage->getDBKey() - ); - if( $oldPage->isTalkPage() ) { - $newNs = $nt->getTalkPage()->getNamespace(); - } else { - $newNs = $nt->getSubjectPage()->getNamespace(); - } - # Bug 14385: we need makeTitleSafe because the new page names may - # be longer than 255 characters. - $newPage = Title::makeTitleSafe( $newNs, $newPageName ); - if( !$newPage ) { - $oldLink = $skin->makeKnownLinkObj( $oldPage ); - $extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, - htmlspecialchars(Title::makeName( $newNs, $newPageName ))); - continue; - } - - # This was copy-pasted from Renameuser, bleh. - if ( $newPage->exists() && !$oldPage->isValidMoveTarget( $newPage ) ) { - $link = $skin->makeKnownLinkObj( $newPage ); - $extraOutput []= wfMsgHtml( 'movepage-page-exists', $link ); - } else { - $success = $oldPage->moveTo( $newPage, true, $this->reason ); - if( $success === true ) { - $oldLink = $skin->makeKnownLinkObj( $oldPage, '', 'redirect=no' ); - $newLink = $skin->makeKnownLinkObj( $newPage ); - $extraOutput []= wfMsgHtml( 'movepage-page-moved', $oldLink, $newLink ); - } else { - $oldLink = $skin->makeKnownLinkObj( $oldPage ); - $newLink = $skin->makeLinkObj( $newPage ); - $extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, $newLink ); - } - } - - ++$count; - if( $count >= $wgMaximumMovedPages ) { - $extraOutput []= wfMsgExt( 'movepage-max-pages', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgMaximumMovedPages ) ); - break; - } - } - - if( $extraOutput !== array() ) { - $wgOut->addHTML( "
      \n
    • " . implode( "
    • \n
    • ", $extraOutput ) . "
    • \n
    " ); - } - - # Deal with watches (we don't watch subpages) - if( $this->watch ) { - $wgUser->addWatch( $ot ); - $wgUser->addWatch( $nt ); - } else { - $wgUser->removeWatch( $ot ); - $wgUser->removeWatch( $nt ); - } - } - - function showLogFragment( $title, &$out ) { - $out->addHTML( Xml::element( 'h2', NULL, LogPage::logName( 'move' ) ) ); - LogEventsList::showLogExtract( $out, 'move', $title->getPrefixedText() ); - } - -} diff --git a/includes/specials/Newimages.php b/includes/specials/Newimages.php deleted file mode 100644 index 5fd37e8e0a..0000000000 --- a/includes/specials/Newimages.php +++ /dev/null @@ -1,209 +0,0 @@ -getText( 'wpIlMatch' ); - $dbr = wfGetDB( DB_SLAVE ); - $sk = $wgUser->getSkin(); - $shownav = !$specialPage->including(); - $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 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)"; - } - } - - $image = $dbr->tableName('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]; - } else { - $ts=false; - } - $dbr->freeResult($res); - $sql=''; - - /** If we were clever, we'd use this to cache. */ - $latestTimestamp = wfTimestamp( TS_MW, $ts); - - /** Hardcode this for now. */ - $limit = 48; - - if ( $parval = intval( $par ) ) { - if ( $parval <= $limit && $parval > 0 ) { - $limit = $parval; - } - } - - $where = array(); - $searchpar = ''; - if ( $wpIlMatch != '' && !$wgMiserMode) { - $nt = Title::newFromUrl( $wpIlMatch ); - if($nt ) { - $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); - $m = str_replace( '%', "\\%", $m ); - $m = str_replace( '_', "\\_", $m ); - $where[] = "LOWER(img_name) LIKE '%{$m}%'"; - $searchpar = '&wpIlMatch=' . urlencode( $wpIlMatch ); - } - } - - $invertSort = false; - if( $until = $wgRequest->getVal( 'until' ) ) { - $where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'"; - } - if( $from = $wgRequest->getVal( 'from' ) ) { - $where[] = "img_timestamp >= '" . $dbr->timestamp( $from ) . "'"; - $invertSort = true; - } - $sql='SELECT img_size, img_name, img_user, img_user_text,'. - "img_description,img_timestamp FROM $image"; - - if($hidebotsql) { - $sql .= $hidebotsql; - $where[]='ug_group IS NULL'; - } - 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'); - - /** - * We have to flip things around to get the last N after a certain date - */ - $images = array(); - while ( $s = $dbr->fetchObject( $res ) ) { - if( $invertSort ) { - array_unshift( $images, $s ); - } else { - array_push( $images, $s ); - } - } - $dbr->freeResult( $res ); - - $gallery = new ImageGallery(); - $firstTimestamp = null; - $lastTimestamp = null; - $shownImages = 0; - foreach( $images as $s ) { - if( ++$shownImages > $limit ) { - # One extra just to test for whether to show a page link; - # don't actually show it. - break; - } - - $name = $s->img_name; - $ut = $s->img_user_text; - - $nt = Title::newFromText( $name, NS_IMAGE ); - $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut ); - - $gallery->add( $nt, "$ul
    \n".$wgLang->timeanddate( $s->img_timestamp, true )."
    \n" ); - - $timestamp = wfTimestamp( TS_MW, $s->img_timestamp ); - if( empty( $firstTimestamp ) ) { - $firstTimestamp = $timestamp; - } - $lastTimestamp = $timestamp; - } - - $bydate = wfMsg( 'bydate' ); - $lt = $wgLang->formatNum( min( $shownImages, $limit ) ); - 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( "
    " . - Xml::input( 'wpIlMatch', 20, $wpIlMatch ) . ' ' . - Xml::submitButton( $sub, array( 'name' => 'wpIlSubmit' ) ) . - "
    " ); - } - - /** - * Paging controls... - */ - - # If we change bot visibility, this needs to be carried along. - if(!$hidebots) { - $botpar='&hidebots=0'; - } else { - $botpar=''; - } - $now = wfTimestampNow(); - $d = $wgLang->date( $now, true ); - $t = $wgLang->time( $now, true ); - $dateLink = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml( 'sp-newimages-showfrom', $d, $t ), - 'from='.$now.$botpar.$searchpar ); - - $botLink = $sk->makeKnownLinkObj($titleObj, wfMsgHtml( 'showhidebots', - ($hidebots ? wfMsgHtml('show') : wfMsgHtml('hide'))),'hidebots='.($hidebots ? '0' : '1').$searchpar); - - $prevLink = wfMsgHtml( 'prevn', $wgLang->formatNum( $limit ) ); - if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) { - $prevLink = $sk->makeKnownLinkObj( $titleObj, $prevLink, 'from=' . $firstTimestamp . $botpar . $searchpar ); - } - - $nextLink = wfMsgHtml( 'nextn', $wgLang->formatNum( $limit ) ); - if( $shownImages > $limit && $lastTimestamp ) { - $nextLink = $sk->makeKnownLinkObj( $titleObj, $nextLink, 'until=' . $lastTimestamp.$botpar.$searchpar ); - } - - $prevnext = '

    ' . $botLink . ' '. wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'

    '; - - if ($shownav) - $wgOut->addHTML( $prevnext ); - - if( count( $images ) ) { - $wgOut->addHTML( $gallery->toHTML() ); - if ($shownav) - $wgOut->addHTML( $prevnext ); - } else { - $wgOut->addWikiMsg( 'noimages' ); - } -} diff --git a/includes/specials/Newpages.php b/includes/specials/Newpages.php deleted file mode 100644 index 72a01836d0..0000000000 --- a/includes/specials/Newpages.php +++ /dev/null @@ -1,437 +0,0 @@ -includable( true ); - } - - protected function setup( $par ) { - global $wgRequest, $wgUser, $wgEnableNewpagesUserFilter; - - // Options - $opts = new FormOptions(); - $this->opts = $opts; // bind - $opts->add( 'hideliu', false ); - $opts->add( 'hidepatrolled', false ); - $opts->add( 'hidebots', false ); - $opts->add( 'limit', 50 ); - $opts->add( 'offset', '' ); - $opts->add( 'namespace', '0' ); - $opts->add( 'username', '' ); - $opts->add( 'feed', '' ); - - // Set values - $opts->fetchValuesFromRequest( $wgRequest ); - if ( $par ) $this->parseParams( $par ); - - // Validate - $opts->validateIntBounds( 'limit', 0, 5000 ); - if( !$wgEnableNewpagesUserFilter ) { - $opts->setValue( 'username', '' ); - } - - // Store some objects - $this->skin = $wgUser->getSkin(); - } - - protected function parseParams( $par ) { - global $wgLang; - $bits = preg_split( '/\s*,\s*/', trim( $par ) ); - foreach ( $bits as $bit ) { - if ( 'shownav' == $bit ) - $this->showNavigation = true; - if ( 'hideliu' === $bit ) - $this->opts->setValue( 'hideliu', true ); - if ( 'hidepatrolled' == $bit ) - $this->opts->setValue( 'hidepatrolled', true ); - if ( 'hidebots' == $bit ) - $this->opts->setValue( 'hidebots', true ); - if ( is_numeric( $bit ) ) - $this->opts->setValue( 'limit', intval( $bit ) ); - - $m = array(); - if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) - $this->opts->setValue( 'limit', intval($m[1]) ); - // PG offsets not just digits! - if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) - $this->opts->setValue( 'offset', intval($m[1]) ); - if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { - $ns = $wgLang->getNsIndex( $m[1] ); - if( $ns !== false ) { - $this->opts->setValue( 'namespace', $ns ); - } - } - } - } - - /** - * Show a form for filtering namespace and username - * - * @param string $par - * @return string - */ - public function execute( $par ) { - global $wgLang, $wgGroupPermissions, $wgUser, $wgOut; - - $this->setHeaders(); - $this->outputHeader(); - - $this->showNavigation = !$this->including(); // Maybe changed in setup - $this->setup( $par ); - - if( !$this->including() ) { - // Settings - $this->form(); - - $this->setSyndicated(); - $feedType = $this->opts->getValue( 'feed' ); - if( $feedType ) { - return $this->feed( $feedType ); - } - } - - $pager = new NewPagesPager( $this, $this->opts ); - $pager->mLimit = $this->opts->getValue( 'limit' ); - $pager->mOffset = $this->opts->getValue( 'offset' ); - - if( $pager->getNumRows() ) { - $navigation = ''; - if ( $this->showNavigation ) $navigation = $pager->getNavigationBar(); - $wgOut->addHTML( $navigation . $pager->getBody() . $navigation ); - } else { - $wgOut->addWikiMsg( 'specialpage-empty' ); - } - } - - protected function filterLinks() { - global $wgGroupPermissions, $wgUser; - - // show/hide links - $showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) ); - - // Option value -> message mapping - $filters = array( - 'hideliu' => 'rcshowhideliu', - 'hidepatrolled' => 'rcshowhidepatr', - 'hidebots' => 'rcshowhidebots' - ); - - // Disable some if needed - if ( $wgGroupPermissions['*']['createpage'] !== true ) - unset($filters['hideliu']); - - if ( !$wgUser->useNPPatrol() ) - unset($filters['hidepatrolled']); - - $links = array(); - $changed = $this->opts->getChangedValues(); - unset($changed['offset']); // Reset offset if query type changes - - $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 ) - ); - $links[$key] = wfMsgHtml( $msg, $link ); - } - - return implode( ' | ', $links ); - } - - protected function form() { - global $wgOut, $wgEnableNewpagesUserFilter, $wgScript; - - // Consume values - $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW - $namespace = $this->opts->consumeValue( 'namespace' ); - $username = $this->opts->consumeValue( 'username' ); - - // Check username input validity - $ut = Title::makeTitleSafe( NS_USER, $username ); - $userText = $ut ? $ut->getText() : ''; - - // Store query values in hidden fields so that form submission doesn't lose them - $hidden = array(); - foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { - $hidden[] = Xml::hidden( $key, $value ); - } - $hidden = implode( "\n", $hidden ); - - $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . - Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . - Xml::fieldset( wfMsg( 'newpages' ) ) . - Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . - " - " . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . - " - " . - Xml::namespaceSelector( $namespace, 'all' ) . - " - " . - ($wgEnableNewpagesUserFilter ? - " - " . - Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) . - " - " . - Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) . - " - " : "" ) . - " - " . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - " - " . - " - - " . - $this->filterLinks() . - " - " . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ) . - $hidden . - Xml::closeElement( 'form' ); - - $wgOut->addHTML( $form ); - } - - protected function setSyndicated() { - global $wgOut; - $queryParams = array( - 'namespace' => $this->opts->getValue( 'namespace' ), - 'username' => $this->opts->getValue( 'username' ) - ); - $wgOut->setSyndicated( true ); - $wgOut->setFeedAppendQuery( wfArrayToCGI( $queryParams ) ); - } - - /** - * Format a row, providing the timestamp, links to the page/history, size, user links, and a comment - * - * @param $skin Skin to use - * @param $result Result row - * @return string - */ - public function formatRow( $result ) { - global $wgLang, $wgContLang, $wgUser; - $dm = $wgContLang->getDirMark(); - - $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' ); - $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->length ) ); - $ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' . - $this->skin->userToolLinks( $result->rc_user, $result->rc_user_text ); - $comment = $this->skin->commentBlock( $result->rc_comment ); - $css = $this->patrollable( $result ) ? " class='not-patrolled'" : ''; - - return "{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment}\n"; - } - - /** - * Should a specific result row provide "patrollable" links? - * - * @param $result Result row - * @return bool - */ - protected function patrollable( $result ) { - global $wgUser; - return ( $wgUser->useNPPatrol() && !$result->rc_patrolled ); - } - - /** - * Output a subscription feed listing recent edits to this page. - * @param string $type - */ - protected function feed( $type ) { - global $wgFeed, $wgFeedClasses; - - 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(), - wfMsg( 'tagline' ), - $this->getTitle()->getFullUrl() ); - - $pager = new NewPagesPager( $this, $this->opts ); - $limit = $this->opts->getValue( 'limit' ); - global $wgFeedLimit; - if( $limit > $wgFeedLimit ) { - $limit = $wgFeedLimit; - } - $pager->mLimit = $limit; - - $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( 'Newpages' ); - $desc = $page->getDescription(); - return "$wgSitename - $desc [$wgContLanguageCode]"; - } - - protected function feedItem( $row ) { - $title = Title::MakeTitle( intval( $row->rc_namespace ), $row->rc_title ); - if( $title ) { - $date = $row->rc_timestamp; - $comments = $title->getTalkPage()->getFullURL(); - - return new FeedItem( - $title->getPrefixedText(), - $this->feedItemDesc( $row ), - $title->getFullURL(), - $date, - $this->feedItemAuthor( $row ), - $comments); - } else { - return NULL; - } - } - - /** - * 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 : ''; - } - - protected function feedItemDesc( $row ) { - $revision = Revision::newFromId( $row->rev_id ); - if( $revision ) { - return '

    ' . htmlspecialchars( $revision->getUserText() ) . ': ' . - htmlspecialchars( $revision->getComment() ) . - "

    \n
    \n
    " . - nl2br( htmlspecialchars( $revision->getText() ) ) . "
    "; - } - return ''; - } -} - -/** - * @ingroup SpecialPage Pager - */ -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(){ - static $title = null; - if ( $title === null ) - $title = $this->mForm->getTitle(); - return $title; - } - - function getQueryInfo() { - global $wgEnableNewpagesUserFilter, $wgGroupPermissions, $wgUser; - $conds = array(); - $conds['rc_new'] = 1; - - $namespace = $this->opts->getValue( 'namespace' ); - $namespace = ( $namespace === 'all' ) ? false : intval( $namespace ); - - $username = $this->opts->getValue( 'username' ); - $user = Title::makeTitleSafe( NS_USER, $username ); - - if( $namespace !== false ) { - $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(); - $rcIndexes = 'rc_user_text'; - # If anons cannot make new pages, don't "exclude logged in users"! - } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) { - $conds['rc_user'] = 0; - } - # If this user cannot see patrolled edits or they are off, don't do dumb queries! - if( $this->opts->getValue( 'hidepatrolled' ) && $wgUser->useNPPatrol() ) { - $conds['rc_patrolled'] = 0; - } - if( $this->opts->getValue( 'hidebots' ) ) { - $conds['rc_bot'] = 0; - } - - return array( - 'tables' => array( 'recentchanges', 'page' ), - '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) ) - ); - } - - function getIndexField() { - return 'rc_timestamp'; - } - - function formatRow( $row ) { - return $this->mForm->formatRow( $row ); - } - - function getStartBody() { - # Do a batch existence check on pages - $linkBatch = new LinkBatch(); - 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->rc_namespace, $row->rc_title ); - } - $linkBatch->execute(); - return "
      "; - } - - function getEndBody() { - return "
    "; - } -} diff --git a/includes/specials/Popularpages.php b/includes/specials/Popularpages.php deleted file mode 100644 index eb5727367c..0000000000 --- a/includes/specials/Popularpages.php +++ /dev/null @@ -1,67 +0,0 @@ -tableName( 'page' ); - - $query = - "SELECT 'Popularpages' as type, - page_namespace as namespace, - page_title as title, - page_counter as value - FROM $page "; - $where = - "WHERE page_is_redirect=0 AND page_namespace"; - - global $wgContentNamespaces; - if( empty( $wgContentNamespaces ) ) { - $where .= '='.NS_MAIN; - } else if( count( $wgContentNamespaces ) > 1 ) { - $where .= ' in (' . implode( ', ', $wgContentNamespaces ) . ')'; - } else { - $where .= '='.$wgContentNamespaces[0]; - } - - return $query . $where; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - $title = Title::makeTitle( $result->namespace, $result->title ); - $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); - $nv = wfMsgExt( 'nviews', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - return wfSpecialList($link, $nv); - } -} - -/** - * Constructor - */ -function wfSpecialPopularpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $ppp = new PopularPagesPage(); - - return $ppp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Preferences.php b/includes/specials/Preferences.php deleted file mode 100644 index 6a16f1cba0..0000000000 --- a/includes/specials/Preferences.php +++ /dev/null @@ -1,1122 +0,0 @@ -execute(); -} - -/** - * Preferences form handling - * This object will show the preferences form and can save it as well. - * @ingroup SpecialPage - */ -class PreferencesForm { - var $mQuickbar, $mOldpass, $mNewpass, $mRetypePass, $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 $mUnderline, $mWatchlistEdits; - - /** - * Constructor - * Load some values - */ - function PreferencesForm( &$request ) { - 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->mMath = $request->getVal( 'wpMath' ); - $this->mDate = $request->getVal( 'wpDate' ); - $this->mUserEmail = $request->getVal( 'wpUserEmail' ); - $this->mRealName = $wgAllowRealName ? $request->getVal( 'wpRealName' ) : ''; - $this->mEmailFlag = $request->getCheck( 'wpEmailFlag' ) ? 0 : 1; - $this->mNick = $request->getVal( 'wpNick' ); - $this->mUserLanguage = $request->getVal( 'wpUserLanguage' ); - $this->mUserVariant = $request->getVal( 'wpUserVariant' ); - $this->mSearch = $request->getVal( 'wpSearch' ); - $this->mRecent = $request->getVal( 'wpRecent' ); - $this->mRecentDays = $request->getVal( 'wpRecentDays' ); - $this->mHourDiff = $request->getVal( 'wpHourDiff' ); - $this->mSearchLines = $request->getVal( 'wpSearchLines' ); - $this->mSearchChars = $request->getVal( 'wpSearchChars' ); - $this->mImageSize = $request->getVal( 'wpImageSize' ); - $this->mThumbSize = $request->getInt( 'wpThumbSize' ); - $this->mUnderline = $request->getInt( 'wpOpunderline' ); - $this->mAction = $request->getVal( 'action' ); - $this->mReset = $request->getCheck( 'wpReset' ); - $this->mPosted = $request->wasPosted(); - $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' ) && - $this->mPosted && - $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - - # User toggles (the big ugly unsorted list of checkboxes) - $this->mToggles = array(); - if ( $this->mPosted ) { - $togs = User::getToggles(); - foreach ( $togs as $tname ) { - $this->mToggles[$tname] = $request->getCheck( "wpOp$tname" ) ? 1 : 0; - } - } - - $this->mUsedToggles = array(); - - # Search namespace options - # Note: namespaces don't necessarily have consecutive keys - $this->mSearchNs = array(); - if ( $this->mPosted ) { - $namespaces = $wgContLang->getNamespaces(); - foreach ( $namespaces as $i => $namespace ) { - if ( $i >= 0 ) { - $this->mSearchNs[$i] = $request->getCheck( "wpNs$i" ) ? 1 : 0; - } - } - } - - # Validate language - if ( !preg_match( '/^[a-z\-]*$/', $this->mUserLanguage ) ) { - $this->mUserLanguage = 'nolanguage'; - } - - wfRunHooks( 'InitPreferencesForm', array( $this, $request ) ); - } - - function execute() { - global $wgUser, $wgOut; - - if ( $wgUser->isAnon() ) { - $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext' ); - return; - } - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - if ( $this->mReset ) { - $this->resetPrefs(); - $this->mainPrefsForm( 'reset', wfMsg( 'prefsreset' ) ); - } else if ( $this->mSaveprefs ) { - $this->savePreferences(); - } else { - $this->resetPrefs(); - $this->mainPrefsForm( '' ); - } - } - /** - * @access private - */ - function validateInt( &$val, $min=0, $max=0x7fffffff ) { - $val = intval($val); - $val = min($val, $max); - $val = max($val, $min); - return $val; - } - - /** - * @access private - */ - function validateFloat( &$val, $min, $max=0x7fffffff ) { - $val = floatval( $val ); - $val = min( $val, $max ); - $val = max( $val, $min ); - return( $val ); - } - - /** - * @access private - */ - function validateIntOrNull( &$val, $min=0, $max=0x7fffffff ) { - $val = trim($val); - if($val === '') { - return null; - } else { - return $this->validateInt( $val, $min, $max ); - } - } - - /** - * @access private - */ - function validateDate( $val ) { - global $wgLang, $wgContLang; - if ( $val !== false && ( - in_array( $val, (array)$wgLang->getDatePreferences() ) || - in_array( $val, (array)$wgContLang->getDatePreferences() ) ) ) - { - return $val; - } else { - return $wgLang->getDefaultDateFormat(); - } - } - - /** - * 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', - * @access private - * @param string $s the user input - * @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 ); - } - return $s; - } - - /** - * @access private - */ - function savePreferences() { - global $wgUser, $wgOut, $wgParser; - global $wgEnableUserEmail, $wgEnableEmail; - 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; - - if( $wgUser->getOption( 'language' ) !== $this->mUserLanguage ) { - $needRedirect = true; - } else { - $needRedirect = false; - } - - # Validate the signature and clean it up as needed - global $wgMaxSigChars; - if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) { - global $wgLang; - $this->mainPrefsForm( 'error', - wfMsgExt( 'badsiglength', 'parsemag', $wgLang->formatNum( $wgMaxSigChars ) ) ); - return; - } elseif( $this->mToggles['fancysig'] ) { - if( $wgParser->validateSig( $this->mNick ) !== false ) { - $this->mNick = $wgParser->cleanSig( $this->mNick ); - } else { - $this->mainPrefsForm( 'error', wfMsg( 'badsig' ) ); - return; - } - } else { - // When no fancy sig used, make sure ~{3,5} get removed. - $this->mNick = $wgParser->cleanSigInSig( $this->mNick ); - } - - $wgUser->setOption( 'language', $this->mUserLanguage ); - $wgUser->setOption( 'variant', $this->mUserVariant ); - $wgUser->setOption( 'nickname', $this->mNick ); - $wgUser->setOption( 'quickbar', $this->mQuickbar ); - $wgUser->setOption( 'skin', $this->mSkin ); - global $wgUseTeX; - if( $wgUseTeX ) { - $wgUser->setOption( 'math', $this->mMath ); - } - $wgUser->setOption( 'date', $this->validateDate( $this->mDate ) ); - $wgUser->setOption( 'searchlimit', $this->validateIntOrNull( $this->mSearch ) ); - $wgUser->setOption( 'contextlines', $this->validateIntOrNull( $this->mSearchLines ) ); - $wgUser->setOption( 'contextchars', $this->validateIntOrNull( $this->mSearchChars ) ); - $wgUser->setOption( 'rclimit', $this->validateIntOrNull( $this->mRecent ) ); - $wgUser->setOption( 'rcdays', $this->validateInt($this->mRecentDays, 1, ceil($wgRCMaxAge / (3600*24)))); - $wgUser->setOption( 'wllimit', $this->validateIntOrNull( $this->mWatchlistEdits, 0, 1000 ) ); - $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( '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 - foreach( $this->mSearchNs as $i => $value ) { - $wgUser->setOption( "searchNs{$i}", $value ); - } - - if( $wgEnableEmail && $wgEnableUserEmail ) { - $wgUser->setOption( 'disablemail', $this->mEmailFlag ); - } - - # Set user toggles - foreach ( $this->mToggles as $tname => $tvalue ) { - $wgUser->setOption( $tname, $tvalue ); - } - - $error = false; - if( $wgEnableEmail ) { - $newadr = $this->mUserEmail; - $oldadr = $wgUser->getEmail(); - if( ($newadr != '') && ($newadr != $oldadr) ) { - # the user has supplied a new email address on the login page - if( $wgUser->isValidEmailAddr( $newadr ) ) { - # new behaviour: set this new emailaddr from login-page into user database record - $wgUser->setEmail( $newadr ); - # but flag as "dirty" = unauthenticated - $wgUser->invalidateEmail(); - if ($wgEmailAuthentication) { - # Mail a temporary password to the dirty address. - # User can come back through the confirmation URL to re-enable email. - $result = $wgUser->sendConfirmationMail(); - if( WikiError::isError( $result ) ) { - $error = wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) ); - } else { - $error = wfMsg( 'eauthentsent', $wgUser->getName() ); - } - } - } else { - $error = wfMsg( 'invalidemailaddress' ); - } - } else { - if( $wgEmailConfirmToEdit && empty( $newadr ) ) { - $this->mainPrefsForm( 'error', wfMsg( 'noemailtitle' ) ); - return; - } - $wgUser->setEmail( $this->mUserEmail ); - } - if( $oldadr != $newadr ) { - wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) ); - } - } - - if( !$wgAuth->updateExternalDB( $wgUser ) ){ - $this->mainPrefsForm( 'error', wfMsg( 'externaldberror' ) ); - return; - } - - $msg = ''; - if ( !wfRunHooks( 'SavePreferences', array( $this, $wgUser, &$msg, $oldOptions ) ) ) { - $this->mainPrefsForm( 'error', $msg ); - return; - } - - $wgUser->setCookies(); - $wgUser->saveSettings(); - - if( $needRedirect && $error === false ) { - $title = SpecialPage::getTitleFor( 'Preferences' ); - $wgOut->redirect( $title->getFullURL( 'success' ) ); - return; - } - - $wgOut->parserOptions( ParserOptions::newFromUser( $wgUser ) ); - $this->mainPrefsForm( $error === false ? 'success' : 'error', $error); - } - - /** - * @access private - */ - function resetPrefs() { - global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName; - - $this->mOldpass = $this->mNewpass = $this->mRetypePass = ''; - $this->mUserEmail = $wgUser->getEmail(); - $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp(); - $this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : ''; - - # language value might be blank, default to content language - $this->mUserLanguage = $wgUser->getOption( 'language', $wgContLanguageCode ); - - $this->mUserVariant = $wgUser->getOption( 'variant'); - $this->mEmailFlag = $wgUser->getOption( 'disablemail' ) == 1 ? 1 : 0; - $this->mNick = $wgUser->getOption( 'nickname' ); - - $this->mQuickbar = $wgUser->getOption( 'quickbar' ); - $this->mSkin = Skin::normalizeKey( $wgUser->getOption( 'skin' ) ); - $this->mMath = $wgUser->getOption( 'math' ); - $this->mDate = $wgUser->getDatePreference(); - $this->mRows = $wgUser->getOption( 'rows' ); - $this->mCols = $wgUser->getOption( 'cols' ); - $this->mStubs = $wgUser->getOption( 'stubthreshold' ); - $this->mHourDiff = $wgUser->getOption( 'timecorrection' ); - $this->mSearch = $wgUser->getOption( 'searchlimit' ); - $this->mSearchLines = $wgUser->getOption( 'contextlines' ); - $this->mSearchChars = $wgUser->getOption( 'contextchars' ); - $this->mImageSize = $wgUser->getOption( 'imagesize' ); - $this->mThumbSize = $wgUser->getOption( 'thumbsize' ); - $this->mRecent = $wgUser->getOption( 'rclimit' ); - $this->mRecentDays = $wgUser->getOption( 'rcdays' ); - $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(); - foreach ( $togs as $tname ) { - $this->mToggles[$tname] = $wgUser->getOption( $tname ); - } - - $namespaces = $wgContLang->getNamespaces(); - foreach ( $namespaces as $i => $namespace ) { - if ( $i >= NS_MAIN ) { - $this->mSearchNs[$i] = $wgUser->getOption( 'searchNs'.$i ); - } - } - - wfRunHooks( 'ResetPreferences', array( $this, $wgUser ) ); - } - - /** - * @access private - */ - function namespacesCheckboxes() { - global $wgContLang; - - # Determine namespace checkboxes - $namespaces = $wgContLang->getNamespaces(); - $r1 = null; - - foreach ( $namespaces as $i => $name ) { - if ($i < 0) - continue; - $checked = $this->mSearchNs[$i] ? "checked='checked'" : ''; - $name = str_replace( '_', ' ', $namespaces[$i] ); - - if ( empty($name) ) - $name = wfMsg( 'blanknamespace' ); - - $r1 .= "
    \n"; - } - return $r1; - } - - - function getToggle( $tname, $trailer = false, $disabled = false ) { - global $wgUser, $wgLang; - - $this->mUsedToggles[$tname] = true; - $ttext = $wgLang->getUserToggle( $tname ); - - $checked = $wgUser->getOption( $tname ) == 1 ? ' checked="checked"' : ''; - $disabled = $disabled ? ' disabled="disabled"' : ''; - $trailer = $trailer ? $trailer : ''; - return "
    " . - " $trailer
    \n"; - } - - function getToggles( $items ) { - $out = ""; - foreach( $items as $item ) { - if( $item === false ) - continue; - if( is_array( $item ) ) { - list( $key, $trailer ) = $item; - } else { - $key = $item; - $trailer = false; - } - $out .= $this->getToggle( $key, $trailer ); - } - return $out; - } - - function addRow($td1, $td2) { - return "$td1$td2"; - } - - /** - * Helper function for user information panel - * @param $td1 label for an item - * @param $td2 item or null - * @param $td3 optional help or null - * @return xhtml block - */ - function tableRow( $td1, $td2 = null, $td3 = null ) { - - if ( is_null( $td3 ) ) { - $td3 = ''; - } else { - $td3 = Xml::tags( 'tr', null, - Xml::tags( 'td', array( 'class' => 'pref-label', 'colspan' => '2' ), $td3 ) - ); - } - - if ( is_null( $td2 ) ) { - $td1 = Xml::tags( 'td', array( 'class' => 'pref-label', 'colspan' => '2' ), $td1 ); - $td2 = ''; - } else { - $td1 = Xml::tags( 'td', array( 'class' => 'pref-label' ), $td1 ); - $td2 = Xml::tags( 'td', array( 'class' => 'pref-input' ), $td2 ); - } - - return Xml::tags( 'tr', null, $td1 . $td2 ). $td3 . "\n"; - - } - - /** - * @access private - */ - function mainPrefsForm( $status , $message = '' ) { - global $wgUser, $wgOut, $wgLang, $wgContLang; - global $wgAllowRealName, $wgImageLimits, $wgThumbLimits; - global $wgDisableLangConversion; - global $wgEnotifWatchlist, $wgEnotifUserTalk,$wgEnotifMinorEdits; - global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress; - global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication; - global $wgContLanguageCode, $wgDefaultSkin, $wgSkipSkins, $wgAuth; - global $wgEmailConfirmToEdit, $wgAjaxSearch, $wgEnableMWSuggest; - - $wgOut->setPageTitle( wfMsg( 'preferences' ) ); - $wgOut->setArticleRelated( false ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->addScriptFile( 'prefs.js' ); - - $wgOut->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc. - - if ( $this->mSuccess || 'success' == $status ) { - $wgOut->wrapWikiMsg( '
    $1
    ', 'savedprefs' ); - } else if ( 'error' == $status ) { - $wgOut->addWikiText( '
    ' . $message . '
    ' ); - } else if ( '' != $status ) { - $wgOut->addWikiText( $message . "\n----" ); - } - - $qbs = $wgLang->getQuickbarSettings(); - $skinNames = $wgLang->getSkinNames(); - $mathopts = $wgLang->getMathNames(); - $dateopts = $wgLang->getDatePreferences(); - $togs = User::getToggles(); - - $titleObj = SpecialPage::getTitleFor( 'Preferences' ); - $action = $titleObj->escapeLocalURL(); - - # Pre-expire some toggles so they won't show if disabled - $this->mUsedToggles[ 'shownumberswatching' ] = true; - $this->mUsedToggles[ 'showupdated' ] = true; - $this->mUsedToggles[ 'enotifwatchlistpages' ] = true; - $this->mUsedToggles[ 'enotifusertalkpages' ] = true; - $this->mUsedToggles[ 'enotifminoredits' ] = true; - $this->mUsedToggles[ 'enotifrevealaddr' ] = true; - $this->mUsedToggles[ 'ccmeonemails' ] = true; - $this->mUsedToggles[ 'uselivepreview' ] = true; - - - if ( !$this->mEmailFlag ) { $emfc = 'checked="checked"'; } - else { $emfc = ''; } - - - if ($wgEmailAuthentication && ($this->mUserEmail != '') ) { - if( $wgUser->getEmailAuthenticationTimestamp() ) { - $emailauthenticated = wfMsg('emailauthenticated',$wgLang->timeanddate($wgUser->getEmailAuthenticationTimestamp(), true ) ).'
    '; - $disableEmailPrefs = false; - } else { - $disableEmailPrefs = true; - $skin = $wgUser->getSkin(); - $emailauthenticated = wfMsg('emailnotauthenticated').'
    ' . - $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Confirmemail' ), - wfMsg( 'emailconfirmlink' ) ) . '
    '; - } - } else { - $emailauthenticated = ''; - $disableEmailPrefs = false; - } - - if ($this->mUserEmail == '') { - $emailauthenticated = wfMsg( 'noemailprefs' ) . '
    '; - } - - $ps = $this->namespacesCheckboxes(); - - $enotifwatchlistpages = ($wgEnotifWatchlist) ? $this->getToggle( 'enotifwatchlistpages', false, $disableEmailPrefs ) : ''; - $enotifusertalkpages = ($wgEnotifUserTalk) ? $this->getToggle( 'enotifusertalkpages', false, $disableEmailPrefs ) : ''; - $enotifminoredits = ($wgEnotifWatchlist && $wgEnotifMinorEdits) ? $this->getToggle( 'enotifminoredits', false, $disableEmailPrefs ) : ''; - $enotifrevealaddr = (($wgEnotifWatchlist || $wgEnotifUserTalk) && $wgEnotifRevealEditorAddress) ? $this->getToggle( 'enotifrevealaddr', false, $disableEmailPrefs ) : ''; - - # - - $wgOut->addHTML( "
    " ); - $wgOut->addHTML( "
    " ); - - # User data - - $wgOut->addHTML( - Xml::openElement( 'fieldset ' ) . - Xml::element( 'legend', null, wfMsg('prefs-personal') ) . - Xml::openElement( 'table' ) . - $this->tableRow( Xml::element( 'h2', null, wfMsg( 'prefs-personal' ) ) ) - ); - - # Get groups to which the user belongs - $userEffectiveGroups = $wgUser->getEffectiveGroups(); - $userEffectiveGroupsArray = array(); - foreach( $userEffectiveGroups as $ueg ) { - if( $ueg == '*' ) { - // Skip the default * group, seems useless here - continue; - } - $userEffectiveGroupsArray[] = User::makeGroupLinkHTML( $ueg ); - } - asort( $userEffectiveGroupsArray ); - - $sk = $wgUser->getSkin(); - $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. - # $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( - wfMsgExt( 'prefs-memberingroups', array( 'parseinline' ), count( $userEffectiveGroupsArray ) ), - implode( wfMsg( 'comma-separator' ), $userEffectiveGroupsArray ) . - '
    (' . implode( ' | ', $toolLinks ) . ')' - ) . - - $this->tableRow( - wfMsgHtml( 'prefs-edits' ), - $wgLang->formatNum( User::edits( $wgUser->getId() ) ) - ); - - if( wfRunHooks( 'PreferencesUserInformationPanel', array( $this, &$userInformationHtml ) ) ) { - $wgOut->addHtml( $userInformationHtml ); - } - - if ( $wgAllowRealName ) { - $wgOut->addHTML( - $this->tableRow( - Xml::label( wfMsg('yourrealname'), 'wpRealName' ), - Xml::input( 'wpRealName', 25, $this->mRealName, array( 'id' => 'wpRealName' ) ), - Xml::tags('div', array( 'class' => 'prefsectiontip' ), - wfMsgExt( 'prefs-help-realname', 'parseinline' ) - ) - ) - ); - } - if ( $wgEnableEmail ) { - $wgOut->addHTML( - $this->tableRow( - Xml::label( wfMsg('youremail'), 'wpUserEmail' ), - Xml::input( 'wpUserEmail', 25, $this->mUserEmail, array( 'id' => 'wpUserEmail' ) ), - Xml::tags('div', array( 'class' => 'prefsectiontip' ), - wfMsgExt( $wgEmailConfirmToEdit ? 'prefs-help-email-required' : 'prefs-help-email', 'parseinline' ) - ) - ) - ); - } - - global $wgParser, $wgMaxSigChars; - if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) { - $invalidSig = $this->tableRow( - ' ', - Xml::element( 'span', array( 'class' => 'error' ), - wfMsgExt( 'badsiglength', 'parsemag', $wgLang->formatNum( $wgMaxSigChars ) ) ) - ); - } elseif( !empty( $this->mToggles['fancysig'] ) && - false === $wgParser->validateSig( $this->mNick ) ) { - $invalidSig = $this->tableRow( - ' ', - Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) ) - ); - } else { - $invalidSig = ''; - } - - $wgOut->addHTML( - $this->tableRow( - Xml::label( wfMsg( 'yournick' ), 'wpNick' ), - Xml::input( 'wpNick', 25, $this->mNick, - array( - 'id' => 'wpNick', - // Note: $wgMaxSigChars is enforced in Unicode characters, - // both on the backend and now in the browser. - // Badly-behaved requests may still try to submit - // an overlong string, however. - 'maxlength' => $wgMaxSigChars ) ) - ) . - $invalidSig . - $this->tableRow( ' ', $this->getToggle( 'fancysig' ) ) - ); - - list( $lsLabel, $lsSelect) = Xml::languageSelector( $this->mUserLanguage ); - $wgOut->addHTML( - $this->tableRow( $lsLabel, $lsSelect ) - ); - - /* see if there are multiple language variants to choose from*/ - if(!$wgDisableLangConversion) { - $variants = $wgContLang->getVariants(); - $variantArray = array(); - - $languages = Language::getLanguageNames( true ); - foreach($variants as $v) { - $v = str_replace( '_', '-', strtolower($v)); - if( array_key_exists( $v, $languages ) ) { - // If it doesn't have a name, we'll pretend it doesn't exist - $variantArray[$v] = $languages[$v]; - } - } - - $options = "\n"; - foreach( $variantArray as $code => $name ) { - $selected = ($code == $this->mUserVariant); - $options .= Xml::option( "$code - $name", $code, $selected ) . "\n"; - } - - if(count($variantArray) > 1) { - $wgOut->addHtml( - $this->tableRow( - Xml::label( wfMsg( 'yourvariant' ), 'wpUserVariant' ), - Xml::tags( 'select', - array( 'name' => 'wpUserVariant', 'id' => 'wpUserVariant' ), - $options - ) - ) - ); - } - } - - # Password - if( $wgAuth->allowPasswordChange() ) { - $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" ) - ) - ) - ); - } - - # - # Enotif - if ( $wgEnableEmail ) { - - $moreEmail = ''; - if ($wgEnableUserEmail) { - // fixme -- the "allowemail" pseudotoggle is a hacked-together - // inversion for the "disableemail" preference. - $emf = wfMsg( 'allowemail' ); - $disabled = $disableEmailPrefs ? ' disabled="disabled"' : ''; - $moreEmail = - " " . - $this->getToggle( 'ccmeonemails', '', $disableEmailPrefs ); - } - - - $wgOut->addHTML( - $this->tableRow( Xml::element( 'h2', null, wfMsg( 'email' ) ) ) . - $this->tableRow( - $emailauthenticated. - $enotifrevealaddr. - $enotifwatchlistpages. - $enotifusertalkpages. - $enotifminoredits. - $moreEmail - ) - ); - } - # - - $wgOut->addHTML( - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ) - ); - - - # Quickbar - # - if ($this->mSkin == 'cologneblue' || $this->mSkin == 'standard') { - $wgOut->addHtml( "
    \n" . wfMsg( 'qbsettings' ) . "\n" ); - for ( $i = 0; $i < count( $qbs ); ++$i ) { - if ( $i == $this->mQuickbar ) { $checked = ' checked="checked"'; } - else { $checked = ""; } - $wgOut->addHTML( "
    \n" ); - } - $wgOut->addHtml( "
    \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 ) ); - } - - # Skin - # - $wgOut->addHTML( "
    \n\n" . wfMsg('skin') . "\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]; - } - } - asort($validSkinNames); - foreach ($validSkinNames as $skinkey => $sn ) { - if ( in_array( $skinkey, $wgSkipSkins ) ) { - continue; - } - $checked = $skinkey == $this->mSkin ? ' checked="checked"' : ''; - - $mplink = htmlspecialchars($mptitle->getLocalURL("useskin=$skinkey")); - $previewlink = "$previewtext"; - if( $skinkey == $wgDefaultSkin ) - $sn .= ' (' . wfMsg( 'default' ) . ')'; - $wgOut->addHTML( " $previewlink
    \n" ); - } - $wgOut->addHTML( "
    \n\n" ); - - # Math - # - global $wgUseTeX; - if( $wgUseTeX ) { - $wgOut->addHTML( "
    \n" . wfMsg('math') . '' ); - foreach ( $mathopts as $k => $v ) { - $checked = ($k == $this->mMath); - $wgOut->addHTML( - Xml::openElement( 'div' ) . - Xml::radioLabel( wfMsg( $v ), 'wpMath', $k, "mw-sp-math-$k", $checked ) . - Xml::closeElement( 'div' ) . "\n" - ); - } - $wgOut->addHTML( "
    \n\n" ); - } - - # Files - # - $wgOut->addHTML( - "
    \n" . Xml::element( 'legend', null, wfMsg( 'files' ) ) . "\n" - ); - - $imageLimitOptions = null; - foreach ( $wgImageLimits as $index => $limits ) { - $selected = ($index == $this->mImageSize); - $imageLimitOptions .= Xml::option( "{$limits[0]}×{$limits[1]}" . - wfMsg('unit-pixel'), $index, $selected ); - } - - $imageSizeId = 'wpImageSize'; - $wgOut->addHTML( - "
    " . Xml::label( wfMsg('imagemaxsize'), $imageSizeId ) . " " . - Xml::openElement( 'select', array( 'name' => $imageSizeId, 'id' => $imageSizeId ) ) . - $imageLimitOptions . - Xml::closeElement( 'select' ) . "
    \n" - ); - - $imageThumbOptions = null; - foreach ( $wgThumbLimits as $index => $size ) { - $selected = ($index == $this->mThumbSize); - $imageThumbOptions .= Xml::option($size . wfMsg('unit-pixel'), $index, - $selected); - } - - $thumbSizeId = 'wpThumbSize'; - $wgOut->addHTML( - "
    " . Xml::label( wfMsg('thumbsize'), $thumbSizeId ) . " " . - Xml::openElement( 'select', array( 'name' => $thumbSizeId, 'id' => $thumbSizeId ) ) . - $imageThumbOptions . - Xml::closeElement( 'select' ) . "
    \n" - ); - - $wgOut->addHTML( "
    \n\n" ); - - # Date format - # - # Date/Time - # - - $wgOut->addHTML( - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'datetime' ) ) . "\n" - ); - - if ($dateopts) { - $wgOut->addHTML( - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'dateformat' ) ) . "\n" - ); - $idCnt = 0; - $epoch = '20010115161234'; # Wikipedia day - foreach( $dateopts as $key ) { - if( $key == 'default' ) { - $formatted = wfMsg( 'datedefault' ); - } else { - $formatted = $wgLang->timeanddate( $epoch, false, $key ); - } - $wgOut->addHTML( - Xml::tags( 'div', null, - Xml::radioLabel( $formatted, 'wpDate', $key, "wpDate$idCnt", $key == $this->mDate ) - ) . "\n" - ); - $idCnt++; - } - $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . "\n" ); - } - - $nowlocal = $wgLang->time( $now = wfTimestampNow(), true ); - $nowserver = $wgLang->time( $now, false ); - - $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( - Xml::label( wfMsg( 'timezoneoffset' ), 'wpHourDiff' ), - Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array( 'id' => 'wpHourDiff' ) ) ) . - " - - " . - Xml::element( 'input', - array( 'type' => 'button', - 'value' => wfMsg( 'guesstimezone' ), - 'onclick' => 'javascript:guessTimezone()', - 'id' => 'guesstimezonebutton', - 'style' => 'display:none;' ) ) . - " - " . - Xml::closeElement( 'table' ) . - Xml::tags( 'div', array( 'class' => 'prefsectiontip' ), wfMsgExt( 'timezonetext', 'parseinline' ) ). - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'fieldset' ) . "\n\n" - ); - - # Editing - # - global $wgLivePreview; - $wgOut->addHTML( '
    ' . wfMsg( 'textboxsize' ) . ' -
    ' . - wfInputLabel( wfMsg( 'rows' ), 'wpRows', 'wpRows', 3, $this->mRows ) . - ' ' . - wfInputLabel( wfMsg( 'columns' ), 'wpCols', 'wpCols', 3, $this->mCols ) . - "
    " . - $this->getToggles( array( - 'editsection', - 'editsectiononrightclick', - 'editondblclick', - 'editwidth', - 'showtoolbar', - 'previewonfirst', - 'previewontop', - 'minordefault', - 'externaleditor', - 'externaldiff', - $wgLivePreview ? 'uselivepreview' : false, - 'forceeditsummary', - ) ) . '
    ' - ); - - # Recent changes - $wgOut->addHtml( '
    ' . wfMsgHtml( 'prefs-rc' ) . '' ); - - $rc = ''; - $rc .= ''; - $rc .= ''; - $rc .= ''; - $rc .= ''; - $rc .= ''; - $rc .= '
    ' . Xml::label( wfMsg( 'recentchangesdays' ), 'wpRecentDays' ) . '' . Xml::input( 'wpRecentDays', 3, $this->mRecentDays, array( 'id' => 'wpRecentDays' ) ) . '
    ' . Xml::label( wfMsg( 'recentchangescount' ), 'wpRecent' ) . '' . Xml::input( 'wpRecent', 3, $this->mRecent, array( 'id' => 'wpRecent' ) ) . '
    '; - $wgOut->addHtml( $rc ); - - $wgOut->addHtml( '
    ' ); - - $toggles[] = 'hideminor'; - if( $wgRCShowWatchingUsers ) - $toggles[] = 'shownumberswatching'; - $toggles[] = 'usenewrc'; - $wgOut->addHtml( $this->getToggles( $toggles ) ); - - $wgOut->addHtml( '
    ' ); - - # Watchlist - $wgOut->addHtml( '
    ' . wfMsgHtml( 'prefs-watchlist' ) . '' ); - - $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) ); - $wgOut->addHtml( '

    ' ); - - $wgOut->addHtml( $this->getToggle( 'extendwatchlist' ) ); - $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) ); - $wgOut->addHtml( '

    ' ); - - $wgOut->addHtml( $this->getToggles( array( 'watchlisthideown', 'watchlisthidebots', 'watchlisthideminor' ) ) ); - - 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 ) ); - } - $this->mUsedToggles['watchcreations'] = true; - $this->mUsedToggles['watchdefault'] = true; - $this->mUsedToggles['watchmoves'] = true; - $this->mUsedToggles['watchdeletion'] = true; - - $wgOut->addHtml( '
    ' ); - - # 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' ), - Xml::check( 'wpDisableMWSuggest', $this->mDisableMWSuggest, array( 'id' => 'wpDisableMWSuggest' ) ) - ) : ''; - $wgOut->addHTML( - // Elements for the search tab itself - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'searchresultshead' ) ) . - // Elements for the search options in the search tab - 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' ) ) - ) . - $this->addRow( - Xml::label( wfMsg( 'contextlines' ), 'wpSearchLines' ), - Xml::input( 'wpSearchLines', 4, $this->mSearchLines, array( 'id' => 'wpSearchLines' ) ) - ) . - $this->addRow( - Xml::label( wfMsg( 'contextchars' ), 'wpSearchChars' ), - Xml::input( 'wpSearchChars', 4, $this->mSearchChars, array( 'id' => 'wpSearchChars' ) ) - ) . - $mwsuggest . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ) . - // Elements for the namespace options in the search tab - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'prefs-namespaces' ) ) . - wfMsgExt( 'defaultns', array( 'parse' ) ) . - $ps . - Xml::closeElement( 'fieldset' ) . - // End of the search tab - Xml::closeElement( 'fieldset' ) - ); - - # Misc - # - $wgOut->addHTML('
    ' . wfMsg('prefs-misc') . ''); - $wgOut->addHtml( ' ' ); - $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' ) ); - $msgUnderlinedefault = htmlspecialchars( wfMsg ( 'underline-default' ) ); - $uopt = $wgUser->getOption("underline"); - $s0 = $uopt == 0 ? ' selected="selected"' : ''; - $s1 = $uopt == 1 ? ' selected="selected"' : ''; - $s2 = $uopt == 2 ? ' selected="selected"' : ''; - $wgOut->addHTML(" -

    -

    "); - - foreach ( $togs as $tname ) { - if( !array_key_exists( $tname, $this->mUsedToggles ) ) { - $wgOut->addHTML( $this->getToggle( $tname ) ); - } - } - $wgOut->addHTML( '
    ' ); - - wfRunHooks( 'RenderPreferencesForm', array( $this, $wgOut ) ); - - $token = htmlspecialchars( $wgUser->editToken() ); - $skin = $wgUser->getSkin(); - $wgOut->addHTML( " -
    -
    - tooltipAndAccesskey('save')." /> - -
    - -
    - - -
    \n" ); - - $wgOut->addHtml( Xml::tags( 'div', array( 'class' => "prefcache" ), - wfMsgExt( 'clearyourcache', 'parseinline' ) ) - ); - } -} diff --git a/includes/specials/Prefixindex.php b/includes/specials/Prefixindex.php deleted file mode 100644 index 1819d4e106..0000000000 --- a/includes/specials/Prefixindex.php +++ /dev/null @@ -1,152 +0,0 @@ -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 - * @ingroup SpecialPage - */ -class SpecialPrefixindex extends SpecialAllpages { - // Inherit $maxPerPage - - // Define other properties - protected $name = 'Prefixindex'; - protected $nsfromMsg = 'allpagesprefix'; - - /** - * @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 ) { - global $wgOut, $wgUser, $wgContLang; - - $fname = 'indexShowChunk'; - - $sk = $wgUser->getSkin(); - - if (!isset($from)) $from = $prefix; - - $fromList = $this->getNamespaceKeyAndText($namespace, $from); - $prefixList = $this->getNamespaceKeyAndText($namespace, $prefix); - $namespaces = $wgContLang->getNamespaces(); - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - if ( !$prefixList || !$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, $prefixKey, $prefix ) = $prefixList; - list( /* $fromNs */, $fromKey, $from ) = $fromList; - - ### FIXME: should complain if $fromNs != $namespace - - $dbr = wfGetDB( DB_SLAVE ); - - $res = $dbr->select( 'page', - array( 'page_namespace', 'page_title', 'page_is_redirect' ), - array( - 'page_namespace' => $namespace, - 'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'', - 'page_title >= ' . $dbr->addQuotes( $fromKey ), - ), - $fname, - array( - 'ORDER BY' => 'page_title', - 'LIMIT' => $this->maxPerPage + 1, - 'USE INDEX' => 'name_title', - ) - ); - - ### FIXME: side link to previous - - $n = 0; - if( $res->numRows() > 0 ) { - $out = ''; - - while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $t = Title::makeTitle( $s->page_namespace, $s->page_title ); - if( $t ) { - $link = ($s->page_is_redirect ? '
    ' : '' ) . - $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . - ($s->page_is_redirect ? '
    ' : '' ); - } else { - $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; - } - if( $n % 3 == 0 ) { - $out .= ''; - } - $out .= ""; - $n++; - if( $n % 3 == 0 ) { - $out .= ''; - } - } - if( ($n % 3) != 0 ) { - $out .= ''; - } - $out .= '
    $link
    '; - } else { - $out = ''; - } - } - - if ( $including ) { - $out2 = ''; - } else { - $nsForm = $this->namespaceForm ( $namespace, $prefix ); - $out2 = ''; - $out2 .= '
    ' . $nsForm; - $out2 .= '' . - $sk->makeKnownLink( $wgContLang->specialPage( $this->name ), - wfMsg ( 'allpages' ) ); - if ( isset($dbr) && $dbr && ($n == $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $namespaceparam = $namespace ? "&namespace=$namespace" : ""; - $out2 .= " | " . $sk->makeKnownLink( - $wgContLang->specialPage( $this->name ), - wfMsg ( 'nextpage', $s->page_title ), - "from=" . wfUrlEncode ( $s->page_title ) . - "&prefix=" . wfUrlEncode ( $prefix ) . $namespaceparam ); - } - $out2 .= "

    "; - } - - $wgOut->addHtml( $out2 . $out ); - } -} diff --git a/includes/specials/Protectedpages.php b/includes/specials/Protectedpages.php deleted file mode 100644 index e168902897..0000000000 --- a/includes/specials/Protectedpages.php +++ /dev/null @@ -1,313 +0,0 @@ -setPagetitle( wfMsg( "protectedpages" ) ); - if ( "" != $msg ) { - $wgOut->setSubtitle( $msg ); - } - - // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { - Title::purgeExpiredRestrictions(); - } - - $type = $wgRequest->getVal( $this->IdType ); - $level = $wgRequest->getVal( $this->IdLevel ); - $sizetype = $wgRequest->getVal( 'sizetype' ); - $size = $wgRequest->getIntOrNull( 'size' ); - $NS = $wgRequest->getIntOrNull( 'namespace' ); - $indefOnly = $wgRequest->getBool( 'indefonly' ) ? 1 : 0; - - $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly ); - - $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly ) ); - - if ( $pager->getNumRows() ) { - $s = $pager->getNavigationBar(); - $s .= "
      " . - $pager->getBody() . - "
    "; - $s .= $pager->getNavigationBar(); - } else { - $s = '

    ' . wfMsgHtml( 'protectedpagesempty' ) . '

    '; - } - $wgOut->addHTML( $s ); - } - - /** - * Callback function to output a restriction - * @param $row object Protected title - * @return string Formatted
  • element - */ - public function formatRow( $row ) { - global $wgUser, $wgLang, $wgContLang; - - wfProfileIn( __METHOD__ ); - - static $skin=null; - - if( is_null( $skin ) ) - $skin = $wgUser->getSkin(); - - $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - $link = $skin->makeLinkObj( $title ); - - $description_items = array (); - - $protType = wfMsgHtml( 'restriction-level-' . $row->pr_level ); - - $description_items[] = $protType; - - if ( $row->pr_cascade ) { - $description_items[] = wfMsg( 'protect-summary-cascade' ); - } - - $expiry_description = ''; - $stxt = ''; - - if ( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { - $expiry = Block::decodeExpiry( $row->pr_expiry ); - - $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); - - $description_items[] = $expiry_description; - } - - if (!is_null($size = $row->page_len)) { - if ($size == 0) - $stxt = ' ' . wfMsgHtml('historyempty') . ''; - else - $stxt = ' ' . wfMsgHtml('historysize', $wgLang->formatNum( $size ) ) . ''; - $stxt = $wgContLang->getDirMark() . $stxt; - } - - # Show a link to the change protection form for allowed users otherwise a link to the protection log - if( $wgUser->isAllowed( 'protect' ) ) { - $changeProtection = ' (' . $skin->makeKnownLinkObj( $title, wfMsgHtml( 'protect_change' ), 'action=unprotect' ) . ')'; - } else { - $ltitle = SpecialPage::getTitleFor( 'Log' ); - $changeProtection = ' (' . $skin->makeKnownLinkObj( $ltitle, wfMsgHtml( 'protectlogpage' ), 'type=protect&page=' . $title->getPrefixedUrl() ) . ')'; - } - - wfProfileOut( __METHOD__ ); - - return '
  • ' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . $changeProtection . "
  • \n"; - } - - /** - * @param $namespace int - * @param $type string - * @param $level string - * @param $minsize int - * @param $indefOnly bool - * @return string Input form - * @private - */ - protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly ) { - 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" . - $this->getNamespaceMenu( $namespace ) . " \n" . - $this->getTypeMenu( $type ) . " \n" . - $this->getLevelMenu( $level ) . " \n" . - "
      " . - $this->getExpiryCheck( $indefOnly ) . " \n" . - $this->getSizeLimit( $sizetype, $size ) . " \n" . - "" . - " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ); - } - - /** - * Prepare the namespace filter drop-down; standard namespace - * selector, sans the MediaWiki namespace - * - * @param mixed $namespace Pre-select namespace - * @return string - */ - protected function getNamespaceMenu( $namespace = null ) { - return "" . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' - . Xml::namespaceSelector( $namespace, '' ) . ""; - } - - /** - * @return string Formatted HTML - */ - protected function getExpiryCheck( $indefOnly ) { - return - Xml::checkLabel( wfMsg('protectedpages-indef'), 'indefonly', 'indefonly', $indefOnly ) . "\n"; - } - - /** - * @return string Formatted HTML - */ - protected function getSizeLimit( $sizetype, $size ) { - $max = $sizetype === 'max'; - - return - Xml::radioLabel( wfMsg('minimum-size'), 'sizetype', 'min', 'wpmin', !$max ) . - ' ' . - Xml::radioLabel( wfMsg('maximum-size'), 'sizetype', 'max', 'wpmax', $max ) . - ' ' . - Xml::input( 'size', 9, $size, array( 'id' => 'wpsize' ) ) . - ' ' . - Xml::label( wfMsg('pagesize'), 'wpsize' ); - } - - /** - * @return string Formatted HTML - */ - protected function getTypeMenu( $pr_type ) { - global $wgRestrictionTypes; - - $m = array(); // Temporary array - $options = array(); - - // First pass to load the log names - foreach( $wgRestrictionTypes as $type ) { - $text = wfMsg("restriction-$type"); - $m[$text] = $type; - } - - // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_type ); - $options[] = Xml::option( $text, $type, $selected ) . "\n"; - } - - return "" . - Xml::label( wfMsg('restriction-type') , $this->IdType ) . ' ' . - Xml::tags( 'select', - array( 'id' => $this->IdType, 'name' => $this->IdType ), - implode( "\n", $options ) ) . ""; - } - - /** - * @return string Formatted HTML - */ - protected function getLevelMenu( $pr_level ) { - global $wgRestrictionLevels; - - $m = array( wfMsg('restriction-level-all') => 0 ); // Temporary array - $options = array(); - - // First pass to load the log names - foreach( $wgRestrictionLevels as $type ) { - if ( $type !='' && $type !='*') { - $text = wfMsg("restriction-level-$type"); - $m[$text] = $type; - } - } - - // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_level ); - $options[] = Xml::option( $text, $type, $selected ); - } - - return - Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . - Xml::tags( 'select', - array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), - implode( "\n", $options ) ); - } -} - -/** - * @todo document - * @ingroup Pager - */ -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 ) { - $this->mForm = $form; - $this->mConds = $conds; - $this->type = ( $type ) ? $type : 'edit'; - $this->level = $level; - $this->namespace = $namespace; - $this->sizetype = $sizetype; - $this->size = intval($size); - $this->indefonly = (bool)$indefonly; - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $lb = new LinkBatch; - while( $row = $this->mResult->fetchObject() ) { - $lb->add( $row->page_namespace, $row->page_title ); - } - $lb->execute(); - - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - return $this->mForm->formatRow( $row ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds[] = 'pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - $conds[] = 'page_id=pr_page'; - $conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type ); - - if( $this->sizetype=='min' ) { - $conds[] = 'page_len>=' . $this->size; - } else if( $this->sizetype=='max' ) { - $conds[] = 'page_len<=' . $this->size; - } - - if( $this->indefonly ) { - $conds[] = "pr_expiry = 'infinity' OR pr_expiry IS NULL"; - } - - if( $this->level ) - $conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level ); - if( !is_null($this->namespace) ) - $conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace ); - return array( - 'tables' => array( 'page_restrictions', 'page' ), - 'fields' => 'pr_id,page_namespace,page_title,page_len,pr_type,pr_level,pr_expiry,pr_cascade', - 'conds' => $conds - ); - } - - function getIndexField() { - return 'pr_id'; - } -} - -/** - * Constructor - */ -function wfSpecialProtectedpages() { - - $ppForm = new ProtectedPagesForm(); - - $ppForm->showList(); -} diff --git a/includes/specials/Protectedtitles.php b/includes/specials/Protectedtitles.php deleted file mode 100644 index 2ec68a66b2..0000000000 --- a/includes/specials/Protectedtitles.php +++ /dev/null @@ -1,216 +0,0 @@ -setPagetitle( wfMsg( "protectedtitles" ) ); - if ( "" != $msg ) { - $wgOut->setSubtitle( $msg ); - } - - // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { - Title::purgeExpiredRestrictions(); - } - - $type = $wgRequest->getVal( $this->IdType ); - $level = $wgRequest->getVal( $this->IdLevel ); - $sizetype = $wgRequest->getVal( 'sizetype' ); - $size = $wgRequest->getIntOrNull( 'size' ); - $NS = $wgRequest->getIntOrNull( 'namespace' ); - - $pager = new ProtectedTitlesPager( $this, array(), $type, $level, $NS, $sizetype, $size ); - - $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size ) ); - - if ( $pager->getNumRows() ) { - $s = $pager->getNavigationBar(); - $s .= "
      " . - $pager->getBody() . - "
    "; - $s .= $pager->getNavigationBar(); - } else { - $s = '

    ' . wfMsgHtml( 'protectedtitlesempty' ) . '

    '; - } - $wgOut->addHTML( $s ); - } - - /** - * Callback function to output a restriction - */ - function formatRow( $row ) { - global $wgUser, $wgLang, $wgContLang; - - wfProfileIn( __METHOD__ ); - - static $skin=null; - - if( is_null( $skin ) ) - $skin = $wgUser->getSkin(); - - $title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title ); - $link = $skin->makeLinkObj( $title ); - - $description_items = array (); - - $protType = wfMsgHtml( 'restriction-level-' . $row->pt_create_perm ); - - $description_items[] = $protType; - - $expiry_description = ''; $stxt = ''; - - if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) { - $expiry = Block::decodeExpiry( $row->pt_expiry ); - - $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); - - $description_items[] = $expiry_description; - } - - wfProfileOut( __METHOD__ ); - - return '
  • ' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . "
  • \n"; - } - - /** - * @param $namespace int - * @param $type string - * @param $level string - * @param $minsize int - * @private - */ - function showOptions( $namespace, $type='edit', $level, $sizetype, $size ) { - global $wgScript; - $action = htmlspecialchars( $wgScript ); - $title = SpecialPage::getTitleFor( 'ProtectedTitles' ); - $special = htmlspecialchars( $title->getPrefixedDBkey() ); - return "
    \n" . - '
    ' . - Xml::element( 'legend', array(), wfMsg( 'protectedtitles' ) ) . - Xml::hidden( 'title', $special ) . " \n" . - $this->getNamespaceMenu( $namespace ) . " \n" . - // $this->getLevelMenu( $level ) . "
    \n" . - " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . - "
    "; - } - - /** - * Prepare the namespace filter drop-down; standard namespace - * selector, sans the MediaWiki namespace - * - * @param mixed $namespace Pre-select namespace - * @return string - */ - function getNamespaceMenu( $namespace = null ) { - return Xml::label( wfMsg( 'namespace' ), 'namespace' ) - . ' ' - . Xml::namespaceSelector( $namespace, '' ); - } - - /** - * @return string Formatted HTML - * @private - */ - function getLevelMenu( $pr_level ) { - global $wgRestrictionLevels; - - $m = array( wfMsg('restriction-level-all') => 0 ); // Temporary array - $options = array(); - - // First pass to load the log names - foreach( $wgRestrictionLevels as $type ) { - if ( $type !='' && $type !='*') { - $text = wfMsg("restriction-level-$type"); - $m[$text] = $type; - } - } - - // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_level ); - $options[] = Xml::option( $text, $type, $selected ); - } - - return - Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . - Xml::tags( 'select', - array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), - implode( "\n", $options ) ); - } -} - -/** - * @todo document - * @ingroup Pager - */ -class ProtectedtitlesPager extends AlphabeticPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) { - $this->mForm = $form; - $this->mConds = $conds; - $this->level = $level; - $this->namespace = $namespace; - $this->size = intval($size); - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $lb = new LinkBatch; - - while ( $row = $this->mResult->fetchObject() ) { - $lb->add( $row->pt_namespace, $row->pt_title ); - } - - $lb->execute(); - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - return $this->mForm->formatRow( $row ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - - if( !is_null($this->namespace) ) - $conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace ); - return array( - 'tables' => 'protected_titles', - 'fields' => 'pt_namespace,pt_title,pt_create_perm,pt_expiry,pt_timestamp', - 'conds' => $conds - ); - } - - function getIndexField() { - return 'pt_timestamp'; - } -} - -/** - * Constructor - */ -function wfSpecialProtectedtitles() { - - $ppForm = new ProtectedTitlesForm(); - - $ppForm->showList(); -} diff --git a/includes/specials/Randompage.php b/includes/specials/Randompage.php deleted file mode 100644 index 0e7ada1d17..0000000000 --- a/includes/specials/Randompage.php +++ /dev/null @@ -1,100 +0,0 @@ -, Ilmari Karonen - * @license GNU General Public Licence 2.0 or later - */ -class RandomPage extends SpecialPage { - private $namespace = NS_MAIN; // namespace to select pages from - - function __construct( $name = 'Randompage' ){ - parent::__construct( $name ); - } - - public function getNamespace() { - return $this->namespace; - } - - public function setNamespace ( $ns ) { - if( $ns < NS_MAIN ) $ns = NS_MAIN; - $this->namespace = $ns; - } - - // select redirects instead of normal pages? - // Overriden by SpecialRandomredirect - public function isRedirect(){ - return false; - } - - public function execute( $par ) { - global $wgOut, $wgContLang; - - if ($par) - $this->setNamespace( $wgContLang->getNsIndex( $par ) ); - - $title = $this->getRandomTitle(); - - if( is_null( $title ) ) { - $this->setHeaders(); - $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages' ); - return; - } - - $query = $this->isRedirect() ? 'redirect=no' : ''; - $wgOut->redirect( $title->getFullUrl( $query ) ); - } - - - /** - * Choose a random title. - * @return Title object (or null if nothing to choose from) - */ - public function getRandomTitle() { - $randstr = wfRandom(); - $row = $this->selectRandomPageFromDB( $randstr ); - - /* If we picked a value that was higher than any in - * the DB, wrap around and select the page with the - * lowest value instead! One might think this would - * skew the distribution, but in fact it won't cause - * any more bias than what the page_random scheme - * causes anyway. Trust me, I'm a mathematician. :) - */ - if( !$row ) - $row = $this->selectRandomPageFromDB( "0" ); - - if( $row ) - return Title::makeTitleSafe( $this->namespace, $row->page_title ); - else - return null; - } - - private function selectRandomPageFromDB( $randstr ) { - global $wgExtraRandompageSQL; - $fname = 'RandomPage::selectRandomPageFromDB'; - - $dbr = wfGetDB( DB_SLAVE ); - - $use_index = $dbr->useIndexClause( 'page_random' ); - $page = $dbr->tableName( 'page' ); - - $ns = (int) $this->namespace; - $redirect = $this->isRedirect() ? 1 : 0; - - $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : ""; - $sql = "SELECT page_title - FROM $page $use_index - WHERE page_namespace = $ns - AND page_is_redirect = $redirect - AND page_random >= $randstr - $extra - ORDER BY page_random"; - - $sql = $dbr->limitResult( $sql, 1, 0 ); - $res = $dbr->query( $sql, $fname ); - return $dbr->fetchObject( $res ); - } -} diff --git a/includes/specials/Randomredirect.php b/includes/specials/Randomredirect.php deleted file mode 100644 index 629d5b3c85..0000000000 --- a/includes/specials/Randomredirect.php +++ /dev/null @@ -1,19 +0,0 @@ -, Ilmari Karonen - * @license GNU General Public Licence 2.0 or later - */ -class SpecialRandomredirect extends RandomPage { - function __construct(){ - parent::__construct( 'Randomredirect' ); - } - - // Override parent::isRedirect() - public function isRedirect(){ - return true; - } -} diff --git a/includes/specials/Recentchanges.php b/includes/specials/Recentchanges.php deleted file mode 100644 index 16904b9f47..0000000000 --- a/includes/specials/Recentchanges.php +++ /dev/null @@ -1,613 +0,0 @@ -includable( true ); - } - - public function getDefaultOptions() { - $opts = new FormOptions(); - - $opts->add( 'days', (int)User::getDefaultOption( 'rcdays' ) ); - $opts->add( 'limit', (int)User::getDefaultOption( 'rclimit' ) ); - $opts->add( 'from', '' ); - - $opts->add( 'hideminor', false ); - $opts->add( 'hidebots', true ); - $opts->add( 'hideanons', false ); - $opts->add( 'hideliu', false ); - $opts->add( 'hidepatrolled', false ); - $opts->add( 'hidemyself', false ); - - $opts->add( 'namespace', '', FormOptions::INTNULL ); - $opts->add( 'invert', false ); - - $opts->add( 'categories', '' ); - $opts->add( 'categories_any', false ); - - return $opts; -} - - public function setup( $parameters ) { - global $wgUser, $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 ) { - $this->parseParameters( $parameters, $opts ); - } - - $opts->validateIntBounds( 'limit', 0, 5000 ); - return $opts; - } - - public function feedSetup() { - global $wgFeedLimit, $wgRequest; - $opts = $this->getDefaultOptions(); - $opts->fetchValuesFromRequest( $wgRequest, array( 'days', 'limit', 'hideminor' ) ); - $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); - return $opts; - } - - public function execute( $parameters ) { - global $wgRequest, $wgOut; - $feedFormat = $wgRequest->getVal( 'feed' ); - - # 10 seconds server-side caching max - $wgOut->setSquidMaxage( 10 ); - - $lastmod = $this->checkLastModified( $feedFormat ); - if( $lastmod === false ){ - return; - } - - $opts = $feedFormat ? $this->feedSetup() : $this->setup( $parameters ); - $this->setHeaders(); - - // Fetch results, prepare a batch link existence check query - $rows = array(); - $batch = new LinkBatch; - $conds = $this->buildMainQueryConds( $opts ); - $res = $this->doMainQuery( $conds, $opts ); - $dbr = wfGetDB( DB_SLAVE ); - while( $row = $dbr->fetchObject( $res ) ){ - $rows[] = $row; - if ( !$feedFormat ) { - // User page and talk links - $batch->add( NS_USER, $row->rc_user_text ); - $batch->add( NS_USER_TALK, $row->rc_user_text ); - } - - } - $dbr->freeResult( $res ); - - if ( $feedFormat ) { - $feed = new ChangesFeed( $feedFormat, 'rcfeed' ); - $feedObj = $feed->getFeedObject( - wfMsgForContent( 'recentchanges' ), - wfMsgForContent( 'recentchanges-feed-description' ) - ); - $feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod ); - } else { - $batch->execute(); - $this->webOutput( $rows, $opts ); - } - - } - - 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; - - $m = array(); - if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) $opts['limit'] = $m[1]; - if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) $opts['days'] = $m[1]; - } - } - - # Get last modified date, for client caching - # Don't use this if we are using the patrol feature, patrol changes don't update the timestamp - 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 ) ){ - # Client cache fresh and headers sent, nothing more to do. - return false; - } - } - return $lastmod; - } - - public function buildMainQueryConds( FormOptions $opts ) { - global $wgUser; - - $dbr = wfGetDB( DB_SLAVE ); - $conds = array(); - - # It makes no sense to hide both anons and logged-in users - # Where this occurs, force anons to be shown - $forcebot = false; - if( $opts['hideanons'] && $opts['hideliu'] ){ - # Check if the user wants to show bots only - if( $opts['hidebots'] ){ - $opts['hideanons'] = false; - } else { - $forcebot = true; - $opts['hidebots'] = false; - } - } - - // Calculate cutoff - $cutoff_unixtime = time() - ( $opts['days'] * 86400 ); - $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); - $cutoff = $dbr->timestamp( $cutoff_unixtime ); - - $fromValid = preg_match('/^[0-9]{14}$/', $opts['from']); - if( $fromValid && $opts['from'] > wfTimestamp(TS_MW,$cutoff) ) { - $cutoff = $dbr->timestamp($opts['from']); - } else { - $opts->reset( 'from' ); - } - - $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff ); - - - $hidePatrol = $wgUser->useRCPatrol() && $opts['hidepatrolled']; - $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['hidemyself'] ) { - if( $wgUser->getId() ) { - $conds[] = 'rc_user != ' . $dbr->addQuotes( $wgUser->getId() ); - } else { - $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() ); - } - } - - # Namespace filtering - if ( $opts['namespace'] !== '' ) { - if ( !$opts['invert'] ) { - $conds[] = 'rc_namespace = ' . $dbr->addQuotes( $opts['namespace'] ); - } else { - $conds[] = 'rc_namespace != ' . $dbr->addQuotes( $opts['namespace'] ); - } - } - - return $conds; - } - - public function doMainQuery( $conds, $opts ) { - global $wgUser; - - $tables = array( 'recentchanges' ); - $join_conds = array(); - - $uid = $wgUser->getId(); - $dbr = wfGetDB( DB_SLAVE ); - $limit = $opts['limit']; - $namespace = $opts['namespace']; - $invert = $opts['invert']; - - // JOIN on watchlist for users - if( $wgUser->getId() ) { - $tables[] = 'watchlist'; - $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 ) ); - - // Is there either one namespace selected or excluded? - // Also, if this is "all" or main namespace, just use timestamp index. - if( is_null($namespace) || $invert || $namespace == NS_MAIN ) { - $res = $dbr->select( $tables, '*', $conds, __METHOD__, - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, - 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ), - $join_conds ); - // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! - } else { - // New pages - $sqlNew = $dbr->selectSQLText( $tables, '*', - array( 'rc_new' => 1 ) + $conds, - __METHOD__, - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, - 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ), - $join_conds ); - // Old pages - $sqlOld = $dbr->selectSQLText( $tables, '*', - array( 'rc_new' => 0 ) + $conds, - __METHOD__, - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, - 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ), - $join_conds ); - # Join the two fast queries, and sort the result set - $sql = "($sqlNew) UNION ($sqlOld) ORDER BY rc_timestamp DESC LIMIT $limit"; - $res = $dbr->query( $sql, __METHOD__ ); - } - - return $res; - } - - public function webOutput( $rows, $opts ) { - global $wgOut, $wgUser, $wgRCShowWatchingUsers, $wgShowUpdatedMarker; - global $wgAllowCategorizedRecentChanges; - - $limit = $opts['limit']; - - if ( !$this->including() ) { - // Output options box - $this->doHeader( $opts ); - } - - // And now for the content - $wgOut->setSyndicated( true ); - - $list = ChangesList::newFromUser( $wgUser ); - - if ( $wgAllowCategorizedRecentChanges ) { - rcFilterByCategories( $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; - } - - $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->recentChangesLine( $rc, !empty( $obj->wl_user ) ); - --$limit; - } - } - $s .= $list->endRecentChangesList(); - $wgOut->addHTML( $s ); - } - - public function doHeader( $opts ) { - global $wgScript, $wgOut; - $wgOut->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) ); - - $defaults = $opts->getAllValues(); - $nondefaults = $opts->getChangedValues(); - $opts->consumeValues( array( 'namespace', 'invert' ) ); - - $panel = array(); - $panel[] = rcOptionsPanel( $defaults, $nondefaults ); - $panel[] = '
    '; - - $extraOpts = array(); - $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); - - global $wgAllowCategorizedRecentChanges; - if ( $wgAllowCategorizedRecentChanges ) { - $extraOpts['category'] = $this->categoryFilterForm( $opts ); - } - - wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); - $extraOpts['submit'] = Xml::submitbutton( wfMsg('allpagessubmit') ); - - $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] ); - } else { - $out .= Xml::tags( 'td', array( 'colspan' => 2 ), $optionRow ); - } - $out .= Xml::closeElement( 'tr' ); - } - $out .= Xml::closeElement( 'table' ); - - $unconsumed = $opts->getUnconsumedValues(); - foreach ( $unconsumed as $key => $value ) { - $out .= Xml::hidden( $key, $value ); - } - - $t = $this->getTitle(); - $out .= Xml::hidden( 'title', $t->getPrefixedText() ); - $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); - $panel[] = $form; - $panelString = implode( "\n", $panel ); - - $wgOut->addHTML( - Xml::fieldset( wfMsg( 'recentchanges' ), $panelString, array( 'class' => 'rcoptions' ) ) - ); - } - - /** - * Creates the choose namespace selection - * - * @return string - */ - protected function namespaceFilterForm( FormOptions $opts ) { - $nsSelect = HTMLnamespaceselector( $opts['namespace'], '' ); - $nsLabel = Xml::label( wfMsg('namespace'), 'namespace' ); - $invert = Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $opts['invert'] ); - return array( $nsLabel, "$nsSelect $invert" ); - } - - protected function categoryFilterForm( FormOptions $opts ) { - list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'), - 'categories', 'mw-categories', false, $opts['categories'] ); - - $input .= ' ' . Xml::checkLabel( wfMsg('rc_categories_any'), - 'categories_any', 'mw-categories_any', $opts['categories_any'] ); - - return array( $label, $input ); - } - -} - -function rcFilterByCategories ( &$rows, FormOptions $opts ) { - $categories = array_map( 'trim', explode( "|" , $categories ) ); - - if( empty($categories) ) { - return; - } - - # Filter categories - $cats = array(); - foreach ( $opts['categories'] AS $cat ) { - $cat = trim( $cat ); - if ( $cat == "" ) continue; - $cats[] = $cat; - } - - # Filter articles - $articles = array(); - $a2r = array(); - 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) ) { - $articles[] = $id; - } - if ( !isset($a2r[$id]) ) { - $a2r[$id] = array(); - } - $a2r[$id][] = $k; - } - - # Shortcut? - if ( !count($articles) || !count($cats) ) - return ; - - # Look up - $c = new Categoryfinder ; - $c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ; - $match = $c->run(); - - # Filter - $newrows = array(); - foreach ( $match AS $id ) { - foreach ( $a2r[$id] AS $rev ) { - $k = $rev; - $newrows[$k] = $rows[$k]; - } - } - $rows = $newrows; -} - -/** - * - */ -function rcCountLink( $lim, $d, $page='Recentchanges', $more='', $active = false ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ), - ($lim ? $wgLang->formatNum( "{$lim}" ) : wfMsg( 'recentchangesall' ) ), "{$more}" . - ($d ? "days={$d}&" : '') . 'limit='.$lim, '', '', - $active ? 'style="font-weight: bold;"' : '' ); - return $s; -} - -/** - * - */ -function rcDaysLink( $lim, $d, $page='Recentchanges', $more='', $active = false ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ), - ($d ? $wgLang->formatNum( "{$d}" ) : wfMsg( 'recentchangesall' ) ), $more.'days='.$d . - ($lim ? '&limit='.$lim : ''), '', '', - $active ? 'style="font-weight: bold;"' : '' ); - return $s; -} - -/** - * Used by Recentchangeslinked - */ -function rcDayLimitLinks( $days, $limit, $page='Recentchanges', $more='', $doall = false, $minorLink = '', - $botLink = '', $liuLink = '', $patrLink = '', $myselfLink = '' ) { - global $wgRCLinkLimits, $wgRCLinkDays; - if ($more != '') $more .= '&'; - - # Sort data for display and make sure it's unique after we've added user data. - # FIXME: why does this piss around with globals like this? Why is $limit added on globally? - $wgRCLinkLimits[] = $limit; - $wgRCLinkDays[] = $days; - sort($wgRCLinkLimits); - sort($wgRCLinkDays); - $wgRCLinkLimits = array_unique($wgRCLinkLimits); - $wgRCLinkDays = array_unique($wgRCLinkDays); - - $cl = array(); - foreach( $wgRCLinkLimits as $countLink ) { - $cl[] = rcCountLink( $countLink, $days, $page, $more, $countLink == $limit ); - } - if( $doall ) $cl[] = rcCountLink( 0, $days, $page, $more ); - $cl = implode( ' | ', $cl); - - $dl = array(); - foreach( $wgRCLinkDays as $daysLink ) { - $dl[] = rcDaysLink( $limit, $daysLink, $page, $more, $daysLink == $days ); - } - if( $doall ) $dl[] = rcDaysLink( $limit, 0, $page, $more ); - $dl = implode( ' | ', $dl); - - $linkParts = array( 'minorLink' => 'minor', 'botLink' => 'bots', 'liuLink' => 'liu', 'patrLink' => 'patr', 'myselfLink' => 'mine' ); - foreach( $linkParts as $linkVar => $linkMsg ) { - if( $$linkVar != '' ) - $links[] = wfMsgHtml( 'rcshowhide' . $linkMsg, $$linkVar ); - } - - $shm = implode( ' | ', $links ); - $note = wfMsg( 'rclinks', $cl, $dl, $shm ); - return $note; -} - - -/** - * 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, $wgContLang; - $sk = $wgUser->getSkin(); - return $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchanges' ), - htmlspecialchars( $title ), wfArrayToCGI( $override, $options ), '', '', - $active ? 'style="font-weight: bold;"' : '' ); -} - -/** - * Creates the options panel. - * @param $defaults - * @param $nondefaults - */ -function rcOptionsPanel( $defaults, $nondefaults ) { - global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays; - - $options = $nondefaults + $defaults; - - 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 ) ); - - # 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); - - // limit links - foreach( $wgRCLinkLimits as $value ) { - $cl[] = makeOptionsLink( $wgLang->formatNum( $value ), - array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ; - } - $cl = implode( ' | ', $cl); - - // day links, reset 'from' to none - foreach( $wgRCLinkDays as $value ) { - $dl[] = makeOptionsLink( $wgLang->formatNum( $value ), - array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ; - } - $dl = implode( ' | ', $dl); - - - // show/hide links - $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' )); - $minorLink = makeOptionsLink( $showhide[1-$options['hideminor']], - array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults); - $botLink = makeOptionsLink( $showhide[1-$options['hidebots']], - array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults); - $anonsLink = makeOptionsLink( $showhide[ 1 - $options['hideanons'] ], - array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults ); - $liuLink = makeOptionsLink( $showhide[1-$options['hideliu']], - array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults); - $patrLink = makeOptionsLink( $showhide[1-$options['hidepatrolled']], - array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults); - $myselfLink = makeOptionsLink( $showhide[1-$options['hidemyself']], - array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults); - - $links[] = wfMsgHtml( 'rcshowhideminor', $minorLink ); - $links[] = wfMsgHtml( 'rcshowhidebots', $botLink ); - $links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink ); - $links[] = wfMsgHtml( 'rcshowhideliu', $liuLink ); - if( $wgUser->useRCPatrol() ) - $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink ); - $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink ); - $hl = implode( ' | ', $links ); - - // show from this onward link - $now = $wgLang->timeanddate( wfTimestampNow(), true ); - $tl = makeOptionsLink( $now, array( 'from' => wfTimestampNow()), $nondefaults ); - - $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter'), - $cl, $dl, $hl ); - $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter'), $tl ); - return "$note
    $rclinks
    $rclistfrom"; - -} \ No newline at end of file diff --git a/includes/specials/Recentchangeslinked.php b/includes/specials/Recentchangeslinked.php deleted file mode 100644 index 73d46f81a4..0000000000 --- a/includes/specials/Recentchangeslinked.php +++ /dev/null @@ -1,191 +0,0 @@ -getInt( 'days' ); - $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); - $hideminor = $wgRequest->getBool( 'hideminor' ) ? 1 : 0; - $showlinkedto = $wgRequest->getBool( 'showlinkedto' ) ? 1 : 0; - - $title = Title::newFromURL( $target ); - $target = $title ? $title->getPrefixedText() : ''; - - $wgOut->setPagetitle( wfMsg( 'recentchangeslinked' ) ); - $sk = $wgUser->getSkin(); - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'recentchangeslinked' ) ) . "\n" . - Xml::inputLabel( wfMsg( 'recentchangeslinked-page' ), 'target', 'recentchangeslinked-target', 40, $target ) . - "   " . - Xml::check( 'showlinkedto', $showlinkedto, array('id' => 'showlinkedto') ) . ' ' . - Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) . - "
    \n" . - Xml::hidden( 'title', $wgTitle->getPrefixedText() ). "\n" . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" - ); - - if ( !$target ) { - return; - } - $nt = Title::newFromURL( $target ); - if( !$nt ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; - } - $id = $nt->getArticleId(); - - $wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $nt->getPrefixedText() ) ); - $wgOut->setSyndicated(); - $wgOut->setFeedAppendQuery( "target=" . urlencode( $target ) ); - - if ( !$days ) { - $days = (int)$wgUser->getOption( 'rcdays', 7 ); - } - list( $limit, /* offset */ ) = wfCheckLimits( 100, 'rclimit' ); - - $dbr = wfGetDB( DB_SLAVE,'recentchangeslinked' ); - $cutoff = $dbr->timestamp( time() - ( $days * 86400 ) ); - - $hideminor = ($hideminor ? 1 : 0); - if ( $hideminor ) { - $mlink = $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchangeslinked' ), - wfMsg( 'show' ), 'target=' . htmlspecialchars( $nt->getPrefixedURL() ) . - "&days={$days}&limit={$limit}&hideminor=0&showlinkedto={$showlinkedto}" ); - } else { - $mlink = $sk->makeKnownLink( $wgContLang->specialPage( "Recentchangeslinked" ), - wfMsg( "hide" ), "target=" . htmlspecialchars( $nt->getPrefixedURL() ) . - "&days={$days}&limit={$limit}&hideminor=1&showlinkedto={$showlinkedto}" ); - } - if ( $hideminor ) { - $cmq = 'AND rc_minor=0'; - } else { $cmq = ''; } - - list($recentchanges, $categorylinks, $pagelinks, $watchlist) = - $dbr->tableNamesN( 'recentchanges', 'categorylinks', 'pagelinks', "watchlist" ); - - $uid = $wgUser->getId(); - // The fields we are selecting - $fields = "rc_cur_id,rc_namespace,rc_title, - rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,rc_log_type,rc_log_action,rc_params,rc_deleted, - rc_new, rc_id, rc_this_oldid, rc_last_oldid, rc_bot, rc_patrolled, rc_type, rc_old_len, rc_new_len"; - $fields .= $uid ? ",wl_user" : ""; - - // Check if this should be a feed - - $feed = false; - global $wgFeedLimit; - $feedFormat = $wgRequest->getVal( 'feed' ); - if( $feedFormat ) { - $feed = new ChangesFeed( $feedFormat, false ); - # Sanity check - if( $limit > $wgFeedLimit ) { - $limit = $wgFeedLimit; - } - } - - // If target is a Category, use categorylinks and invert from and to - if( $nt->getNamespace() == NS_CATEGORY ) { - $catkey = $dbr->addQuotes( $nt->getDBkey() ); - # The table clauses - $tables = "$categorylinks, $recentchanges"; - $tables .= $uid ? " LEFT JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : ""; - - $sql = "SELECT /* wfSpecialRecentchangeslinked */ $fields FROM $tables - WHERE rc_timestamp > '{$cutoff}' {$cmq} - AND cl_from=rc_cur_id - AND cl_to=$catkey - GROUP BY $fields ORDER BY rc_timestamp DESC LIMIT {$limit}"; // Shitty-ass GROUP BY by for postgres apparently - } else { - if( $showlinkedto ) { - $ns = $dbr->addQuotes( $nt->getNamespace() ); - $dbkey = $dbr->addQuotes( $nt->getDBkey() ); - $joinConds = "AND pl_namespace={$ns} AND pl_title={$dbkey} AND pl_from=rc_cur_id"; - } else { - $joinConds = "AND pl_namespace=rc_namespace AND pl_title=rc_title AND pl_from=$id"; - } - # The table clauses - $tables = "$pagelinks, $recentchanges"; - $tables .= $uid ? " LEFT JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : ""; - - $sql = "SELECT /* wfSpecialRecentchangeslinked */ $fields FROM $tables - WHERE rc_timestamp > '{$cutoff}' {$cmq} - {$joinConds} - GROUP BY $fields ORDER BY rc_timestamp DESC LIMIT {$limit}"; // Shitty-ass GROUP BY by for postgres apparently - } - # Actually do the query - $res = $dbr->query( $sql, __METHOD__ ); - $count = $dbr->numRows( $res ); - $rchanges = array(); - # Output feeds now and be done with it! - if( $feed ) { - if( $count ) { - $counter = 1; - while ( $limit ) { - if ( 0 == $count ) { break; } - $obj = $dbr->fetchObject( $res ); - --$count; - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - --$limit; - $rchanges[] = $obj; - } - } - $wgOut->disable(); - - $feedObj = $feed->getFeedObject( - wfMsgForContent( 'recentchangeslinked-title', $nt->getPrefixedText() ), - wfMsgForContent( 'recentchangeslinked' ) - ); - ChangesFeed::generateFeed( $rchanges, $feedObj ); - return; - } - - # Otherwise, carry on with regular output... - $wgOut->addHTML("< ".$sk->makeLinkObj($nt, "", "redirect=no" )."
    \n"); - $note = wfMsgExt( "rcnote", array ( 'parseinline' ), $limit, $days, $wgLang->timeAndDate( wfTimestampNow(), true ) ); - $wgOut->addHTML( "
    \n{$note}\n
    " ); - - $note = rcDayLimitlinks( $days, $limit, "Recentchangeslinked", - "target=" . $nt->getPrefixedURL() . "&hideminor={$hideminor}&showlinkedto={$showlinkedto}", - false, $mlink ); - - $wgOut->addHTML( $note."\n" ); - - $list = ChangesList::newFromUser( $wgUser ); - $s = $list->beginRecentChangesList(); - - if ( $count ) { - $counter = 1; - while ( $limit ) { - if ( 0 == $count ) { break; } - $obj = $dbr->fetchObject( $res ); - --$count; - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - $s .= $list->recentChangesLine( $rc , !empty( $obj->wl_user) ); - --$limit; - } - } else { - $wgOut->addWikiMsg('recentchangeslinked-noresult'); - } - $s .= $list->endRecentChangesList(); - - $dbr->freeResult( $res ); - $wgOut->addHTML( $s ); -} diff --git a/includes/specials/Resetpass.php b/includes/specials/Resetpass.php deleted file mode 100644 index 707b941dd4..0000000000 --- a/includes/specials/Resetpass.php +++ /dev/null @@ -1,167 +0,0 @@ -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' ); - } - } - - /** - * Main execution point - */ - function execute( $par ) { - global $wgUser, $wgAuth, $wgOut, $wgRequest; - - if( !$wgAuth->allowPasswordChange() ) { - $this->error( wfMsg( 'resetpass_forbidden' ) ); - return; - } - - if( $this->mName === null && !$wgRequest->wasPosted() ) { - $this->error( wfMsg( 'resetpass_missing' ) ); - return; - } - - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) { - $newpass = $wgRequest->getVal( 'wpNewPassword' ); - $retype = $wgRequest->getVal( 'wpRetype' ); - try { - $this->attemptReset( $newpass, $retype ); - $wgOut->addWikiMsg( 'resetpass_success' ); - - $data = array( - 'action' => 'submitlogin', - 'wpName' => $this->mName, - 'wpPassword' => $newpass, - 'returnto' => $wgRequest->getVal( 'returnto' ), - ); - if( $wgRequest->getCheck( 'wpRemember' ) ) { - $data['wpRemember'] = 1; - } - $login = new LoginForm( new FauxRequest( $data, true ) ); - $login->execute(); - - return; - } catch( PasswordError $e ) { - $this->error( $e->getMessage() ); - } - } - $this->showForm(); - } - - function error( $msg ) { - global $wgOut; - $wgOut->addHtml( '
    ' . - htmlspecialchars( $msg ) . - '
    ' ); - } - - function showForm() { - global $wgOut, $wgUser, $wgRequest; - - $wgOut->disallowUserJs(); - - $self = SpecialPage::getTitleFor( 'Resetpass' ); - $form = - '
    ' . - wfOpenElement( 'form', - array( - 'method' => 'post', - 'action' => $self->getLocalUrl() ) ) . - '

    ' . wfMsgHtml( 'resetpass_header' ) . '

    ' . - '
    ' . - wfMsgExt( 'resetpass_text', array( 'parse' ) ) . - '
    ' . - '' . - wfHidden( 'token', $wgUser->editToken() ) . - wfHidden( 'wpName', $this->mName ) . - wfHidden( 'wpPassword', $this->mTemporaryPassword ) . - wfHidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . - $this->pretty( array( - array( 'wpName', 'username', 'text', $this->mName ), - array( 'wpNewPassword', 'newpassword', 'password', '' ), - array( 'wpRetype', 'yourpasswordagain', 'password', '' ), - ) ) . - '' . - '' . - '' . - '' . - '' . - '' . - '' . - '' . - '
    ' . - Xml::checkLabel( wfMsg( 'remembermypassword' ), - 'wpRemember', 'wpRemember', - $wgRequest->getCheck( 'wpRemember' ) ) . - '
    ' . - wfSubmitButton( wfMsgHtml( 'resetpass_submit' ) ) . - '
    ' . - wfCloseElement( 'form' ) . - '
    '; - $wgOut->addHtml( $form ); - } - - function pretty( $fields ) { - $out = ''; - foreach( $fields as $list ) { - list( $name, $label, $type, $value ) = $list; - if( $type == 'text' ) { - $field = '' . htmlspecialchars( $value ) . ''; - } else { - $field = Xml::input( $name, 20, $value, - array( 'id' => $name, 'type' => $type ) ); - } - $out .= ''; - $out .= ''; - $out .= Xml::label( wfMsg( $label ), $name ); - $out .= ''; - $out .= ''; - $out .= $field; - $out .= ''; - $out .= ''; - } - return $out; - } - - /** - * @throws PasswordError when cannot set the new password because requirements not met. - */ - function attemptReset( $newpass, $retype ) { - $user = User::newFromName( $this->mName ); - if( $user->isAnon() ) { - throw new PasswordError( 'no such user' ); - } - - if( !$user->checkTemporaryPassword( $this->mTemporaryPassword ) ) { - throw new PasswordError( wfMsg( 'resetpass_bad_temporary' ) ); - } - - if( $newpass !== $retype ) { - throw new PasswordError( wfMsg( 'badretype' ) ); - } - - $user->setPassword( $newpass ); - $user->saveSettings(); - } -} diff --git a/includes/specials/Revisiondelete.php b/includes/specials/Revisiondelete.php deleted file mode 100644 index a3d4b7ee82..0000000000 --- a/includes/specials/Revisiondelete.php +++ /dev/null @@ -1,1470 +0,0 @@ -getText( 'target' ); - $oldid = $wgRequest->getArray( 'oldid' ); - $artimestamp = $wgRequest->getArray( 'artimestamp' ); - $logid = $wgRequest->getArray( 'logid' ); - $img = $wgRequest->getArray( 'oldimage' ); - $fileid = $wgRequest->getArray( 'fileid' ); - # For reviewing deleted files... - $file = $wgRequest->getVal( 'file' ); - # If this is a revision, then we need a target page - $page = Title::newFromUrl( $target ); - if( is_null($page) ) { - $wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) ); - return; - } - # Only one target set at a time please! - $i = (bool)$file + (bool)$oldid + (bool)$logid + (bool)$artimestamp + (bool)$fileid + (bool)$img; - if( $i !== 1 ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - # Logs must have a type given - if( $logid && !strpos($page->getDBKey(),'/') ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - # Either submit or create our form - $form = new RevisionDeleteForm( $page, $oldid, $logid, $artimestamp, $fileid, $img, $file ); - if( $wgRequest->wasPosted() ) { - $form->submit( $wgRequest ); - } else if( $oldid || $artimestamp ) { - $form->showRevs(); - } else if( $fileid || $img ) { - $form->showImages(); - } else if( $logid ) { - $form->showLogItems(); - } - # Show relevant lines from the deletion log. This will show even if said ID - # does not exist...might be helpful - $wgOut->addHTML( "

    " . htmlspecialchars( LogPage::logName( 'delete' ) ) . "

    \n" ); - LogEventsList::showLogExtract( $wgOut, 'delete', $page->getPrefixedText() ); - if( $wgUser->isAllowed( 'suppressionlog' ) ){ - $wgOut->addHTML( "

    " . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "

    \n" ); - LogEventsList::showLogExtract( $wgOut, 'suppress', $page->getPrefixedText() ); - } -} - -/** - * Implements the GUI for Revision Deletion. - * @ingroup SpecialPage - */ -class RevisionDeleteForm { - /** - * @param Title $page - * @param array $oldids - * @param array $logids - * @param array $artimestamps - * @param array $fileids - * @param array $img - * @param string $file - */ - function __construct( $page, $oldids, $logids, $artimestamps, $fileids, $img, $file ) { - global $wgUser, $wgOut; - - $this->page = $page; - # For reviewing deleted files... - if( $file ) { - $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $page, $file ); - $oimage->load(); - // Check if user is allowed to see this file - if( !$oimage->userCan(File::DELETED_FILE) ) { - $wgOut->permissionRequired( 'suppressrevision' ); - } else { - $this->showFile( $file ); - } - return; - } - $this->skin = $wgUser->getSkin(); - # Give a link to the log for this page - if( !is_null($this->page) && $this->page->getNamespace() > -1 ) { - $links = array(); - - $logtitle = SpecialPage::getTitleFor( 'Log' ); - $links[] = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'viewpagelogs' ), - wfArrayToCGI( array( 'page' => $this->page->getPrefixedUrl() ) ) ); - # Give a link to the page history - $links[] = $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml( 'pagehist' ), - wfArrayToCGI( array( 'action' => 'history' ) ) ); - # Link to deleted edits - if( $wgUser->isAllowed('undelete') ) { - $undelete = SpecialPage::getTitleFor( 'Undelete' ); - $links[] = $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml( 'deletedhist' ), - wfArrayToCGI( array( 'target' => $this->page->getPrefixedUrl() ) ) ); - } - # Logs themselves don't have histories or archived revisions - $wgOut->setSubtitle( '

    '.implode($links,' / ').'

    ' ); - } - // At this point, we should only have one of these - if( $oldids ) { - $this->revisions = $oldids; - $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ); - $this->deleteKey='oldid'; - } else if( $artimestamps ) { - $this->archrevs = $artimestamps; - $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ); - $this->deleteKey='artimestamp'; - } else if( $img ) { - $this->ofiles = $img; - $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE ); - $this->deleteKey='oldimage'; - } else if( $fileids ) { - $this->afiles = $fileids; - $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE ); - $this->deleteKey='fileid'; - } else if( $logids ) { - $this->events = $logids; - $hide_content_name = array( 'revdelete-hide-name', 'wpHideName', LogPage::DELETED_ACTION ); - $this->deleteKey='logid'; - } - // Our checkbox messages depends one what we are doing, - // e.g. we don't hide "text" for logs or images - $this->checks = array( - $hide_content_name, - array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ), - array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ) ); - if( $wgUser->isAllowed('suppressrevision') ) { - $this->checks[] = array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED ); - } - } - - /** - * Show a deleted file version requested by the visitor. - */ - private function showFile( $key ) { - global $wgOut, $wgRequest; - $wgOut->disable(); - - # We mustn't allow the output to be Squid cached, otherwise - # if an admin previews a deleted image, and it's cached, then - # a user without appropriate permissions can toddle off and - # nab the image, and Squid will serve it - $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); - $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); - $wgRequest->response()->header( 'Pragma: no-cache' ); - - $store = FileStore::get( 'deleted' ); - $store->stream( $key ); - } - - /** - * This lets a user set restrictions for live and archived revisions - */ - function showRevs() { - global $wgOut, $wgUser, $action; - - $UserAllowed = true; - - $count = ($this->deleteKey=='oldid') ? - count($this->revisions) : count($this->archrevs); - $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(), $count ); - - $bitfields = 0; - $wgOut->addHtml( "
      " ); - - $where = $revObjs = array(); - $dbr = wfGetDB( DB_SLAVE ); - - $revisions = 0; - // Live revisions... - if( $this->deleteKey=='oldid' ) { - // Run through and pull all our data in one query - foreach( $this->revisions as $revid ) { - $where[] = intval($revid); - } - $whereClause = 'rev_id IN(' . implode(',',$where) . ')'; - $result = $dbr->select( array('revision','page'), '*', - array( 'rev_page' => $this->page->getArticleID(), - $whereClause, 'rev_page = page_id' ), - __METHOD__ ); - while( $row = $dbr->fetchObject( $result ) ) { - $revObjs[$row->rev_id] = new Revision( $row ); - } - foreach( $this->revisions as $revid ) { - // Hiding top revisison is bad - if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) { - continue; - } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) { - // If a rev is hidden from sysops - if( $action != 'submit') { - $wgOut->permissionRequired( 'suppressrevision' ); - return; - } - $UserAllowed = false; - } - $revisions++; - $wgOut->addHtml( $this->historyLine( $revObjs[$revid] ) ); - $bitfields |= $revObjs[$revid]->mDeleted; - } - // The archives... - } else { - // Run through and pull all our data in one query - foreach( $this->archrevs as $timestamp ) { - $where[] = $dbr->addQuotes( $timestamp ); - } - $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')'; - $result = $dbr->select( 'archive', '*', - array( 'ar_namespace' => $this->page->getNamespace(), - 'ar_title' => $this->page->getDBKey(), - $whereClause ), - __METHOD__ ); - while( $row = $dbr->fetchObject( $result ) ) { - $revObjs[$row->ar_timestamp] = new Revision( array( - 'page' => $this->page->getArticleId(), - 'id' => $row->ar_rev_id, - 'text' => $row->ar_text_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, - 'text_id' => $row->ar_text_id, - 'deleted' => $row->ar_deleted, - 'len' => $row->ar_len) ); - } - foreach( $this->archrevs as $timestamp ) { - if( !isset($revObjs[$timestamp]) ) { - continue; - } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) { - // If a rev is hidden from sysops - if( $action != 'submit') { - $wgOut->permissionRequired( 'suppressrevision' ); - return; - } - $UserAllowed = false; - } - $revisions++; - $wgOut->addHtml( $this->historyLine( $revObjs[$timestamp] ) ); - $bitfields |= $revObjs[$timestamp]->mDeleted; - } - } - if( !$revisions ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - - $wgOut->addHtml( "
    " ); - - $wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) ); - - // Normal sysops can always see what they did, but can't always change it - if( !$UserAllowed ) return; - - $items = array( - Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), - Xml::submitButton( wfMsg( 'revdelete-submit' ) ) - ); - $hidden = array( - Xml::hidden( 'wpEditToken', $wgUser->editToken() ), - Xml::hidden( 'target', $this->page->getPrefixedText() ), - Xml::hidden( 'type', $this->deleteKey ) - ); - if( $this->deleteKey=='oldid' ) { - foreach( $revObjs as $rev ) - $hidden[] = wfHidden( 'oldid[]', $rev->getId() ); - } else { - foreach( $revObjs as $rev ) - $hidden[] = wfHidden( 'artimestamp[]', $rev->getTimestamp() ); - } - $special = SpecialPage::getTitleFor( 'Revisiondelete' ); - $wgOut->addHtml( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), - 'id' => 'mw-revdel-form-revisions' ) ) . - Xml::openElement( 'fieldset' ) . - xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) ) - ); - // 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 ) ) ); - } - foreach( $items as $item ) { - $wgOut->addHtml( Xml::tags( 'p', null, $item ) ); - } - foreach( $hidden as $item ) { - $wgOut->addHtml( $item ); - } - $wgOut->addHtml( - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" - ); - - } - - /** - * This lets a user set restrictions for archived images - */ - function showImages() { - global $wgOut, $wgUser, $action; - - $UserAllowed = true; - - $count = ($this->deleteKey=='oldimage') ? count($this->ofiles) : count($this->afiles); - $wgOut->addWikiText( wfMsgExt( 'revdelete-selected', array('parsemag'), - $this->page->getPrefixedText(), $count ) ); - - $bitfields = 0; - $wgOut->addHtml( "
      " ); - - $where = $filesObjs = array(); - $dbr = wfGetDB( DB_SLAVE ); - // Live old revisions... - $revisions = 0; - 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() ); - } - $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; - $result = $dbr->select( 'oldimage', '*', - array( 'oi_name' => $this->page->getDbKey(), - $whereClause ), - __METHOD__ ); - while( $row = $dbr->fetchObject( $result ) ) { - $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row ); - $filesObjs[$row->oi_archive_name]->user = $row->oi_user; - $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text; - } - // Check through our images - foreach( $this->ofiles as $timestamp ) { - $archivename = $timestamp.'!'.$this->page->getDbKey(); - if( !isset($filesObjs[$archivename]) ) { - continue; - } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) { - // If a rev is hidden from sysops - if( $action != 'submit' ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return; - } - $UserAllowed = false; - } - $revisions++; - // Inject history info - $wgOut->addHtml( $this->fileLine( $filesObjs[$archivename] ) ); - $bitfields |= $filesObjs[$archivename]->deleted; - } - // Archived files... - } else { - // Run through and pull all our data in one query - foreach( $this->afiles as $id ) { - $where[] = intval($id); - } - $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; - $result = $dbr->select( 'filearchive', '*', - array( 'fa_name' => $this->page->getDbKey(), - $whereClause ), - __METHOD__ ); - while( $row = $dbr->fetchObject( $result ) ) { - $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row ); - } - - foreach( $this->afiles as $fileid ) { - if( !isset($filesObjs[$fileid]) ) { - continue; - } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) { - // If a rev is hidden from sysops - if( $action != 'submit' ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return; - } - $UserAllowed = false; - } - $revisions++; - // Inject history info - $wgOut->addHtml( $this->archivedfileLine( $filesObjs[$fileid] ) ); - $bitfields |= $filesObjs[$fileid]->deleted; - } - } - if( !$revisions ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - - $wgOut->addHtml( "
    " ); - - $wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) ); - //Normal sysops can always see what they did, but can't always change it - if( !$UserAllowed ) return; - - $items = array( - Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), - Xml::submitButton( wfMsg( 'revdelete-submit' ) ) - ); - $hidden = array( - Xml::hidden( 'wpEditToken', $wgUser->editToken() ), - Xml::hidden( 'target', $this->page->getPrefixedText() ), - Xml::hidden( 'type', $this->deleteKey ) - ); - if( $this->deleteKey=='oldimage' ) { - foreach( $this->ofiles as $filename ) - $hidden[] = wfHidden( 'oldimage[]', $filename ); - } else { - foreach( $this->afiles as $fileid ) - $hidden[] = wfHidden( 'fileid[]', $fileid ); - } - $special = SpecialPage::getTitleFor( 'Revisiondelete' ); - $wgOut->addHtml( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), - 'id' => 'mw-revdel-form-filerevisions' ) ) . - Xml::openElement( 'fieldset' ) . - xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) ) - ); - // 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 ) ) ); - } - foreach( $items as $item ) { - $wgOut->addHtml( Xml::tags( 'p', null, $item ) ); - } - foreach( $hidden as $item ) { - $wgOut->addHtml( $item ); - } - - $wgOut->addHtml( - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" - ); - } - - /** - * This lets a user set restrictions for log items - */ - function showLogItems() { - global $wgOut, $wgUser, $action, $wgMessageCache; - - $UserAllowed = true; - $wgOut->addWikiText( wfMsgExt( 'logdelete-selected', array('parsemag'), count($this->events) ) ); - - $bitfields = 0; - $wgOut->addHtml( "
      " ); - - $where = $logRows = array(); - $dbr = wfGetDB( DB_SLAVE ); - // Run through and pull all our data in one query - $logItems = 0; - foreach( $this->events as $logid ) { - $where[] = intval($logid); - } - list($log,$logtype) = explode( '/',$this->page->getDBKey(), 2 ); - $whereClause = "log_type = '$logtype' AND log_id IN(" . implode(',',$where) . ")"; - $result = $dbr->select( 'logging', '*', - array( $whereClause ), - __METHOD__ ); - while( $row = $dbr->fetchObject( $result ) ) { - $logRows[$row->log_id] = $row; - } - $wgMessageCache->loadAllMessages(); - foreach( $this->events as $logid ) { - // Don't hide from oversight log!!! - if( !isset( $logRows[$logid] ) || $logRows[$logid]->log_type=='suppress' ) { - continue; - } else if( !LogEventsList::userCan( $logRows[$logid],Revision::DELETED_RESTRICTED) ) { - // If an event is hidden from sysops - if( $action != 'submit') { - $wgOut->permissionRequired( 'suppressrevision' ); - return; - } - $UserAllowed = false; - } - $logItems++; - $wgOut->addHtml( $this->logLine( $logRows[$logid] ) ); - $bitfields |= $logRows[$logid]->log_deleted; - } - if( !$logItems ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - - $wgOut->addHtml( "
    " ); - - $wgOut->addWikiMsg( 'revdelete-text' ); - // Normal sysops can always see what they did, but can't always change it - if( !$UserAllowed ) return; - - $items = array( - Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), - Xml::submitButton( wfMsg( 'revdelete-submit' ) ) ); - $hidden = array( - Xml::hidden( 'wpEditToken', $wgUser->editToken() ), - Xml::hidden( 'target', $this->page->getPrefixedText() ), - Xml::hidden( 'type', $this->deleteKey ) ); - foreach( $this->events as $logid ) { - $hidden[] = Xml::hidden( 'logid[]', $logid ); - } - - $special = SpecialPage::getTitleFor( 'Revisiondelete' ); - $wgOut->addHtml( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), - 'id' => 'mw-revdel-form-logs' ) ) . - Xml::openElement( 'fieldset' ) . - xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) ) - ); - // 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 ) ) ); - } - foreach( $items as $item ) { - $wgOut->addHtml( Xml::tags( 'p', null, $item ) ); - } - foreach( $hidden as $item ) { - $wgOut->addHtml( $item ); - } - - $wgOut->addHtml( - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" - ); - } - - /** - * @param Revision $rev - * @returns string - */ - private function historyLine( $rev ) { - global $wgContLang; - - $date = $wgContLang->timeanddate( $rev->getTimestamp() ); - $difflink = $del = ''; - // Live revisions - if( $this->deleteKey=='oldid' ) { - $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ); - $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'), - 'diff=' . $rev->getId() . '&oldid=prev' ) . ')'; - // Archived revisions - } else { - $undelete = SpecialPage::getTitleFor( 'Undelete' ); - $target = $this->page->getPrefixedText(); - $revlink = $this->skin->makeLinkObj( $undelete, $date, - "target=$target×tamp=" . $rev->getTimestamp() ); - $difflink = '(' . $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml('diff'), - "target=$target&diff=prev×tamp=" . $rev->getTimestamp() ) . ')'; - } - - if( $rev->isDeleted(Revision::DELETED_TEXT) ) { - $revlink = ''.$revlink.''; - $del = ' ' . wfMsgHtml( 'deletedrev' ) . ''; - if( !$rev->userCan(Revision::DELETED_TEXT) ) { - $revlink = ''.$date.''; - $difflink = '(' . wfMsgHtml('diff') . ')'; - } - } - - return "
  • $difflink $revlink ".$this->skin->revUserLink( $rev )." ".$this->skin->revComment( $rev )."$del
  • "; - } - - /** - * @param File $file - * @returns string - */ - private function fileLine( $file ) { - global $wgContLang, $wgTitle; - - $target = $this->page->getPrefixedText(); - $date = $wgContLang->timeanddate( $file->getTimestamp(), true ); - - $del = ''; - # Hidden files... - if( $file->isDeleted(File::DELETED_FILE) ) { - $del = ' ' . wfMsgHtml( 'deletedrev' ) . ''; - if( !$file->userCan(File::DELETED_FILE) ) { - $pageLink = $date; - } else { - $pageLink = $this->skin->makeKnownLinkObj( $wgTitle, $date, - "target=$target&file=$file->sha1.".$file->getExtension() ); - } - $pageLink = '' . $pageLink . ''; - # Regular files... - } else { - $url = $file->getUrlRel(); - $pageLink = "{$date}"; - } - - $data = wfMsgHtml( 'widthheight', - $wgContLang->formatNum( $file->getWidth() ), - $wgContLang->formatNum( $file->getHeight() ) ) . - ' (' . wfMsgHtml( 'nbytes', $wgContLang->formatNum( $file->getSize() ) ) . ')'; - - return "
  • $pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del
  • "; - } - - /** - * @param ArchivedFile $file - * @returns string - */ - private function archivedfileLine( $file ) { - global $wgContLang, $wgTitle; - - $target = $this->page->getPrefixedText(); - $date = $wgContLang->timeanddate( $file->getTimestamp(), true ); - - $undelete = SpecialPage::getTitleFor( 'Undelete' ); - $pageLink = $this->skin->makeKnownLinkObj( $undelete, $date, "target=$target&file={$file->getKey()}" ); - - $del = ''; - if( $file->isDeleted(File::DELETED_FILE) ) { - $del = ' ' . wfMsgHtml( 'deletedrev' ) . ''; - } - - $data = wfMsgHtml( 'widthheight', - $wgContLang->formatNum( $file->getWidth() ), - $wgContLang->formatNum( $file->getHeight() ) ) . - ' (' . wfMsgHtml( 'nbytes', $wgContLang->formatNum( $file->getSize() ) ) . ')'; - - return "
  • $pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del
  • "; - } - - /** - * @param Array $row row - * @returns string - */ - private function logLine( $row ) { - global $wgContLang; - - $date = $wgContLang->timeanddate( $row->log_timestamp ); - $paramArray = LogPage::extractParams( $row->log_params ); - $title = Title::makeTitle( $row->log_namespace, $row->log_title ); - - $logtitle = SpecialPage::getTitleFor( 'Log' ); - $loglink = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'log' ), - wfArrayToCGI( array( 'page' => $title->getPrefixedUrl() ) ) ); - // Action text - if( !LogEventsList::userCan($row,LogPage::DELETED_ACTION) ) { - $action = '' . wfMsgHtml('rev-deleted-event') . ''; - } else { - $action = LogPage::actionText( $row->log_type, $row->log_action, $title, - $this->skin, $paramArray, true, true ); - if( $row->log_deleted & LogPage::DELETED_ACTION ) - $action = '' . $action . ''; - } - // User links - $userLink = $this->skin->userLink( $row->log_user, User::WhoIs($row->log_user) ); - if( LogEventsList::isDeleted($row,LogPage::DELETED_USER) ) { - $userLink = '' . $userLink . ''; - } - // Comment - $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $row->log_comment ); - if( LogEventsList::isDeleted($row,LogPage::DELETED_COMMENT) ) { - $comment = '' . $comment . ''; - } - return "
  • ($loglink) $date $userLink $action $comment
  • "; - } - - /** - * Generate a user tool link cluster if the current user is allowed to view it - * @param ArchivedFile $file - * @return string HTML - */ - private function fileUserTools( $file ) { - if( $file->userCan( Revision::DELETED_USER ) ) { - $link = $this->skin->userLink( $file->user, $file->user_text ) . - $this->skin->userToolLinks( $file->user, $file->user_text ); - } else { - $link = wfMsgHtml( 'rev-deleted-user' ); - } - if( $file->isDeleted( Revision::DELETED_USER ) ) { - return '' . $link . ''; - } - return $link; - } - - /** - * Wrap and format the given file's comment block, if the current - * user is allowed to view it. - * - * @param ArchivedFile $file - * @return string HTML - */ - private function fileComment( $file ) { - if( $file->userCan( File::DELETED_COMMENT ) ) { - $block = $this->skin->commentBlock( $file->description ); - } else { - $block = ' ' . wfMsgHtml( 'rev-deleted-comment' ); - } - if( $file->isDeleted( File::DELETED_COMMENT ) ) { - return "$block"; - } - return $block; - } - - /** - * @param WebRequest $request - */ - function submit( $request ) { - global $wgUser, $wgOut; - - $bitfield = $this->extractBitfield( $request ); - $comment = $request->getText( 'wpReason' ); - # Can the user set this field? - if( $bitfield & Revision::DELETED_RESTRICTED && !$wgUser->isAllowed('suppressrevision') ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return false; - } - # If the save went through, go to success message. Otherwise - # bounce back to form... - if( $this->save( $bitfield, $comment, $this->page ) ) { - $this->success(); - } else if( $request->getCheck( 'oldid' ) || $request->getCheck( 'artimestamp' ) ) { - return $this->showRevs(); - } else if( $request->getCheck( 'logid' ) ) { - return $this->showLogs(); - } else if( $request->getCheck( 'oldimage' ) || $request->getCheck( 'fileid' ) ) { - return $this->showImages(); - } - } - - private function success() { - global $wgOut; - - $wgOut->setPagetitle( wfMsgHtml( 'actioncomplete' ) ); - - if( $this->deleteKey=='logid' ) { - $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'logdelete-success' ) ), false ); - $this->showLogItems(); - } else if( $this->deleteKey=='oldid' || $this->deleteKey=='artimestamp' ) { - $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'revdelete-success' ) ), false ); - $this->showRevs(); - } else if( $this->deleteKey=='fileid' ) { - $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'revdelete-success' ) ), false ); - $this->showImages(); - } else if( $this->deleteKey=='oldimage' ) { - $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'revdelete-success' ) ), false ); - $this->showImages(); - } - } - - /** - * Put together a rev_deleted bitfield from the submitted checkboxes - * @param WebRequest $request - * @return int - */ - private function extractBitfield( $request ) { - $bitfield = 0; - foreach( $this->checks as $item ) { - list( /* message */ , $name, $field ) = $item; - if( $request->getCheck( $name ) ) { - $bitfield |= $field; - } - } - return $bitfield; - } - - private function save( $bitfield, $reason, $title ) { - $dbw = wfGetDB( DB_MASTER ); - // Don't allow simply locking the interface for no reason - if( $bitfield == Revision::DELETED_RESTRICTED ) { - $bitfield = 0; - } - $deleter = new RevisionDeleter( $dbw ); - // By this point, only one of the below should be set - if( isset($this->revisions) ) { - return $deleter->setRevVisibility( $title, $this->revisions, $bitfield, $reason ); - } else if( isset($this->archrevs) ) { - return $deleter->setArchiveVisibility( $title, $this->archrevs, $bitfield, $reason ); - } else if( isset($this->ofiles) ) { - return $deleter->setOldImgVisibility( $title, $this->ofiles, $bitfield, $reason ); - } else if( isset($this->afiles) ) { - return $deleter->setArchFileVisibility( $title, $this->afiles, $bitfield, $reason ); - } else if( isset($this->events) ) { - return $deleter->setEventVisibility( $title, $this->events, $bitfield, $reason ); - } - } -} - -/** - * Implements the actions for Revision Deletion. - * @ingroup SpecialPage - */ -class RevisionDeleter { - function __construct( $db ) { - $this->dbw = $db; - } - - /** - * @param $title, the page these events apply to - * @param array $items list of revision ID numbers - * @param int $bitfield new rev_deleted value - * @param string $comment Comment for log records - */ - function setRevVisibility( $title, $items, $bitfield, $comment ) { - global $wgOut; - - $userAllowedAll = $success = true; - $revIDs = array(); - $revCount = 0; - // Run through and pull all our data in one query - foreach( $items as $revid ) { - $where[] = intval($revid); - } - $whereClause = 'rev_id IN(' . implode(',',$where) . ')'; - $result = $this->dbw->select( 'revision', '*', - array( 'rev_page' => $title->getArticleID(), - $whereClause ), - __METHOD__ ); - while( $row = $this->dbw->fetchObject( $result ) ) { - $revObjs[$row->rev_id] = new Revision( $row ); - } - // To work! - foreach( $items as $revid ) { - if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) { - $success = false; - continue; // Must exist - } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) { - $userAllowedAll=false; - continue; - } - // For logging, maintain a count of revisions - if( $revObjs[$revid]->mDeleted != $bitfield ) { - $revCount++; - $revIDs[]=$revid; - - $this->updateRevision( $revObjs[$revid], $bitfield ); - $this->updateRecentChangesEdits( $revObjs[$revid], $bitfield, false ); - } - } - // Clear caches... - // Don't log or touch if nothing changed - if( $revCount > 0 ) { - $this->updateLog( $title, $revCount, $bitfield, $revObjs[$revid]->mDeleted, - $comment, $title, 'oldid', $revIDs ); - $this->updatePage( $title ); - } - // Where all revs allowed to be set? - if( !$userAllowedAll ) { - //FIXME: still might be confusing??? - $wgOut->permissionRequired( 'suppressrevision' ); - return false; - } - - return $success; - } - - /** - * @param $title, the page these events apply to - * @param array $items list of revision ID numbers - * @param int $bitfield new rev_deleted value - * @param string $comment Comment for log records - */ - function setArchiveVisibility( $title, $items, $bitfield, $comment ) { - global $wgOut; - - $userAllowedAll = $success = true; - $count = 0; - $Id_set = array(); - // Run through and pull all our data in one query - foreach( $items as $timestamp ) { - $where[] = $this->dbw->addQuotes( $timestamp ); - } - $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')'; - $result = $this->dbw->select( 'archive', '*', - array( 'ar_namespace' => $title->getNamespace(), - 'ar_title' => $title->getDBKey(), - $whereClause ), - __METHOD__ ); - while( $row = $this->dbw->fetchObject( $result ) ) { - $revObjs[$row->ar_timestamp] = new Revision( array( - 'page' => $title->getArticleId(), - 'id' => $row->ar_rev_id, - 'text' => $row->ar_text_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, - 'text_id' => $row->ar_text_id, - 'deleted' => $row->ar_deleted, - 'len' => $row->ar_len) ); - } - // To work! - foreach( $items as $timestamp ) { - // This will only select the first revision with this timestamp. - // Since they are all selected/deleted at once, we can just check the - // permissions of one. UPDATE is done via timestamp, so all revs are set. - if( !is_object($revObjs[$timestamp]) ) { - $success = false; - continue; // Must exist - } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) { - $userAllowedAll=false; - continue; - } - // Which revisions did we change anything about? - if( $revObjs[$timestamp]->mDeleted != $bitfield ) { - $Id_set[]=$timestamp; - $count++; - - $this->updateArchive( $revObjs[$timestamp], $title, $bitfield ); - } - } - // For logging, maintain a count of revisions - if( $count > 0 ) { - $this->updateLog( $title, $count, $bitfield, $revObjs[$timestamp]->mDeleted, - $comment, $title, 'artimestamp', $Id_set ); - } - // Where all revs allowed to be set? - if( !$userAllowedAll ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return false; - } - - return $success; - } - - /** - * @param $title, the page these events apply to - * @param array $items list of revision ID numbers - * @param int $bitfield new rev_deleted value - * @param string $comment Comment for log records - */ - function setOldImgVisibility( $title, $items, $bitfield, $comment ) { - global $wgOut; - - $userAllowedAll = $success = true; - $count = 0; - $set = array(); - // Run through and pull all our data in one query - foreach( $items as $timestamp ) { - $where[] = $this->dbw->addQuotes( $timestamp.'!'.$title->getDbKey() ); - } - $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; - $result = $this->dbw->select( 'oldimage', '*', - array( 'oi_name' => $title->getDbKey(), - $whereClause ), - __METHOD__ ); - while( $row = $this->dbw->fetchObject( $result ) ) { - $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row ); - $filesObjs[$row->oi_archive_name]->user = $row->oi_user; - $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text; - } - // To work! - foreach( $items as $timestamp ) { - $archivename = $timestamp.'!'.$title->getDbKey(); - if( !isset($filesObjs[$archivename]) ) { - $success = false; - continue; // Must exist - } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) { - $userAllowedAll=false; - continue; - } - - $transaction = true; - // Which revisions did we change anything about? - if( $filesObjs[$archivename]->deleted != $bitfield ) { - $count++; - - $this->dbw->begin(); - $this->updateOldFiles( $filesObjs[$archivename], $bitfield ); - // If this image is currently hidden... - if( $filesObjs[$archivename]->deleted & File::DELETED_FILE ) { - if( $bitfield & File::DELETED_FILE ) { - # Leave it alone if we are not changing this... - $set[]=$archivename; - $transaction = true; - } else { - # We are moving this out - $transaction = $this->makeOldImagePublic( $filesObjs[$archivename] ); - $set[]=$transaction; - } - // Is it just now becoming hidden? - } else if( $bitfield & File::DELETED_FILE ) { - $transaction = $this->makeOldImagePrivate( $filesObjs[$archivename] ); - $set[]=$transaction; - } else { - $set[]=$timestamp; - } - // If our file operations fail, then revert back the db - if( $transaction==false ) { - $this->dbw->rollback(); - return false; - } - $this->dbw->commit(); - } - } - - // Log if something was changed - if( $count > 0 ) { - $this->updateLog( $title, $count, $bitfield, $filesObjs[$archivename]->deleted, - $comment, $title, 'oldimage', $set ); - # Purge page/history - $file = wfLocalFile( $title ); - $file->purgeCache(); - $file->purgeHistory(); - # Invalidate cache for all pages using this file - $update = new HTMLCacheUpdate( $title, 'imagelinks' ); - $update->doUpdate(); - } - // Where all revs allowed to be set? - if( !$userAllowedAll ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return false; - } - - return $success; - } - - /** - * @param $title, the page these events apply to - * @param array $items list of revision ID numbers - * @param int $bitfield new rev_deleted value - * @param string $comment Comment for log records - */ - function setArchFileVisibility( $title, $items, $bitfield, $comment ) { - global $wgOut; - - $userAllowedAll = $success = true; - $count = 0; - $Id_set = array(); - - // Run through and pull all our data in one query - foreach( $items as $id ) { - $where[] = intval($id); - } - $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; - $result = $this->dbw->select( 'filearchive', '*', - array( 'fa_name' => $title->getDbKey(), - $whereClause ), - __METHOD__ ); - while( $row = $this->dbw->fetchObject( $result ) ) { - $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row ); - } - // To work! - foreach( $items as $fileid ) { - if( !isset($filesObjs[$fileid]) ) { - $success = false; - continue; // Must exist - } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) { - $userAllowedAll=false; - continue; - } - // Which revisions did we change anything about? - if( $filesObjs[$fileid]->deleted != $bitfield ) { - $Id_set[]=$fileid; - $count++; - - $this->updateArchFiles( $filesObjs[$fileid], $bitfield ); - } - } - // Log if something was changed - if( $count > 0 ) { - $this->updateLog( $title, $count, $bitfield, $comment, - $filesObjs[$fileid]->deleted, $title, 'fileid', $Id_set ); - } - // Where all revs allowed to be set? - if( !$userAllowedAll ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return false; - } - - return $success; - } - - /** - * @param $title, the log page these events apply to - * @param array $items list of log ID numbers - * @param int $bitfield new log_deleted value - * @param string $comment Comment for log records - */ - function setEventVisibility( $title, $items, $bitfield, $comment ) { - global $wgOut; - - $userAllowedAll = $success = true; - $count = 0; - $log_Ids = array(); - - // Run through and pull all our data in one query - foreach( $items as $logid ) { - $where[] = intval($logid); - } - list($log,$logtype) = explode( '/',$title->getDBKey(), 2 ); - $whereClause = "log_type ='$logtype' AND log_id IN(" . implode(',',$where) . ")"; - $result = $this->dbw->select( 'logging', '*', - array( $whereClause ), - __METHOD__ ); - while( $row = $this->dbw->fetchObject( $result ) ) { - $logRows[$row->log_id] = $row; - } - // To work! - foreach( $items as $logid ) { - if( !isset($logRows[$logid]) ) { - $success = false; - continue; // Must exist - } else if( !LogEventsList::userCan($logRows[$logid], LogPage::DELETED_RESTRICTED) - || $logRows[$logid]->log_type == 'suppress' ) { - // Don't hide from oversight log!!! - $userAllowedAll=false; - continue; - } - // Which logs did we change anything about? - if( $logRows[$logid]->log_deleted != $bitfield ) { - $log_Ids[]=$logid; - $count++; - - $this->updateLogs( $logRows[$logid], $bitfield ); - $this->updateRecentChangesLog( $logRows[$logid], $bitfield, true ); - } - } - // Don't log or touch if nothing changed - if( $count > 0 ) { - $this->updateLog( $title, $count, $bitfield, $logRows[$logid]->log_deleted, - $comment, $title, 'logid', $log_Ids ); - } - // Were all revs allowed to be set? - if( !$userAllowedAll ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return false; - } - - return $success; - } - - /** - * Moves an image to a safe private location - * Caller is responsible for clearing caches - * @param File $oimage - * @returns mixed, timestamp string on success, false on failure - */ - function makeOldImagePrivate( $oimage ) { - $transaction = new FSTransaction(); - if( !FileStore::lock() ) { - wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); - return false; - } - $oldpath = $oimage->getArchivePath() . DIRECTORY_SEPARATOR . $oimage->archive_name; - // Dupe the file into the file store - if( file_exists( $oldpath ) ) { - // Is our directory configured? - if( $store = FileStore::get( 'deleted' ) ) { - if( !$oimage->sha1 ) { - $oimage->upgradeRow(); // sha1 may be missing - } - $key = $oimage->sha1 . '.' . $oimage->getExtension(); - $transaction->add( $store->insert( $key, $oldpath, FileStore::DELETE_ORIGINAL ) ); - } else { - $group = null; - $key = null; - $transaction = false; // Return an error and do nothing - } - } else { - wfDebug( __METHOD__." deleting already-missing '$oldpath'; moving on to database\n" ); - $group = null; - $key = ''; - $transaction = new FSTransaction(); // empty - } - - if( $transaction === false ) { - // Fail to restore? - wfDebug( __METHOD__.": import to file store failed, aborting\n" ); - throw new MWException( "Could not archive and delete file $oldpath" ); - return false; - } - - wfDebug( __METHOD__.": set db items, applying file transactions\n" ); - $transaction->commit(); - FileStore::unlock(); - - $m = explode('!',$oimage->archive_name,2); - $timestamp = $m[0]; - - return $timestamp; - } - - /** - * Moves an image from a safe private location - * Caller is responsible for clearing caches - * @param File $oimage - * @returns mixed, string timestamp on success, false on failure - */ - function makeOldImagePublic( $oimage ) { - $transaction = new FSTransaction(); - if( !FileStore::lock() ) { - wfDebug( __METHOD__." could not acquire filestore lock\n" ); - return false; - } - - $store = FileStore::get( 'deleted' ); - if( !$store ) { - wfDebug( __METHOD__.": skipping row with no file.\n" ); - return false; - } - - $key = $oimage->sha1.'.'.$oimage->getExtension(); - $destDir = $oimage->getArchivePath(); - if( !is_dir( $destDir ) ) { - wfMkdirParents( $destDir ); - } - $destPath = $destDir . DIRECTORY_SEPARATOR . $oimage->archive_name; - // Check if any other stored revisions use this file; - // if so, we shouldn't remove the file from the hidden - // archives so they will still work. Check hidden files first. - $useCount = $this->dbw->selectField( 'oldimage', '1', - array( 'oi_sha1' => $oimage->sha1, - 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ), - __METHOD__, array( 'FOR UPDATE' ) ); - // Check the rest of the deleted archives too. - // (these are the ones that don't show in the image history) - if( !$useCount ) { - $useCount = $this->dbw->selectField( 'filearchive', '1', - array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ), - __METHOD__, array( 'FOR UPDATE' ) ); - } - - if( $useCount == 0 ) { - wfDebug( __METHOD__.": nothing else using {$oimage->sha1}, will deleting after\n" ); - $flags = FileStore::DELETE_ORIGINAL; - } else { - $flags = 0; - } - $transaction->add( $store->export( $key, $destPath, $flags ) ); - - wfDebug( __METHOD__.": set db items, applying file transactions\n" ); - $transaction->commit(); - FileStore::unlock(); - - $m = explode('!',$oimage->archive_name,2); - $timestamp = $m[0]; - - return $timestamp; - } - - /** - * Update the revision's rev_deleted field - * @param Revision $rev - * @param int $bitfield new rev_deleted bitfield value - */ - function updateRevision( $rev, $bitfield ) { - $this->dbw->update( 'revision', - array( 'rev_deleted' => $bitfield ), - array( 'rev_id' => $rev->getId(), - 'rev_page' => $rev->getPage() ), - __METHOD__ ); - } - - /** - * Update the revision's rev_deleted field - * @param Revision $rev - * @param Title $title - * @param int $bitfield new rev_deleted bitfield value - */ - function updateArchive( $rev, $title, $bitfield ) { - $this->dbw->update( 'archive', - array( 'ar_deleted' => $bitfield ), - array( 'ar_namespace' => $title->getNamespace(), - 'ar_title' => $title->getDBKey(), - 'ar_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ), - 'ar_rev_id' => $rev->getId() ), - __METHOD__ ); - } - - /** - * Update the images's oi_deleted field - * @param File $file - * @param int $bitfield new rev_deleted bitfield value - */ - function updateOldFiles( $file, $bitfield ) { - $this->dbw->update( 'oldimage', - array( 'oi_deleted' => $bitfield ), - array( 'oi_name' => $file->getName(), - 'oi_timestamp' => $this->dbw->timestamp( $file->getTimestamp() ) ), - __METHOD__ ); - } - - /** - * Update the images's fa_deleted field - * @param ArchivedFile $file - * @param int $bitfield new rev_deleted bitfield value - */ - function updateArchFiles( $file, $bitfield ) { - $this->dbw->update( 'filearchive', - array( 'fa_deleted' => $bitfield ), - array( 'fa_id' => $file->getId() ), - __METHOD__ ); - } - - /** - * Update the logging log_deleted field - * @param Row $row - * @param int $bitfield new rev_deleted bitfield value - */ - function updateLogs( $row, $bitfield ) { - $this->dbw->update( 'logging', - array( 'log_deleted' => $bitfield ), - array( 'log_id' => $row->log_id ), - __METHOD__ ); - } - - /** - * Update the revision's recentchanges record if fields have been hidden - * @param Revision $rev - * @param int $bitfield new rev_deleted bitfield value - */ - function updateRecentChangesEdits( $rev, $bitfield ) { - $this->dbw->update( 'recentchanges', - array( 'rc_deleted' => $bitfield, - 'rc_patrolled' => 1 ), - array( 'rc_this_oldid' => $rev->getId(), - 'rc_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ) ), - __METHOD__ ); - } - - /** - * Update the revision's recentchanges record if fields have been hidden - * @param Row $row - * @param int $bitfield new rev_deleted bitfield value - */ - function updateRecentChangesLog( $row, $bitfield ) { - $this->dbw->update( 'recentchanges', - array( 'rc_deleted' => $bitfield, - 'rc_patrolled' => 1 ), - array( 'rc_logid' => $row->log_id, - 'rc_timestamp' => $row->log_timestamp ), - __METHOD__ ); - } - - /** - * Touch the page's cache invalidation timestamp; this forces cached - * history views to refresh, so any newly hidden or shown fields will - * update properly. - * @param Title $title - */ - function updatePage( $title ) { - $title->invalidateCache(); - $title->purgeSquid(); - - // Extensions that require referencing previous revisions may need this - wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$title ) ); - } - - /** - * Checks for a change in the bitfield for a certain option and updates the - * provided array accordingly. - * - * @param String $desc Description to add to the array if the option was - * enabled / disabled. - * @param int $field The bitmask describing the single option. - * @param int $diff The xor of the old and new bitfields. - * @param array $arr The array to update. - */ - function checkItem ( $desc, $field, $diff, $new, &$arr ) { - if ( $diff & $field ) { - $arr [ ( $new & $field ) ? 0 : 1 ][] = $desc; - } - } - - /** - * Gets an array describing the changes made to the visibilit of the revision. - * If the resulting array is $arr, then $arr[0] will contain an array of strings - * describing the items that were hidden, $arr[2] will contain an array of strings - * describing the items that were unhidden, and $arr[3] will contain an array with - * a single string, which can be one of "applied restrictions to sysops", - * "removed restrictions from sysops", or null. - * - * @param int $n The new bitfield. - * @param int $o The old bitfield. - * @return An array as described above. - */ - function getChanges ( $n, $o ) { - $diff = $n ^ $o; - $ret = array ( 0 => array(), 1 => array(), 2 => array() ); - - $this->checkItem ( wfMsgForContent ( 'revdelete-content' ), - Revision::DELETED_TEXT, $diff, $n, $ret ); - $this->checkItem ( wfMsgForContent ( 'revdelete-summary' ), - Revision::DELETED_COMMENT, $diff, $n, $ret ); - $this->checkItem ( wfMsgForContent ( 'revdelete-uname' ), - Revision::DELETED_USER, $diff, $n, $ret ); - - // Restriction application to sysops - if ( $diff & Revision::DELETED_RESTRICTED ) { - if ( $n & Revision::DELETED_RESTRICTED ) - $ret[2][] = wfMsgForContent ( 'revdelete-restricted' ); - else - $ret[2][] = wfMsgForContent ( 'revdelete-unrestricted' ); - } - - return $ret; - } - - /** - * Gets a log message to describe the given revision visibility change. This - * message will be of the form "[hid {content, edit summary, username}]; - * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment". - * - * @param int $count The number of effected revisions. - * @param int $nbitfield The new bitfield for the revision. - * @param int $obitfield The old bitfield for the revision. - * @param string $comment The comment associated with the change. - * @param bool $isForLog - */ - function getLogMessage ( $count, $nbitfield, $obitfield, $comment, $isForLog = false ) { - global $wgContLang; - - $s = ''; - $changes = $this->getChanges( $nbitfield, $obitfield ); - - if ( count ( $changes[0] ) ) { - $s .= wfMsgForContent ( 'revdelete-hid', implode ( ', ', $changes[0] ) ); - } - - if ( count ( $changes[1] ) ) { - if ($s) $s .= '; '; - - $s .= wfMsgForContent ( 'revdelete-unhid', implode ( ', ', $changes[1] ) ); - } - - if ( count ( $changes[2] )) { - if ($s) - $s .= ' (' . $changes[2][0] . ')'; - else - $s = $changes[2][0]; - } - - $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message'; - $ret = wfMsgExt ( $msg, array( 'parsemag', 'content' ), - $s, $wgContLang->formatNum( $count ) ); - - if ( $comment ) - $ret .= ": $comment"; - - return $ret; - - } - - /** - * Record a log entry on the action - * @param Title $title, page where item was removed from - * @param int $count the number of revisions altered for this page - * @param int $nbitfield the new _deleted value - * @param int $obitfield the old _deleted value - * @param string $comment - * @param Title $target, the relevant page - * @param string $param, URL param - * @param Array $items - */ - function updateLog( $title, $count, $nbitfield, $obitfield, $comment, $target, $param, $items = array() ) { - // Put things hidden from sysops in the oversight log - $logtype = ( ($nbitfield | $obitfield) & Revision::DELETED_RESTRICTED ) ? 'suppress' : 'delete'; - $log = new LogPage( $logtype ); - - $reason = $this->getLogMessage ( $count, $nbitfield, $obitfield, $comment, $param == 'logid' ); - - if( $param == 'logid' ) { - $params = array( implode( ',', $items) ); - $log->addEntry( 'event', $title, $reason, $params ); - } else { - // Add params for effected page and ids - $params = array( $param, implode( ',', $items) ); - $log->addEntry( 'revision', $title, $reason, $params ); - } - } -} diff --git a/includes/specials/Search.php b/includes/specials/Search.php deleted file mode 100644 index 0a483af52f..0000000000 --- a/includes/specials/Search.php +++ /dev/null @@ -1,651 +0,0 @@ - -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html - -/** - * Run text & title search and display the output - * @file - * @ingroup SpecialPage - */ - -/** - * Entry point - * - * @param $par String: (default '') - */ -function wfSpecialSearch( $par = '' ) { - global $wgRequest, $wgUser; - - $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $par ) ); - $searchPage = new SpecialSearch( $wgRequest, $wgUser ); - if( $wgRequest->getVal( 'fulltext' ) - || !is_null( $wgRequest->getVal( 'offset' )) - || !is_null( $wgRequest->getVal( 'searchx' ))) { - $searchPage->showResults( $search, 'search' ); - } else { - $searchPage->goResult( $search ); - } -} - -/** - * implements Special:Search - Run text & title search and display the output - * @ingroup SpecialPage - */ -class SpecialSearch { - - /** - * 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 SpecialSearch( &$request, &$user ) { - list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); - - $this->namespaces = $this->powerSearch( $request ); - if( empty( $this->namespaces ) ) { - $this->namespaces = SearchEngine::userNamespaces( $user ); - } - - $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; - } - - /** - * If an exact title match can be found, jump straight ahead to it. - * @param string $term - * @public - */ - function goResult( $term ) { - global $wgOut; - global $wgGoToEdit; - - $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 ) ) { - wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) ); - # If the feature is enabled, go straight to the edit page - if ( $wgGoToEdit ) { - $wgOut->redirect( $t->getFullURL( 'action=edit' ) ); - return; - } - } - - $wgOut->wrapWikiMsg( "==$1==\n", 'notitlematches' ); - if( $t->quickUserCan( 'create' ) && $t->quickUserCan( 'edit' ) ) { - $wgOut->addWikiMsg( 'noexactmatch', wfEscapeWikiText( $term ) ); - } else { - $wgOut->addWikiMsg( 'noexactmatch-nocreate', wfEscapeWikiText( $term ) ); - } - - return $this->showResults( $term ); - } - - /** - * @param string $term - * @public - */ - function showResults( $term ) { - $fname = 'SpecialSearch::showResults'; - wfProfileIn( $fname ); - global $wgOut, $wgUser; - $sk = $wgUser->getSkin(); - - $this->setupPage( $term ); - - $wgOut->addWikiMsg( 'searchresulttext' ); - - if( '' === trim( $term ) ) { - // Empty query -- straight view of search form - $wgOut->setSubtitle( '' ); - $wgOut->addHTML( $this->powerSearchBox( $term ) ); - $wgOut->addHTML( $this->powerSearchFocus() ); - wfProfileOut( $fname ); - return; - } - - global $wgDisableTextSearch; - if ( $wgDisableTextSearch ) { - global $wgForwardSearchUrl; - if( $wgForwardSearchUrl ) { - $url = str_replace( '$1', urlencode( $term ), $wgForwardSearchUrl ); - $wgOut->redirect( $url ); - 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( $fname ); - 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 ); - - // 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 ); - 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 = ''. - $textMatches->getSuggestionSnippet().''; - - $wgOut->addHTML('
    '.wfMsg('search-suggest',$suggestLink).'
    '); - } - - // show number of results - $num = ( $titleMatches ? $titleMatches->numRows() : 0 ) - + ( $textMatches ? $textMatches->numRows() : 0); - $totalNum = 0; - if($titleMatches && !is_null($titleMatches->getTotalHits())) - $totalNum += $titleMatches->getTotalHits(); - if($textMatches && !is_null($textMatches->getTotalHits())) - $totalNum += $textMatches->getTotalHits(); - if ( $num > 0 ) { - if ( $totalNum > 0 ){ - $top = wfMsgExt('showingresultstotal', array( 'parseinline' ), - $this->offset+1, $this->offset+$num, $totalNum ); - } elseif ( $num >= $this->limit ) { - $top = wfShowingResults( $this->offset, $this->limit ); - } else { - $top = wfShowingResultsNum( $this->offset, $this->limit, $num ); - } - $wgOut->addHTML( "

    {$top}

    \n" ); - } - - // prev/next links - if( $num || $this->offset ) { - $prevnext = wfViewPrevNext( $this->offset, $this->limit, - SpecialPage::getTitleFor( 'Search' ), - wfArrayToCGI( - $this->powerSearchOptions(), - array( 'search' => $term ) ), - ($num < $this->limit) ); - $wgOut->addHTML( "

    {$prevnext}

    \n" ); - wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) ); - } else { - wfRunHooks( 'SpecialSearchNoResults', array( $term ) ); - } - - if( $titleMatches ) { - if( $titleMatches->numRows() ) { - $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' ); - $wgOut->addHTML( $this->showMatches( $titleMatches ) ); - } - $titleMatches->free(); - } - - if( $textMatches ) { - // output appropriate heading - if( $textMatches->numRows() ) { - if($titleMatches) - $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' ); - else // if no title matches the heading is redundant - $wgOut->addHTML("
    "); - } elseif( $num == 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( $textMatches->numRows() ) - $wgOut->addHTML( $this->showMatches( $textMatches ) ); - - $textMatches->free(); - } - - if ( $num == 0 ) { - $wgOut->addWikiMsg( 'nonefound' ); - } - if( $num || $this->offset ) { - $wgOut->addHTML( "

    {$prevnext}

    \n" ); - } - $wgOut->addHTML( $this->powerSearchBox( $term ) ); - wfProfileOut( $fname ); - } - - #------------------------------------------------------------------ - # Private methods below this line - - /** - * - */ - function setupPage( $term ) { - global $wgOut; - if( !empty( $term ) ) - $wgOut->setPageTitle( wfMsg( 'searchresults' ) ); - $subtitlemsg = ( Title::newFromText( $term ) ? 'searchsubtitle' : 'searchsubtitleinvalid' ); - $wgOut->setSubtitle( $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($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 - * @private - */ - 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 - * @private - */ - function powerSearchOptions() { - $opt = array(); - foreach( $this->namespaces as $n ) { - $opt['ns' . $n] = 1; - } - $opt['redirs'] = $this->searchRedirects ? 1 : 0; - return $opt; - } - - /** - * Show whole set of results - * - * @param SearchResultSet $matches - */ - function showMatches( &$matches ) { - $fname = 'SpecialSearch::showMatches'; - wfProfileIn( $fname ); - - global $wgContLang; - $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); - - $out = ""; - - $infoLine = $matches->getInfo(); - if( !is_null($infoLine) ) - $out .= "\n\n"; - - - $off = $this->offset + 1; - $out .= "
      \n"; - - while( $result = $matches->next() ) { - $out .= $this->showHit( $result, $terms ); - } - $out .= "
    \n"; - - // convert the whole thing to desired language variant - global $wgContLang; - $out = $wgContLang->convert( $out ); - wfProfileOut( $fname ); - return $out; - } - - /** - * Format a single hit result - * @param SearchResult $result - * @param array $terms terms to highlight - */ - function showHit( $result, $terms ) { - $fname = 'SpecialSearch::showHit'; - wfProfileIn( $fname ); - global $wgUser, $wgContLang, $wgLang; - - if( $result->isBrokenTitle() ) { - wfProfileOut( $fname ); - return "\n"; - } - - $t = $result->getTitle(); - $sk = $wgUser->getSkin(); - - $link = $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( $fname ); - return "
  • {$link}
  • \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( $fname ); - return "\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 = "" - .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText)) - .""; - $section = ''; - if( !is_null($sectionTitle) ) - $section = "" - .wfMsg('search-section', $sk->makeKnownLinkObj( $sectionTitle, $sectionText)) - .""; - - // format text extract - $extract = "
    ".$result->getTextSnippet($terms)."
    "; - - // 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' ), - $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 = ' -- '. - wfMsg('search-relatedarticle').''; - } - - // Include a thumbnail for media files... - if( $t->getNamespace() == NS_IMAGE ) { - $img = wfFindFile( $t ); - if( $img ) { - $thumb = $img->getThumbnail( 120, 120 ); - if( $thumb ) { - $desc = $img->getShortDesc(); - wfProfileOut( $fname ); - // Ugly table. :D - // Float doesn't seem to interact well with the bullets. - // Table messes up vertical alignment of the bullet, but I'm - // not sure what more I can do about that. :( - return "
  • " . - '' . - '' . - '' . - '' . - '' . - '
    ' . - $thumb->toHtml( array( 'desc-link' => true ) ) . - '' . - $link . - $extract . - "
    {$score}{$desc} - {$date}{$related}
    " . - '
    ' . - "
  • \n"; - } - } - } - - wfProfileOut( $fname ); - return "
  • {$link} {$redirect} {$section} {$extract}\n" . - "
    {$score}{$size} - {$date}{$related}
    " . - "
  • \n"; - - } - - /** - * Show results from other wikis - * - * @param SearchResultSet $matches - */ - function showInterwiki( &$matches, $query ) { - $fname = 'SpecialSearch::showInterwiki'; - wfProfileIn( $fname ); - - global $wgContLang; - $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); - - $out = "
    ".wfMsg('search-interwiki-caption')."
    \n"; - $off = $this->offset + 1; - $out .= "
      \n"; - - // work out custom project captions - $customCaptions = array(); - $customLines = explode("\n",wfMsg('search-interwiki-custom')); // format per line : - 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 .= "
    \n"; - - // convert the whole thing to desired language variant - global $wgContLang; - $out = $wgContLang->convert( $out ); - wfProfileOut( $fname ); - return $out; - } - - /** - * Show single interwiki link - * - * @param SearchResult $result - * @param string $lastInterwiki - * @param array $terms - * @param string $query - * @param array $customCaptions iw prefix -> caption - */ - function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions){ - $fname = 'SpecialSearch::showInterwikiHit'; - wfProfileIn( $fname ); - global $wgUser, $wgContLang, $wgLang; - - if( $result->isBrokenTitle() ) { - wfProfileOut( $fname ); - return "\n"; - } - - $t = $result->getTitle(); - $sk = $wgUser->getSkin(); - - $link = $sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms)); - - // format redirect if any - $redirectTitle = $result->getRedirectTitle(); - $redirectText = $result->getRedirectSnippet($terms); - $redirect = ''; - if( !is_null($redirectTitle) ) - $redirect = "" - .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText)) - .""; - - $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 = $sk->makeKnownLinkObj( $searchTitle, wfMsg('search-interwiki-more'), - wfArrayToCGI(array('search' => $query, 'fulltext' => 'Search'))); - $out .= "
    {$searchLink}{$caption}
    \n
      "; - } - - $out .= "
    • {$link} {$redirect}
    • \n"; - wfProfileOut( $fname ); - return $out; - } - - - /** - * Generates the power search box at bottom of [[Special:Search]] - * @param $term string: search term - * @return $out string: HTML form - */ - function powerSearchBox( $term ) { - global $wgScript; - - $namespaces = ''; - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - $name = str_replace( '_', ' ', $name ); - if( '' == $name ) { - $name = wfMsg( 'blanknamespace' ); - } - $namespaces .= Xml::openElement( 'span', array( 'style' => 'white-space: nowrap' ) ) . - Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $this->namespaces ) ) . - Xml::closeElement( 'span' ) . "\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"; - - $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) . - Xml::fieldset( wfMsg( 'powersearch-legend' ), - Xml::hidden( 'title', 'Special:Search' ) . - "

      " . - wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) . - "
      " . - $namespaces . - "

      " . - "

      " . - $redirect . " " . $redirectLabel . - "

      " . - wfMsgExt( 'powersearch-field', array( 'parseinline' ) ) . - " " . - $searchField . - " " . - $searchButton ) . - ""; - - return $out; - } - - function powerSearchFocus() { - global $wgJsMimeType; - return ""; - } - - function shortDialog($term) { - global $wgScript; - - $out = Xml::openElement( 'form', array( - 'id' => 'search', - 'method' => 'get', - 'action' => $wgScript - )); - $out .= Xml::hidden( 'title', 'Special:Search' ); - $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . ' '; - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - if( in_array( $ns, $this->namespaces ) ) { - $out .= Xml::hidden( "ns{$ns}", '1' ); - } - } - $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) ); - $out .= Xml::closeElement( 'form' ); - - return $out; - } -} diff --git a/includes/specials/Shortpages.php b/includes/specials/Shortpages.php deleted file mode 100644 index 2e7d24a5a0..0000000000 --- a/includes/specials/Shortpages.php +++ /dev/null @@ -1,98 +0,0 @@ -tableName( 'page' ); - $name = $dbr->addQuotes( $this->getName() ); - - $forceindex = $dbr->useIndexClause("page_len"); - - if ($wgContentNamespaces) - $nsclause = "page_namespace IN (" . $dbr->makeList($wgContentNamespaces) . ")"; - else - $nsclause = "page_namespace = " . NS_MAIN; - - return - "SELECT $name as type, - page_namespace as namespace, - page_title as title, - page_len AS value - FROM $page $forceindex - WHERE $nsclause AND page_is_redirect=0"; - } - - function preprocessResults( $db, $res ) { - # There's no point doing a batch check if we aren't caching results; - # the page must exist for it to have been pulled out of the table - if( $this->isCached() ) { - $batch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) - $batch->add( $row->namespace, $row->title ); - $batch->execute(); - if( $db->numRows( $res ) > 0 ) - $db->dataSeek( $res, 0 ); - } - } - - function sortDescending() { - return false; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - $dm = $wgContLang->getDirMark(); - - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if ( !$title ) { - return ''; - } - $hlink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' ); - $plink = $this->isCached() - ? $skin->makeLinkObj( $title ) - : $skin->makeKnownLinkObj( $title ); - $size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->value ) ) ); - - return $title->exists() - ? "({$hlink}) {$dm}{$plink} {$dm}[{$size}]" - : "({$hlink}) {$dm}{$plink} {$dm}[{$size}]"; - } -} - -/** - * constructor - */ -function wfSpecialShortpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $spp = new ShortPagesPage(); - - return $spp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php new file mode 100644 index 0000000000..c2a8de4ef0 --- /dev/null +++ b/includes/specials/SpecialAllmessages.php @@ -0,0 +1,217 @@ +addWikiMsg( 'allmessagesnotsupportedDB' ); + return; + } + + wfProfileIn( __METHOD__ ); + + wfProfileIn( __METHOD__ . '-setup' ); + $ot = $wgRequest->getText( 'ot' ); + + $navText = wfMsg( 'allmessagestext' ); + + # Make sure all extension messages are available + + $wgMessageCache->loadAllMessages(); + + $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) ); + ksort( $sortedArray ); + $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]['msg'] = wfMsgNoTrans( $key ); + } + + wfProfileOut( __METHOD__ . '-setup' ); + + wfProfileIn( __METHOD__ . '-output' ); + $wgOut->addScriptFile( 'allmessages.js' ); + if ( $ot == 'php' ) { + $navText .= wfAllMessagesMakePhp( $messages ); + $wgOut->addHTML( 'PHP | HTML | ' . + 'XML' . + '
      ' . htmlspecialchars( $navText ) . '
      ' ); + } else if ( $ot == 'xml' ) { + $wgOut->disable(); + header( 'Content-type: text/xml' ); + echo wfAllMessagesMakeXml( $messages ); + } else { + $wgOut->addHTML( 'PHP | ' . + 'HTML | XML' ); + $wgOut->addWikiText( $navText ); + $wgOut->addHTML( wfAllMessagesMakeHTMLText( $messages ) ); + } + wfProfileOut( __METHOD__ . '-output' ); + + wfProfileOut( __METHOD__ ); +} + +function wfAllMessagesMakeXml( $messages ) { + global $wgLang; + $lang = $wgLang->getCode(); + $txt = "\n"; + $txt .= "\n"; + foreach( $messages as $key => $m ) { + $txt .= "\t" . Xml::element( 'message', array( 'name' => $key ), $m['msg'] ) . "\n"; + } + $txt .= ""; + return $txt; +} + +/** + * Create the messages array, formatted in PHP to copy to language files. + * @param $messages Messages array. + * @return The PHP messages array. + * @todo Make suitable for language files. + */ +function wfAllMessagesMakePhp( $messages ) { + global $wgLang; + $txt = "\n\n\$messages = array(\n"; + foreach( $messages as $key => $m ) { + if( $wgLang->getCode() != 'en' && $m['msg'] == $m['enmsg'] ) { + continue; + } else if ( wfEmptyMsg( $key, $m['msg'] ) ) { + $m['msg'] = ''; + $comment = ' #empty'; + } else { + $comment = ''; + } + $txt .= "'$key' => '" . preg_replace( '/(?getSkin(); + $talk = wfMsg( 'talkpagelinktext' ); + + $input = Xml::element( 'input', array( + 'type' => 'text', + 'id' => 'allmessagesinput', + 'onkeyup' => 'allmessagesfilter()' + ), '' ); + $checkbox = Xml::element( 'input', array( + 'type' => 'button', + 'value' => wfMsgHtml( 'allmessagesmodified' ), + 'id' => 'allmessagescheckbox', + 'onclick' => 'allmessagesmodified()' + ), '' ); + + $txt = ''; + + $txt .= ' + + + + + + + + '; + + wfProfileIn( __METHOD__ . "-check" ); + + # This is a nasty hack to avoid doing independent existence checks + # without sending the links and table through the slow wiki parser. + $pageExists = array( + NS_MEDIAWIKI => array(), + 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 ); + while( $s = $dbr->fetchObject( $res ) ) { + $pageExists[$s->page_namespace][$s->page_title] = true; + } + $dbr->freeResult( $res ); + wfProfileOut( __METHOD__ . "-check" ); + + wfProfileIn( __METHOD__ . "-output" ); + + $i = 0; + + foreach( $messages as $key => $m ) { + $title = $wgLang->ucfirst( $key ); + if( $wgLang->getCode() != $wgContLang->getCode() ) { + $title .= '/' . $wgLang->getCode(); + } + + $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, "" . htmlspecialchars( $key ) . '' ); + } else { + $pageLink = $sk->makeBrokenLinkObj( $titleObj, "" . htmlspecialchars( $key ) . '' ); + } + if( isset( $pageExists[NS_MEDIAWIKI_TALK][$title] ) ) { + $talkLink = $sk->makeKnownLinkObj( $talkPage, htmlspecialchars( $talk ) ); + } else { + $talkLink = $sk->makeBrokenLinkObj( $talkPage, htmlspecialchars( $talk ) ); + } + + $anchor = 'msg_' . htmlspecialchars( strtolower( $title ) ); + $anchor = ""; + + if( $changed ) { + $txt .= " + + + + + "; + } else { + $txt .= " + + + "; + } + $i++; + } + $txt .= '
      ' . wfMsgHtml( 'allmessagesname' ) . '' . wfMsgHtml( 'allmessagesdefault' ) . '
      ' . wfMsgHtml( 'allmessagescurrent' ) . '
      + $anchor$pageLink
      $talkLink +
      +$message +
      +$mw +
      + $anchor$pageLink
      $talkLink +
      +$mw +
      '; + wfProfileOut( __METHOD__ . '-output' ); + + wfProfileOut( __METHOD__ ); + return $txt; +} diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php new file mode 100644 index 0000000000..7223e31747 --- /dev/null +++ b/includes/specials/SpecialAllpages.php @@ -0,0 +1,404 @@ +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 { + /** + * Maximum number of pages to show on single subpage. + */ + protected $maxPerPage = 960; + + /** + * Name of this special page. Used to make title objects that reference back + * to this page. + */ + protected $name = 'Allpages'; + + /** + * 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 .= " + " . + Xml::label( wfMsg( $this->nsfromMsg ), 'nsfrom' ) . + " + " . + Xml::input( 'from', 20, $from, array( 'id' => 'nsfrom' ) ) . + " + + + " . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . + " + " . + Xml::namespaceSelector( $namespace, null ) . ' ' . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . + " + "; + $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, $including = false ) { + 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 ); + + global $wgMemc; + $key = wfMemcKey( 'allpages', 'ns', $namespace ); + $lines = $wgMemc->get( $key ); + + if( !is_array( $lines ) ) { + $options = array( 'LIMIT' => 1 ); + if ( ! $dbr->implicitOrderby() ) { + $options['ORDER BY'] = 'page_title'; + } + $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; + } + if( $s = $dbr->fetchObject( $res ) ) { + 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; + } + $dbr->freeResult( $res ); + } + $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 .= ""; + while ( count ( $lines ) > 0 ) { + $inpoint = array_shift ( $lines ); + $outpoint = array_shift ( $lines ); + $out .= $this->showline ( $inpoint, $outpoint, $namespace, false ); + } + $out .= '
      '; + $nsForm = $this->namespaceForm( $namespace, '', false ); + + # Is there more? + if ( $including ) { + $out2 = ''; + } else { + $morelinks = ''; + if ( $morelinks != '' ) { + $out2 = ''; + $out2 .= '
      ' . $nsForm; + $out2 .= ''; + $out2 .= $morelinks . '

      '; + } else { + $out2 = $nsForm . '
      '; + } + } + + $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', + "$inpointf", + "$outpointf" + ); + return ''.$out.''; +} + +/** + * @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; + + $sk = $wgUser->getSkin(); + + $fromList = $this->getNamespaceKeyAndText($namespace, $from); + $namespaces = $wgContLang->getNamespaces(); + $align = $wgContLang->isRtl() ? 'left' : 'right'; + + $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; + + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'page', + array( 'page_namespace', 'page_title', 'page_is_redirect' ), + array( + 'page_namespace' => $namespace, + 'page_title >= ' . $dbr->addQuotes( $fromKey ) + ), + __METHOD__, + array( + 'ORDER BY' => 'page_title', + 'LIMIT' => $this->maxPerPage + 1, + 'USE INDEX' => 'name_title', + ) + ); + + if( $res->numRows() > 0 ) { + $out = ''; + + while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { + $t = Title::makeTitle( $s->page_namespace, $s->page_title ); + if( $t ) { + $link = ($s->page_is_redirect ? '
      ' : '' ) . + $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . + ($s->page_is_redirect ? '
      ' : '' ); + } else { + $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; + } + if( $n % 3 == 0 ) { + $out .= ''; + } + $out .= ""; + $n++; + if( $n % 3 == 0 ) { + $out .= ''; + } + } + if( ($n % 3) != 0 ) { + $out .= ''; + } + $out .= '
      $link
      '; + } else { + $out = ''; + } + } + + if ( $including ) { + $out2 = ''; + } else { + if( $from == '' ) { + // First chunk; no previous link. + $prevTitle = null; + } 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 ); + } 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 ); + } else { + $prevTitle = null; + } + } + } + + $nsForm = $this->namespaceForm( $namespace, $from ); + $out2 = ''; + $out2 .= '
      ' . $nsForm; + $out2 .= '' . + $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; + } + + 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 .= "

      "; + } + + $wgOut->addHtml( $out2 . $out ); + if( isset($prevLink) or isset($nextLink) ) { + $wgOut->addHtml( '

      ' ); + if( isset( $prevLink ) ) { + $wgOut->addHTML( $prevLink ); + } + if( isset( $prevLink ) && isset( $nextLink ) ) { + $wgOut->addHTML( ' | ' ); + } + if( isset( $nextLink ) ) { + $wgOut->addHTML( $nextLink ); + } + $wgOut->addHTML( '

      ' ); + + } + +} + +/** + * @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/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php new file mode 100644 index 0000000000..724d34b152 --- /dev/null +++ b/includes/specials/SpecialAncientpages.php @@ -0,0 +1,61 @@ +tableName( 'page' ); + $revision = $db->tableName( 'revision' ); + #$use_index = $db->useIndexClause( 'cur_timestamp' ); # FIXME! this is gone + $epoch = $wgDBtype == 'mysql' ? 'UNIX_TIMESTAMP(rev_timestamp)' : + 'EXTRACT(epoch FROM rev_timestamp)'; + return + "SELECT 'Ancientpages' as type, + page_namespace as namespace, + page_title as title, + $epoch as value + FROM $page, $revision + WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0 + AND page_latest=rev_id"; + } + + function sortDescending() { + return false; + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + + $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $result->value ), true ); + $title = Title::makeTitle( $result->namespace, $result->title ); + $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); + return wfSpecialList($link, $d); + } +} + +function wfSpecialAncientpages() { + list( $limit, $offset ) = wfCheckLimits(); + + $app = new AncientPagesPage(); + + $app->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php new file mode 100644 index 0000000000..5ea25ca46a --- /dev/null +++ b/includes/specials/SpecialBlockip.php @@ -0,0 +1,494 @@ +readOnlyPage(); + return; + } + + # Permission check + if( !$wgUser->isAllowed( 'block' ) ) { + $wgOut->permissionRequired( 'block' ); + return; + } + + $ipb = new IPBlockForm( $par ); + + $action = $wgRequest->getVal( 'action' ); + if ( 'success' == $action ) { + $ipb->showSuccess(); + } else if ( $wgRequest->wasPosted() && 'submit' == $action && + $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $ipb->doSubmit(); + } else { + $ipb->showForm( '' ); + } +} + +/** + * Form object for the Special:Blockip page. + * + * @ingroup SpecialPage + */ +class IPBlockForm { + var $BlockAddress, $BlockExpiry, $BlockReason; +# var $BlockEmail; + + function IPBlockForm( $par ) { + global $wgRequest, $wgUser; + + $this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) ); + $this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' ); + $this->BlockReason = $wgRequest->getText( 'wpBlockReason' ); + $this->BlockReasonList = $wgRequest->getText( 'wpBlockReasonList' ); + $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') ); + $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' ); + + # Unchecked checkboxes are not included in the form data at all, so having one + # that is true by default is a bit tricky + $byDefault = !$wgRequest->wasPosted(); + $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault ); + $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault ); + $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault ); + $this->BlockEmail = $wgRequest->getBool( 'wpEmailBan', false ); + $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; + } + + function showForm( $err ) { + global $wgOut, $wgUser, $wgSysopUserBans; + + $wgOut->setPagetitle( wfMsg( 'blockip' ) ); + $wgOut->addWikiMsg( 'blockiptext' ); + + if($wgSysopUserBans) { + $mIpaddress = Xml::label( wfMsg( 'ipadressorusername' ), 'mw-bi-target' ); + } else { + $mIpaddress = Xml::label( wfMsg( 'ipaddress' ), 'mw-bi-target' ); + } + $mIpbexpiry = Xml::label( wfMsg( 'ipbexpiry' ), 'wpBlockExpiry' ); + $mIpbother = Xml::label( wfMsg( 'ipbother' ), 'mw-bi-other' ); + $mIpbreasonother = Xml::label( wfMsg( 'ipbreason' ), 'wpBlockReasonList' ); + $mIpbreason = Xml::label( wfMsg( 'ipbotherreason' ), 'mw-bi-reason' ); + + $titleObj = SpecialPage::getTitleFor( 'Blockip' ); + + if ( "" != $err ) { + $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) ); + $wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $err ) ); + } + + $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' ); + + $showblockoptions = $scBlockExpiryOptions != '-'; + if (!$showblockoptions) + $mIpbother = $mIpbexpiry; + + $blockExpiryFormOptions = Xml::option( wfMsg( 'ipbotheroption' ), 'other' ); + foreach (explode(',', $scBlockExpiryOptions) as $option) { + if ( strpos($option, ":") === false ) $option = "$option:$option"; + list($show, $value) = explode(":", $option); + $show = htmlspecialchars($show); + $value = htmlspecialchars($value); + $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ? true : false ) . "\n"; + } + + $reasonDropDown = Xml::listDropDown( 'wpBlockReasonList', + wfMsgForContent( 'ipbreason-dropdown' ), + wfMsgForContent( 'ipbreasonotherlist' ), '', 'wpBlockDropDown', 4 ); + + global $wgStylePath, $wgStyleVersion; + $wgOut->addHTML( + Xml::tags( 'script', array( 'type' => 'text/javascript', 'src' => "$wgStylePath/common/block.js?$wgStyleVersion" ), '' ) . + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( "action=submit" ), 'id' => 'blockip' ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'blockip-legend' ) ) . + Xml::openElement( 'table', array ( 'border' => '0', 'id' => 'mw-blockip-table' ) ) . + " + + {$mIpaddress} + + " . + Xml::input( 'wpBlockAddress', 45, $this->BlockAddress, + array( + 'tabindex' => '1', + 'id' => 'mw-bi-target', + 'onchange' => 'updateBlockOptions()' ) ). " + + + " + ); + if ( $showblockoptions ) { + $wgOut->addHTML(" + + {$mIpbexpiry} + + " . + Xml::tags( 'select', + array( + 'id' => 'wpBlockExpiry', + 'name' => 'wpBlockExpiry', + 'onchange' => 'considerChangingExpiryFocus()', + 'tabindex' => '2' ), + $blockExpiryFormOptions ) . + "" + ); + } + $wgOut->addHTML(" + + + + {$mIpbother} + + " . + Xml::input( 'wpBlockOther', 45, $this->BlockOther, + array( 'tabindex' => '3', 'id' => 'mw-bi-other' ) ) . " + + + + + {$mIpbreasonother} + + + {$reasonDropDown} + + + + + {$mIpbreason} + + " . + Xml::input( 'wpBlockReason', 45, $this->BlockReason, + array( 'tabindex' => '5', 'id' => 'mw-bi-reason', 'maxlength'=> '200' ) ) . " + + + +   + " . + Xml::checkLabel( wfMsg( 'ipbanononly' ), + 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly, + array( 'tabindex' => '6' ) ) . " + + + +   + " . + Xml::checkLabel( wfMsg( 'ipbcreateaccount' ), + 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount, + array( 'tabindex' => '7' ) ) . " + + + +   + " . + Xml::checkLabel( wfMsg( 'ipbenableautoblock' ), + 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, + array( 'tabindex' => '8' ) ) . " + + " + ); + + global $wgSysopEmailBans; + if ( $wgSysopEmailBans && $wgUser->isAllowed( 'blockemail' ) ) { + $wgOut->addHTML(" + +   + " . + Xml::checkLabel( wfMsg( 'ipbemailban' ), + 'wpEmailBan', 'wpEmailBan', $this->BlockEmail, + array( 'tabindex' => '9' )) . " + + " + ); + } + + // Allow some users to hide name from block log, blocklist and listusers + if ( $wgUser->isAllowed( 'hideuser' ) ) { + $wgOut->addHTML(" + +   + " . + Xml::checkLabel( wfMsg( 'ipbhidename' ), + 'wpHideName', 'wpHideName', $this->BlockHideName, + array( 'tabindex' => '10' ) ) . " + + " + ); + } + + # Watchlist their user page? + $wgOut->addHTML(" + +   + " . + Xml::checkLabel( wfMsg( 'ipbwatchuser' ), + 'wpWatchUser', 'wpWatchUser', $this->BlockWatchUser, + array( 'tabindex' => '11' ) ) . " + + " + ); + + $wgOut->addHTML(" + +   + " . + Xml::submitButton( wfMsg( 'ipbsubmit' ), + array( 'name' => 'wpBlock', 'tabindex' => '12' ) ) . " + + " . + Xml::closeElement( 'table' ) . + Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . + Xml::tags( 'script', array( 'type' => 'text/javascript' ), 'updateBlockOptions()' ) . "\n" + ); + + $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 ) ) { + $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); + } elseif( preg_match( '/^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/', $this->BlockAddress ) ) { + $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); + } + } + + /** + * Backend block code. + * $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; + + $userId = 0; + # Expand valid IPv6 addresses, usernames are left as is + $this->BlockAddress = IP::sanitizeIP( $this->BlockAddress ); + # isIPv4() and IPv6() are used for final validation + $rxIP4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; + $rxIP6 = '\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}'; + $rxIP = "($rxIP4|$rxIP6)"; + + # Check for invalid specifications + if ( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) { + $matches = array(); + if ( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { + # IPv4 + if ( $wgSysopRangeBans ) { + if ( !IP::isIPv4( $this->BlockAddress ) || $matches[2] < 16 || $matches[2] > 32 ) { + return array('ip_range_invalid'); + } + $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); + } else { + # Range block illegal + return array('range_block_disabled'); + } + } else if ( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) { + # IPv6 + if ( $wgSysopRangeBans ) { + if ( !IP::isIPv6( $this->BlockAddress ) || $matches[2] < 64 || $matches[2] > 128 ) { + return array('ip_range_invalid'); + } + $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); + } else { + # Range block illegal + return array('range_block_disabled'); + } + } else { + # Username block + if ( $wgSysopUserBans ) { + $user = User::newFromName( $this->BlockAddress ); + if( !is_null( $user ) && $user->getId() ) { + # Use canonical name + $userId = $user->getId(); + $this->BlockAddress = $user->getName(); + } else { + return array('nosuchusershort', htmlspecialchars( $user ? $user->getName() : $this->BlockAddress ) ); + } + } else { + return array('badipaddress'); + } + } + } + + $reasonstr = $this->BlockReasonList; + if ( $reasonstr != 'other' && $this->BlockReason != '') { + // Entry from drop down menu + additional comment + $reasonstr .= ': ' . $this->BlockReason; + } elseif ( $reasonstr == 'other' ) { + $reasonstr = $this->BlockReason; + } + + $expirestr = $this->BlockExpiry; + if( $expirestr == 'other' ) + $expirestr = $this->BlockOther; + + if (strlen($expirestr) == 0) { + return array('ipb_expiry_invalid'); + } + + if ( false === ($expiry = Block::parseExpiryInput( $expirestr )) ) { + // Bad expiry. + return array('ipb_expiry_invalid'); + } + + if( $this->BlockHideName && $expiry != 'infinity' ) { + // Bad expiry. + return array('ipb_expiry_temp'); + } + + # Create block + # Note: for a user block, ipb_address is only for display purposes + $block = new Block( $this->BlockAddress, $userId, $wgUser->getId(), + $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, + $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, + $this->BlockEmail ); + + if ( wfRunHooks('BlockIp', array(&$block, &$wgUser)) ) { + + if ( !$block->insert() ) { + return array('ipb_already_blocked', htmlspecialchars($this->BlockAddress)); + } + + wfRunHooks('BlockIpComplete', array($block, $wgUser)); + + if ( $this->BlockWatchUser ) { + $wgUser->addWatch ( Title::makeTitle( NS_USER, $this->BlockAddress ) ); + } + + # Prepare log parameters + $logParams = array(); + $logParams[] = $expirestr; + $logParams[] = $this->blockLogFlags(); + + # 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 ), + $reasonstr, $logParams ); + + # Report to the user + return array(); + } + else + return array('hookaborted'); + } + + /** + * UI entry point for blocking + * Wraps around doBlock() + */ + function doSubmit() + { + global $wgOut; + $retval = $this->doBlock(); + if(empty($retval)) { + $titleObj = SpecialPage::getTitleFor( 'Blockip' ); + $wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' . + urlencode( $this->BlockAddress ) ) ); + return; + } + $key = array_shift($retval); + $this->showForm(wfMsgReal($key, $retval)); + } + + function showSuccess() { + global $wgOut; + + $wgOut->setPagetitle( wfMsg( 'blockip' ) ); + $wgOut->setSubtitle( wfMsg( 'blockipsuccesssub' ) ); + $text = wfMsgExt( 'blockipsuccesstext', array( 'parse' ), $this->BlockAddress ); + $wgOut->addHtml( $text ); + } + + function showLogFragment( $out, $title ) { + $out->addHtml( Xml::element( 'h2', NULL, LogPage::logName( 'block' ) ) ); + LogEventsList::showLogExtract( $out, 'block', $title->getPrefixedText() ); + } + + /** + * Return a comma-delimited list of "flags" to be passed to the log + * reader for this block, to provide more information in the logs + * + * @return array + */ + private function blockLogFlags() { + $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 + $flags[] = 'anononly'; + if( $this->BlockCreateAccount ) + $flags[] = 'nocreate'; + if( !$this->BlockEnableAutoblock ) + $flags[] = 'noautoblock'; + if ( $this->BlockEmail ) + $flags[] = 'noemail'; + return implode( ',', $flags ); + } + + /** + * Builds unblock and block list links + * + * @return string + */ + private function getConvenienceLinks() { + global $wgUser; + $skin = $wgUser->getSkin(); + $links[] = $skin->makeLink ( 'MediaWiki:Ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) ); + $links[] = $this->getUnblockLink( $skin ); + $links[] = $this->getBlockListLink( $skin ); + return ''; + } + + /** + * Build a convenient link to unblock the given username or IP + * address, if available; otherwise link to a blank unblock + * form + * + * @param $skin Skin to use + * @return string + */ + private function getUnblockLink( $skin ) { + $list = SpecialPage::getTitleFor( 'Ipblocklist' ); + if( $this->BlockAddress ) { + $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock-addr', $addr ), + 'action=unblock&ip=' . urlencode( $this->BlockAddress ) ); + } else { + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock' ), 'action=unblock' ); + } + } + + /** + * Build a convenience link to the block list + * + * @param $skin Skin to use + * @return string + */ + private function getBlockListLink( $skin ) { + $list = SpecialPage::getTitleFor( 'Ipblocklist' ); + if( $this->BlockAddress ) { + $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist-addr', $addr ), + 'ip=' . urlencode( $this->BlockAddress ) ); + } else { + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist' ) ); + } + } +} diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php new file mode 100644 index 0000000000..f222e3c63e --- /dev/null +++ b/includes/specials/SpecialBlockme.php @@ -0,0 +1,37 @@ +getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) { + $wgOut->addWikiMsg( 'proxyblocker-disabled' ); + return; + } + + $blockerName = wfMsg( "proxyblocker" ); + $reason = wfMsg( "proxyblockreason" ); + + $u = User::newFromName( $blockerName ); + $id = $u->idForName(); + if ( !$id ) { + $u = User::newFromName( $blockerName ); + $u->addToDatabase(); + $u->setPassword( bin2hex( mt_rand(0, 0x7fffffff ) ) ); + $u->saveSettings(); + $id = $u->getID(); + } + + $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() ); + $block->insert(); + + $wgOut->addWikiMsg( "proxyblocksuccess" ); +} diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php new file mode 100644 index 0000000000..0690c5c05d --- /dev/null +++ b/includes/specials/SpecialBooksources.php @@ -0,0 +1,110 @@ + + * @todo Validate ISBNs using the standard check-digit method + * @ingroup SpecialPages + */ +class SpecialBookSources extends SpecialPage { + + /** + * ISBN passed to the page, if any + */ + private $isbn = ''; + + /** + * Constructor + */ + public function __construct() { + parent::__construct( 'Booksources' ); + } + + /** + * Show the special page + * + * @param $isbn ISBN passed as a subpage parameter + */ + public function execute( $isbn ) { + global $wgOut, $wgRequest; + $this->setHeaders(); + $this->isbn = $this->cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); + $wgOut->addWikiMsg( 'booksources-summary' ); + $wgOut->addHtml( $this->makeForm() ); + if( strlen( $this->isbn ) > 0 ) + $this->showList(); + } + + /** + * Trim ISBN and remove characters which aren't required + * + * @param $isbn Unclean ISBN + * @return string + */ + private function cleanIsbn( $isbn ) { + return trim( preg_replace( '![^0-9X]!', '', $isbn ) ); + } + + /** + * Generate a form to allow users to enter an ISBN + * + * @return string + */ + private function makeForm() { + global $wgScript; + $title = self::getTitleFor( 'Booksources' ); + $form = '
      ' . wfMsgHtml( 'booksources-search-legend' ) . ''; + $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); + $form .= Xml::hidden( 'title', $title->getPrefixedText() ); + $form .= '

      ' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn ); + $form .= ' ' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '

      '; + $form .= Xml::closeElement( 'form' ); + $form .= '
      '; + return $form; + } + + /** + * Determine where to get the list of book sources from, + * format and output them + * + * @return string + */ + private function showList() { + global $wgOut, $wgContLang; + + # Hook to allow extensions to insert additional HTML, + # e.g. for API-interacting plugins and so on + wfRunHooks( 'BookInformation', array( $this->isbn, &$wgOut ) ); + + # Check for a local page such as Project:Book_sources and use that if available + $title = Title::makeTitleSafe( NS_PROJECT, wfMsgForContent( 'booksources' ) ); # Show list in content language + if( is_object( $title ) && $title->exists() ) { + $rev = Revision::newFromTitle( $title ); + $wgOut->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); + return true; + } + + # Fall back to the defaults given in the language file + $wgOut->addWikiMsg( 'booksources-text' ); + $wgOut->addHtml( '
        ' ); + $items = $wgContLang->getBookstoreList(); + foreach( $items as $label => $url ) + $wgOut->addHtml( $this->makeListItem( $label, $url ) ); + $wgOut->addHtml( '
      ' ); + return true; + } + + /** + * Format a book source list item + * + * @param $label Book source label + * @param $url Book source URL + * @return string + */ + private function makeListItem( $label, $url ) { + $url = str_replace( '$1', $this->isbn, $url ); + return '
    • ' . htmlspecialchars( $label ) . '
    • '; + } +} diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php new file mode 100644 index 0000000000..0a16e6de32 --- /dev/null +++ b/includes/specials/SpecialBrokenRedirects.php @@ -0,0 +1,93 @@ +tableNamesN( 'page', 'redirect' ); + + $sql = "SELECT 'BrokenRedirects' AS type, + p1.page_namespace AS namespace, + p1.page_title AS title, + rd_namespace, + rd_title + FROM $redirect AS rd + JOIN $page p1 ON (rd.rd_from=p1.page_id) + LEFT JOIN $page AS p2 ON (rd_namespace=p2.page_namespace AND rd_title=p2.page_title ) + WHERE rd_namespace >= 0 + AND p2.page_namespace IS NULL"; + return $sql; + } + + function getOrder() { + return ''; + } + + function formatResult( $skin, $result ) { + global $wgUser, $wgContLang; + + $fromObj = Title::makeTitle( $result->namespace, $result->title ); + if ( isset( $result->rd_title ) ) { + $toObj = Title::makeTitle( $result->rd_namespace, $result->rd_title ); + } else { + $blinks = $fromObj->getBrokenLinksFrom(); # TODO: check for redirect, not for links + if ( $blinks ) { + $toObj = $blinks[0]; + } else { + $toObj = false; + } + } + + // $toObj may very easily be false if the $result list is cached + if ( !is_object( $toObj ) ) { + return '' . $skin->makeLinkObj( $fromObj ) . ''; + } + + $from = $skin->makeKnownLinkObj( $fromObj ,'', 'redirect=no' ); + $edit = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-edit' ), 'action=edit' ); + $to = $skin->makeBrokenLinkObj( $toObj ); + $arr = $wgContLang->getArrow(); + + $out = "{$from} {$edit}"; + + if( $wgUser->isAllowed( 'delete' ) ) { + $delete = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-delete' ), 'action=delete' ); + $out .= " {$delete}"; + } + + $out .= " {$arr} {$to}"; + return $out; + } +} + +/** + * constructor + */ +function wfSpecialBrokenRedirects() { + list( $limit, $offset ) = wfCheckLimits(); + + $sbr = new BrokenRedirectsPage(); + + return $sbr->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php new file mode 100644 index 0000000000..951c2228e4 --- /dev/null +++ b/includes/specials/SpecialCategories.php @@ -0,0 +1,112 @@ +getText( 'from' ); + } else { + $from = $par; + } + $cap = new CategoryPager( $from ); + $wgOut->addHTML( + wfMsgExt( 'categoriespagetext', array( 'parse' ) ) . + $cap->getStartForm( $from ) . + $cap->getNavigationBar() . + '
        ' . $cap->getBody() . '
      ' . + $cap->getNavigationBar() + ); +} + +/** + * TODO: Allow sorting by count. We need to have a unique index to do this + * properly. + * + * @ingroup SpecialPage Pager + */ +class CategoryPager extends AlphabeticPager { + function __construct( $from ) { + parent::__construct(); + $from = str_replace( ' ', '_', $from ); + if( $from !== '' ) { + global $wgCapitalLinks, $wgContLang; + if( $wgCapitalLinks ) { + $from = $wgContLang->ucfirst( $from ); + } + $this->mOffset = $from; + } + } + + function getQueryInfo() { + global $wgRequest; + return array( + 'tables' => array( 'category' ), + 'fields' => array( 'cat_title','cat_pages' ), + 'conds' => array( 'cat_pages > 0' ), + 'options' => array( 'USE INDEX' => 'cat_title' ), + ); + } + + function getIndexField() { +# return array( 'abc' => 'cat_title', 'count' => 'cat_pages' ); + return 'cat_title'; + } + + function getDefaultQuery() { + parent::getDefaultQuery(); + unset( $this->mDefaultQuery['from'] ); + } +# protected function getOrderTypeMessages() { +# return array( 'abc' => 'special-categories-sort-abc', +# 'count' => 'special-categories-sort-count' ); +# } + + protected function getDefaultDirections() { +# return array( 'abc' => false, 'count' => true ); + return false; + } + + /* Override getBody to apply LinksBatch on resultset before actually outputting anything. */ + public function getBody() { + if (!$this->mQueryDone) { + $this->doQuery(); + } + $batch = new LinkBatch; + + $this->mResult->rewind(); + + while ( $row = $this->mResult->fetchObject() ) { + $batch->addObj( Title::makeTitleSafe( NS_CATEGORY, $row->cat_title ) ); + } + $batch->execute(); + $this->mResult->rewind(); + return parent::getBody(); + } + + function formatRow($result) { + global $wgLang; + $title = Title::makeTitle( NS_CATEGORY, $result->cat_title ); + $titleText = $this->getSkin()->makeLinkObj( $title, htmlspecialchars( $title->getText() ) ); + $count = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $result->cat_pages ) ); + return Xml::tags('li', null, "$titleText ($count)" ) . "\n"; + } + + public function getStartForm( $from ) { + global $wgScript; + $t = SpecialPage::getTitleFor( 'Categories' ); + + return + Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ), + Xml::hidden( 'title', $t->getPrefixedText() ) . + Xml::fieldset( wfMsg( 'categories' ), + Xml::inputLabel( wfMsg( 'categoriesfrom' ), + 'from', 'from', 20, $from ) . + ' ' . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) ) ); + } +} diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php new file mode 100644 index 0000000000..9075fb9573 --- /dev/null +++ b/includes/specials/SpecialConfirmemail.php @@ -0,0 +1,139 @@ + + */ +class EmailConfirmation extends UnlistedSpecialPage { + + /** + * Constructor + */ + public function __construct() { + parent::__construct( 'Confirmemail' ); + } + + /** + * Main execution point + * + * @param $code Confirmation code passed to the page + */ + function execute( $code ) { + global $wgUser, $wgOut; + $this->setHeaders(); + if( empty( $code ) ) { + if( $wgUser->isLoggedIn() ) { + if( User::isValidEmailAddr( $wgUser->getEmail() ) ) { + $this->showRequestForm(); + } else { + $wgOut->addWikiMsg( 'confirmemail_noemail' ); + } + } else { + $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 ) ); + } + } else { + $this->attemptConfirm( $code ); + } + } + + /** + * Show a nice form for the user to request a confirmation mail + */ + function showRequestForm() { + global $wgOut, $wgUser, $wgLang, $wgRequest; + if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) { + $ok = $wgUser->sendConfirmationMail(); + if ( WikiError::isError( $ok ) ) { + $wgOut->addWikiMsg( 'confirmemail_sendfailed', $ok->toString() ); + } else { + $wgOut->addWikiMsg( 'confirmemail_sent' ); + } + } else { + if( $wgUser->isEmailConfirmed() ) { + $time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true ); + $wgOut->addWikiMsg( 'emailauthenticated', $time ); + } + 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 ); + } + } + + /** + * Attempt to confirm the user's email address and show success or failure + * as needed; if successful, take the user to log in + * + * @param $code Confirmation code + */ + function attemptConfirm( $code ) { + global $wgUser, $wgOut; + $user = User::newFromConfirmationCode( $code ); + if( is_object( $user ) ) { + $user->confirmEmail(); + $user->saveSettings(); + $message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success'; + $wgOut->addWikiMsg( $message ); + if( !$wgUser->isLoggedIn() ) { + $title = SpecialPage::getTitleFor( 'Userlogin' ); + $wgOut->returnToMain( true, $title ); + } + } else { + $wgOut->addWikiMsg( 'confirmemail_invalid' ); + } + } + +} + +/** + * Special page allows users to cancel an email confirmation using the e-mail + * confirmation code + * + * @ingroup SpecialPage + */ +class EmailInvalidation extends UnlistedSpecialPage { + + public function __construct() { + parent::__construct( 'Invalidateemail' ); + } + + function execute( $code ) { + $this->setHeaders(); + $this->attemptInvalidate( $code ); + } + + /** + * Attempt to invalidate the user's email address and show success or failure + * as needed; if successful, link to main page + * + * @param $code Confirmation code + */ + function attemptInvalidate( $code ) { + global $wgUser, $wgOut; + $user = User::newFromConfirmationCode( $code ); + if( is_object( $user ) ) { + $user->invalidateEmail(); + $user->saveSettings(); + $wgOut->addWikiMsg( 'confirmemail_invalidated' ); + if( !$wgUser->isLoggedIn() ) { + $wgOut->returnToMain(); + } + } else { + $wgOut->addWikiMsg( 'confirmemail_invalid' ); + } + } +} diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php new file mode 100644 index 0000000000..c9cbc180fa --- /dev/null +++ b/includes/specials/SpecialContributions.php @@ -0,0 +1,465 @@ +messages[$msg] = wfMsgExt( $msg, array( 'escape') ); + } + $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->mDb = wfGetDB( DB_SLAVE, 'contributions' ); + } + + 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(); + $conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() ); + return array( + 'tables' => array( 'page', 'revision' ), + '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' => $index ) + ); + } + + function getUserCond() { + $condition = array(); + + if ( $this->target == 'newbies' ) { + $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ ); + $condition[] = 'rev_user >' . (int)($max - $max / 100); + $index = 'user_timestamp'; + } else { + $condition['rev_user_text'] = $this->target; + $index = 'usertext_timestamp'; + } + return array( $index, $condition ); + } + + function getNamespaceCond() { + 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'; + } + + function getStartBody() { + return "
        \n"; + } + + function getEndBody() { + return "
      \n"; + } + + /** + * 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 roll- + * back 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, $wgContLang; + + $sk = $this->getSkin(); + $rev = new Revision( $row ); + + $page = Title::makeTitle( $row->page_namespace, $row->page_title ); + $link = $sk->makeKnownLinkObj( $page ); + $difftext = $topmarktext = ''; + if( $row->rev_id == $row->page_latest ) { + $topmarktext .= '' . $this->messages['uctop'] . ''; + if( !$row->page_is_new ) { + $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')'; + } else { + $difftext .= $this->messages['newarticle']; + } + + if( !$page->getUserPermissionsErrors( 'rollback', $wgUser ) + && !$page->getUserPermissionsErrors( 'edit', $wgUser ) ) { + $topmarktext .= ' '.$sk->generateRollback( $rev ); + } + + } + # Is there a visible previous revision? + if( $rev->userCan(Revision::DELETED_TEXT) ) { + $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; + } else { + $difftext = '(' . $this->messages['diff'] . ')'; + } + $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 ); + + if( $this->target == 'newbies' ) { + $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text ); + $userlink .= ' (' . $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) . ') '; + } else { + $userlink = ''; + } + + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $d = '' . $d . ''; + } + + if( $rev->getParentId() === 0 ) { + $nflag = '' . $this->messages['newpageletter'] . ''; + } else { + $nflag = ''; + } + + if( $row->rev_minor_edit ) { + $mflag = '' . $this->messages['minoreditletter'] . ' '; + } else { + $mflag = ''; + } + + $ret = "{$d} {$histlink} {$difftext} {$nflag}{$mflag} {$link}{$userlink}{$comment} {$topmarktext}"; + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $ret .= ' ' . wfMsgHtml( 'deletedrev' ); + } + $ret = "
    • $ret
    • \n"; + wfProfileOut( __METHOD__ ); + return $ret; + } + + /** + * Get the Database object in use + * + * @return Database + */ + public function getDatabase() { + return $this->mDb; + } + +} + +/** + * 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( + '

      ' . $pager->getNavigationBar() . '

      ' . + $pager->getBody() . + '

      ' . $pager->getNavigationBar() . '

      ' ); + + # 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( '' ); + } + } +} + +/** + * 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 .= '
      ' . + Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . + Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), 'contribs' , 'newbie' , 'newbie', $options['contribs'] == 'newbie' ? true : false ) . '
      ' . + Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ), 'contribs' , 'user', 'user', $options['contribs'] == 'user' ? true : false ) . ' ' . + Xml::input( 'target', 20, $options['target']) . ' '. + '' . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $options['namespace'], '' ) . + '' . + Xml::openElement( 'p' ) . + '' . + Xml::label( wfMsg( 'year' ), 'year' ) . ' '. + Xml::input( 'year', 4, $options['year'], array('id' => 'year', 'maxlength' => 4) ) . + '' . + ' '. + '' . + Xml::label( wfMsg( 'month' ), 'month' ) . ' '. + Xml::monthSelector( $options['month'], -1 ) . ' '. + '' . + Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . + Xml::closeElement( 'p' ); + + $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); + if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) + $f .= "

      {$explain}

      "; + + $f .= '
      ' . + Xml::closeElement( 'form' ); + return $f; +} diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php new file mode 100644 index 0000000000..a8416c9741 --- /dev/null +++ b/includes/specials/SpecialDeadendpages.php @@ -0,0 +1,62 @@ +tableNamesN( 'page', 'pagelinks' ); + return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " . + "FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " . + "WHERE pl_from IS NULL " . + "AND page_namespace = 0 " . + "AND page_is_redirect = 0"; + } +} + +/** + * Constructor + */ +function wfSpecialDeadendpages() { + + list( $limit, $offset ) = wfCheckLimits(); + + $depp = new DeadendPagesPage(); + + return $depp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php new file mode 100644 index 0000000000..34045660ff --- /dev/null +++ b/includes/specials/SpecialDisambiguations.php @@ -0,0 +1,108 @@ +getNamespace() != NS_TEMPLATE) { + # FIXME we assume the disambiguation message is a template but + # the page can potentially be from another namespace :/ + wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); + } + $linkBatch->addObj( $dp ); + } else { + # Get all the templates linked from the Mediawiki:Disambiguationspage + $disPageObj = Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage' ); + $res = $dbr->select( + array('pagelinks', 'page'), + 'pl_title', + array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, + 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), + __METHOD__ ); + + while ( $row = $dbr->fetchObject( $res ) ) { + $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); + } + + $dbr->freeResult( $res ); + } + + $set = $linkBatch->constructSet( 'lb.tl', $dbr ); + if( $set === false ) { + # We must always return a valid sql query, but this way DB will always quicly return an empty result + $set = 'FALSE'; + wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n"); + } + + list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); + + $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," + ." pb.page_title AS title, la.pl_from AS value" + ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" + ." WHERE $set" # disambiguation template(s) + .' AND pa.page_id = la.pl_from' + .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace + .' AND pb.page_id = lb.tl_from' + .' AND pb.page_namespace = la.pl_namespace' + .' AND pb.page_title = la.pl_title' + .' ORDER BY lb.tl_namespace, lb.tl_title'; + + return $sql; + } + + function getOrder() { + return ''; + } + + function formatResult( $skin, $result ) { + global $wgContLang; + $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' ); + $arr = $wgContLang->getArrow(); + $to = $skin->makeKnownLinkObj( $dp, '' ); + + return "$from $edit $arr $to"; + } +} + +/** + * Constructor + */ +function wfSpecialDisambiguations() { + list( $limit, $offset ) = wfCheckLimits(); + + $sd = new DisambiguationsPage(); + + return $sd->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php new file mode 100644 index 0000000000..b1bad0c311 --- /dev/null +++ b/includes/specials/SpecialDoubleRedirects.php @@ -0,0 +1,103 @@ +tableNamesN( 'page', 'redirect' ); + + $limitToTitle = !( $namespace === null && $title === null ); + $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ; + $sql .= + " pa.page_namespace as namespace, pa.page_title as title," . + " pb.page_namespace as nsb, pb.page_title as tb," . + " pc.page_namespace as nsc, pc.page_title as tc" . + " FROM $redirect AS ra, $redirect AS rb, $page AS pa, $page AS pb, $page AS pc" . + " WHERE ra.rd_from=pa.page_id" . + " AND ra.rd_namespace=pb.page_namespace" . + " AND ra.rd_title=pb.page_title" . + " AND rb.rd_from=pb.page_id" . + " AND rb.rd_namespace=pc.page_namespace" . + " AND rb.rd_title=pc.page_title"; + + if( $limitToTitle ) { + $encTitle = $dbr->addQuotes( $title ); + $sql .= " AND pa.page_namespace=$namespace" . + " AND pa.page_title=$encTitle"; + } + + return $sql; + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + return $this->getSQLText( $dbr ); + } + + function getOrder() { + return ''; + } + + function formatResult( $skin, $result ) { + global $wgContLang; + + $fname = 'DoubleRedirectsPage::formatResult'; + $titleA = Title::makeTitle( $result->namespace, $result->title ); + + if ( $result && !isset( $result->nsb ) ) { + $dbr = wfGetDB( DB_SLAVE ); + $sql = $this->getSQLText( $dbr, $result->namespace, $result->title ); + $res = $dbr->query( $sql, $fname ); + if ( $res ) { + $result = $dbr->fetchObject( $res ); + $dbr->freeResult( $res ); + } + } + if ( !$result ) { + return '' . $skin->makeLinkObj( $titleA, '', 'redirect=no' ) . ''; + } + + $titleB = Title::makeTitle( $result->nsb, $result->tb ); + $titleC = Title::makeTitle( $result->nsc, $result->tc ); + + $linkA = $skin->makeKnownLinkObj( $titleA, '', 'redirect=no' ); + $edit = $skin->makeBrokenLinkObj( $titleA, "(".wfMsg("qbedit").")" , 'redirect=no'); + $linkB = $skin->makeKnownLinkObj( $titleB, '', 'redirect=no' ); + $linkC = $skin->makeKnownLinkObj( $titleC ); + $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); + + return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" ); + } +} + +/** + * constructor + */ +function wfSpecialDoubleRedirects() { + list( $limit, $offset ) = wfCheckLimits(); + + $sdr = new DoubleRedirectsPage(); + + return $sdr->doQuery( $offset, $limit ); + +} diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php new file mode 100644 index 0000000000..596f16bab6 --- /dev/null +++ b/includes/specials/SpecialEmailuser.php @@ -0,0 +1,291 @@ +getVal( 'action' ); + $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); + $targetUser = EmailUserForm::validateEmailTarget( $target ); + + if ( !( $targetUser instanceof User ) ) { + $wgOut->showErrorPage( $targetUser[0], $targetUser[1] ); + return; + } + + $form = new EmailUserForm( $targetUser, + $wgRequest->getText( 'wpText' ), + $wgRequest->getText( 'wpSubject' ), + $wgRequest->getBool( 'wpCCMe' ) ); + if ( $action == 'success' ) { + $form->showSuccess(); + return; + } + + $error = EmailUserForm::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) ); + if ( $error ) { + switch ( $error[0] ) { + case 'blockedemailuser': + $wgOut->blockedPage(); + return; + case 'actionthrottledtext': + $wgOut->rateLimited(); + return; + case 'sessionfailure': + $form->showForm(); + return; + default: + $wgOut->showErrorPage( $error[0], $error[1] ); + return; + } + } + + + if ( "submit" == $action && $wgRequest->wasPosted() ) { + $result = $form->doSubmit(); + + if ( !is_null( $result ) ) { + $wgOut->addHTML( wfMsg( "usermailererror" ) . + ' ' . htmlspecialchars( $result->getMessage() ) ); + } else { + $titleObj = SpecialPage::getTitleFor( "Emailuser" ); + $encTarget = wfUrlencode( $form->getTarget()->getName() ); + $wgOut->redirect( $titleObj->getFullURL( "target={$encTarget}&action=success" ) ); + } + } else { + $form->showForm(); + } +} + +/** + * Implements the Special:Emailuser web interface, and invokes userMailer for sending the email message. + * @ingroup SpecialPage + */ +class EmailUserForm { + + var $target; + var $text, $subject; + var $cc_me; // Whether user requested to be sent a separate copy of their email. + + /** + * @param User $target + */ + function EmailUserForm( $target, $text, $subject, $cc_me ) { + $this->target = $target; + $this->text = $text; + $this->subject = $subject; + $this->cc_me = $cc_me; + } + + function showForm() { + global $wgOut, $wgUser; + $skin = $wgUser->getSkin(); + + $wgOut->setPagetitle( wfMsg( "emailpage" ) ); + $wgOut->addWikiMsg( "emailpagetext" ); + + if ( $this->subject === "" ) { + $this->subject = wfMsgForContent( "defemailsubject" ); + } + + $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=" . + urlencode( $this->target->getName() ) . "&action=submit" ); + $token = htmlspecialchars( $wgUser->editToken() ); + + $wgOut->addHTML( " +
      + + + + + + + + + + +
      {$emf}:{$senderLink}
      {$emt}:{$recipientLink}
      {$emr}: + +
      +
      + +" . wfCheckLabel( $emc, 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) . "
      + + +
      \n" ); + + } + + /* + * Really send a mail. Permissions should have been checked using + * EmailUserForm::getPermissionsError. It is probably also a good idea to + * check the edit token and ping limiter in advance. + */ + function doSubmit() { + global $wgUser, $wgUserEmailUseReplyTo, $wgSiteName; + + $to = new MailAddress( $this->target ); + $from = new MailAddress( $wgUser ); + $subject = $this->subject; + + $prefsTitle = Title::newFromText( 'Preferences', NS_SPECIAL ); + + // Add a standard footer + $footerArgs[0] = $from->name; + $footerArgs[1] = $to->name; + $footerArgs[2] = $prefsTitle->getFullURL(); + $footerArgs[3] = wfMsg ('allowemail'); + $this->text = $this->text . "\n" . wfMsgExt( 'emailuserfooter', 'parsemag', $footerArgs ); + + if( wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$this->text ) ) ) { + + if( $wgUserEmailUseReplyTo ) { + // Put the generic wiki autogenerated address in the From: + // header and reserve the user for Reply-To. + // + // This is a bit ugly, but will serve to differentiate + // wiki-borne mails from direct mails and protects against + // SPF and bounce problems with some mailers (see below). + global $wgPasswordSender; + $mailFrom = new MailAddress( $wgPasswordSender ); + $replyTo = $from; + } else { + // Put the sending user's e-mail address in the From: header. + // + // This is clean-looking and convenient, but has issues. + // One is that it doesn't as clearly differentiate the wiki mail + // from "directly" sent mails. + // + // Another is that some mailers (like sSMTP) will use the From + // address as the envelope sender as well. For open sites this + // can cause mails to be flunked for SPF violations (since the + // wiki server isn't an authorized sender for various users' + // domains) as well as creating a privacy issue as bounces + // containing the recipient's e-mail address may get sent to + // the sending user. + $mailFrom = $from; + $replyTo = null; + } + + $mailResult = UserMailer::send( $to, $mailFrom, $subject, $this->text, $replyTo ); + + if( WikiError::isError( $mailResult ) ) { + return $mailResult; + + } else { + + // if the user requested a copy of this mail, do this now, + // unless they are emailing themselves, in which case one copy of the message is sufficient. + if ($this->cc_me && $to != $from) { + $cc_subject = wfMsg('emailccsubject', $this->target->getName(), $subject); + if( wfRunHooks( 'EmailUser', array( &$from, &$from, &$cc_subject, &$this->text ) ) ) { + $ccResult = UserMailer::send( $from, $from, $cc_subject, $this->text ); + if( WikiError::isError( $ccResult ) ) { + // At this stage, the user's CC mail has failed, but their + // original mail has succeeded. It's unlikely, but still, what to do? + // We can either show them an error, or we can say everything was fine, + // or we can say we sort of failed AND sort of succeeded. Of these options, + // simply saying there was an error is probably best. + return $ccResult; + } + } + } + + wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $this->text ) ); + return; + } + } + } + + function showSuccess( &$user = null ) { + global $wgOut; + + if ( is_null($user) ) + $user = $this->target; + + $wgOut->setPagetitle( wfMsg( "emailsent" ) ); + $wgOut->addHTML( wfMsg( "emailsenttext" ) ); + + $wgOut->returnToMain( false, $user->getUserPage() ); + } + + function getTarget() { + return $this->target; + } + + static function validateEmailTarget ( $target ) { + global $wgEnableEmail, $wgEnableUserEmail; + + if( !( $wgEnableEmail && $wgEnableUserEmail ) ) + return array( "nosuchspecialpage", "nospecialpagetext" ); + + if ( "" == $target ) { + wfDebug( "Target is empty.\n" ); + return array( "notargettitle", "notargettext" ); + } + + $nt = Title::newFromURL( $target ); + if ( is_null( $nt ) ) { + wfDebug( "Target is invalid title.\n" ); + return array( "notargettitle", "notargettext" ); + } + + $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" ); + } + + return $nu; + } + static function getPermissionsError ( $user, $editToken ) { + if( !$user->canSendEmail() ) { + wfDebug( "User can't send.\n" ); + return array( "mailnologin", "mailnologintext" ); + } + + if( $user->isBlockedFromEmailuser() ) { + wfDebug( "User is blocked from sending e-mail.\n" ); + return array( "blockedemailuser", "" ); + } + + if( $user->pingLimiter( 'emailuser' ) ) { + wfDebug( "Ping limiter triggered.\n" ); + return array( 'actionthrottledtext', '' ); + } + + if( !$user->matchEditToken( $editToken ) ) { + wfDebug( "Matching edit token failed.\n" ); + return array( 'sessionfailure', '' ); + } + + return; + } + + static function newFromURL( $target, $text, $subject, $cc_me ) + { + $nt = Title::newFromURL( $target ); + $nu = User::newFromName( $nt->getText() ); + return new EmailUserForm( $nu, $text, $subject, $cc_me ); + } +} diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php new file mode 100644 index 0000000000..38bfc83e83 --- /dev/null +++ b/includes/specials/SpecialExport.php @@ -0,0 +1,284 @@ + +# http://www.mediawiki.org/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# http://www.gnu.org/copyleft/gpl.html +/** + * @file + * @ingroup SpecialPage + */ + +function wfExportGetPagesFromCategory( $title ) { + global $wgContLang; + + $name = $title->getDBkey(); + + $dbr = wfGetDB( DB_SLAVE ); + + list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); + $sql = "SELECT page_namespace, page_title FROM $page " . + "JOIN $categorylinks ON cl_from = page_id " . + "WHERE cl_to = " . $dbr->addQuotes( $name ); + + $pages = array(); + $res = $dbr->query( $sql, 'wfExportGetPagesFromCategory' ); + while ( $row = $dbr->fetchObject( $res ) ) { + $n = $row->page_title; + if ($row->page_namespace) { + $ns = $wgContLang->getNsText( $row->page_namespace ); + $n = $ns . ':' . $n; + } + + $pages[] = $n; + } + $dbr->freeResult($res); + + return $pages; +} + +/** + * Expand a list of pages to include templates used in those pages. + * @param $inputPages array, list of titles to look up + * @param $pageSet array, associative array indexed by titles for output + * @return array associative array index by titles + */ +function wfExportGetTemplates( $inputPages, $pageSet ) { + return wfExportGetLinks( $inputPages, $pageSet, + 'templatelinks', + array( 'tl_namespace AS namespace', 'tl_title AS title' ), + array( 'page_id=tl_from' ) ); +} + +/** + * Expand a list of pages to include images used in those pages. + * @param $inputPages array, list of titles to look up + * @param $pageSet array, associative array indexed by titles for output + * @return array associative array index by titles + */ +function wfExportGetImages( $inputPages, $pageSet ) { + return wfExportGetLinks( $inputPages, $pageSet, + 'imagelinks', + array( NS_IMAGE . ' AS namespace', 'il_to AS title' ), + array( 'page_id=il_from' ) ); +} + +/** + * Expand a list of pages to include items used in those pages. + * @private + */ +function wfExportGetLinks( $inputPages, $pageSet, $table, $fields, $join ) { + $dbr = wfGetDB( DB_SLAVE ); + foreach( $inputPages as $page ) { + $title = Title::newFromText( $page ); + if( $title ) { + $pageSet[$title->getPrefixedText()] = true; + /// @fixme May or may not be more efficient to batch these + /// by namespace when given multiple input pages. + $result = $dbr->select( + array( 'page', $table ), + $fields, + array_merge( $join, + array( + 'page_namespace' => $title->getNamespace(), + 'page_title' => $title->getDbKey() ) ), + __METHOD__ ); + foreach( $result as $row ) { + $template = Title::makeTitle( $row->namespace, $row->title ); + $pageSet[$template->getPrefixedText()] = true; + } + } + } + return $pageSet; +} + +/** + * Callback function to remove empty strings from the pages array. + */ +function wfFilterPage( $page ) { + return $page !== '' && $page !== null; +} + +/** + * + */ +function wfSpecialExport( $page = '' ) { + global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors; + global $wgExportAllowHistory, $wgExportMaxHistory; + + $curonly = true; + $doexport = false; + + if ( $wgRequest->getCheck( 'addcat' ) ) { + $page = $wgRequest->getText( 'pages' ); + $catname = $wgRequest->getText( 'catname' ); + + if ( $catname !== '' && $catname !== NULL && $catname !== false ) { + $t = Title::makeTitleSafe( NS_CATEGORY, $catname ); + if ( $t ) { + /** + * @fixme This can lead to hitting memory limit for very large + * categories. Ideally we would do the lookup synchronously + * during the export in a single query. + */ + $catpages = wfExportGetPagesFromCategory( $t ); + if ( $catpages ) $page .= "\n" . implode( "\n", $catpages ); + } + } + } + else if( $wgRequest->wasPosted() && $page == '' ) { + $page = $wgRequest->getText( 'pages' ); + $curonly = $wgRequest->getCheck( 'curonly' ); + $rawOffset = $wgRequest->getVal( 'offset' ); + if( $rawOffset ) { + $offset = wfTimestamp( TS_MW, $rawOffset ); + } else { + $offset = null; + } + $limit = $wgRequest->getInt( 'limit' ); + $dir = $wgRequest->getVal( 'dir' ); + $history = array( + 'dir' => 'asc', + 'offset' => false, + 'limit' => $wgExportMaxHistory, + ); + $historyCheck = $wgRequest->getCheck( 'history' ); + if ( $curonly ) { + $history = WikiExporter::CURRENT; + } elseif ( !$historyCheck ) { + if ( $limit > 0 && $limit < $wgExportMaxHistory ) { + $history['limit'] = $limit; + } + if ( !is_null( $offset ) ) { + $history['offset'] = $offset; + } + if ( strtolower( $dir ) == 'desc' ) { + $history['dir'] = 'desc'; + } + } + + if( $page != '' ) $doexport = true; + } else { + // Default to current-only for GET requests + $page = $wgRequest->getText( 'pages', $page ); + $historyCheck = $wgRequest->getCheck( 'history' ); + if( $historyCheck ) { + $history = WikiExporter::FULL; + } else { + $history = WikiExporter::CURRENT; + } + + if( $page != '' ) $doexport = true; + } + + if( !$wgExportAllowHistory ) { + // Override + $history = WikiExporter::CURRENT; + } + + $list_authors = $wgRequest->getCheck( 'listauthors' ); + if ( !$curonly || !$wgExportAllowListContributors ) $list_authors = false ; + + if ( $doexport ) { + $wgOut->disable(); + + // Cancel output buffering and gzipping if set + // This should provide safer streaming for pages with history + wfResetOutputBuffers(); + header( "Content-type: application/xml; charset=utf-8" ); + if( $wgRequest->getCheck( 'wpDownload' ) ) { + // Provide a sane filename suggestion + $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); + $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" ); + } + + /* Split up the input and look up linked pages */ + $inputPages = array_filter( explode( "\n", $page ), 'wfFilterPage' ); + $pageSet = array_flip( $inputPages ); + + if( $wgRequest->getCheck( 'templates' ) ) { + $pageSet = wfExportGetTemplates( $inputPages, $pageSet ); + } + + /* + // Enable this when we can do something useful exporting/importing image information. :) + if( $wgRequest->getCheck( 'images' ) ) { + $pageSet = wfExportGetImages( $inputPages, $pageSet ); + } + */ + + $pages = array_keys( $pageSet ); + + /* Ok, let's get to it... */ + + $db = wfGetDB( DB_SLAVE ); + $exporter = new WikiExporter( $db, $history ); + $exporter->list_authors = $list_authors ; + $exporter->openStream(); + + foreach( $pages as $page ) { + /* + if( $wgExportMaxHistory && !$curonly ) { + $title = Title::newFromText( $page ); + if( $title ) { + $count = Revision::countByTitle( $db, $title ); + if( $count > $wgExportMaxHistory ) { + wfDebug( __FUNCTION__ . + ": Skipped $page, $count revisions too big\n" ); + continue; + } + } + }*/ + + #Bug 8824: Only export pages the user can read + $title = Title::newFromText( $page ); + if( is_null( $title ) ) continue; #TODO: perhaps output an tag or something. + if( !$title->userCanRead() ) continue; #TODO: perhaps output an tag or something. + + $exporter->pageByTitle( $title ); + } + + $exporter->closeStream(); + return; + } + + $self = SpecialPage::getTitleFor( 'Export' ); + $wgOut->addHtml( wfMsgExt( 'exporttext', 'parse' ) ); + + $form = Xml::openElement( 'form', array( 'method' => 'post', + 'action' => $self->getLocalUrl( 'action=submit' ) ) ); + + $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . ' '; + $form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '
      '; + + $form .= Xml::openElement( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ) ); + $form .= htmlspecialchars( $page ); + $form .= Xml::closeElement( 'textarea' ); + $form .= '
      '; + + if( $wgExportAllowHistory ) { + $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '
      '; + } else { + $wgOut->addHtml( wfMsgExt( 'exportnohistory', 'parse' ) ); + } + $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '
      '; + // Enable this when we can do something useful exporting/importing image information. :) + //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '
      '; + $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '
      '; + + $form .= Xml::submitButton( wfMsg( 'export-submit' ) ); + $form .= Xml::closeElement( 'form' ); + $wgOut->addHtml( $form ); +} diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php new file mode 100644 index 0000000000..5ad8136939 --- /dev/null +++ b/includes/specials/SpecialFewestrevisions.php @@ -0,0 +1,66 @@ +tableNamesN( 'revision', 'page' ); + + return "SELECT 'Fewestrevisions' as type, + page_namespace as namespace, + page_title as title, + COUNT(*) as value + FROM $revision + JOIN $page ON page_id = rev_page + WHERE page_namespace = " . NS_MAIN . " + GROUP BY 1,2,3 + HAVING COUNT(*) > 1"; + } + + function sortDescending() { + return false; + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + + $nt = Title::makeTitleSafe( $result->namespace, $result->title ); + $text = $wgContLang->convert( $nt->getPrefixedText() ); + + $plink = $skin->makeKnownLinkObj( $nt, $text ); + + $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ); + $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ); + + return wfSpecialList( $plink, $nlink ); + } +} + +function wfSpecialFewestrevisions() { + list( $limit, $offset ) = wfCheckLimits(); + $frp = new FewestrevisionsPage(); + $frp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php new file mode 100644 index 0000000000..5236ca2519 --- /dev/null +++ b/includes/specials/SpecialFileDuplicateSearch.php @@ -0,0 +1,135 @@ +hash = $hash; + $this->filename = $filename; + } + + function getName() { return 'FileDuplicateSearch'; } + function isExpensive() { return false; } + function isSyndicated() { return false; } + + function linkParameters() { + return array( 'filename' => $this->filename ); + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + $image = $dbr->tableName( 'image' ); + $hash = $dbr->addQuotes( $this->hash ); + + return "SELECT 'FileDuplicateSearch' AS type, + img_name AS title, + img_sha1 AS value, + img_user_text, + img_timestamp + FROM $image + WHERE img_sha1 = $hash + "; + } + + function formatResult( $skin, $result ) { + global $wgContLang, $wgLang; + + $nt = Title::makeTitle( NS_IMAGE, $result->title ); + $text = $wgContLang->convert( $nt->getText() ); + $plink = $skin->makeLink( $nt->getPrefixedText(), $text ); + + $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text ); + $time = $wgLang->timeanddate( $result->img_timestamp ); + + return "$plink . . $user . . $time"; + } +} + +/** + * Output the HTML search form, and constructs the FileDuplicateSearch object. + */ +function wfSpecialFileDuplicateSearch( $par = null ) { + global $wgRequest, $wgTitle, $wgOut, $wgLang, $wgContLang; + + $hash = ''; + $filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' ); + + $title = Title::newFromText( $filename ); + if( $title && $title->getText() != '' ) { + $dbr = wfGetDB( DB_SLAVE ); + $image = $dbr->tableName( 'image' ); + $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDbKey() ) ); + $sql = "SELECT img_sha1 from $image where img_name = $encFilename"; + $res = $dbr->query( $sql ); + $row = $dbr->fetchRow( $res ); + if( $row !== false ) { + $hash = $row[0]; + } + $dbr->freeResult( $res ); + } + + # Create the input form + $wgOut->addHTML( + Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgTitle->getLocalUrl() ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) . + Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $filename ) . ' ' . + Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); + + if( $hash != '' ) { + $align = $wgContLang->isRtl() ? 'left' : 'right'; + + # Show a thumbnail of the file + $img = wfFindFile( $title ); + if ( $img ) { + $thumb = $img->getThumbnail( 120, 120 ); + if( $thumb ) { + $wgOut->addHTML( '
      ' . + $thumb->toHtml( array( 'desc-link' => false ) ) . '
      ' . + wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ), + $wgLang->formatNum( $img->getWidth() ), + $wgLang->formatNum( $img->getHeight() ), + $wgLang->formatSize( $img->getSize() ), + $img->getMimeType() + ) . + '
      ' ); + } + } + + # Do the query + $wpp = new FileDuplicateSearchPage( $hash, $filename ); + list( $limit, $offset ) = wfCheckLimits(); + $count = $wpp->doQuery( $offset, $limit ); + + # Show a short summary + if( $count == 1 ) { + $wgOut->addHTML( '

      ' . + wfMsgHtml( 'fileduplicatesearch-result-1', $filename ) . + '

      ' + ); + } elseif ( $count > 1 ) { + $wgOut->addHTML( '

      ' . + wfMsgExt( 'fileduplicatesearch-result-n', array( 'parseinline' ), $filename, $wgLang->formatNum( $count - 1 ) ) . + '

      ' + ); + } + } +} diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php new file mode 100644 index 0000000000..a2ba3e57a1 --- /dev/null +++ b/includes/specials/SpecialFilepath.php @@ -0,0 +1,53 @@ +getText( 'file' ); + + $title = Title::newFromText( $file, NS_IMAGE ); + + if ( ! $title instanceof Title || $title->getNamespace() != NS_IMAGE ) { + $cform = new FilepathForm( $title ); + $cform->execute(); + } else { + $file = wfFindFile( $title ); + if ( $file && $file->exists() ) { + $wgOut->redirect( $file->getURL() ); + } else { + $wgOut->setStatusCode( 404 ); + $cform = new FilepathForm( $title ); + $cform->execute(); + } + } +} + +/** + * @ingroup SpecialPage + */ +class FilepathForm { + var $mTitle; + + function FilepathForm( &$title ) { + $this->mTitle =& $title; + } + + function execute() { + global $wgOut, $wgTitle, $wgScript; + + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'filepath' ) ) . + Xml::hidden( 'title', $wgTitle->getPrefixedText() ) . + Xml::inputLabel( wfMsg( 'filepath-page' ), 'file', 'file', 25, is_object( $this->mTitle ) ? $this->mTitle->getText() : '' ) . ' ' . + Xml::submitButton( wfMsg( 'filepath-submit' ) ) . "\n" . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); + } +} diff --git a/includes/specials/SpecialImagelist.php b/includes/specials/SpecialImagelist.php new file mode 100644 index 0000000000..3d449b5484 --- /dev/null +++ b/includes/specials/SpecialImagelist.php @@ -0,0 +1,161 @@ +getForm(); + $body = $pager->getBody(); + $nav = $pager->getNavigationBar(); + $wgOut->addHTML( "$limit
      \n$body
      \n$nav" ); +} + +/** + * @ingroup SpecialPage Pager + */ +class ImageListPager extends TablePager { + var $mFieldNames = null; + var $mQueryConds = array(); + + function __construct() { + global $wgRequest, $wgMiserMode; + if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) { + $this->mDefaultDirection = true; + } else { + $this->mDefaultDirection = false; + } + $search = $wgRequest->getText( 'ilsearch' ); + if ( $search != '' && !$wgMiserMode ) { + $nt = Title::newFromUrl( $search ); + if( $nt ) { + $dbr = wfGetDB( DB_SLAVE ); + $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); + $m = str_replace( "%", "\\%", $m ); + $m = str_replace( "_", "\\_", $m ); + $this->mQueryConds = array( "LOWER(img_name) LIKE '%{$m}%'" ); + } + } + + parent::__construct(); + } + + function getFieldNames() { + if ( !$this->mFieldNames ) { + $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' ), + ); + } + return $this->mFieldNames; + } + + function isFieldSortable( $field ) { + static $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); + return in_array( $field, $sortable ); + } + + function getQueryInfo() { + $fields = $this->getFieldNames(); + $fields = array_keys( $fields ); + $fields[] = 'img_user'; + return array( + 'tables' => 'image', + 'fields' => $fields, + 'conds' => $this->mQueryConds + ); + } + + function getDefaultSort() { + return 'img_timestamp'; + } + + function getStartBody() { + # Do a link batch query for user pages + if ( $this->mResult->numRows() ) { + $lb = new LinkBatch; + $this->mResult->seek( 0 ); + while ( $row = $this->mResult->fetchObject() ) { + if ( $row->img_user ) { + $lb->add( NS_USER, str_replace( ' ', '_', $row->img_user_text ) ); + } + } + $lb->execute(); + } + + return parent::getStartBody(); + } + + function formatValue( $field, $value ) { + global $wgLang; + switch ( $field ) { + case 'img_timestamp': + return $wgLang->timeanddate( $value, true ); + case 'img_name': + static $imgfile = null; + if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' ); + + $name = $this->mCurrentRow->img_name; + $link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), $value ); + $image = wfLocalFile( $value ); + $url = $image->getURL(); + $download = Xml::element('a', array( 'href' => $url ), $imgfile ); + return "$link ($download)"; + case 'img_user_text': + if ( $this->mCurrentRow->img_user ) { + $link = $this->getSkin()->makeLinkObj( Title::makeTitle( NS_USER, $value ), + htmlspecialchars( $value ) ); + } else { + $link = htmlspecialchars( $value ); + } + return $link; + case 'img_size': + return $this->getSkin()->formatSize( $value ); + case 'img_description': + return $this->getSkin()->commentBlock( $value ); + } + } + + function getForm() { + global $wgRequest, $wgMiserMode; + $search = $wgRequest->getText( 'ilsearch' ); + + $s = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getTitle()->getLocalURL(), 'id' => 'mw-imagelist-form' ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'imagelist' ) ) . + Xml::tags( 'label', null, wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) ); + + if ( !$wgMiserMode ) { + $s .= "
      \n" . + Xml::inputLabel( wfMsg( 'imagelist_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search ); + } + $s .= ' ' . + Xml::submitButton( wfMsg( 'table_pager_limit_submit' ) ) ."\n" . + $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n"; + return $s; + } + + function getTableClass() { + return 'imagelist ' . parent::getTableClass(); + } + + function getNavClass() { + return 'imagelist_nav ' . parent::getNavClass(); + } + + function getSortHeaderClass() { + return 'imagelist_sort ' . parent::getSortHeaderClass(); + } +} diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php new file mode 100644 index 0000000000..4c37f1f9ea --- /dev/null +++ b/includes/specials/SpecialImport.php @@ -0,0 +1,1154 @@ + + * http://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @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; + } + + if( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit') { + $isUpload = false; + $namespace = $wgRequest->getIntOrNull( 'namespace' ); + + switch( $wgRequest->getVal( "source" ) ) { + case "upload": + $isUpload = true; + if( $wgUser->isAllowed( 'importupload' ) ) { + $source = ImportStreamSource::newFromUpload( "xmlimport" ); + } else { + return $wgOut->permissionRequired( 'importupload' ); + } + break; + case "interwiki": + $interwiki = $wgRequest->getVal( 'interwiki' ); + $history = $wgRequest->getCheck( 'interwikiHistory' ); + $frompage = $wgRequest->getText( "frompage" ); + $source = ImportStreamSource::newFromInterwiki( + $interwiki, + $frompage, + $history ); + break; + default: + $source = new WikiErrorMsg( "importunknownsource" ); + } + + if( WikiError::isError( $source ) ) { + $wgOut->wrapWikiMsg( '

      $1

      ', array( 'importfailed', $source->getMessage() ) ); + } else { + $wgOut->addWikiMsg( "importstart" ); + + $importer = new WikiImporter( $source ); + if( !is_null( $namespace ) ) { + $importer->setTargetNamespace( $namespace ); + } + $reporter = new ImportReporter( $importer, $isUpload, $interwiki ); + + $reporter->open(); + $result = $importer->doImport(); + $resultCount = $reporter->close(); + + if( WikiError::isError( $result ) ) { + # No source or XML parse error + $wgOut->wrapWikiMsg( '

      $1

      ', array( 'importfailed', $result->getMessage() ) ); + } elseif( WikiError::isError( $resultCount ) ) { + # Zero revisions + $wgOut->wrapWikiMsg( '

      $1

      ', array( 'importfailed', $resultCount->getMessage() ) ); + } else { + # Success! + $wgOut->addWikiMsg( 'importsuccess' ); + } + $wgOut->addWikiText( '
      ' ); + } + } + + $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::submitButton( wfMsg( 'uploadbtn' ) ) . + 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::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . + " + " . + Xml::openElement( 'select', array( 'name' => 'interwiki' ) ) + ); + foreach( $wgImportSources as $prefix ) { + $selected = ( $interwiki === $prefix ) ? ' selected="selected"' : ''; + $wgOut->addHTML( Xml::option( $prefix, $prefix, $selected ) ); + } + $wgOut->addHTML( + Xml::closeElement( 'select' ) . + " + " . + Xml::input( 'frompage', 50, $frompage ) . + " + + + + + " . + Xml::checkLabel( wfMsg( 'import-interwiki-history' ), 'interwikiHistory', 'interwikiHistory', $history ) . + " + + + + + " . + Xml::label( wfMsg( 'import-interwiki-namespace' ), 'namespace' ) . + Xml::namespaceSelector( $namespace, '' ) . + " + + + + + " . + Xml::submitButton( wfMsg( 'import-interwiki-submit' ) ) . + " + " . + Xml::closeElement( 'table' ). + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) + ); + } +} + +/** + * Reporting callback + * @ingroup SpecialPage + */ +class ImportReporter { + function __construct( $importer, $upload, $interwiki ) { + $importer->setPageOutCallback( array( $this, 'reportPage' ) ); + $this->mPageCount = 0; + $this->mIsUpload = $upload; + $this->mInterwiki = $interwiki; + } + + function open() { + global $wgOut; + $wgOut->addHtml( "
        \n" ); + } + + function reportPage( $title, $origTitle, $revisionCount, $successCount ) { + global $wgOut, $wgUser, $wgLang, $wgContLang; + + $skin = $wgUser->getSkin(); + + $this->mPageCount++; + + $localCount = $wgLang->formatNum( $successCount ); + $contentCount = $wgContLang->formatNum( $successCount ); + + if( $successCount > 0 ) { + $wgOut->addHtml( "
      • " . $skin->makeKnownLinkObj( $title ) . " " . + wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) . + "
      • \n" + ); + + $log = new LogPage( 'import' ); + if( $this->mIsUpload ) { + $detail = wfMsgExt( 'import-logentry-upload-detail', array( 'content', 'parsemag' ), + $contentCount ); + $log->addEntry( 'upload', $title, $detail ); + } else { + $interwiki = '[[:' . $this->mInterwiki . ':' . + $origTitle->getPrefixedText() . ']]'; + $detail = wfMsgExt( 'import-logentry-interwiki-detail', array( 'content', 'parsemag' ), + $contentCount, $interwiki ); + $log->addEntry( 'interwiki', $title, $detail ); + } + + $comment = $detail; // quick + $dbw = wfGetDB( DB_MASTER ); + $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) ); + } else { + $wgOut->addHtml( '
      • ' . wfMsgHtml( 'import-nonewrevisions' ) . '
      • ' ); + } + } + + function close() { + global $wgOut; + if( $this->mPageCount == 0 ) { + $wgOut->addHtml( "
      \n" ); + return new WikiErrorMsg( "importnopages" ); + } + $wgOut->addHtml( "
    \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( "
  • " . htmlspecialchars( $data ) . "
  • \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: " ); + } + $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 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 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 , 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 , got <$name>" ); + } + } + function out_mediawiki( $parser, $name ) { + $this->debug( "out_mediawiki $name" ); + if( $name != "mediawiki" ) { + return $this->throwXMLerror( "Expected , got " ); + } + 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 ." ); + } + } + + 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 ." ); + } + } + + function out_page( $parser, $name ) { + $this->debug( "out_page $name" ); + $this->pop(); + if( $name != "page" ) { + return $this->throwXMLerror( "Expected , got " ); + } + 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 appendfield}>, got " ); + } + + 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 ." ); + } + } + + function out_revision( $parser, $name ) { + $this->debug( "out_revision $name" ); + $this->pop(); + if( $name != "revision" ) { + return $this->throwXMLerror( "Expected , got " ); + } + 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 ." ); + } + } + + function out_upload( $parser, $name ) { + $this->debug( "out_revision $name" ); + $this->pop(); + if( $name != "upload" ) { + return $this->throwXMLerror( "Expected , got " ); + } + 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 " ); + } + } + + function out_contributor( $parser, $name ) { + $this->debug( "out_contributor $name" ); + $this->pop(); + if( $name != "contributor" ) { + return $this->throwXMLerror( "Expected , got " ); + } + $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 new file mode 100644 index 0000000000..696c7efe84 --- /dev/null +++ b/includes/specials/SpecialIpblocklist.php @@ -0,0 +1,427 @@ +getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) ); + $id = $wgRequest->getVal( 'id' ); + $reason = $wgRequest->getText( 'wpUnblockReason' ); + $action = $wgRequest->getText( 'action' ); + $successip = $wgRequest->getVal( 'successip' ); + + $ipu = new IPUnblockForm( $ip, $id, $reason ); + + if( $action == 'unblock' ) { + # Check permissions + if( !$wgUser->isAllowed( 'block' ) ) { + $wgOut->permissionRequired( 'block' ); + return; + } + # Check for database lock + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + # Show unblock form + $ipu->showForm( '' ); + } elseif( $action == 'submit' && $wgRequest->wasPosted() + && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + # Check permissions + if( !$wgUser->isAllowed( 'block' ) ) { + $wgOut->permissionRequired( 'block' ); + return; + } + # Check for database lock + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + # Remove blocks and redirect user to success page + $ipu->doSubmit(); + } elseif( $action == 'success' ) { + # Inform the user of a successful unblock + # (No need to check permissions or locks here, + # if something was done, then it's too late!) + if ( substr( $successip, 0, 1) == '#' ) { + // A block ID was unblocked + $ipu->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) ); + } else { + // A username/IP was unblocked + $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) ); + } + } else { + # Just show the block list + $ipu->showList( '' ); + } + +} + +/** + * implements Special:ipblocklist GUI + * @ingroup SpecialPage + */ +class IPUnblockForm { + var $ip, $reason, $id; + + function IPUnblockForm( $ip, $id, $reason ) { + $this->ip = strtr( $ip, '_', ' ' ); + $this->id = $id; + $this->reason = $reason; + } + + /** + * Generates the unblock form + * @param $err string: error message + * @return $out string: HTML form + */ + function showForm( $err ) { + global $wgOut, $wgUser, $wgSysopUserBans; + + $wgOut->setPagetitle( wfMsg( 'unblockip' ) ); + $wgOut->addWikiMsg( 'unblockiptext' ); + + $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); + $action = $titleObj->getLocalURL( "action=submit" ); + + if ( "" != $err ) { + $wgOut->setSubtitle( wfMsg( "formerror" ) ); + $wgOut->addWikiText( Xml::tags( 'span', array( 'class' => 'error' ), $err ) . "\n" ); + } + + $addressPart = false; + if ( $this->id ) { + $block = Block::newFromID( $this->id ); + if ( $block ) { + $encName = htmlspecialchars( $block->getRedactedName() ); + $encId = $this->id; + $addressPart = $encName . Xml::hidden( 'id', $encId ); + $ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ); + } + } + if ( !$addressPart ) { + $addressPart = Xml::input( 'wpUnblockAddress', 40, $this->ip, array( 'type' => 'text', 'tabindex' => '1' ) ); + $ipa = Xml::label( wfMsg( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ), 'wpUnblockAddress' ); + } + + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'ipb-unblock' ) ) . + Xml::openElement( 'table', array( 'id' => 'mw-unblock-table' ) ). + " + + {$ipa} + + + {$addressPart} + + + + " . + Xml::label( wfMsg( 'ipbreason' ), 'wpUnblockReason' ) . + " + " . + Xml::input( 'wpUnblockReason', 40, $this->reason, array( 'type' => 'text', 'tabindex' => '2' ) ) . + " + + +   + " . + Xml::submitButton( wfMsg( 'ipusubmit' ), array( 'name' => 'wpBlock', 'tabindex' => '3' ) ) . + " + " . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ) . + Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . + Xml::closeElement( 'form' ) . "\n" + ); + + } + + const UNBLOCK_SUCCESS = 0; // Success + const UNBLOCK_NO_SUCH_ID = 1; // No such block ID + const UNBLOCK_USER_NOT_BLOCKED = 2; // IP wasn't blocked + const UNBLOCK_BLOCKED_AS_RANGE = 3; // IP is part of a range block + const UNBLOCK_UNKNOWNERR = 4; // Unknown error + + /** + * Backend code for unblocking. doSubmit() wraps around this. + * $range is only used when UNBLOCK_BLOCKED_AS_RANGE is returned, in which + * case it contains the range $ip is part of. + * @return array array(message key, parameters) on failure, empty array on success + */ + + static function doUnblock(&$id, &$ip, &$reason, &$range = null) + { + if ( $id ) { + $block = Block::newFromID( $id ); + if ( !$block ) { + return array('ipb_cant_unblock', htmlspecialchars($id)); + } + $ip = $block->getRedactedName(); + } else { + $block = new Block(); + $ip = trim( $ip ); + if ( substr( $ip, 0, 1 ) == "#" ) { + $id = substr( $ip, 1 ); + $block = Block::newFromID( $id ); + if( !$block ) { + return array('ipb_cant_unblock', htmlspecialchars($id)); + } + $ip = $block->getRedactedName(); + } else { + $block = Block::newFromDB( $ip ); + if ( !$block ) { + return array('ipb_cant_unblock', htmlspecialchars($id)); + } + if( $block->mRangeStart != $block->mRangeEnd + && !strstr( $ip, "/" ) ) { + /* If the specified IP is a single address, and the block is + * a range block, don't unblock the range. */ + $range = $block->mAddress; + return array('ipb_blocked_as_range', $ip, $range); + } + } + } + // Yes, this is really necessary + $id = $block->mId; + + # Delete block + if ( !$block->delete() ) { + return array('ipb_cant_unblock', htmlspecialchars($id)); + } + + # Make log entry + $log = new LogPage( 'block' ); + $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $ip ), $reason ); + return array(); + } + + function doSubmit() { + global $wgOut; + $retval = self::doUnblock($this->id, $this->ip, $this->reason, $range); + if(!empty($retval)) + { + $key = array_shift($retval); + $this->showForm(wfMsgReal($key, $retval)); + return; + } + # Report to the user + $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); + $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) ); + $wgOut->redirect( $success ); + } + + function showList( $msg ) { + global $wgOut, $wgUser; + + $wgOut->setPagetitle( wfMsg( "ipblocklist" ) ); + if ( "" != $msg ) { + $wgOut->setSubtitle( $msg ); + } + + // Purge expired entries on one in every 10 queries + if ( !mt_rand( 0, 10 ) ) { + Block::purgeExpired(); + } + + $conds = array(); + $matches = array(); + // Is user allowed to see all the blocks? + if ( !$wgUser->isAllowed( 'suppress' ) ) + $conds['ipb_deleted'] = 0; + if ( $this->ip == '' ) { + // 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; + $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 ) ) { + $conds['ipb_address'] = Block::normaliseRange( $this->ip ); + $conds['ipb_auto'] = 0; + } else { + $user = User::newFromName( $this->ip ); + if ( $user && ( $id = $user->getId() ) != 0 ) { + $conds['ipb_user'] = $id; + } else { + // Uh...? + $conds['ipb_address'] = $this->ip; + $conds['ipb_auto'] = 0; + } + } + + $pager = new IPBlocklistPager( $this, $conds ); + if ( $pager->getNumRows() ) { + $wgOut->addHTML( + $this->searchForm() . + $pager->getNavigationBar() . + Xml::tags( 'ul', null, $pager->getBody() ) . + $pager->getNavigationBar() + ); + } elseif ( $this->ip != '') { + $wgOut->addHTML( $this->searchForm() ); + $wgOut->addWikiMsg( 'ipblocklist-no-results' ); + } else { + $wgOut->addWikiMsg( 'ipblocklist-empty' ); + } + } + + function searchForm() { + global $wgTitle, $wgScript, $wgRequest; + return + Xml::tags( 'form', array( 'action' => $wgScript ), + Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) . + Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) . + ' ' . + Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . + Xml::closeElement( 'fieldset' ) + ); + } + + /** + * Callback function to output a block + */ + function formatRow( $block ) { + global $wgUser, $wgLang; + + wfProfileIn( __METHOD__ ); + + static $sk=null, $msg=null; + + if( is_null( $sk ) ) + $sk = $wgUser->getSkin(); + if( is_null( $msg ) ) { + $msg = array(); + $keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', + 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock' ); + foreach( $keys as $key ) { + $msg[$key] = wfMsgHtml( $key ); + } + $msg['blocklistline'] = wfMsg( 'blocklistline' ); + } + + # Prepare links to the blocker's user and talk pages + $blocker_id = $block->getBy(); + $blocker_name = $block->getByName(); + $blocker = $sk->userLink( $blocker_id, $blocker_name ); + $blocker .= $sk->userToolLinks( $blocker_id, $blocker_name ); + + # Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks) + if( $block->mAuto ) { + $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy + } else { + $target = $sk->userLink( $block->mUser, $block->mAddress ) + . $sk->userToolLinks( $block->mUser, $block->mAddress, false, Linker::TOOL_LINKS_NOBLOCK ); + } + + $formattedTime = $wgLang->timeanddate( $block->mTimestamp, true ); + + $properties = array(); + $properties[] = Block::formatExpiry( $block->mExpiry ); + if ( $block->mAnonOnly ) { + $properties[] = $msg['anononlyblock']; + } + if ( $block->mCreateAccount ) { + $properties[] = $msg['createaccountblock']; + } + if (!$block->mEnableAutoblock && $block->mUser ) { + $properties[] = $msg['noautoblockblock']; + } + + if ( $block->mBlockEmail && $block->mUser ) { + $properties[] = $msg['emailblock']; + } + + $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 ) ) . ')'; + } + + $comment = $sk->commentBlock( $block->mReason ); + + $s = "{$line} $comment"; + if ( $block->mHideName ) + $s = '' . $s . ''; + + wfProfileOut( __METHOD__ ); + return "
  • $s $unblocklink
  • \n"; + } +} + +/** + * @todo document + * @ingroup Pager + */ +class IPBlocklistPager extends ReverseChronologicalPager { + public $mForm, $mConds; + + function __construct( $form, $conds = array() ) { + $this->mForm = $form; + $this->mConds = $conds; + parent::__construct(); + } + + function getStartBody() { + wfProfileIn( __METHOD__ ); + # Do a link batch query + $this->mResult->seek( 0 ); + $lb = new LinkBatch; + + /* + while ( $row = $this->mResult->fetchObject() ) { + $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); + $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) ); + $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) ); + $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) ); + }*/ + # 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. + while ( $row = $this->mResult->fetchObject() ) { + $name = str_replace( ' ', '_', $row->ipb_by_text ); + $lb->add( NS_USER, $name ); + $lb->add( NS_USER_TALK, $name ); + $name = str_replace( ' ', '_', $row->ipb_address ); + $lb->add( NS_USER, $name ); + $lb->add( NS_USER_TALK, $name ); + } + $lb->execute(); + wfProfileOut( __METHOD__ ); + return ''; + } + + function formatRow( $row ) { + $block = new Block; + $block->initFromRow( $row ); + return $this->mForm->formatRow( $block ); + } + + function getQueryInfo() { + $conds = $this->mConds; + $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); + return array( + 'tables' => 'ipblocks', + 'fields' => '*', + 'conds' => $conds, + ); + } + + function getIndexField() { + return 'ipb_timestamp'; + } +} diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php new file mode 100644 index 0000000000..131c06061a --- /dev/null +++ b/includes/specials/SpecialListgrouprights.php @@ -0,0 +1,112 @@ + + */ +class SpecialListGroupRights extends SpecialPage { + + var $skin; + + /** + * Constructor + */ + function __construct() { + global $wgUser; + parent::__construct( 'Listgrouprights' ); + $this->skin = $wgUser->getSkin(); + } + + /** + * Show the special page + */ + public function execute( $par ) { + global $wgOut, $wgGroupPermissions, $wgImplicitGroups, $wgMessageCache; + $wgMessageCache->loadAllMessages(); + + $this->setHeaders(); + $this->outputHeader(); + + $wgOut->addHTML( + Xml::openElement( 'table', array( 'class' => 'mw-listgrouprights-table' ) ) . + '' . + Xml::element( 'th', null, wfMsg( 'listgrouprights-group' ) ) . + Xml::element( 'th', null, wfMsg( 'listgrouprights-rights' ) ) . + '' + ); + + foreach( $wgGroupPermissions as $group => $permissions ) { + $groupname = ( $group == '*' ) ? 'all' : htmlspecialchars( $group ); // Replace * with a more descriptive groupname + + $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; + } + + if( $group == '*' ) { + // Do not make a link for the generic * group + $grouppage = $groupnameLocalized; + } else { + $grouppage = $this->skin->makeLink( $grouppageLocalized, $groupnameLocalized ); + } + + if ( !in_array( $group, $wgImplicitGroups ) ) { + $grouplink = '
    ' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), 'group=' . $group ); + } else { + // No link to Special:listusers for implicit groups as they are unlistable + $grouplink = ''; + } + + $wgOut->addHTML( + ' + ' . + $grouppage . $grouplink . + ' + ' . + self::formatPermissions( $permissions ) . + ' + ' + ); + } + $wgOut->addHTML( + Xml::closeElement( 'table' ) . "\n" + ); + } + + /** + * Create a user-readable list of permissions from the given array. + * + * @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 ) { + $r = array(); + foreach( $permissions as $permission => $granted ) { + if ( $granted ) { + $description = wfMsgHTML( 'listgrouprights-right-display', + User::getRightDescription($permission), + $permission + ); + $r[] = $description; + } + } + sort( $r ); + if( empty( $r ) ) { + return ''; + } else { + return '
    • ' . implode( "
    • \n
    • ", $r ) . '
    '; + } + } +} diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php new file mode 100644 index 0000000000..808aab14bb --- /dev/null +++ b/includes/specials/SpecialListredirects.php @@ -0,0 +1,58 @@ + + * @copyright © 2006 Rob Church + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ + +/** + * Special:Listredirects - Lists all the redirects on the wiki. + * @ingroup SpecialPage + */ +class ListredirectsPage extends QueryPage { + + function getName() { return( 'Listredirects' ); } + function isExpensive() { return( true ); } + function isSyndicated() { return( false ); } + function sortDescending() { return( false ); } + + 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"; + return( $sql ); + } + + function formatResult( $skin, $result ) { + global $wgContLang; + + # Make a link to the redirect itself + $rd_title = Title::makeTitle( $result->namespace, $result->title ); + $rd_link = $skin->makeLinkObj( $rd_title, '', 'redirect=no' ); + + # Find out where the redirect leads + $revision = Revision::newFromTitle( $rd_title ); + if( $revision ) { + # Make a link to the destination page + $target = Title::newFromRedirect( $revision->getText() ); + if( $target ) { + $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); + $targetLink = $skin->makeLinkObj( $target ); + return "$rd_link $arr $targetLink"; + } else { + return "$rd_link"; + } + } else { + return "$rd_link"; + } + } +} + +function wfSpecialListredirects() { + list( $limit, $offset ) = wfCheckLimits(); + $lrp = new ListredirectsPage(); + $lrp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php new file mode 100644 index 0000000000..7dba44e23a --- /dev/null +++ b/includes/specials/SpecialListusers.php @@ -0,0 +1,235 @@ + +# +# http://www.mediawiki.org/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# http://www.gnu.org/copyleft/gpl.html +/** + * @file + * @ingroup SpecialPage + */ + +/** + * This class is used to get a list of user. The ones with specials + * rights (sysop, bureaucrat, developer) will have them displayed + * next to their names. + * + * @ingroup SpecialPage + */ +class UsersPager extends AlphabeticPager { + + function __construct($group=null) { + global $wgRequest; + $this->requestedGroup = $group != "" ? $group : $wgRequest->getVal( 'group' ); + $un = $wgRequest->getText( 'username' ); + $this->requestedUser = ''; + if ( $un != '' ) { + $username = Title::makeTitleSafe( NS_USER, $un ); + if( ! is_null( $username ) ) { + $this->requestedUser = $username->getText(); + } + } + parent::__construct(); + } + + + function getIndexField() { + return 'user_name'; + } + + function getQueryInfo() { + $dbr = wfGetDB( DB_SLAVE ); + $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 = ''; + } else { + $useIndex = $dbr->useIndexClause('user_name'); + } + if ($this->requestedUser != "") { + $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser ); + } + + list ($user,$user_groups,$ipblocks) = $dbr->tableNamesN('user','user_groups','ipblocks'); + + $query = array( + 'tables' => " $user $useIndex LEFT JOIN $user_groups ON user_id=ug_user + LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_auto=0 ", + 'fields' => array('user_name', + 'MAX(user_id) AS user_id', + 'COUNT(ug_group) AS numgroups', + 'MAX(ug_group) AS singlegroup'), + 'options' => array('GROUP BY' => 'user_name'), + 'conds' => $conds + ); + + wfRunHooks( 'SpecialListusersQueryInfo', array( $this, &$query ) ); + return $query; + } + + function formatRow( $row ) { + $userPage = Title::makeTitle( NS_USER, $row->user_name ); + $name = $this->getSkin()->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ); + + if( $row->numgroups > 1 || ( $this->requestedGroup && $row->numgroups == 1 ) ) { + $list = array(); + foreach( self::getGroups( $row->user_id ) as $group ) + $list[] = self::buildGroupLink( $group ); + $groups = implode( ', ', $list ); + } elseif( $row->numgroups == 1 ) { + $groups = self::buildGroupLink( $row->singlegroup ); + } else { + $groups = ''; + } + + $item = wfSpecialList( $name, $groups ); + wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) ); + return "
  • {$item}
  • "; + } + + function getBody() { + if (!$this->mQueryDone) { + $this->doQuery(); + } + $batch = new LinkBatch; + + $this->mResult->rewind(); + + while ( $row = $this->mResult->fetchObject() ) { + $batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); + } + $batch->execute(); + $this->mResult->rewind(); + return parent::getBody(); + } + + function getPageHeader( ) { + global $wgScript, $wgRequest; + $self = $this->getTitle(); + + # Form tag + $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . + '
    ' . + Xml::element( 'legend', array(), wfMsg( 'listusers' ) ); + $out .= Xml::hidden( 'title', $self->getPrefixedDbKey() ); + + # Username field + $out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' . + Xml::input( 'username', 20, $this->requestedUser, array( 'id' => 'offset' ) ) . ' '; + + # Group drop-down list + $out .= Xml::label( wfMsg( 'group' ), 'group' ) . ' ' . + Xml::openElement('select', array( 'name' => 'group', 'id' => 'group' ) ) . + Xml::option( wfMsg( 'group-all' ), '' ); + foreach( $this->getAllGroups() as $group => $groupText ) + $out .= Xml::option( $groupText, $group, $group == $this->requestedGroup ); + $out .= Xml::closeElement( 'select' ) . ' '; + + wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) ); + + # Submit button and form bottom + if( $this->mLimit ) + $out .= Xml::hidden( 'limit', $this->mLimit ); + $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); + wfRunHooks( 'SpecialListusersHeader', array( $this, &$out ) ); + $out .= '
    ' . + Xml::closeElement( 'form' ); + + return $out; + } + + function getAllGroups() { + $result = array(); + foreach( User::getAllGroups() as $group ) { + $result[$group] = User::getGroupName( $group ); + } + return $result; + } + + /** + * Preserve group and username offset parameters when paging + * @return array + */ + function getDefaultQuery() { + $query = parent::getDefaultQuery(); + if( $this->requestedGroup != '' ) + $query['group'] = $this->requestedGroup; + if( $this->requestedUser != '' ) + $query['username'] = $this->requestedUser; + wfRunHooks( 'SpecialListusersDefaultQuery', array( $this, &$query ) ); + return $query; + } + + /** + * Get a list of groups the specified user belongs to + * + * @param int $uid + * @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 ); + } + return $groups; + } + + /** + * Format a link to a group description page + * + * @param string $group + * @return string + */ + protected static function buildGroupLink( $group ) { + static $cache = array(); + if( !isset( $cache[$group] ) ) + $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) ); + return $cache[$group]; + } +} + +/** + * constructor + * $par string (optional) A group to list users from + */ +function wfSpecialListusers( $par = null ) { + global $wgRequest, $wgOut; + + $up = new UsersPager($par); + + # getBody() first to check, if empty + $usersbody = $up->getBody(); + $s = $up->getPageHeader(); + if( $usersbody ) { + $s .= $up->getNavigationBar(); + $s .= '
      ' . $usersbody . '
    '; + $s .= $up->getNavigationBar() ; + } else { + $s .= '

    ' . wfMsgHTML('listusers-noresult') . '

    '; + }; + + $wgOut->addHTML( $s ); +} diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php new file mode 100644 index 0000000000..04019223f1 --- /dev/null +++ b/includes/specials/SpecialLockdb.php @@ -0,0 +1,131 @@ +isAllowed( 'siteadmin' ) ) { + $wgOut->permissionRequired( 'siteadmin' ); + return; + } + + # If the lock file isn't writable, we can do sweet bugger all + global $wgReadOnlyFile; + if( !is_writable( dirname( $wgReadOnlyFile ) ) ) { + DBLockForm::notWritable(); + return; + } + + $action = $wgRequest->getVal( 'action' ); + $f = new DBLockForm(); + + if ( 'success' == $action ) { + $f->showSuccess(); + } else if ( 'submit' == $action && $wgRequest->wasPosted() && + $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $f->doSubmit(); + } else { + $f->showForm( '' ); + } +} + +/** + * A form to make the database readonly (eg for maintenance purposes). + * @ingroup SpecialPage + */ +class DBLockForm { + var $reason = ''; + + function DBLockForm() { + global $wgRequest; + $this->reason = $wgRequest->getText( 'wpLockReason' ); + } + + function showForm( $err ) { + global $wgOut, $wgUser; + + $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); + $wgOut->addWikiMsg( 'lockdbtext' ); + + if ( "" != $err ) { + $wgOut->setSubtitle( wfMsg( 'formerror' ) ); + $wgOut->addHTML( '

    ' . htmlspecialchars( $err ) . "

    \n" ); + } + $lc = htmlspecialchars( wfMsg( 'lockconfirm' ) ); + $lb = htmlspecialchars( wfMsg( 'lockbtn' ) ); + $elr = htmlspecialchars( wfMsg( 'enterlockreason' ) ); + $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); + $action = $titleObj->escapeLocalURL( 'action=submit' ); + $reason = htmlspecialchars( $this->reason ); + $token = htmlspecialchars( $wgUser->editToken() ); + + $wgOut->addHTML( << +{$elr}: + + + + + + + + + + +
    + + {$lc}
      + +
    + + +END +); + + } + + function doSubmit() { + global $wgOut, $wgUser, $wgLang, $wgRequest; + global $wgReadOnlyFile; + + if ( ! $wgRequest->getCheck( 'wpLockConfirm' ) ) { + $this->showForm( wfMsg( 'locknoconfirm' ) ); + return; + } + $fp = @fopen( $wgReadOnlyFile, 'w' ); + + if ( false === $fp ) { + # This used to show a file not found error, but the likeliest reason for fopen() + # to fail at this point is insufficient permission to write to the file...good old + # is_writable() is plain wrong in some cases, it seems... + self::notWritable(); + return; + } + fwrite( $fp, $this->reason ); + fwrite( $fp, "\n

    (by " . $wgUser->getName() . " at " . + $wgLang->timeanddate( wfTimestampNow() ) . ")\n" ); + fclose( $fp ); + + $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); + $wgOut->redirect( $titleObj->getFullURL( 'action=success' ) ); + } + + function showSuccess() { + global $wgOut; + + $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); + $wgOut->setSubtitle( wfMsg( 'lockdbsuccesssub' ) ); + $wgOut->addWikiMsg( 'lockdbsuccesstext' ); + } + + public static function notWritable() { + global $wgOut; + $wgOut->showErrorPage( 'lockdb', 'lockfilenotwritable' ); + } +} diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php new file mode 100644 index 0000000000..3154ed1335 --- /dev/null +++ b/includes/specials/SpecialLog.php @@ -0,0 +1,65 @@ +getVal( 'type', $par ); + $user = $wgRequest->getText( 'user' ); + $title = $wgRequest->getText( 'page' ); + $pattern = $wgRequest->getBool( 'pattern' ); + $y = $wgRequest->getIntOrNull( 'year' ); + $m = $wgRequest->getIntOrNull( 'month' ); + # Don't let the user get stuck with a certain date + $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; + if( $skip ) { + $y = ''; + $m = ''; + } + # 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() ); + # Insert list + $logBody = $pager->getBody(); + if( $logBody ) { + $wgOut->addHTML( + $pager->getNavigationBar() . + $loglist->beginLogEventsList() . + $logBody . + $loglist->endLogEventsList() . + $pager->getNavigationBar() + ); + } else { + $wgOut->addWikiMsg( 'logempty' ); + } +} diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php new file mode 100644 index 0000000000..5aafac7dd5 --- /dev/null +++ b/includes/specials/SpecialLonelypages.php @@ -0,0 +1,58 @@ +tableNamesN( 'page', 'pagelinks' ); + + return + "SELECT 'Lonelypages' AS type, + page_namespace AS namespace, + page_title AS title, + page_title AS value + FROM $page + LEFT JOIN $pagelinks + ON page_namespace=pl_namespace AND page_title=pl_title + WHERE pl_namespace IS NULL + AND page_namespace=".NS_MAIN." + AND page_is_redirect=0"; + + } +} + +/** + * Constructor + */ +function wfSpecialLonelypages() { + list( $limit, $offset ) = wfCheckLimits(); + + $lpp = new LonelyPagesPage(); + + return $lpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php new file mode 100644 index 0000000000..be16a02942 --- /dev/null +++ b/includes/specials/SpecialLongpages.php @@ -0,0 +1,31 @@ +doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php new file mode 100644 index 0000000000..82ee4be6e1 --- /dev/null +++ b/includes/specials/SpecialMIMEsearch.php @@ -0,0 +1,138 @@ + + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ + +/** + * Searches the database for files of the requested MIME type, comparing this with the + * 'img_major_mime' and 'img_minor_mime' fields in the image table. + * @ingroup SpecialPage + */ +class MIMEsearchPage extends QueryPage { + var $major, $minor; + + function MIMEsearchPage( $major, $minor ) { + $this->major = $major; + $this->minor = $minor; + } + + function getName() { return 'MIMEsearch'; } + + /** + * Due to this page relying upon extra fields being passed in the SELECT it + * will fail if it's set as expensive and misermode is on + */ + function isExpensive() { return true; } + function isSyndicated() { return false; } + + function linkParameters() { + $arr = array( $this->major, $this->minor ); + $mime = implode( '/', $arr ); + return array( 'mime' => $mime ); + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + $image = $dbr->tableName( 'image' ); + $major = $dbr->addQuotes( $this->major ); + $minor = $dbr->addQuotes( $this->minor ); + + return + "SELECT 'MIMEsearch' AS type, + " . NS_IMAGE . " AS namespace, + img_name AS title, + img_major_mime AS value, + + img_size, + img_width, + img_height, + img_user_text, + img_timestamp + FROM $image + WHERE img_major_mime = $major AND img_minor_mime = $minor + "; + } + + function formatResult( $skin, $result ) { + global $wgContLang, $wgLang; + + $nt = Title::makeTitle( $result->namespace, $result->title ); + $text = $wgContLang->convert( $nt->getText() ); + $plink = $skin->makeLink( $nt->getPrefixedText(), $text ); + + $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) ); + $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->img_size ) ); + $dimensions = wfMsgHtml( 'widthheight', $wgLang->formatNum( $result->img_width ), + $wgLang->formatNum( $result->img_height ) ); + $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text ); + $time = $wgLang->timeanddate( $result->img_timestamp ); + + return "($download) $plink . . $dimensions . . $bytes . . $user . . $time"; + } +} + +/** + * Output the HTML search form, and constructs the MIMEsearchPage object. + */ +function wfSpecialMIMEsearch( $par = null ) { + global $wgRequest, $wgTitle, $wgOut; + + $mime = isset( $par ) ? $par : $wgRequest->getText( 'mime' ); + + $wgOut->addHTML( + Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgTitle->getLocalUrl() ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) . + Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' . + Xml::submitButton( wfMsg( 'ilsubmit' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); + + list( $major, $minor ) = wfSpecialMIMEsearchParse( $mime ); + if ( $major == '' or $minor == '' or !wfSpecialMIMEsearchValidType( $major ) ) + return; + $wpp = new MIMEsearchPage( $major, $minor ); + + list( $limit, $offset ) = wfCheckLimits(); + $wpp->doQuery( $offset, $limit ); +} + +function wfSpecialMIMEsearchParse( $str ) { + // searched for an invalid MIME type. + if( strpos( $str, '/' ) === false) { + return array ('', ''); + } + + list( $major, $minor ) = explode( '/', $str, 2 ); + + return array( + ltrim( $major, ' ' ), + rtrim( $minor, ' ' ) + ); +} + +function wfSpecialMIMEsearchValidType( $type ) { + // From maintenance/tables.sql => img_major_mime + $types = array( + 'unknown', + 'application', + 'audio', + 'image', + 'text', + 'video', + 'message', + 'model', + 'multipart' + ); + + return in_array( $type, $types ); +} diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php new file mode 100644 index 0000000000..6183374ee4 --- /dev/null +++ b/includes/specials/SpecialMergeHistory.php @@ -0,0 +1,451 @@ +execute(); +} + +/** + * The HTML form for Special:MergeHistory, which allows users with the appropriate + * permissions to view and restore deleted content. + * @ingroup SpecialPage + */ +class MergehistoryForm { + var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment; + var $mTargetObj, $mDestObj; + + function MergehistoryForm( $request, $par = "" ) { + global $wgUser; + + $this->mAction = $request->getVal( 'action' ); + $this->mTarget = $request->getVal( 'target' ); + $this->mDest = $request->getVal( 'dest' ); + $this->mSubmitted = $request->getBool( 'submitted' ); + + $this->mTargetID = intval( $request->getVal( 'targetID' ) ); + $this->mDestID = intval( $request->getVal( 'destID' ) ); + $this->mTimestamp = $request->getVal( 'mergepoint' ); + if( !preg_match("/[0-9]{14}/",$this->mTimestamp) ) { + $this->mTimestamp = ''; + } + $this->mComment = $request->getText( 'wpComment' ); + + $this->mMerge = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); + // target page + if( $this->mSubmitted ) { + $this->mTargetObj = Title::newFromURL( $this->mTarget ); + $this->mDestObj = Title::newFromURL( $this->mDest ); + } else { + $this->mTargetObj = null; + $this->mDestObj = null; + } + + $this->preCacheMessages(); + } + + /** + * As we use the same small set of messages in various methods and that + * they are called often, we call them once and save them in $this->message + */ + function preCacheMessages() { + // Precache various messages + if( !isset( $this->message ) ) { + $this->message['last'] = wfMsgExt( 'last', array( 'escape') ); + } + } + + function execute() { + global $wgOut, $wgUser; + + $wgOut->setPagetitle( wfMsgHtml( "mergehistory" ) ); + + if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) { + return $this->merge(); + } + + if ( !$this->mSubmitted ) { + $this->showMergeForm(); + return; + } + + $errors = array(); + if ( !$this->mTargetObj instanceof Title ) { + $errors[] = wfMsgExt( 'mergehistory-invalid-source', array( 'parse' ) ); + } elseif( !$this->mTargetObj->exists() ) { + $errors[] = wfMsgExt( 'mergehistory-no-source', array( 'parse' ), + wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) + ); + } + + if ( !$this->mDestObj instanceof Title) { + $errors[] = wfMsgExt( 'mergehistory-invalid-destination', array( 'parse' ) ); + } elseif( !$this->mDestObj->exists() ) { + $errors[] = wfMsgExt( 'mergehistory-no-destination', array( 'parse' ), + wfEscapeWikiText( $this->mDestObj->getPrefixedText() ) + ); + } + + // TODO: warn about target = dest? + + if ( count( $errors ) ) { + $this->showMergeForm(); + $wgOut->addHTML( implode( "\n", $errors ) ); + } else { + $this->showHistory(); + } + + } + + function showMergeForm() { + global $wgOut, $wgScript; + + $wgOut->addWikiMsg( 'mergehistory-header' ); + + $wgOut->addHtml( + Xml::openElement( 'form', array( + 'method' => 'get', + 'action' => $wgScript ) ) . + '

    ' . + Xml::element( 'legend', array(), + wfMsg( 'mergehistory-box' ) ) . + Xml::hidden( 'title', + SpecialPage::getTitleFor( 'Mergehistory' )->getPrefixedDbKey() ) . + Xml::hidden( 'submitted', '1' ) . + Xml::hidden( 'mergepoint', $this->mTimestamp ) . + Xml::openElement( 'table' ) . + " + ".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )." + ".Xml::input( 'target', 30, $this->mTarget, array('id'=>'target') )." + + ".Xml::label( wfMsg( 'mergehistory-into' ), 'dest' )." + ".Xml::input( 'dest', 30, $this->mDest, array('id'=>'dest') )." + " . + Xml::submitButton( wfMsg( 'mergehistory-go' ) ) . + "" . + Xml::closeElement( 'table' ) . + '
    ' . + '' ); + } + + private function showHistory() { + global $wgLang, $wgContLang, $wgUser, $wgOut; + + $this->sk = $wgUser->getSkin(); + + $wgOut->setPagetitle( wfMsg( "mergehistory" ) ); + + $this->showMergeForm(); + + # List all stored revisions + $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj ); + $haveRevisions = $revisions && $revisions->getNumRows() > 0; + + $titleObj = SpecialPage::getTitleFor( "Mergehistory" ); + $action = $titleObj->getLocalURL( "action=submit" ); + # Start the form here + $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) ); + $wgOut->addHtml( $top ); + + if( $haveRevisions ) { + # Format the user-visible controls (comment field, submission button) + # in a nice little table + $align = $wgContLang->isRtl() ? 'left' : 'right'; + $table = + Xml::openElement( 'fieldset' ) . + Xml::openElement( 'table' ) . + " + " . + wfMsgExt( 'mergehistory-merge', array('parseinline'), + $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) . + " + + + " . + Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) . + " + " . + Xml::input( 'wpComment', 50, $this->mComment ) . + " + + +   + " . + Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . + " + " . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ); + + $wgOut->addHtml( $table ); + } + + $wgOut->addHTML( "

    " . wfMsgHtml( "mergehistory-list" ) . "

    \n" ); + + if( $haveRevisions ) { + $wgOut->addHTML( $revisions->getNavigationBar() ); + $wgOut->addHTML( "
      " ); + $wgOut->addHTML( $revisions->getBody() ); + $wgOut->addHTML( "
    " ); + $wgOut->addHTML( $revisions->getNavigationBar() ); + } else { + $wgOut->addWikiMsg( "mergehistory-empty" ); + } + + # Show relevant lines from the deletion log: + $wgOut->addHTML( "

    " . htmlspecialchars( LogPage::logName( 'merge' ) ) . "

    \n" ); + LogEventsList::showLogExtract( $wgOut, 'merge', $this->mTargetObj->getPrefixedText() ); + + # When we submit, go by page ID to avoid some nasty but unlikely collisions. + # Such would happen if a page was renamed after the form loaded, but before submit + $misc = Xml::hidden( 'targetID', $this->mTargetObj->getArticleID() ); + $misc .= Xml::hidden( 'destID', $this->mDestObj->getArticleID() ); + $misc .= Xml::hidden( 'target', $this->mTarget ); + $misc .= Xml::hidden( 'dest', $this->mDest ); + $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); + $misc .= Xml::closeElement( 'form' ); + $wgOut->addHtml( $misc ); + + return true; + } + + function formatRevisionRow( $row ) { + global $wgUser, $wgLang; + + $rev = new Revision( $row ); + + $stxt = ''; + $last = $this->message['last']; + + $ts = wfTimestamp( TS_MW, $row->rev_timestamp ); + $checkBox = wfRadio( "mergepoint", $ts, false ); + + $pageLink = $this->sk->makeKnownLinkObj( $rev->getTitle(), + htmlspecialchars( $wgLang->timeanddate( $ts ) ), 'oldid=' . $rev->getId() ); + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $pageLink = '' . $pageLink . ''; + } + + # Last link + if( !$rev->userCan( Revision::DELETED_TEXT ) ) + $last = $this->message['last']; + else if( isset($this->prevId[$row->rev_id]) ) + $last = $this->sk->makeKnownLinkObj( $rev->getTitle(), $this->message['last'], + "diff=" . $row->rev_id . "&oldid=" . $this->prevId[$row->rev_id] ); + + $userLink = $this->sk->revUserTools( $rev ); + + if(!is_null($size = $row->rev_len)) { + if($size == 0) + $stxt = wfMsgHtml('historyempty'); + else + $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); + } + $comment = $this->sk->revComment( $rev ); + + return "
  • $checkBox ($last) $pageLink . . $userLink $stxt $comment
  • "; + } + + /** + * Fetch revision text link if it's available to all users + * @return string + */ + function getPageLink( $row, $titleObj, $ts, $target ) { + global $wgLang; + + if( !$this->userCan($row, Revision::DELETED_TEXT) ) { + return '' . $wgLang->timeanddate( $ts, true ) . ''; + } else { + $link = $this->sk->makeKnownLinkObj( $titleObj, + $wgLang->timeanddate( $ts, true ), "target=$target×tamp=$ts" ); + if( $this->isDeleted($row, Revision::DELETED_TEXT) ) + $link = '' . $link . ''; + return $link; + } + } + + function merge() { + global $wgOut, $wgUser; + # Get the titles directly from the IDs, in case the target page params + # were spoofed. The queries are done based on the IDs, so it's best to + # keep it consistent... + $targetTitle = Title::newFromID( $this->mTargetID ); + $destTitle = Title::newFromID( $this->mDestID ); + if( is_null($targetTitle) || is_null($destTitle) ) + return false; // validate these + if( $targetTitle->getArticleId() == $destTitle->getArticleId() ) + return false; + # Verify that this timestamp is valid + # Must be older than the destination page + $dbw = wfGetDB( DB_MASTER ); + # Get timestamp into DB format + $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp($this->mTimestamp) : ''; + # Max timestamp should be min of destination page + $maxtimestamp = $dbw->selectField( 'revision', 'MIN(rev_timestamp)', + array('rev_page' => $this->mDestID ), + __METHOD__ ); + # Destination page must exist with revisions + if( !$maxtimestamp ) { + $wgOut->addWikiMsg('mergehistory-fail'); + return false; + } + # Get the latest timestamp of the source + $lasttimestamp = $dbw->selectField( array('page','revision'), + 'rev_timestamp', + array('page_id' => $this->mTargetID, 'page_latest = rev_id' ), + __METHOD__ ); + # $this->mTimestamp must be older than $maxtimestamp + if( $this->mTimestamp >= $maxtimestamp ) { + $wgOut->addWikiMsg('mergehistory-fail'); + return false; + } + # Update the revisions + if( $this->mTimestamp ) { + $timewhere = "rev_timestamp <= {$this->mTimestamp}"; + $TimestampLimit = wfTimestamp(TS_MW,$this->mTimestamp); + } else { + $timewhere = "rev_timestamp <= {$maxtimestamp}"; + $TimestampLimit = wfTimestamp(TS_MW,$lasttimestamp); + } + # Do the moving... + $dbw->update( 'revision', + array( 'rev_page' => $this->mDestID ), + array( 'rev_page' => $this->mTargetID, + $timewhere ), + __METHOD__ ); + + $count = $dbw->affectedRows(); + # Make the source page a redirect if no revisions are left + $haveRevisions = $dbw->selectField( 'revision', + 'rev_timestamp', + array( 'rev_page' => $this->mTargetID ), + __METHOD__, + array( 'FOR UPDATE' ) ); + if( !$haveRevisions ) { + if( $this->mComment ) { + $comment = wfMsgForContent( 'mergehistory-comment', $targetTitle->getPrefixedText(), + $destTitle->getPrefixedText(), $this->mComment ); + } else { + $comment = wfMsgForContent( 'mergehistory-autocomment', $targetTitle->getPrefixedText(), + $destTitle->getPrefixedText() ); + } + $mwRedir = MagicWord::get( 'redirect' ); + $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n"; + $redirectArticle = new Article( $targetTitle ); + $redirectRevision = new Revision( array( + 'page' => $this->mTargetID, + 'comment' => $comment, + 'text' => $redirectText ) ); + $redirectRevision->insertOn( $dbw ); + $redirectArticle->updateRevisionOn( $dbw, $redirectRevision ); + + # Now, we record the link from the redirect to the new title. + # It should have no other outgoing links... + $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ ); + $dbw->insert( 'pagelinks', + array( + 'pl_from' => $this->mDestID, + 'pl_namespace' => $destTitle->getNamespace(), + 'pl_title' => $destTitle->getDBkey() ), + __METHOD__ ); + } else { + $targetTitle->invalidateCache(); // update histories + } + $destTitle->invalidateCache(); // update histories + # Check if this did anything + if( !$count ) { + $wgOut->addWikiMsg('mergehistory-fail'); + return false; + } + # Update our logs + $log = new LogPage( 'merge' ); + $log->addEntry( 'merge', $targetTitle, $this->mComment, + array($destTitle->getPrefixedText(),$TimestampLimit) ); + + $wgOut->addHtml( wfMsgExt( 'mergehistory-success', array('parseinline'), + $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) ); + + wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) ); + + return true; + } +} + +class MergeHistoryPager extends ReverseChronologicalPager { + public $mForm, $mConds; + + function __construct( $form, $conds = array(), $source, $dest ) { + $this->mForm = $form; + $this->mConds = $conds; + $this->title = $source; + $this->articleID = $source->getArticleID(); + + $dbr = wfGetDB( DB_SLAVE ); + $maxtimestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)', + array('rev_page' => $dest->getArticleID() ), + __METHOD__ ); + $this->maxTimestamp = $maxtimestamp; + + parent::__construct(); + } + + function getStartBody() { + wfProfileIn( __METHOD__ ); + # Do a link batch query + $this->mResult->seek( 0 ); + $batch = new LinkBatch(); + # Give some pointers to make (last) links + $this->mForm->prevId = array(); + while( $row = $this->mResult->fetchObject() ) { + $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) ); + $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) ); + + $rev_id = isset($rev_id) ? $rev_id : $row->rev_id; + if( $rev_id > $row->rev_id ) + $this->mForm->prevId[$rev_id] = $row->rev_id; + else if( $rev_id < $row->rev_id ) + $this->mForm->prevId[$row->rev_id] = $rev_id; + + $rev_id = $row->rev_id; + } + + $batch->execute(); + $this->mResult->seek( 0 ); + + wfProfileOut( __METHOD__ ); + return ''; + } + + function formatRow( $row ) { + $block = new Block; + return $this->mForm->formatRevisionRow( $row ); + } + + function getQueryInfo() { + $conds = $this->mConds; + $conds['rev_page'] = $this->articleID; + $conds[] = "rev_timestamp < {$this->maxTimestamp}"; + + return array( + 'tables' => array('revision'), + '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 + ); + } + + function getIndexField() { + return 'rev_timestamp'; + } +} diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php new file mode 100644 index 0000000000..5df9c861b4 --- /dev/null +++ b/includes/specials/SpecialMostcategories.php @@ -0,0 +1,58 @@ + + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ + +/** + * implements Special:Mostcategories + * @ingroup SpecialPage + */ +class MostcategoriesPage extends QueryPage { + + function getName() { return 'Mostcategories'; } + function isExpensive() { return true; } + function isSyndicated() { return false; } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' ); + return + " + SELECT + 'Mostcategories' as type, + page_namespace as namespace, + page_title as title, + COUNT(*) as value + FROM $categorylinks + LEFT JOIN $page ON cl_from = page_id + WHERE page_namespace = " . NS_MAIN . " + GROUP BY 1,2,3 + HAVING COUNT(*) > 1 + "; + } + + 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() ); + return wfSpecialList( $link, $count ); + } +} + +/** + * constructor + */ +function wfSpecialMostcategories() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new MostcategoriesPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php new file mode 100644 index 0000000000..2fed0bd1ae --- /dev/null +++ b/includes/specials/SpecialMostimages.php @@ -0,0 +1,54 @@ + + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ + +/** + * implements Special:Mostimages + * @ingroup SpecialPage + */ +class MostimagesPage extends ImageQueryPage { + + function getName() { return 'Mostimages'; } + function isExpensive() { return true; } + function isSyndicated() { return false; } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + $imagelinks = $dbr->tableName( 'imagelinks' ); + return + " + SELECT + 'Mostimages' as type, + " . NS_IMAGE . " as namespace, + il_to as title, + COUNT(*) as value + FROM $imagelinks + GROUP BY 1,2,3 + HAVING COUNT(*) > 1 + "; + } + + function getCellHtml( $row ) { + global $wgLang; + return wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $row->value ) ) . '
    '; + } + +} + +/** + * Constructor + */ +function wfSpecialMostimages() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new MostimagesPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php new file mode 100644 index 0000000000..a56ac26941 --- /dev/null +++ b/includes/specials/SpecialMostlinked.php @@ -0,0 +1,95 @@ + + * @author Rob Church + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @copyright © 2006 Rob Church + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class MostlinkedPage extends QueryPage { + + function getName() { return 'Mostlinked'; } + function isExpensive() { return true; } + function isSyndicated() { return false; } + + /** + * Note: Getting page_namespace only works if $this->isCached() is false + */ + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' ); + return + "SELECT 'Mostlinked' AS type, + pl_namespace AS namespace, + pl_title AS title, + COUNT(*) AS value, + page_namespace + FROM $pagelinks + LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title + GROUP BY 1,2,3,5 + HAVING COUNT(*) > 1"; + } + + /** + * Pre-fill the link cache + */ + function preprocessResults( $db, $res ) { + if( $db->numRows( $res ) > 0 ) { + $linkBatch = new LinkBatch(); + while( $row = $db->fetchObject( $res ) ) + $linkBatch->add( $row->namespace, $row->title ); + $db->dataSeek( $res, 0 ); + $linkBatch->execute(); + } + } + + /** + * Make a link to "what links here" for the specified title + * + * @param $title Title being queried + * @param $skin Skin to use + * @return string + */ + function makeWlhLink( &$title, $caption, &$skin ) { + $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() ); + return $skin->makeKnownLinkObj( $wlh, $caption ); + } + + /** + * Make links to the page corresponding to the item, and the "what links here" page for it + * + * @param $skin Skin to be used + * @param $result Result row + * @return string + */ + function formatResult( $skin, $result ) { + global $wgLang; + $title = Title::makeTitleSafe( $result->namespace, $result->title ); + $link = $skin->makeLinkObj( $title ); + $wlh = $this->makeWlhLink( $title, + wfMsgExt( 'nlinks', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ), $skin ); + return wfSpecialList( $link, $wlh ); + } +} + +/** + * constructor + */ +function wfSpecialMostlinked() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new MostlinkedPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php new file mode 100644 index 0000000000..1b66d48403 --- /dev/null +++ b/includes/specials/SpecialMostlinkedcategories.php @@ -0,0 +1,78 @@ + + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class MostlinkedCategoriesPage extends QueryPage { + + function getName() { return 'Mostlinkedcategories'; } + function isExpensive() { return true; } + function isSyndicated() { return false; } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + $categorylinks = $dbr->tableName( 'categorylinks' ); + $name = $dbr->addQuotes( $this->getName() ); + return + " + SELECT + $name as type, + " . NS_CATEGORY . " as namespace, + cl_to as title, + COUNT(*) as value + FROM $categorylinks + GROUP BY 1,2,3 + "; + } + + 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 = $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ); + + $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ); + return wfSpecialList($plink, $nlinks); + } +} + +/** + * constructor + */ +function wfSpecialMostlinkedCategories() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new MostlinkedCategoriesPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php new file mode 100644 index 0000000000..b8d47e6d22 --- /dev/null +++ b/includes/specials/SpecialMostlinkedtemplates.php @@ -0,0 +1,132 @@ + + */ +class SpecialMostlinkedtemplates extends QueryPage { + + /** + * Name of the report + * + * @return string + */ + public function getName() { + return 'Mostlinkedtemplates'; + } + + /** + * Is this report expensive, i.e should it be cached? + * + * @return bool + */ + public function isExpensive() { + return true; + } + + /** + * Is there a feed available? + * + * @return bool + */ + public function isSyndicated() { + return false; + } + + /** + * Sort the results in descending order? + * + * @return bool + */ + public function sortDescending() { + return true; + } + + /** + * Generate SQL for the report + * + * @return string + */ + public function getSql() { + $dbr = wfGetDB( DB_SLAVE ); + $templatelinks = $dbr->tableName( 'templatelinks' ); + $name = $dbr->addQuotes( $this->getName() ); + return "SELECT {$name} AS type, + " . NS_TEMPLATE . " AS namespace, + tl_title AS title, + COUNT(*) AS value + FROM {$templatelinks} + WHERE tl_namespace = " . NS_TEMPLATE . " + GROUP BY 1, 2, 3"; + } + + /** + * Pre-cache page existence to speed up link generation + * + * @param Database $dbr Database connection + * @param int $res Result pointer + */ + public function preprocessResults( $db, $res ) { + $batch = new LinkBatch(); + while( $row = $db->fetchObject( $res ) ) { + $batch->add( $row->namespace, $row->title ); + } + $batch->execute(); + if( $db->numRows( $res ) > 0 ) + $db->dataSeek( $res, 0 ); + } + + /** + * Format a result row + * + * @param Skin $skin Skin to use for UI elements + * @param object $result Result row + * @return string + */ + 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}"; + } + } + + /** + * 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->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() ); + } +} + +/** + * Execution function + * + * @param mixed $par Parameters passed to the page + */ +function wfSpecialMostlinkedtemplates( $par = false ) { + list( $limit, $offset ) = wfCheckLimits(); + $mlt = new SpecialMostlinkedtemplates(); + $mlt->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php new file mode 100644 index 0000000000..001a08b1d1 --- /dev/null +++ b/includes/specials/SpecialMostrevisions.php @@ -0,0 +1,64 @@ + + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ + +/** + * @ingroup SpecialPage + */ +class MostrevisionsPage extends QueryPage { + + function getName() { return 'Mostrevisions'; } + function isExpensive() { return true; } + function isSyndicated() { return false; } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); + return + " + SELECT + 'Mostrevisions' as type, + page_namespace as namespace, + page_title as title, + COUNT(*) as value + FROM $revision + JOIN $page ON page_id = rev_page + WHERE page_namespace = " . NS_MAIN . " + GROUP BY 1,2,3 + HAVING COUNT(*) > 1 + "; + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + + $nt = Title::makeTitle( $result->namespace, $result->title ); + $text = $wgContLang->convert( $nt->getPrefixedText() ); + + $plink = $skin->makeKnownLinkObj( $nt, $text ); + + $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ); + $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ); + + return wfSpecialList($plink, $nlink); + } +} + +/** + * constructor + */ +function wfSpecialMostrevisions() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new MostrevisionsPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php new file mode 100644 index 0000000000..d08fb66bd8 --- /dev/null +++ b/includes/specials/SpecialMovepage.php @@ -0,0 +1,428 @@ +readOnlyPage(); + return; + } + + $target = isset( $par ) ? $par : $wgRequest->getVal( 'target' ); + $oldTitle = $wgRequest->getText( 'wpOldTitle', $target ); + $newTitle = $wgRequest->getText( 'wpNewTitle' ); + + # Variables beginning with 'o' for old article 'n' for new article + $ot = Title::newFromText( $oldTitle ); + $nt = Title::newFromText( $newTitle ); + + if( is_null( $ot ) ) { + $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); + return; + } + if( !$ot->exists() ) { + $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' ); + return; + } + + # Check rights + $permErrors = $ot->getUserPermissionsErrors( 'move', $wgUser ); + if( !empty( $permErrors ) ) { + $wgOut->showPermissionsErrorPage( $permErrors ); + return; + } + + $f = new MovePageForm( $ot, $nt ); + + if ( 'submit' == $action && $wgRequest->wasPosted() + && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $f->doSubmit(); + } else { + $f->showForm( '' ); + } +} + +/** + * HTML form for Special:Movepage + * @ingroup SpecialPage + */ +class MovePageForm { + var $oldTitle, $newTitle, $reason; # Text input + var $moveTalk, $deleteAndMove, $moveSubpages; + + private $watch = false; + + function MovePageForm( $oldTitle, $newTitle ) { + global $wgRequest; + $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); + $this->oldTitle = $oldTitle; + $this->newTitle = $newTitle; + $this->reason = $wgRequest->getText( 'wpReason' ); + if ( $wgRequest->wasPosted() ) { + $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', false ); + } else { + $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', 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; + + $ot = $this->oldTitle; + $sk = $wgUser->getSkin(); + + $oldTitleLink = $sk->makeLinkObj( $ot ); + $oldTitle = $ot->getPrefixedText(); + + $wgOut->setPagetitle( wfMsg( 'move-page', $oldTitle ) ); + $wgOut->setSubtitle( wfMsg( 'move-page-backlink', $oldTitleLink ) ); + + if( $this->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 = $ot->isValidMoveOperation( $nt ); + if( is_string( $newerr ) ) { + $err = $newerr; + } + } + } + $newTitle = $this->newTitle; + } + + if ( $err == 'articleexists' && $wgUser->isAllowed( 'delete' ) ) { + $wgOut->addWikiMsg( 'delete_and_move_text', $newTitle ); + $movepagebtn = wfMsg( 'delete_and_move' ); + $submitVar = 'wpDeleteAndMove'; + $confirm = " + + + " . + Xml::checkLabel( wfMsg( 'delete_and_move_confirm' ), 'wpConfirm', 'wpConfirm' ) . + " + "; + $err = ''; + } else { + $wgOut->addWikiMsg( 'movepagetext' ); + $movepagebtn = wfMsg( 'movepagebtn' ); + $submitVar = 'wpMove'; + $confirm = false; + } + + $oldTalk = $ot->getTalkPage(); + $considerTalk = ( !$ot->isTalkPage() && $oldTalk->exists() ); + + if ( $considerTalk ) { + $wgOut->addWikiMsg( 'movepagetalktext' ); + } + + $titleObj = SpecialPage::getTitleFor( 'Movepage' ); + $token = htmlspecialchars( $wgUser->editToken() ); + + if ( $err != '' ) { + $wgOut->setSubtitle( wfMsg( 'formerror' ) ); + $errMsg = ""; + if( $err == 'hookaborted' ) { + $errMsg = "

    $hookErr

    \n"; + } else if (is_array($err)) { + $errMsg = '

    ' . call_user_func_array( 'wfMsgWikiHtml', $err ) . "

    \n"; + } else { + $errMsg = '

    ' . wfMsgWikiHtml( $err ) . "

    \n"; + } + $wgOut->addHTML( $errMsg ); + } + + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'move-page-legend' ) ) . + Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-movepage-table' ) ) . + " + " . + wfMsgHtml( 'movearticle' ) . + " + + {$oldTitleLink} + + + + " . + Xml::label( wfMsg( 'newtitle' ), 'wpNewTitle' ) . + " + " . + Xml::input( 'wpNewTitle', 40, $newTitle, array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) . + Xml::hidden( 'wpOldTitle', $oldTitle ) . + " + + + " . + Xml::label( wfMsg( 'movereason' ), 'wpReason' ) . + " + " . + Xml::tags( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2 ), htmlspecialchars( $this->reason ) ) . + " + " + ); + + if( $considerTalk ) { + $wgOut->addHTML( " + + + " . + Xml::checkLabel( wfMsg( 'movetalk' ), 'wpMovetalk', 'wpMovetalk', $this->moveTalk ) . + " + " + ); + } + + if( ($ot->hasSubpages() || $ot->getTalkPage()->hasSubpages()) + && $ot->userCan( 'move-subpages' ) ) { + $wgOut->addHTML( " + + + " . + Xml::checkLabel( wfMsgHtml( + $ot->hasSubpages() + ? 'move-subpages' + : 'move-talk-subpages' + ), + 'wpMovesubpages', 'wpMovesubpages', + # Don't check the box if we only have talk subpages to + # move and we aren't moving the talk page. + $this->moveSubpages && ($ot->hasSubpages() || $this->moveTalk) + ) . + " + " + ); + } + + $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $ot->userIsWatching(); + $wgOut->addHTML( " + + + " . + Xml::checkLabel( wfMsg( 'move-watch' ), 'wpWatch', 'watch', $watchChecked ) . + " + + {$confirm} + +   + " . + Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) . + " + " . + Xml::closeElement( 'table' ) . + Xml::hidden( 'wpEditToken', $token ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . + "\n" + ); + + $this->showLogFragment( $ot, $wgOut ); + + } + + function doSubmit() { + global $wgOut, $wgUser, $wgRequest, $wgMaximumMovedPages, $wgLang; + + if ( $wgUser->pingLimiter( 'move' ) ) { + $wgOut->rateLimited(); + return; + } + + $ot = $this->oldTitle; + $nt = $this->newTitle; + + # Delete to make way if requested + if ( $wgUser->isAllowed( 'delete' ) && $this->deleteAndMove ) { + $article = new Article( $nt ); + + # Disallow deletions of big articles + $bigHistory = $article->isBigDeletion(); + if( $bigHistory && !$nt->userCan( 'bigdelete' ) ) { + global $wgLang, $wgDeleteRevisionsLimit; + $this->showForm( array('delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) ); + return; + } + + // This may output an error message and exit + $article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) ); + } + + # don't allow moving to pages with # in + if ( !$nt || $nt->getFragment() != '' ) { + $this->showForm( 'badtitletext' ); + return; + } + + $error = $ot->moveTo( $nt, true, $this->reason ); + if ( $error !== true ) { + # FIXME: showForm() should handle multiple errors + call_user_func_array(array($this, 'showForm'), $error[0]); + return; + } + + wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ot , &$nt ) ) ; + + $wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) ); + + $oldUrl = $ot->getFullUrl( 'redirect=no' ); + $newUrl = $nt->getFullUrl(); + $oldText = $ot->getPrefixedText(); + $newText = $nt->getPrefixedText(); + $oldLink = "[$oldUrl $oldText]"; + $newLink = "[$newUrl $newText]"; + + $wgOut->addWikiMsg( 'movepage-moved', $oldLink, $newLink, $oldText, $newText ); + + # 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 + # can't move any talk pages: cancel that. + if( $ot->isTalkPage() || $nt->isTalkPage() ) { + $this->moveTalk = false; + } + + if( !$ot->userCan( 'move-subpages' ) ) { + $this->moveSubpages = false; + } + + # Next make a list of id's. This might be marginally less efficient + # than a more direct method, but this is not a highly performance-cri- + # tical code path and readable code is more important here. + # + # Note: this query works nicely on MySQL 5, but the optimizer in MySQL + # 4 might get confused. If so, consider rewriting as a UNION. + # + # If the target namespace doesn't allow subpages, moving with subpages + # would mean that you couldn't move them back in one operation, which + # is bad. FIXME: A specific error message should be given in this + # case. + $dbr = wfGetDB( DB_MASTER ); + if( $this->moveSubpages && ( + MWNamespace::hasSubpages( $nt->getNamespace() ) || ( + $this->moveTalk && + MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) + ) + ) ) { + $conds = array( + 'page_title LIKE '.$dbr->addQuotes( $dbr->escapeLike( $ot->getDBkey() ) . '/%' ) + .' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() ) + ); + $conds['page_namespace'] = array(); + if( MWNamespace::hasSubpages( $nt->getNamespace() ) ) { + $conds['page_namespace'] []= $ot->getNamespace(); + } + if( $this->moveTalk && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) ) { + $conds['page_namespace'] []= $ot->getTalkPage()->getNamespace(); + } + } elseif( $this->moveTalk ) { + $conds = array( + 'page_namespace' => $ot->getTalkPage()->getNamespace(), + 'page_title' => $ot->getDBKey() + ); + } else { + # Skip the query + $conds = null; + } + + $extrapages = array(); + if( !is_null( $conds ) ) { + $extrapages = $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() ) { + # Already did this one. + continue; + } + + $oldPage = Title::newFromRow( $row ); + $newPageName = preg_replace( + '#^'.preg_quote( $ot->getDBKey(), '#' ).'#', + $nt->getDBKey(), + $oldPage->getDBKey() + ); + if( $oldPage->isTalkPage() ) { + $newNs = $nt->getTalkPage()->getNamespace(); + } else { + $newNs = $nt->getSubjectPage()->getNamespace(); + } + # Bug 14385: we need makeTitleSafe because the new page names may + # be longer than 255 characters. + $newPage = Title::makeTitleSafe( $newNs, $newPageName ); + if( !$newPage ) { + $oldLink = $skin->makeKnownLinkObj( $oldPage ); + $extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, + htmlspecialchars(Title::makeName( $newNs, $newPageName ))); + continue; + } + + # This was copy-pasted from Renameuser, bleh. + if ( $newPage->exists() && !$oldPage->isValidMoveTarget( $newPage ) ) { + $link = $skin->makeKnownLinkObj( $newPage ); + $extraOutput []= wfMsgHtml( 'movepage-page-exists', $link ); + } else { + $success = $oldPage->moveTo( $newPage, true, $this->reason ); + if( $success === true ) { + $oldLink = $skin->makeKnownLinkObj( $oldPage, '', 'redirect=no' ); + $newLink = $skin->makeKnownLinkObj( $newPage ); + $extraOutput []= wfMsgHtml( 'movepage-page-moved', $oldLink, $newLink ); + } else { + $oldLink = $skin->makeKnownLinkObj( $oldPage ); + $newLink = $skin->makeLinkObj( $newPage ); + $extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, $newLink ); + } + } + + ++$count; + if( $count >= $wgMaximumMovedPages ) { + $extraOutput []= wfMsgExt( 'movepage-max-pages', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgMaximumMovedPages ) ); + break; + } + } + + if( $extraOutput !== array() ) { + $wgOut->addHTML( "
      \n
    • " . implode( "
    • \n
    • ", $extraOutput ) . "
    • \n
    " ); + } + + # Deal with watches (we don't watch subpages) + if( $this->watch ) { + $wgUser->addWatch( $ot ); + $wgUser->addWatch( $nt ); + } else { + $wgUser->removeWatch( $ot ); + $wgUser->removeWatch( $nt ); + } + } + + function showLogFragment( $title, &$out ) { + $out->addHTML( Xml::element( 'h2', NULL, LogPage::logName( 'move' ) ) ); + LogEventsList::showLogExtract( $out, 'move', $title->getPrefixedText() ); + } + +} diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php new file mode 100644 index 0000000000..5fd37e8e0a --- /dev/null +++ b/includes/specials/SpecialNewimages.php @@ -0,0 +1,209 @@ +getText( 'wpIlMatch' ); + $dbr = wfGetDB( DB_SLAVE ); + $sk = $wgUser->getSkin(); + $shownav = !$specialPage->including(); + $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 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)"; + } + } + + $image = $dbr->tableName('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]; + } else { + $ts=false; + } + $dbr->freeResult($res); + $sql=''; + + /** If we were clever, we'd use this to cache. */ + $latestTimestamp = wfTimestamp( TS_MW, $ts); + + /** Hardcode this for now. */ + $limit = 48; + + if ( $parval = intval( $par ) ) { + if ( $parval <= $limit && $parval > 0 ) { + $limit = $parval; + } + } + + $where = array(); + $searchpar = ''; + if ( $wpIlMatch != '' && !$wgMiserMode) { + $nt = Title::newFromUrl( $wpIlMatch ); + if($nt ) { + $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); + $m = str_replace( '%', "\\%", $m ); + $m = str_replace( '_', "\\_", $m ); + $where[] = "LOWER(img_name) LIKE '%{$m}%'"; + $searchpar = '&wpIlMatch=' . urlencode( $wpIlMatch ); + } + } + + $invertSort = false; + if( $until = $wgRequest->getVal( 'until' ) ) { + $where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'"; + } + if( $from = $wgRequest->getVal( 'from' ) ) { + $where[] = "img_timestamp >= '" . $dbr->timestamp( $from ) . "'"; + $invertSort = true; + } + $sql='SELECT img_size, img_name, img_user, img_user_text,'. + "img_description,img_timestamp FROM $image"; + + if($hidebotsql) { + $sql .= $hidebotsql; + $where[]='ug_group IS NULL'; + } + 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'); + + /** + * We have to flip things around to get the last N after a certain date + */ + $images = array(); + while ( $s = $dbr->fetchObject( $res ) ) { + if( $invertSort ) { + array_unshift( $images, $s ); + } else { + array_push( $images, $s ); + } + } + $dbr->freeResult( $res ); + + $gallery = new ImageGallery(); + $firstTimestamp = null; + $lastTimestamp = null; + $shownImages = 0; + foreach( $images as $s ) { + if( ++$shownImages > $limit ) { + # One extra just to test for whether to show a page link; + # don't actually show it. + break; + } + + $name = $s->img_name; + $ut = $s->img_user_text; + + $nt = Title::newFromText( $name, NS_IMAGE ); + $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut ); + + $gallery->add( $nt, "$ul
    \n".$wgLang->timeanddate( $s->img_timestamp, true )."
    \n" ); + + $timestamp = wfTimestamp( TS_MW, $s->img_timestamp ); + if( empty( $firstTimestamp ) ) { + $firstTimestamp = $timestamp; + } + $lastTimestamp = $timestamp; + } + + $bydate = wfMsg( 'bydate' ); + $lt = $wgLang->formatNum( min( $shownImages, $limit ) ); + 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( "
    " . + Xml::input( 'wpIlMatch', 20, $wpIlMatch ) . ' ' . + Xml::submitButton( $sub, array( 'name' => 'wpIlSubmit' ) ) . + "
    " ); + } + + /** + * Paging controls... + */ + + # If we change bot visibility, this needs to be carried along. + if(!$hidebots) { + $botpar='&hidebots=0'; + } else { + $botpar=''; + } + $now = wfTimestampNow(); + $d = $wgLang->date( $now, true ); + $t = $wgLang->time( $now, true ); + $dateLink = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml( 'sp-newimages-showfrom', $d, $t ), + 'from='.$now.$botpar.$searchpar ); + + $botLink = $sk->makeKnownLinkObj($titleObj, wfMsgHtml( 'showhidebots', + ($hidebots ? wfMsgHtml('show') : wfMsgHtml('hide'))),'hidebots='.($hidebots ? '0' : '1').$searchpar); + + $prevLink = wfMsgHtml( 'prevn', $wgLang->formatNum( $limit ) ); + if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) { + $prevLink = $sk->makeKnownLinkObj( $titleObj, $prevLink, 'from=' . $firstTimestamp . $botpar . $searchpar ); + } + + $nextLink = wfMsgHtml( 'nextn', $wgLang->formatNum( $limit ) ); + if( $shownImages > $limit && $lastTimestamp ) { + $nextLink = $sk->makeKnownLinkObj( $titleObj, $nextLink, 'until=' . $lastTimestamp.$botpar.$searchpar ); + } + + $prevnext = '

    ' . $botLink . ' '. wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'

    '; + + if ($shownav) + $wgOut->addHTML( $prevnext ); + + if( count( $images ) ) { + $wgOut->addHTML( $gallery->toHTML() ); + if ($shownav) + $wgOut->addHTML( $prevnext ); + } else { + $wgOut->addWikiMsg( 'noimages' ); + } +} diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php new file mode 100644 index 0000000000..72a01836d0 --- /dev/null +++ b/includes/specials/SpecialNewpages.php @@ -0,0 +1,437 @@ +includable( true ); + } + + protected function setup( $par ) { + global $wgRequest, $wgUser, $wgEnableNewpagesUserFilter; + + // Options + $opts = new FormOptions(); + $this->opts = $opts; // bind + $opts->add( 'hideliu', false ); + $opts->add( 'hidepatrolled', false ); + $opts->add( 'hidebots', false ); + $opts->add( 'limit', 50 ); + $opts->add( 'offset', '' ); + $opts->add( 'namespace', '0' ); + $opts->add( 'username', '' ); + $opts->add( 'feed', '' ); + + // Set values + $opts->fetchValuesFromRequest( $wgRequest ); + if ( $par ) $this->parseParams( $par ); + + // Validate + $opts->validateIntBounds( 'limit', 0, 5000 ); + if( !$wgEnableNewpagesUserFilter ) { + $opts->setValue( 'username', '' ); + } + + // Store some objects + $this->skin = $wgUser->getSkin(); + } + + protected function parseParams( $par ) { + global $wgLang; + $bits = preg_split( '/\s*,\s*/', trim( $par ) ); + foreach ( $bits as $bit ) { + if ( 'shownav' == $bit ) + $this->showNavigation = true; + if ( 'hideliu' === $bit ) + $this->opts->setValue( 'hideliu', true ); + if ( 'hidepatrolled' == $bit ) + $this->opts->setValue( 'hidepatrolled', true ); + if ( 'hidebots' == $bit ) + $this->opts->setValue( 'hidebots', true ); + if ( is_numeric( $bit ) ) + $this->opts->setValue( 'limit', intval( $bit ) ); + + $m = array(); + if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) + $this->opts->setValue( 'limit', intval($m[1]) ); + // PG offsets not just digits! + if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) + $this->opts->setValue( 'offset', intval($m[1]) ); + if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { + $ns = $wgLang->getNsIndex( $m[1] ); + if( $ns !== false ) { + $this->opts->setValue( 'namespace', $ns ); + } + } + } + } + + /** + * Show a form for filtering namespace and username + * + * @param string $par + * @return string + */ + public function execute( $par ) { + global $wgLang, $wgGroupPermissions, $wgUser, $wgOut; + + $this->setHeaders(); + $this->outputHeader(); + + $this->showNavigation = !$this->including(); // Maybe changed in setup + $this->setup( $par ); + + if( !$this->including() ) { + // Settings + $this->form(); + + $this->setSyndicated(); + $feedType = $this->opts->getValue( 'feed' ); + if( $feedType ) { + return $this->feed( $feedType ); + } + } + + $pager = new NewPagesPager( $this, $this->opts ); + $pager->mLimit = $this->opts->getValue( 'limit' ); + $pager->mOffset = $this->opts->getValue( 'offset' ); + + if( $pager->getNumRows() ) { + $navigation = ''; + if ( $this->showNavigation ) $navigation = $pager->getNavigationBar(); + $wgOut->addHTML( $navigation . $pager->getBody() . $navigation ); + } else { + $wgOut->addWikiMsg( 'specialpage-empty' ); + } + } + + protected function filterLinks() { + global $wgGroupPermissions, $wgUser; + + // show/hide links + $showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) ); + + // Option value -> message mapping + $filters = array( + 'hideliu' => 'rcshowhideliu', + 'hidepatrolled' => 'rcshowhidepatr', + 'hidebots' => 'rcshowhidebots' + ); + + // Disable some if needed + if ( $wgGroupPermissions['*']['createpage'] !== true ) + unset($filters['hideliu']); + + if ( !$wgUser->useNPPatrol() ) + unset($filters['hidepatrolled']); + + $links = array(); + $changed = $this->opts->getChangedValues(); + unset($changed['offset']); // Reset offset if query type changes + + $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 ) + ); + $links[$key] = wfMsgHtml( $msg, $link ); + } + + return implode( ' | ', $links ); + } + + protected function form() { + global $wgOut, $wgEnableNewpagesUserFilter, $wgScript; + + // Consume values + $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW + $namespace = $this->opts->consumeValue( 'namespace' ); + $username = $this->opts->consumeValue( 'username' ); + + // Check username input validity + $ut = Title::makeTitleSafe( NS_USER, $username ); + $userText = $ut ? $ut->getText() : ''; + + // Store query values in hidden fields so that form submission doesn't lose them + $hidden = array(); + foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { + $hidden[] = Xml::hidden( $key, $value ); + } + $hidden = implode( "\n", $hidden ); + + $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . + Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . + Xml::fieldset( wfMsg( 'newpages' ) ) . + Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . + " + " . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . + " + " . + Xml::namespaceSelector( $namespace, 'all' ) . + " + " . + ($wgEnableNewpagesUserFilter ? + " + " . + Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) . + " + " . + Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) . + " + " : "" ) . + " + " . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . + " + " . + " + + " . + $this->filterLinks() . + " + " . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ) . + $hidden . + Xml::closeElement( 'form' ); + + $wgOut->addHTML( $form ); + } + + protected function setSyndicated() { + global $wgOut; + $queryParams = array( + 'namespace' => $this->opts->getValue( 'namespace' ), + 'username' => $this->opts->getValue( 'username' ) + ); + $wgOut->setSyndicated( true ); + $wgOut->setFeedAppendQuery( wfArrayToCGI( $queryParams ) ); + } + + /** + * Format a row, providing the timestamp, links to the page/history, size, user links, and a comment + * + * @param $skin Skin to use + * @param $result Result row + * @return string + */ + public function formatRow( $result ) { + global $wgLang, $wgContLang, $wgUser; + $dm = $wgContLang->getDirMark(); + + $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' ); + $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $result->length ) ); + $ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' . + $this->skin->userToolLinks( $result->rc_user, $result->rc_user_text ); + $comment = $this->skin->commentBlock( $result->rc_comment ); + $css = $this->patrollable( $result ) ? " class='not-patrolled'" : ''; + + return "{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment}\n"; + } + + /** + * Should a specific result row provide "patrollable" links? + * + * @param $result Result row + * @return bool + */ + protected function patrollable( $result ) { + global $wgUser; + return ( $wgUser->useNPPatrol() && !$result->rc_patrolled ); + } + + /** + * Output a subscription feed listing recent edits to this page. + * @param string $type + */ + protected function feed( $type ) { + global $wgFeed, $wgFeedClasses; + + 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(), + wfMsg( 'tagline' ), + $this->getTitle()->getFullUrl() ); + + $pager = new NewPagesPager( $this, $this->opts ); + $limit = $this->opts->getValue( 'limit' ); + global $wgFeedLimit; + if( $limit > $wgFeedLimit ) { + $limit = $wgFeedLimit; + } + $pager->mLimit = $limit; + + $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( 'Newpages' ); + $desc = $page->getDescription(); + return "$wgSitename - $desc [$wgContLanguageCode]"; + } + + protected function feedItem( $row ) { + $title = Title::MakeTitle( intval( $row->rc_namespace ), $row->rc_title ); + if( $title ) { + $date = $row->rc_timestamp; + $comments = $title->getTalkPage()->getFullURL(); + + return new FeedItem( + $title->getPrefixedText(), + $this->feedItemDesc( $row ), + $title->getFullURL(), + $date, + $this->feedItemAuthor( $row ), + $comments); + } else { + return NULL; + } + } + + /** + * 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 : ''; + } + + protected function feedItemDesc( $row ) { + $revision = Revision::newFromId( $row->rev_id ); + if( $revision ) { + return '

    ' . htmlspecialchars( $revision->getUserText() ) . ': ' . + htmlspecialchars( $revision->getComment() ) . + "

    \n
    \n
    " . + nl2br( htmlspecialchars( $revision->getText() ) ) . "
    "; + } + return ''; + } +} + +/** + * @ingroup SpecialPage Pager + */ +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(){ + static $title = null; + if ( $title === null ) + $title = $this->mForm->getTitle(); + return $title; + } + + function getQueryInfo() { + global $wgEnableNewpagesUserFilter, $wgGroupPermissions, $wgUser; + $conds = array(); + $conds['rc_new'] = 1; + + $namespace = $this->opts->getValue( 'namespace' ); + $namespace = ( $namespace === 'all' ) ? false : intval( $namespace ); + + $username = $this->opts->getValue( 'username' ); + $user = Title::makeTitleSafe( NS_USER, $username ); + + if( $namespace !== false ) { + $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(); + $rcIndexes = 'rc_user_text'; + # If anons cannot make new pages, don't "exclude logged in users"! + } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) { + $conds['rc_user'] = 0; + } + # If this user cannot see patrolled edits or they are off, don't do dumb queries! + if( $this->opts->getValue( 'hidepatrolled' ) && $wgUser->useNPPatrol() ) { + $conds['rc_patrolled'] = 0; + } + if( $this->opts->getValue( 'hidebots' ) ) { + $conds['rc_bot'] = 0; + } + + return array( + 'tables' => array( 'recentchanges', 'page' ), + '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) ) + ); + } + + function getIndexField() { + return 'rc_timestamp'; + } + + function formatRow( $row ) { + return $this->mForm->formatRow( $row ); + } + + function getStartBody() { + # Do a batch existence check on pages + $linkBatch = new LinkBatch(); + 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->rc_namespace, $row->rc_title ); + } + $linkBatch->execute(); + return "
      "; + } + + function getEndBody() { + return "
    "; + } +} diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php new file mode 100644 index 0000000000..eb5727367c --- /dev/null +++ b/includes/specials/SpecialPopularpages.php @@ -0,0 +1,67 @@ +tableName( 'page' ); + + $query = + "SELECT 'Popularpages' as type, + page_namespace as namespace, + page_title as title, + page_counter as value + FROM $page "; + $where = + "WHERE page_is_redirect=0 AND page_namespace"; + + global $wgContentNamespaces; + if( empty( $wgContentNamespaces ) ) { + $where .= '='.NS_MAIN; + } else if( count( $wgContentNamespaces ) > 1 ) { + $where .= ' in (' . implode( ', ', $wgContentNamespaces ) . ')'; + } else { + $where .= '='.$wgContentNamespaces[0]; + } + + return $query . $where; + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + $title = Title::makeTitle( $result->namespace, $result->title ); + $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); + $nv = wfMsgExt( 'nviews', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ); + return wfSpecialList($link, $nv); + } +} + +/** + * Constructor + */ +function wfSpecialPopularpages() { + list( $limit, $offset ) = wfCheckLimits(); + + $ppp = new PopularPagesPage(); + + return $ppp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php new file mode 100644 index 0000000000..6a16f1cba0 --- /dev/null +++ b/includes/specials/SpecialPreferences.php @@ -0,0 +1,1122 @@ +execute(); +} + +/** + * Preferences form handling + * This object will show the preferences form and can save it as well. + * @ingroup SpecialPage + */ +class PreferencesForm { + var $mQuickbar, $mOldpass, $mNewpass, $mRetypePass, $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 $mUnderline, $mWatchlistEdits; + + /** + * Constructor + * Load some values + */ + function PreferencesForm( &$request ) { + 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->mMath = $request->getVal( 'wpMath' ); + $this->mDate = $request->getVal( 'wpDate' ); + $this->mUserEmail = $request->getVal( 'wpUserEmail' ); + $this->mRealName = $wgAllowRealName ? $request->getVal( 'wpRealName' ) : ''; + $this->mEmailFlag = $request->getCheck( 'wpEmailFlag' ) ? 0 : 1; + $this->mNick = $request->getVal( 'wpNick' ); + $this->mUserLanguage = $request->getVal( 'wpUserLanguage' ); + $this->mUserVariant = $request->getVal( 'wpUserVariant' ); + $this->mSearch = $request->getVal( 'wpSearch' ); + $this->mRecent = $request->getVal( 'wpRecent' ); + $this->mRecentDays = $request->getVal( 'wpRecentDays' ); + $this->mHourDiff = $request->getVal( 'wpHourDiff' ); + $this->mSearchLines = $request->getVal( 'wpSearchLines' ); + $this->mSearchChars = $request->getVal( 'wpSearchChars' ); + $this->mImageSize = $request->getVal( 'wpImageSize' ); + $this->mThumbSize = $request->getInt( 'wpThumbSize' ); + $this->mUnderline = $request->getInt( 'wpOpunderline' ); + $this->mAction = $request->getVal( 'action' ); + $this->mReset = $request->getCheck( 'wpReset' ); + $this->mPosted = $request->wasPosted(); + $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' ) && + $this->mPosted && + $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); + + # User toggles (the big ugly unsorted list of checkboxes) + $this->mToggles = array(); + if ( $this->mPosted ) { + $togs = User::getToggles(); + foreach ( $togs as $tname ) { + $this->mToggles[$tname] = $request->getCheck( "wpOp$tname" ) ? 1 : 0; + } + } + + $this->mUsedToggles = array(); + + # Search namespace options + # Note: namespaces don't necessarily have consecutive keys + $this->mSearchNs = array(); + if ( $this->mPosted ) { + $namespaces = $wgContLang->getNamespaces(); + foreach ( $namespaces as $i => $namespace ) { + if ( $i >= 0 ) { + $this->mSearchNs[$i] = $request->getCheck( "wpNs$i" ) ? 1 : 0; + } + } + } + + # Validate language + if ( !preg_match( '/^[a-z\-]*$/', $this->mUserLanguage ) ) { + $this->mUserLanguage = 'nolanguage'; + } + + wfRunHooks( 'InitPreferencesForm', array( $this, $request ) ); + } + + function execute() { + global $wgUser, $wgOut; + + if ( $wgUser->isAnon() ) { + $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext' ); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + if ( $this->mReset ) { + $this->resetPrefs(); + $this->mainPrefsForm( 'reset', wfMsg( 'prefsreset' ) ); + } else if ( $this->mSaveprefs ) { + $this->savePreferences(); + } else { + $this->resetPrefs(); + $this->mainPrefsForm( '' ); + } + } + /** + * @access private + */ + function validateInt( &$val, $min=0, $max=0x7fffffff ) { + $val = intval($val); + $val = min($val, $max); + $val = max($val, $min); + return $val; + } + + /** + * @access private + */ + function validateFloat( &$val, $min, $max=0x7fffffff ) { + $val = floatval( $val ); + $val = min( $val, $max ); + $val = max( $val, $min ); + return( $val ); + } + + /** + * @access private + */ + function validateIntOrNull( &$val, $min=0, $max=0x7fffffff ) { + $val = trim($val); + if($val === '') { + return null; + } else { + return $this->validateInt( $val, $min, $max ); + } + } + + /** + * @access private + */ + function validateDate( $val ) { + global $wgLang, $wgContLang; + if ( $val !== false && ( + in_array( $val, (array)$wgLang->getDatePreferences() ) || + in_array( $val, (array)$wgContLang->getDatePreferences() ) ) ) + { + return $val; + } else { + return $wgLang->getDefaultDateFormat(); + } + } + + /** + * 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', + * @access private + * @param string $s the user input + * @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 ); + } + return $s; + } + + /** + * @access private + */ + function savePreferences() { + global $wgUser, $wgOut, $wgParser; + global $wgEnableUserEmail, $wgEnableEmail; + 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; + + if( $wgUser->getOption( 'language' ) !== $this->mUserLanguage ) { + $needRedirect = true; + } else { + $needRedirect = false; + } + + # Validate the signature and clean it up as needed + global $wgMaxSigChars; + if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) { + global $wgLang; + $this->mainPrefsForm( 'error', + wfMsgExt( 'badsiglength', 'parsemag', $wgLang->formatNum( $wgMaxSigChars ) ) ); + return; + } elseif( $this->mToggles['fancysig'] ) { + if( $wgParser->validateSig( $this->mNick ) !== false ) { + $this->mNick = $wgParser->cleanSig( $this->mNick ); + } else { + $this->mainPrefsForm( 'error', wfMsg( 'badsig' ) ); + return; + } + } else { + // When no fancy sig used, make sure ~{3,5} get removed. + $this->mNick = $wgParser->cleanSigInSig( $this->mNick ); + } + + $wgUser->setOption( 'language', $this->mUserLanguage ); + $wgUser->setOption( 'variant', $this->mUserVariant ); + $wgUser->setOption( 'nickname', $this->mNick ); + $wgUser->setOption( 'quickbar', $this->mQuickbar ); + $wgUser->setOption( 'skin', $this->mSkin ); + global $wgUseTeX; + if( $wgUseTeX ) { + $wgUser->setOption( 'math', $this->mMath ); + } + $wgUser->setOption( 'date', $this->validateDate( $this->mDate ) ); + $wgUser->setOption( 'searchlimit', $this->validateIntOrNull( $this->mSearch ) ); + $wgUser->setOption( 'contextlines', $this->validateIntOrNull( $this->mSearchLines ) ); + $wgUser->setOption( 'contextchars', $this->validateIntOrNull( $this->mSearchChars ) ); + $wgUser->setOption( 'rclimit', $this->validateIntOrNull( $this->mRecent ) ); + $wgUser->setOption( 'rcdays', $this->validateInt($this->mRecentDays, 1, ceil($wgRCMaxAge / (3600*24)))); + $wgUser->setOption( 'wllimit', $this->validateIntOrNull( $this->mWatchlistEdits, 0, 1000 ) ); + $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( '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 + foreach( $this->mSearchNs as $i => $value ) { + $wgUser->setOption( "searchNs{$i}", $value ); + } + + if( $wgEnableEmail && $wgEnableUserEmail ) { + $wgUser->setOption( 'disablemail', $this->mEmailFlag ); + } + + # Set user toggles + foreach ( $this->mToggles as $tname => $tvalue ) { + $wgUser->setOption( $tname, $tvalue ); + } + + $error = false; + if( $wgEnableEmail ) { + $newadr = $this->mUserEmail; + $oldadr = $wgUser->getEmail(); + if( ($newadr != '') && ($newadr != $oldadr) ) { + # the user has supplied a new email address on the login page + if( $wgUser->isValidEmailAddr( $newadr ) ) { + # new behaviour: set this new emailaddr from login-page into user database record + $wgUser->setEmail( $newadr ); + # but flag as "dirty" = unauthenticated + $wgUser->invalidateEmail(); + if ($wgEmailAuthentication) { + # Mail a temporary password to the dirty address. + # User can come back through the confirmation URL to re-enable email. + $result = $wgUser->sendConfirmationMail(); + if( WikiError::isError( $result ) ) { + $error = wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) ); + } else { + $error = wfMsg( 'eauthentsent', $wgUser->getName() ); + } + } + } else { + $error = wfMsg( 'invalidemailaddress' ); + } + } else { + if( $wgEmailConfirmToEdit && empty( $newadr ) ) { + $this->mainPrefsForm( 'error', wfMsg( 'noemailtitle' ) ); + return; + } + $wgUser->setEmail( $this->mUserEmail ); + } + if( $oldadr != $newadr ) { + wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) ); + } + } + + if( !$wgAuth->updateExternalDB( $wgUser ) ){ + $this->mainPrefsForm( 'error', wfMsg( 'externaldberror' ) ); + return; + } + + $msg = ''; + if ( !wfRunHooks( 'SavePreferences', array( $this, $wgUser, &$msg, $oldOptions ) ) ) { + $this->mainPrefsForm( 'error', $msg ); + return; + } + + $wgUser->setCookies(); + $wgUser->saveSettings(); + + if( $needRedirect && $error === false ) { + $title = SpecialPage::getTitleFor( 'Preferences' ); + $wgOut->redirect( $title->getFullURL( 'success' ) ); + return; + } + + $wgOut->parserOptions( ParserOptions::newFromUser( $wgUser ) ); + $this->mainPrefsForm( $error === false ? 'success' : 'error', $error); + } + + /** + * @access private + */ + function resetPrefs() { + global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName; + + $this->mOldpass = $this->mNewpass = $this->mRetypePass = ''; + $this->mUserEmail = $wgUser->getEmail(); + $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp(); + $this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : ''; + + # language value might be blank, default to content language + $this->mUserLanguage = $wgUser->getOption( 'language', $wgContLanguageCode ); + + $this->mUserVariant = $wgUser->getOption( 'variant'); + $this->mEmailFlag = $wgUser->getOption( 'disablemail' ) == 1 ? 1 : 0; + $this->mNick = $wgUser->getOption( 'nickname' ); + + $this->mQuickbar = $wgUser->getOption( 'quickbar' ); + $this->mSkin = Skin::normalizeKey( $wgUser->getOption( 'skin' ) ); + $this->mMath = $wgUser->getOption( 'math' ); + $this->mDate = $wgUser->getDatePreference(); + $this->mRows = $wgUser->getOption( 'rows' ); + $this->mCols = $wgUser->getOption( 'cols' ); + $this->mStubs = $wgUser->getOption( 'stubthreshold' ); + $this->mHourDiff = $wgUser->getOption( 'timecorrection' ); + $this->mSearch = $wgUser->getOption( 'searchlimit' ); + $this->mSearchLines = $wgUser->getOption( 'contextlines' ); + $this->mSearchChars = $wgUser->getOption( 'contextchars' ); + $this->mImageSize = $wgUser->getOption( 'imagesize' ); + $this->mThumbSize = $wgUser->getOption( 'thumbsize' ); + $this->mRecent = $wgUser->getOption( 'rclimit' ); + $this->mRecentDays = $wgUser->getOption( 'rcdays' ); + $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(); + foreach ( $togs as $tname ) { + $this->mToggles[$tname] = $wgUser->getOption( $tname ); + } + + $namespaces = $wgContLang->getNamespaces(); + foreach ( $namespaces as $i => $namespace ) { + if ( $i >= NS_MAIN ) { + $this->mSearchNs[$i] = $wgUser->getOption( 'searchNs'.$i ); + } + } + + wfRunHooks( 'ResetPreferences', array( $this, $wgUser ) ); + } + + /** + * @access private + */ + function namespacesCheckboxes() { + global $wgContLang; + + # Determine namespace checkboxes + $namespaces = $wgContLang->getNamespaces(); + $r1 = null; + + foreach ( $namespaces as $i => $name ) { + if ($i < 0) + continue; + $checked = $this->mSearchNs[$i] ? "checked='checked'" : ''; + $name = str_replace( '_', ' ', $namespaces[$i] ); + + if ( empty($name) ) + $name = wfMsg( 'blanknamespace' ); + + $r1 .= "
    \n"; + } + return $r1; + } + + + function getToggle( $tname, $trailer = false, $disabled = false ) { + global $wgUser, $wgLang; + + $this->mUsedToggles[$tname] = true; + $ttext = $wgLang->getUserToggle( $tname ); + + $checked = $wgUser->getOption( $tname ) == 1 ? ' checked="checked"' : ''; + $disabled = $disabled ? ' disabled="disabled"' : ''; + $trailer = $trailer ? $trailer : ''; + return "
    " . + " $trailer
    \n"; + } + + function getToggles( $items ) { + $out = ""; + foreach( $items as $item ) { + if( $item === false ) + continue; + if( is_array( $item ) ) { + list( $key, $trailer ) = $item; + } else { + $key = $item; + $trailer = false; + } + $out .= $this->getToggle( $key, $trailer ); + } + return $out; + } + + function addRow($td1, $td2) { + return "$td1$td2"; + } + + /** + * Helper function for user information panel + * @param $td1 label for an item + * @param $td2 item or null + * @param $td3 optional help or null + * @return xhtml block + */ + function tableRow( $td1, $td2 = null, $td3 = null ) { + + if ( is_null( $td3 ) ) { + $td3 = ''; + } else { + $td3 = Xml::tags( 'tr', null, + Xml::tags( 'td', array( 'class' => 'pref-label', 'colspan' => '2' ), $td3 ) + ); + } + + if ( is_null( $td2 ) ) { + $td1 = Xml::tags( 'td', array( 'class' => 'pref-label', 'colspan' => '2' ), $td1 ); + $td2 = ''; + } else { + $td1 = Xml::tags( 'td', array( 'class' => 'pref-label' ), $td1 ); + $td2 = Xml::tags( 'td', array( 'class' => 'pref-input' ), $td2 ); + } + + return Xml::tags( 'tr', null, $td1 . $td2 ). $td3 . "\n"; + + } + + /** + * @access private + */ + function mainPrefsForm( $status , $message = '' ) { + global $wgUser, $wgOut, $wgLang, $wgContLang; + global $wgAllowRealName, $wgImageLimits, $wgThumbLimits; + global $wgDisableLangConversion; + global $wgEnotifWatchlist, $wgEnotifUserTalk,$wgEnotifMinorEdits; + global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress; + global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication; + global $wgContLanguageCode, $wgDefaultSkin, $wgSkipSkins, $wgAuth; + global $wgEmailConfirmToEdit, $wgAjaxSearch, $wgEnableMWSuggest; + + $wgOut->setPageTitle( wfMsg( 'preferences' ) ); + $wgOut->setArticleRelated( false ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->addScriptFile( 'prefs.js' ); + + $wgOut->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc. + + if ( $this->mSuccess || 'success' == $status ) { + $wgOut->wrapWikiMsg( '
    $1
    ', 'savedprefs' ); + } else if ( 'error' == $status ) { + $wgOut->addWikiText( '
    ' . $message . '
    ' ); + } else if ( '' != $status ) { + $wgOut->addWikiText( $message . "\n----" ); + } + + $qbs = $wgLang->getQuickbarSettings(); + $skinNames = $wgLang->getSkinNames(); + $mathopts = $wgLang->getMathNames(); + $dateopts = $wgLang->getDatePreferences(); + $togs = User::getToggles(); + + $titleObj = SpecialPage::getTitleFor( 'Preferences' ); + $action = $titleObj->escapeLocalURL(); + + # Pre-expire some toggles so they won't show if disabled + $this->mUsedToggles[ 'shownumberswatching' ] = true; + $this->mUsedToggles[ 'showupdated' ] = true; + $this->mUsedToggles[ 'enotifwatchlistpages' ] = true; + $this->mUsedToggles[ 'enotifusertalkpages' ] = true; + $this->mUsedToggles[ 'enotifminoredits' ] = true; + $this->mUsedToggles[ 'enotifrevealaddr' ] = true; + $this->mUsedToggles[ 'ccmeonemails' ] = true; + $this->mUsedToggles[ 'uselivepreview' ] = true; + + + if ( !$this->mEmailFlag ) { $emfc = 'checked="checked"'; } + else { $emfc = ''; } + + + if ($wgEmailAuthentication && ($this->mUserEmail != '') ) { + if( $wgUser->getEmailAuthenticationTimestamp() ) { + $emailauthenticated = wfMsg('emailauthenticated',$wgLang->timeanddate($wgUser->getEmailAuthenticationTimestamp(), true ) ).'
    '; + $disableEmailPrefs = false; + } else { + $disableEmailPrefs = true; + $skin = $wgUser->getSkin(); + $emailauthenticated = wfMsg('emailnotauthenticated').'
    ' . + $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Confirmemail' ), + wfMsg( 'emailconfirmlink' ) ) . '
    '; + } + } else { + $emailauthenticated = ''; + $disableEmailPrefs = false; + } + + if ($this->mUserEmail == '') { + $emailauthenticated = wfMsg( 'noemailprefs' ) . '
    '; + } + + $ps = $this->namespacesCheckboxes(); + + $enotifwatchlistpages = ($wgEnotifWatchlist) ? $this->getToggle( 'enotifwatchlistpages', false, $disableEmailPrefs ) : ''; + $enotifusertalkpages = ($wgEnotifUserTalk) ? $this->getToggle( 'enotifusertalkpages', false, $disableEmailPrefs ) : ''; + $enotifminoredits = ($wgEnotifWatchlist && $wgEnotifMinorEdits) ? $this->getToggle( 'enotifminoredits', false, $disableEmailPrefs ) : ''; + $enotifrevealaddr = (($wgEnotifWatchlist || $wgEnotifUserTalk) && $wgEnotifRevealEditorAddress) ? $this->getToggle( 'enotifrevealaddr', false, $disableEmailPrefs ) : ''; + + # + + $wgOut->addHTML( "
    " ); + $wgOut->addHTML( "
    " ); + + # User data + + $wgOut->addHTML( + Xml::openElement( 'fieldset ' ) . + Xml::element( 'legend', null, wfMsg('prefs-personal') ) . + Xml::openElement( 'table' ) . + $this->tableRow( Xml::element( 'h2', null, wfMsg( 'prefs-personal' ) ) ) + ); + + # Get groups to which the user belongs + $userEffectiveGroups = $wgUser->getEffectiveGroups(); + $userEffectiveGroupsArray = array(); + foreach( $userEffectiveGroups as $ueg ) { + if( $ueg == '*' ) { + // Skip the default * group, seems useless here + continue; + } + $userEffectiveGroupsArray[] = User::makeGroupLinkHTML( $ueg ); + } + asort( $userEffectiveGroupsArray ); + + $sk = $wgUser->getSkin(); + $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. + # $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( + wfMsgExt( 'prefs-memberingroups', array( 'parseinline' ), count( $userEffectiveGroupsArray ) ), + implode( wfMsg( 'comma-separator' ), $userEffectiveGroupsArray ) . + '
    (' . implode( ' | ', $toolLinks ) . ')' + ) . + + $this->tableRow( + wfMsgHtml( 'prefs-edits' ), + $wgLang->formatNum( User::edits( $wgUser->getId() ) ) + ); + + if( wfRunHooks( 'PreferencesUserInformationPanel', array( $this, &$userInformationHtml ) ) ) { + $wgOut->addHtml( $userInformationHtml ); + } + + if ( $wgAllowRealName ) { + $wgOut->addHTML( + $this->tableRow( + Xml::label( wfMsg('yourrealname'), 'wpRealName' ), + Xml::input( 'wpRealName', 25, $this->mRealName, array( 'id' => 'wpRealName' ) ), + Xml::tags('div', array( 'class' => 'prefsectiontip' ), + wfMsgExt( 'prefs-help-realname', 'parseinline' ) + ) + ) + ); + } + if ( $wgEnableEmail ) { + $wgOut->addHTML( + $this->tableRow( + Xml::label( wfMsg('youremail'), 'wpUserEmail' ), + Xml::input( 'wpUserEmail', 25, $this->mUserEmail, array( 'id' => 'wpUserEmail' ) ), + Xml::tags('div', array( 'class' => 'prefsectiontip' ), + wfMsgExt( $wgEmailConfirmToEdit ? 'prefs-help-email-required' : 'prefs-help-email', 'parseinline' ) + ) + ) + ); + } + + global $wgParser, $wgMaxSigChars; + if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) { + $invalidSig = $this->tableRow( + ' ', + Xml::element( 'span', array( 'class' => 'error' ), + wfMsgExt( 'badsiglength', 'parsemag', $wgLang->formatNum( $wgMaxSigChars ) ) ) + ); + } elseif( !empty( $this->mToggles['fancysig'] ) && + false === $wgParser->validateSig( $this->mNick ) ) { + $invalidSig = $this->tableRow( + ' ', + Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) ) + ); + } else { + $invalidSig = ''; + } + + $wgOut->addHTML( + $this->tableRow( + Xml::label( wfMsg( 'yournick' ), 'wpNick' ), + Xml::input( 'wpNick', 25, $this->mNick, + array( + 'id' => 'wpNick', + // Note: $wgMaxSigChars is enforced in Unicode characters, + // both on the backend and now in the browser. + // Badly-behaved requests may still try to submit + // an overlong string, however. + 'maxlength' => $wgMaxSigChars ) ) + ) . + $invalidSig . + $this->tableRow( ' ', $this->getToggle( 'fancysig' ) ) + ); + + list( $lsLabel, $lsSelect) = Xml::languageSelector( $this->mUserLanguage ); + $wgOut->addHTML( + $this->tableRow( $lsLabel, $lsSelect ) + ); + + /* see if there are multiple language variants to choose from*/ + if(!$wgDisableLangConversion) { + $variants = $wgContLang->getVariants(); + $variantArray = array(); + + $languages = Language::getLanguageNames( true ); + foreach($variants as $v) { + $v = str_replace( '_', '-', strtolower($v)); + if( array_key_exists( $v, $languages ) ) { + // If it doesn't have a name, we'll pretend it doesn't exist + $variantArray[$v] = $languages[$v]; + } + } + + $options = "\n"; + foreach( $variantArray as $code => $name ) { + $selected = ($code == $this->mUserVariant); + $options .= Xml::option( "$code - $name", $code, $selected ) . "\n"; + } + + if(count($variantArray) > 1) { + $wgOut->addHtml( + $this->tableRow( + Xml::label( wfMsg( 'yourvariant' ), 'wpUserVariant' ), + Xml::tags( 'select', + array( 'name' => 'wpUserVariant', 'id' => 'wpUserVariant' ), + $options + ) + ) + ); + } + } + + # Password + if( $wgAuth->allowPasswordChange() ) { + $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" ) + ) + ) + ); + } + + # + # Enotif + if ( $wgEnableEmail ) { + + $moreEmail = ''; + if ($wgEnableUserEmail) { + // fixme -- the "allowemail" pseudotoggle is a hacked-together + // inversion for the "disableemail" preference. + $emf = wfMsg( 'allowemail' ); + $disabled = $disableEmailPrefs ? ' disabled="disabled"' : ''; + $moreEmail = + " " . + $this->getToggle( 'ccmeonemails', '', $disableEmailPrefs ); + } + + + $wgOut->addHTML( + $this->tableRow( Xml::element( 'h2', null, wfMsg( 'email' ) ) ) . + $this->tableRow( + $emailauthenticated. + $enotifrevealaddr. + $enotifwatchlistpages. + $enotifusertalkpages. + $enotifminoredits. + $moreEmail + ) + ); + } + # + + $wgOut->addHTML( + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ) + ); + + + # Quickbar + # + if ($this->mSkin == 'cologneblue' || $this->mSkin == 'standard') { + $wgOut->addHtml( "
    \n" . wfMsg( 'qbsettings' ) . "\n" ); + for ( $i = 0; $i < count( $qbs ); ++$i ) { + if ( $i == $this->mQuickbar ) { $checked = ' checked="checked"'; } + else { $checked = ""; } + $wgOut->addHTML( "
    \n" ); + } + $wgOut->addHtml( "
    \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 ) ); + } + + # Skin + # + $wgOut->addHTML( "
    \n\n" . wfMsg('skin') . "\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]; + } + } + asort($validSkinNames); + foreach ($validSkinNames as $skinkey => $sn ) { + if ( in_array( $skinkey, $wgSkipSkins ) ) { + continue; + } + $checked = $skinkey == $this->mSkin ? ' checked="checked"' : ''; + + $mplink = htmlspecialchars($mptitle->getLocalURL("useskin=$skinkey")); + $previewlink = "$previewtext"; + if( $skinkey == $wgDefaultSkin ) + $sn .= ' (' . wfMsg( 'default' ) . ')'; + $wgOut->addHTML( " $previewlink
    \n" ); + } + $wgOut->addHTML( "
    \n\n" ); + + # Math + # + global $wgUseTeX; + if( $wgUseTeX ) { + $wgOut->addHTML( "
    \n" . wfMsg('math') . '' ); + foreach ( $mathopts as $k => $v ) { + $checked = ($k == $this->mMath); + $wgOut->addHTML( + Xml::openElement( 'div' ) . + Xml::radioLabel( wfMsg( $v ), 'wpMath', $k, "mw-sp-math-$k", $checked ) . + Xml::closeElement( 'div' ) . "\n" + ); + } + $wgOut->addHTML( "
    \n\n" ); + } + + # Files + # + $wgOut->addHTML( + "
    \n" . Xml::element( 'legend', null, wfMsg( 'files' ) ) . "\n" + ); + + $imageLimitOptions = null; + foreach ( $wgImageLimits as $index => $limits ) { + $selected = ($index == $this->mImageSize); + $imageLimitOptions .= Xml::option( "{$limits[0]}×{$limits[1]}" . + wfMsg('unit-pixel'), $index, $selected ); + } + + $imageSizeId = 'wpImageSize'; + $wgOut->addHTML( + "
    " . Xml::label( wfMsg('imagemaxsize'), $imageSizeId ) . " " . + Xml::openElement( 'select', array( 'name' => $imageSizeId, 'id' => $imageSizeId ) ) . + $imageLimitOptions . + Xml::closeElement( 'select' ) . "
    \n" + ); + + $imageThumbOptions = null; + foreach ( $wgThumbLimits as $index => $size ) { + $selected = ($index == $this->mThumbSize); + $imageThumbOptions .= Xml::option($size . wfMsg('unit-pixel'), $index, + $selected); + } + + $thumbSizeId = 'wpThumbSize'; + $wgOut->addHTML( + "
    " . Xml::label( wfMsg('thumbsize'), $thumbSizeId ) . " " . + Xml::openElement( 'select', array( 'name' => $thumbSizeId, 'id' => $thumbSizeId ) ) . + $imageThumbOptions . + Xml::closeElement( 'select' ) . "
    \n" + ); + + $wgOut->addHTML( "
    \n\n" ); + + # Date format + # + # Date/Time + # + + $wgOut->addHTML( + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'datetime' ) ) . "\n" + ); + + if ($dateopts) { + $wgOut->addHTML( + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'dateformat' ) ) . "\n" + ); + $idCnt = 0; + $epoch = '20010115161234'; # Wikipedia day + foreach( $dateopts as $key ) { + if( $key == 'default' ) { + $formatted = wfMsg( 'datedefault' ); + } else { + $formatted = $wgLang->timeanddate( $epoch, false, $key ); + } + $wgOut->addHTML( + Xml::tags( 'div', null, + Xml::radioLabel( $formatted, 'wpDate', $key, "wpDate$idCnt", $key == $this->mDate ) + ) . "\n" + ); + $idCnt++; + } + $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . "\n" ); + } + + $nowlocal = $wgLang->time( $now = wfTimestampNow(), true ); + $nowserver = $wgLang->time( $now, false ); + + $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( + Xml::label( wfMsg( 'timezoneoffset' ), 'wpHourDiff' ), + Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array( 'id' => 'wpHourDiff' ) ) ) . + " + + " . + Xml::element( 'input', + array( 'type' => 'button', + 'value' => wfMsg( 'guesstimezone' ), + 'onclick' => 'javascript:guessTimezone()', + 'id' => 'guesstimezonebutton', + 'style' => 'display:none;' ) ) . + " + " . + Xml::closeElement( 'table' ) . + Xml::tags( 'div', array( 'class' => 'prefsectiontip' ), wfMsgExt( 'timezonetext', 'parseinline' ) ). + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'fieldset' ) . "\n\n" + ); + + # Editing + # + global $wgLivePreview; + $wgOut->addHTML( '
    ' . wfMsg( 'textboxsize' ) . ' +
    ' . + wfInputLabel( wfMsg( 'rows' ), 'wpRows', 'wpRows', 3, $this->mRows ) . + ' ' . + wfInputLabel( wfMsg( 'columns' ), 'wpCols', 'wpCols', 3, $this->mCols ) . + "
    " . + $this->getToggles( array( + 'editsection', + 'editsectiononrightclick', + 'editondblclick', + 'editwidth', + 'showtoolbar', + 'previewonfirst', + 'previewontop', + 'minordefault', + 'externaleditor', + 'externaldiff', + $wgLivePreview ? 'uselivepreview' : false, + 'forceeditsummary', + ) ) . '
    ' + ); + + # Recent changes + $wgOut->addHtml( '
    ' . wfMsgHtml( 'prefs-rc' ) . '' ); + + $rc = ''; + $rc .= ''; + $rc .= ''; + $rc .= ''; + $rc .= ''; + $rc .= ''; + $rc .= '
    ' . Xml::label( wfMsg( 'recentchangesdays' ), 'wpRecentDays' ) . '' . Xml::input( 'wpRecentDays', 3, $this->mRecentDays, array( 'id' => 'wpRecentDays' ) ) . '
    ' . Xml::label( wfMsg( 'recentchangescount' ), 'wpRecent' ) . '' . Xml::input( 'wpRecent', 3, $this->mRecent, array( 'id' => 'wpRecent' ) ) . '
    '; + $wgOut->addHtml( $rc ); + + $wgOut->addHtml( '
    ' ); + + $toggles[] = 'hideminor'; + if( $wgRCShowWatchingUsers ) + $toggles[] = 'shownumberswatching'; + $toggles[] = 'usenewrc'; + $wgOut->addHtml( $this->getToggles( $toggles ) ); + + $wgOut->addHtml( '
    ' ); + + # Watchlist + $wgOut->addHtml( '
    ' . wfMsgHtml( 'prefs-watchlist' ) . '' ); + + $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) ); + $wgOut->addHtml( '

    ' ); + + $wgOut->addHtml( $this->getToggle( 'extendwatchlist' ) ); + $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) ); + $wgOut->addHtml( '

    ' ); + + $wgOut->addHtml( $this->getToggles( array( 'watchlisthideown', 'watchlisthidebots', 'watchlisthideminor' ) ) ); + + 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 ) ); + } + $this->mUsedToggles['watchcreations'] = true; + $this->mUsedToggles['watchdefault'] = true; + $this->mUsedToggles['watchmoves'] = true; + $this->mUsedToggles['watchdeletion'] = true; + + $wgOut->addHtml( '
    ' ); + + # 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' ), + Xml::check( 'wpDisableMWSuggest', $this->mDisableMWSuggest, array( 'id' => 'wpDisableMWSuggest' ) ) + ) : ''; + $wgOut->addHTML( + // Elements for the search tab itself + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'searchresultshead' ) ) . + // Elements for the search options in the search tab + 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' ) ) + ) . + $this->addRow( + Xml::label( wfMsg( 'contextlines' ), 'wpSearchLines' ), + Xml::input( 'wpSearchLines', 4, $this->mSearchLines, array( 'id' => 'wpSearchLines' ) ) + ) . + $this->addRow( + Xml::label( wfMsg( 'contextchars' ), 'wpSearchChars' ), + Xml::input( 'wpSearchChars', 4, $this->mSearchChars, array( 'id' => 'wpSearchChars' ) ) + ) . + $mwsuggest . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ) . + // Elements for the namespace options in the search tab + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'prefs-namespaces' ) ) . + wfMsgExt( 'defaultns', array( 'parse' ) ) . + $ps . + Xml::closeElement( 'fieldset' ) . + // End of the search tab + Xml::closeElement( 'fieldset' ) + ); + + # Misc + # + $wgOut->addHTML('
    ' . wfMsg('prefs-misc') . ''); + $wgOut->addHtml( ' ' ); + $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' ) ); + $msgUnderlinedefault = htmlspecialchars( wfMsg ( 'underline-default' ) ); + $uopt = $wgUser->getOption("underline"); + $s0 = $uopt == 0 ? ' selected="selected"' : ''; + $s1 = $uopt == 1 ? ' selected="selected"' : ''; + $s2 = $uopt == 2 ? ' selected="selected"' : ''; + $wgOut->addHTML(" +

    +

    "); + + foreach ( $togs as $tname ) { + if( !array_key_exists( $tname, $this->mUsedToggles ) ) { + $wgOut->addHTML( $this->getToggle( $tname ) ); + } + } + $wgOut->addHTML( '
    ' ); + + wfRunHooks( 'RenderPreferencesForm', array( $this, $wgOut ) ); + + $token = htmlspecialchars( $wgUser->editToken() ); + $skin = $wgUser->getSkin(); + $wgOut->addHTML( " +
    +
    + tooltipAndAccesskey('save')." /> + +
    + +
    + + +
    \n" ); + + $wgOut->addHtml( Xml::tags( 'div', array( 'class' => "prefcache" ), + wfMsgExt( 'clearyourcache', 'parseinline' ) ) + ); + } +} diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php new file mode 100644 index 0000000000..1819d4e106 --- /dev/null +++ b/includes/specials/SpecialPrefixindex.php @@ -0,0 +1,152 @@ +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 + * @ingroup SpecialPage + */ +class SpecialPrefixindex extends SpecialAllpages { + // Inherit $maxPerPage + + // Define other properties + protected $name = 'Prefixindex'; + protected $nsfromMsg = 'allpagesprefix'; + + /** + * @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 ) { + global $wgOut, $wgUser, $wgContLang; + + $fname = 'indexShowChunk'; + + $sk = $wgUser->getSkin(); + + if (!isset($from)) $from = $prefix; + + $fromList = $this->getNamespaceKeyAndText($namespace, $from); + $prefixList = $this->getNamespaceKeyAndText($namespace, $prefix); + $namespaces = $wgContLang->getNamespaces(); + $align = $wgContLang->isRtl() ? 'left' : 'right'; + + if ( !$prefixList || !$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, $prefixKey, $prefix ) = $prefixList; + list( /* $fromNs */, $fromKey, $from ) = $fromList; + + ### FIXME: should complain if $fromNs != $namespace + + $dbr = wfGetDB( DB_SLAVE ); + + $res = $dbr->select( 'page', + array( 'page_namespace', 'page_title', 'page_is_redirect' ), + array( + 'page_namespace' => $namespace, + 'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'', + 'page_title >= ' . $dbr->addQuotes( $fromKey ), + ), + $fname, + array( + 'ORDER BY' => 'page_title', + 'LIMIT' => $this->maxPerPage + 1, + 'USE INDEX' => 'name_title', + ) + ); + + ### FIXME: side link to previous + + $n = 0; + if( $res->numRows() > 0 ) { + $out = ''; + + while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { + $t = Title::makeTitle( $s->page_namespace, $s->page_title ); + if( $t ) { + $link = ($s->page_is_redirect ? '
    ' : '' ) . + $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . + ($s->page_is_redirect ? '
    ' : '' ); + } else { + $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; + } + if( $n % 3 == 0 ) { + $out .= ''; + } + $out .= ""; + $n++; + if( $n % 3 == 0 ) { + $out .= ''; + } + } + if( ($n % 3) != 0 ) { + $out .= ''; + } + $out .= '
    $link
    '; + } else { + $out = ''; + } + } + + if ( $including ) { + $out2 = ''; + } else { + $nsForm = $this->namespaceForm ( $namespace, $prefix ); + $out2 = ''; + $out2 .= '
    ' . $nsForm; + $out2 .= '' . + $sk->makeKnownLink( $wgContLang->specialPage( $this->name ), + wfMsg ( 'allpages' ) ); + if ( isset($dbr) && $dbr && ($n == $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { + $namespaceparam = $namespace ? "&namespace=$namespace" : ""; + $out2 .= " | " . $sk->makeKnownLink( + $wgContLang->specialPage( $this->name ), + wfMsg ( 'nextpage', $s->page_title ), + "from=" . wfUrlEncode ( $s->page_title ) . + "&prefix=" . wfUrlEncode ( $prefix ) . $namespaceparam ); + } + $out2 .= "

    "; + } + + $wgOut->addHtml( $out2 . $out ); + } +} diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php new file mode 100644 index 0000000000..e168902897 --- /dev/null +++ b/includes/specials/SpecialProtectedpages.php @@ -0,0 +1,313 @@ +setPagetitle( wfMsg( "protectedpages" ) ); + if ( "" != $msg ) { + $wgOut->setSubtitle( $msg ); + } + + // Purge expired entries on one in every 10 queries + if ( !mt_rand( 0, 10 ) ) { + Title::purgeExpiredRestrictions(); + } + + $type = $wgRequest->getVal( $this->IdType ); + $level = $wgRequest->getVal( $this->IdLevel ); + $sizetype = $wgRequest->getVal( 'sizetype' ); + $size = $wgRequest->getIntOrNull( 'size' ); + $NS = $wgRequest->getIntOrNull( 'namespace' ); + $indefOnly = $wgRequest->getBool( 'indefonly' ) ? 1 : 0; + + $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly ); + + $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly ) ); + + if ( $pager->getNumRows() ) { + $s = $pager->getNavigationBar(); + $s .= "
      " . + $pager->getBody() . + "
    "; + $s .= $pager->getNavigationBar(); + } else { + $s = '

    ' . wfMsgHtml( 'protectedpagesempty' ) . '

    '; + } + $wgOut->addHTML( $s ); + } + + /** + * Callback function to output a restriction + * @param $row object Protected title + * @return string Formatted
  • element + */ + public function formatRow( $row ) { + global $wgUser, $wgLang, $wgContLang; + + wfProfileIn( __METHOD__ ); + + static $skin=null; + + if( is_null( $skin ) ) + $skin = $wgUser->getSkin(); + + $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); + $link = $skin->makeLinkObj( $title ); + + $description_items = array (); + + $protType = wfMsgHtml( 'restriction-level-' . $row->pr_level ); + + $description_items[] = $protType; + + if ( $row->pr_cascade ) { + $description_items[] = wfMsg( 'protect-summary-cascade' ); + } + + $expiry_description = ''; + $stxt = ''; + + if ( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { + $expiry = Block::decodeExpiry( $row->pr_expiry ); + + $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); + + $description_items[] = $expiry_description; + } + + if (!is_null($size = $row->page_len)) { + if ($size == 0) + $stxt = ' ' . wfMsgHtml('historyempty') . ''; + else + $stxt = ' ' . wfMsgHtml('historysize', $wgLang->formatNum( $size ) ) . ''; + $stxt = $wgContLang->getDirMark() . $stxt; + } + + # Show a link to the change protection form for allowed users otherwise a link to the protection log + if( $wgUser->isAllowed( 'protect' ) ) { + $changeProtection = ' (' . $skin->makeKnownLinkObj( $title, wfMsgHtml( 'protect_change' ), 'action=unprotect' ) . ')'; + } else { + $ltitle = SpecialPage::getTitleFor( 'Log' ); + $changeProtection = ' (' . $skin->makeKnownLinkObj( $ltitle, wfMsgHtml( 'protectlogpage' ), 'type=protect&page=' . $title->getPrefixedUrl() ) . ')'; + } + + wfProfileOut( __METHOD__ ); + + return '
  • ' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . $changeProtection . "
  • \n"; + } + + /** + * @param $namespace int + * @param $type string + * @param $level string + * @param $minsize int + * @param $indefOnly bool + * @return string Input form + * @private + */ + protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly ) { + 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" . + $this->getNamespaceMenu( $namespace ) . " \n" . + $this->getTypeMenu( $type ) . " \n" . + $this->getLevelMenu( $level ) . " \n" . + "
      " . + $this->getExpiryCheck( $indefOnly ) . " \n" . + $this->getSizeLimit( $sizetype, $size ) . " \n" . + "" . + " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ); + } + + /** + * Prepare the namespace filter drop-down; standard namespace + * selector, sans the MediaWiki namespace + * + * @param mixed $namespace Pre-select namespace + * @return string + */ + protected function getNamespaceMenu( $namespace = null ) { + return "" . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' + . Xml::namespaceSelector( $namespace, '' ) . ""; + } + + /** + * @return string Formatted HTML + */ + protected function getExpiryCheck( $indefOnly ) { + return + Xml::checkLabel( wfMsg('protectedpages-indef'), 'indefonly', 'indefonly', $indefOnly ) . "\n"; + } + + /** + * @return string Formatted HTML + */ + protected function getSizeLimit( $sizetype, $size ) { + $max = $sizetype === 'max'; + + return + Xml::radioLabel( wfMsg('minimum-size'), 'sizetype', 'min', 'wpmin', !$max ) . + ' ' . + Xml::radioLabel( wfMsg('maximum-size'), 'sizetype', 'max', 'wpmax', $max ) . + ' ' . + Xml::input( 'size', 9, $size, array( 'id' => 'wpsize' ) ) . + ' ' . + Xml::label( wfMsg('pagesize'), 'wpsize' ); + } + + /** + * @return string Formatted HTML + */ + protected function getTypeMenu( $pr_type ) { + global $wgRestrictionTypes; + + $m = array(); // Temporary array + $options = array(); + + // First pass to load the log names + foreach( $wgRestrictionTypes as $type ) { + $text = wfMsg("restriction-$type"); + $m[$text] = $type; + } + + // Third pass generates sorted XHTML content + foreach( $m as $text => $type ) { + $selected = ($type == $pr_type ); + $options[] = Xml::option( $text, $type, $selected ) . "\n"; + } + + return "" . + Xml::label( wfMsg('restriction-type') , $this->IdType ) . ' ' . + Xml::tags( 'select', + array( 'id' => $this->IdType, 'name' => $this->IdType ), + implode( "\n", $options ) ) . ""; + } + + /** + * @return string Formatted HTML + */ + protected function getLevelMenu( $pr_level ) { + global $wgRestrictionLevels; + + $m = array( wfMsg('restriction-level-all') => 0 ); // Temporary array + $options = array(); + + // First pass to load the log names + foreach( $wgRestrictionLevels as $type ) { + if ( $type !='' && $type !='*') { + $text = wfMsg("restriction-level-$type"); + $m[$text] = $type; + } + } + + // Third pass generates sorted XHTML content + foreach( $m as $text => $type ) { + $selected = ($type == $pr_level ); + $options[] = Xml::option( $text, $type, $selected ); + } + + return + Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . + Xml::tags( 'select', + array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), + implode( "\n", $options ) ); + } +} + +/** + * @todo document + * @ingroup Pager + */ +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 ) { + $this->mForm = $form; + $this->mConds = $conds; + $this->type = ( $type ) ? $type : 'edit'; + $this->level = $level; + $this->namespace = $namespace; + $this->sizetype = $sizetype; + $this->size = intval($size); + $this->indefonly = (bool)$indefonly; + parent::__construct(); + } + + function getStartBody() { + wfProfileIn( __METHOD__ ); + # Do a link batch query + $lb = new LinkBatch; + while( $row = $this->mResult->fetchObject() ) { + $lb->add( $row->page_namespace, $row->page_title ); + } + $lb->execute(); + + wfProfileOut( __METHOD__ ); + return ''; + } + + function formatRow( $row ) { + return $this->mForm->formatRow( $row ); + } + + function getQueryInfo() { + $conds = $this->mConds; + $conds[] = 'pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); + $conds[] = 'page_id=pr_page'; + $conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type ); + + if( $this->sizetype=='min' ) { + $conds[] = 'page_len>=' . $this->size; + } else if( $this->sizetype=='max' ) { + $conds[] = 'page_len<=' . $this->size; + } + + if( $this->indefonly ) { + $conds[] = "pr_expiry = 'infinity' OR pr_expiry IS NULL"; + } + + if( $this->level ) + $conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level ); + if( !is_null($this->namespace) ) + $conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace ); + return array( + 'tables' => array( 'page_restrictions', 'page' ), + 'fields' => 'pr_id,page_namespace,page_title,page_len,pr_type,pr_level,pr_expiry,pr_cascade', + 'conds' => $conds + ); + } + + function getIndexField() { + return 'pr_id'; + } +} + +/** + * Constructor + */ +function wfSpecialProtectedpages() { + + $ppForm = new ProtectedPagesForm(); + + $ppForm->showList(); +} diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php new file mode 100644 index 0000000000..2ec68a66b2 --- /dev/null +++ b/includes/specials/SpecialProtectedtitles.php @@ -0,0 +1,216 @@ +setPagetitle( wfMsg( "protectedtitles" ) ); + if ( "" != $msg ) { + $wgOut->setSubtitle( $msg ); + } + + // Purge expired entries on one in every 10 queries + if ( !mt_rand( 0, 10 ) ) { + Title::purgeExpiredRestrictions(); + } + + $type = $wgRequest->getVal( $this->IdType ); + $level = $wgRequest->getVal( $this->IdLevel ); + $sizetype = $wgRequest->getVal( 'sizetype' ); + $size = $wgRequest->getIntOrNull( 'size' ); + $NS = $wgRequest->getIntOrNull( 'namespace' ); + + $pager = new ProtectedTitlesPager( $this, array(), $type, $level, $NS, $sizetype, $size ); + + $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size ) ); + + if ( $pager->getNumRows() ) { + $s = $pager->getNavigationBar(); + $s .= "
      " . + $pager->getBody() . + "
    "; + $s .= $pager->getNavigationBar(); + } else { + $s = '

    ' . wfMsgHtml( 'protectedtitlesempty' ) . '

    '; + } + $wgOut->addHTML( $s ); + } + + /** + * Callback function to output a restriction + */ + function formatRow( $row ) { + global $wgUser, $wgLang, $wgContLang; + + wfProfileIn( __METHOD__ ); + + static $skin=null; + + if( is_null( $skin ) ) + $skin = $wgUser->getSkin(); + + $title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title ); + $link = $skin->makeLinkObj( $title ); + + $description_items = array (); + + $protType = wfMsgHtml( 'restriction-level-' . $row->pt_create_perm ); + + $description_items[] = $protType; + + $expiry_description = ''; $stxt = ''; + + if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) { + $expiry = Block::decodeExpiry( $row->pt_expiry ); + + $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); + + $description_items[] = $expiry_description; + } + + wfProfileOut( __METHOD__ ); + + return '
  • ' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . "
  • \n"; + } + + /** + * @param $namespace int + * @param $type string + * @param $level string + * @param $minsize int + * @private + */ + function showOptions( $namespace, $type='edit', $level, $sizetype, $size ) { + global $wgScript; + $action = htmlspecialchars( $wgScript ); + $title = SpecialPage::getTitleFor( 'ProtectedTitles' ); + $special = htmlspecialchars( $title->getPrefixedDBkey() ); + return "
    \n" . + '
    ' . + Xml::element( 'legend', array(), wfMsg( 'protectedtitles' ) ) . + Xml::hidden( 'title', $special ) . " \n" . + $this->getNamespaceMenu( $namespace ) . " \n" . + // $this->getLevelMenu( $level ) . "
    \n" . + " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . + "
    "; + } + + /** + * Prepare the namespace filter drop-down; standard namespace + * selector, sans the MediaWiki namespace + * + * @param mixed $namespace Pre-select namespace + * @return string + */ + function getNamespaceMenu( $namespace = null ) { + return Xml::label( wfMsg( 'namespace' ), 'namespace' ) + . ' ' + . Xml::namespaceSelector( $namespace, '' ); + } + + /** + * @return string Formatted HTML + * @private + */ + function getLevelMenu( $pr_level ) { + global $wgRestrictionLevels; + + $m = array( wfMsg('restriction-level-all') => 0 ); // Temporary array + $options = array(); + + // First pass to load the log names + foreach( $wgRestrictionLevels as $type ) { + if ( $type !='' && $type !='*') { + $text = wfMsg("restriction-level-$type"); + $m[$text] = $type; + } + } + + // Third pass generates sorted XHTML content + foreach( $m as $text => $type ) { + $selected = ($type == $pr_level ); + $options[] = Xml::option( $text, $type, $selected ); + } + + return + Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . + Xml::tags( 'select', + array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), + implode( "\n", $options ) ); + } +} + +/** + * @todo document + * @ingroup Pager + */ +class ProtectedtitlesPager extends AlphabeticPager { + public $mForm, $mConds; + + function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) { + $this->mForm = $form; + $this->mConds = $conds; + $this->level = $level; + $this->namespace = $namespace; + $this->size = intval($size); + parent::__construct(); + } + + function getStartBody() { + wfProfileIn( __METHOD__ ); + # Do a link batch query + $this->mResult->seek( 0 ); + $lb = new LinkBatch; + + while ( $row = $this->mResult->fetchObject() ) { + $lb->add( $row->pt_namespace, $row->pt_title ); + } + + $lb->execute(); + wfProfileOut( __METHOD__ ); + return ''; + } + + function formatRow( $row ) { + return $this->mForm->formatRow( $row ); + } + + function getQueryInfo() { + $conds = $this->mConds; + $conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); + + if( !is_null($this->namespace) ) + $conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace ); + return array( + 'tables' => 'protected_titles', + 'fields' => 'pt_namespace,pt_title,pt_create_perm,pt_expiry,pt_timestamp', + 'conds' => $conds + ); + } + + function getIndexField() { + return 'pt_timestamp'; + } +} + +/** + * Constructor + */ +function wfSpecialProtectedtitles() { + + $ppForm = new ProtectedTitlesForm(); + + $ppForm->showList(); +} diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php new file mode 100644 index 0000000000..0e7ada1d17 --- /dev/null +++ b/includes/specials/SpecialRandompage.php @@ -0,0 +1,100 @@ +, Ilmari Karonen + * @license GNU General Public Licence 2.0 or later + */ +class RandomPage extends SpecialPage { + private $namespace = NS_MAIN; // namespace to select pages from + + function __construct( $name = 'Randompage' ){ + parent::__construct( $name ); + } + + public function getNamespace() { + return $this->namespace; + } + + public function setNamespace ( $ns ) { + if( $ns < NS_MAIN ) $ns = NS_MAIN; + $this->namespace = $ns; + } + + // select redirects instead of normal pages? + // Overriden by SpecialRandomredirect + public function isRedirect(){ + return false; + } + + public function execute( $par ) { + global $wgOut, $wgContLang; + + if ($par) + $this->setNamespace( $wgContLang->getNsIndex( $par ) ); + + $title = $this->getRandomTitle(); + + if( is_null( $title ) ) { + $this->setHeaders(); + $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages' ); + return; + } + + $query = $this->isRedirect() ? 'redirect=no' : ''; + $wgOut->redirect( $title->getFullUrl( $query ) ); + } + + + /** + * Choose a random title. + * @return Title object (or null if nothing to choose from) + */ + public function getRandomTitle() { + $randstr = wfRandom(); + $row = $this->selectRandomPageFromDB( $randstr ); + + /* If we picked a value that was higher than any in + * the DB, wrap around and select the page with the + * lowest value instead! One might think this would + * skew the distribution, but in fact it won't cause + * any more bias than what the page_random scheme + * causes anyway. Trust me, I'm a mathematician. :) + */ + if( !$row ) + $row = $this->selectRandomPageFromDB( "0" ); + + if( $row ) + return Title::makeTitleSafe( $this->namespace, $row->page_title ); + else + return null; + } + + private function selectRandomPageFromDB( $randstr ) { + global $wgExtraRandompageSQL; + $fname = 'RandomPage::selectRandomPageFromDB'; + + $dbr = wfGetDB( DB_SLAVE ); + + $use_index = $dbr->useIndexClause( 'page_random' ); + $page = $dbr->tableName( 'page' ); + + $ns = (int) $this->namespace; + $redirect = $this->isRedirect() ? 1 : 0; + + $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : ""; + $sql = "SELECT page_title + FROM $page $use_index + WHERE page_namespace = $ns + AND page_is_redirect = $redirect + AND page_random >= $randstr + $extra + ORDER BY page_random"; + + $sql = $dbr->limitResult( $sql, 1, 0 ); + $res = $dbr->query( $sql, $fname ); + return $dbr->fetchObject( $res ); + } +} diff --git a/includes/specials/SpecialRandomredirect.php b/includes/specials/SpecialRandomredirect.php new file mode 100644 index 0000000000..629d5b3c85 --- /dev/null +++ b/includes/specials/SpecialRandomredirect.php @@ -0,0 +1,19 @@ +, Ilmari Karonen + * @license GNU General Public Licence 2.0 or later + */ +class SpecialRandomredirect extends RandomPage { + function __construct(){ + parent::__construct( 'Randomredirect' ); + } + + // Override parent::isRedirect() + public function isRedirect(){ + return true; + } +} diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php new file mode 100644 index 0000000000..16904b9f47 --- /dev/null +++ b/includes/specials/SpecialRecentchanges.php @@ -0,0 +1,613 @@ +includable( true ); + } + + public function getDefaultOptions() { + $opts = new FormOptions(); + + $opts->add( 'days', (int)User::getDefaultOption( 'rcdays' ) ); + $opts->add( 'limit', (int)User::getDefaultOption( 'rclimit' ) ); + $opts->add( 'from', '' ); + + $opts->add( 'hideminor', false ); + $opts->add( 'hidebots', true ); + $opts->add( 'hideanons', false ); + $opts->add( 'hideliu', false ); + $opts->add( 'hidepatrolled', false ); + $opts->add( 'hidemyself', false ); + + $opts->add( 'namespace', '', FormOptions::INTNULL ); + $opts->add( 'invert', false ); + + $opts->add( 'categories', '' ); + $opts->add( 'categories_any', false ); + + return $opts; +} + + public function setup( $parameters ) { + global $wgUser, $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 ) { + $this->parseParameters( $parameters, $opts ); + } + + $opts->validateIntBounds( 'limit', 0, 5000 ); + return $opts; + } + + public function feedSetup() { + global $wgFeedLimit, $wgRequest; + $opts = $this->getDefaultOptions(); + $opts->fetchValuesFromRequest( $wgRequest, array( 'days', 'limit', 'hideminor' ) ); + $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); + return $opts; + } + + public function execute( $parameters ) { + global $wgRequest, $wgOut; + $feedFormat = $wgRequest->getVal( 'feed' ); + + # 10 seconds server-side caching max + $wgOut->setSquidMaxage( 10 ); + + $lastmod = $this->checkLastModified( $feedFormat ); + if( $lastmod === false ){ + return; + } + + $opts = $feedFormat ? $this->feedSetup() : $this->setup( $parameters ); + $this->setHeaders(); + + // Fetch results, prepare a batch link existence check query + $rows = array(); + $batch = new LinkBatch; + $conds = $this->buildMainQueryConds( $opts ); + $res = $this->doMainQuery( $conds, $opts ); + $dbr = wfGetDB( DB_SLAVE ); + while( $row = $dbr->fetchObject( $res ) ){ + $rows[] = $row; + if ( !$feedFormat ) { + // User page and talk links + $batch->add( NS_USER, $row->rc_user_text ); + $batch->add( NS_USER_TALK, $row->rc_user_text ); + } + + } + $dbr->freeResult( $res ); + + if ( $feedFormat ) { + $feed = new ChangesFeed( $feedFormat, 'rcfeed' ); + $feedObj = $feed->getFeedObject( + wfMsgForContent( 'recentchanges' ), + wfMsgForContent( 'recentchanges-feed-description' ) + ); + $feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod ); + } else { + $batch->execute(); + $this->webOutput( $rows, $opts ); + } + + } + + 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; + + $m = array(); + if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) $opts['limit'] = $m[1]; + if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) $opts['days'] = $m[1]; + } + } + + # Get last modified date, for client caching + # Don't use this if we are using the patrol feature, patrol changes don't update the timestamp + 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 ) ){ + # Client cache fresh and headers sent, nothing more to do. + return false; + } + } + return $lastmod; + } + + public function buildMainQueryConds( FormOptions $opts ) { + global $wgUser; + + $dbr = wfGetDB( DB_SLAVE ); + $conds = array(); + + # It makes no sense to hide both anons and logged-in users + # Where this occurs, force anons to be shown + $forcebot = false; + if( $opts['hideanons'] && $opts['hideliu'] ){ + # Check if the user wants to show bots only + if( $opts['hidebots'] ){ + $opts['hideanons'] = false; + } else { + $forcebot = true; + $opts['hidebots'] = false; + } + } + + // Calculate cutoff + $cutoff_unixtime = time() - ( $opts['days'] * 86400 ); + $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); + $cutoff = $dbr->timestamp( $cutoff_unixtime ); + + $fromValid = preg_match('/^[0-9]{14}$/', $opts['from']); + if( $fromValid && $opts['from'] > wfTimestamp(TS_MW,$cutoff) ) { + $cutoff = $dbr->timestamp($opts['from']); + } else { + $opts->reset( 'from' ); + } + + $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff ); + + + $hidePatrol = $wgUser->useRCPatrol() && $opts['hidepatrolled']; + $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['hidemyself'] ) { + if( $wgUser->getId() ) { + $conds[] = 'rc_user != ' . $dbr->addQuotes( $wgUser->getId() ); + } else { + $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() ); + } + } + + # Namespace filtering + if ( $opts['namespace'] !== '' ) { + if ( !$opts['invert'] ) { + $conds[] = 'rc_namespace = ' . $dbr->addQuotes( $opts['namespace'] ); + } else { + $conds[] = 'rc_namespace != ' . $dbr->addQuotes( $opts['namespace'] ); + } + } + + return $conds; + } + + public function doMainQuery( $conds, $opts ) { + global $wgUser; + + $tables = array( 'recentchanges' ); + $join_conds = array(); + + $uid = $wgUser->getId(); + $dbr = wfGetDB( DB_SLAVE ); + $limit = $opts['limit']; + $namespace = $opts['namespace']; + $invert = $opts['invert']; + + // JOIN on watchlist for users + if( $wgUser->getId() ) { + $tables[] = 'watchlist'; + $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 ) ); + + // Is there either one namespace selected or excluded? + // Also, if this is "all" or main namespace, just use timestamp index. + if( is_null($namespace) || $invert || $namespace == NS_MAIN ) { + $res = $dbr->select( $tables, '*', $conds, __METHOD__, + array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, + 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ), + $join_conds ); + // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! + } else { + // New pages + $sqlNew = $dbr->selectSQLText( $tables, '*', + array( 'rc_new' => 1 ) + $conds, + __METHOD__, + array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, + 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ), + $join_conds ); + // Old pages + $sqlOld = $dbr->selectSQLText( $tables, '*', + array( 'rc_new' => 0 ) + $conds, + __METHOD__, + array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, + 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ), + $join_conds ); + # Join the two fast queries, and sort the result set + $sql = "($sqlNew) UNION ($sqlOld) ORDER BY rc_timestamp DESC LIMIT $limit"; + $res = $dbr->query( $sql, __METHOD__ ); + } + + return $res; + } + + public function webOutput( $rows, $opts ) { + global $wgOut, $wgUser, $wgRCShowWatchingUsers, $wgShowUpdatedMarker; + global $wgAllowCategorizedRecentChanges; + + $limit = $opts['limit']; + + if ( !$this->including() ) { + // Output options box + $this->doHeader( $opts ); + } + + // And now for the content + $wgOut->setSyndicated( true ); + + $list = ChangesList::newFromUser( $wgUser ); + + if ( $wgAllowCategorizedRecentChanges ) { + rcFilterByCategories( $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; + } + + $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->recentChangesLine( $rc, !empty( $obj->wl_user ) ); + --$limit; + } + } + $s .= $list->endRecentChangesList(); + $wgOut->addHTML( $s ); + } + + public function doHeader( $opts ) { + global $wgScript, $wgOut; + $wgOut->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) ); + + $defaults = $opts->getAllValues(); + $nondefaults = $opts->getChangedValues(); + $opts->consumeValues( array( 'namespace', 'invert' ) ); + + $panel = array(); + $panel[] = rcOptionsPanel( $defaults, $nondefaults ); + $panel[] = '
    '; + + $extraOpts = array(); + $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); + + global $wgAllowCategorizedRecentChanges; + if ( $wgAllowCategorizedRecentChanges ) { + $extraOpts['category'] = $this->categoryFilterForm( $opts ); + } + + wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); + $extraOpts['submit'] = Xml::submitbutton( wfMsg('allpagessubmit') ); + + $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] ); + } else { + $out .= Xml::tags( 'td', array( 'colspan' => 2 ), $optionRow ); + } + $out .= Xml::closeElement( 'tr' ); + } + $out .= Xml::closeElement( 'table' ); + + $unconsumed = $opts->getUnconsumedValues(); + foreach ( $unconsumed as $key => $value ) { + $out .= Xml::hidden( $key, $value ); + } + + $t = $this->getTitle(); + $out .= Xml::hidden( 'title', $t->getPrefixedText() ); + $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); + $panel[] = $form; + $panelString = implode( "\n", $panel ); + + $wgOut->addHTML( + Xml::fieldset( wfMsg( 'recentchanges' ), $panelString, array( 'class' => 'rcoptions' ) ) + ); + } + + /** + * Creates the choose namespace selection + * + * @return string + */ + protected function namespaceFilterForm( FormOptions $opts ) { + $nsSelect = HTMLnamespaceselector( $opts['namespace'], '' ); + $nsLabel = Xml::label( wfMsg('namespace'), 'namespace' ); + $invert = Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $opts['invert'] ); + return array( $nsLabel, "$nsSelect $invert" ); + } + + protected function categoryFilterForm( FormOptions $opts ) { + list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'), + 'categories', 'mw-categories', false, $opts['categories'] ); + + $input .= ' ' . Xml::checkLabel( wfMsg('rc_categories_any'), + 'categories_any', 'mw-categories_any', $opts['categories_any'] ); + + return array( $label, $input ); + } + +} + +function rcFilterByCategories ( &$rows, FormOptions $opts ) { + $categories = array_map( 'trim', explode( "|" , $categories ) ); + + if( empty($categories) ) { + return; + } + + # Filter categories + $cats = array(); + foreach ( $opts['categories'] AS $cat ) { + $cat = trim( $cat ); + if ( $cat == "" ) continue; + $cats[] = $cat; + } + + # Filter articles + $articles = array(); + $a2r = array(); + 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) ) { + $articles[] = $id; + } + if ( !isset($a2r[$id]) ) { + $a2r[$id] = array(); + } + $a2r[$id][] = $k; + } + + # Shortcut? + if ( !count($articles) || !count($cats) ) + return ; + + # Look up + $c = new Categoryfinder ; + $c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ; + $match = $c->run(); + + # Filter + $newrows = array(); + foreach ( $match AS $id ) { + foreach ( $a2r[$id] AS $rev ) { + $k = $rev; + $newrows[$k] = $rows[$k]; + } + } + $rows = $newrows; +} + +/** + * + */ +function rcCountLink( $lim, $d, $page='Recentchanges', $more='', $active = false ) { + global $wgUser, $wgLang, $wgContLang; + $sk = $wgUser->getSkin(); + $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ), + ($lim ? $wgLang->formatNum( "{$lim}" ) : wfMsg( 'recentchangesall' ) ), "{$more}" . + ($d ? "days={$d}&" : '') . 'limit='.$lim, '', '', + $active ? 'style="font-weight: bold;"' : '' ); + return $s; +} + +/** + * + */ +function rcDaysLink( $lim, $d, $page='Recentchanges', $more='', $active = false ) { + global $wgUser, $wgLang, $wgContLang; + $sk = $wgUser->getSkin(); + $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ), + ($d ? $wgLang->formatNum( "{$d}" ) : wfMsg( 'recentchangesall' ) ), $more.'days='.$d . + ($lim ? '&limit='.$lim : ''), '', '', + $active ? 'style="font-weight: bold;"' : '' ); + return $s; +} + +/** + * Used by Recentchangeslinked + */ +function rcDayLimitLinks( $days, $limit, $page='Recentchanges', $more='', $doall = false, $minorLink = '', + $botLink = '', $liuLink = '', $patrLink = '', $myselfLink = '' ) { + global $wgRCLinkLimits, $wgRCLinkDays; + if ($more != '') $more .= '&'; + + # Sort data for display and make sure it's unique after we've added user data. + # FIXME: why does this piss around with globals like this? Why is $limit added on globally? + $wgRCLinkLimits[] = $limit; + $wgRCLinkDays[] = $days; + sort($wgRCLinkLimits); + sort($wgRCLinkDays); + $wgRCLinkLimits = array_unique($wgRCLinkLimits); + $wgRCLinkDays = array_unique($wgRCLinkDays); + + $cl = array(); + foreach( $wgRCLinkLimits as $countLink ) { + $cl[] = rcCountLink( $countLink, $days, $page, $more, $countLink == $limit ); + } + if( $doall ) $cl[] = rcCountLink( 0, $days, $page, $more ); + $cl = implode( ' | ', $cl); + + $dl = array(); + foreach( $wgRCLinkDays as $daysLink ) { + $dl[] = rcDaysLink( $limit, $daysLink, $page, $more, $daysLink == $days ); + } + if( $doall ) $dl[] = rcDaysLink( $limit, 0, $page, $more ); + $dl = implode( ' | ', $dl); + + $linkParts = array( 'minorLink' => 'minor', 'botLink' => 'bots', 'liuLink' => 'liu', 'patrLink' => 'patr', 'myselfLink' => 'mine' ); + foreach( $linkParts as $linkVar => $linkMsg ) { + if( $$linkVar != '' ) + $links[] = wfMsgHtml( 'rcshowhide' . $linkMsg, $$linkVar ); + } + + $shm = implode( ' | ', $links ); + $note = wfMsg( 'rclinks', $cl, $dl, $shm ); + return $note; +} + + +/** + * 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, $wgContLang; + $sk = $wgUser->getSkin(); + return $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchanges' ), + htmlspecialchars( $title ), wfArrayToCGI( $override, $options ), '', '', + $active ? 'style="font-weight: bold;"' : '' ); +} + +/** + * Creates the options panel. + * @param $defaults + * @param $nondefaults + */ +function rcOptionsPanel( $defaults, $nondefaults ) { + global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays; + + $options = $nondefaults + $defaults; + + 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 ) ); + + # 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); + + // limit links + foreach( $wgRCLinkLimits as $value ) { + $cl[] = makeOptionsLink( $wgLang->formatNum( $value ), + array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ; + } + $cl = implode( ' | ', $cl); + + // day links, reset 'from' to none + foreach( $wgRCLinkDays as $value ) { + $dl[] = makeOptionsLink( $wgLang->formatNum( $value ), + array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ; + } + $dl = implode( ' | ', $dl); + + + // show/hide links + $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' )); + $minorLink = makeOptionsLink( $showhide[1-$options['hideminor']], + array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults); + $botLink = makeOptionsLink( $showhide[1-$options['hidebots']], + array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults); + $anonsLink = makeOptionsLink( $showhide[ 1 - $options['hideanons'] ], + array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults ); + $liuLink = makeOptionsLink( $showhide[1-$options['hideliu']], + array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults); + $patrLink = makeOptionsLink( $showhide[1-$options['hidepatrolled']], + array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults); + $myselfLink = makeOptionsLink( $showhide[1-$options['hidemyself']], + array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults); + + $links[] = wfMsgHtml( 'rcshowhideminor', $minorLink ); + $links[] = wfMsgHtml( 'rcshowhidebots', $botLink ); + $links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink ); + $links[] = wfMsgHtml( 'rcshowhideliu', $liuLink ); + if( $wgUser->useRCPatrol() ) + $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink ); + $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink ); + $hl = implode( ' | ', $links ); + + // show from this onward link + $now = $wgLang->timeanddate( wfTimestampNow(), true ); + $tl = makeOptionsLink( $now, array( 'from' => wfTimestampNow()), $nondefaults ); + + $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter'), + $cl, $dl, $hl ); + $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter'), $tl ); + return "$note
    $rclinks
    $rclistfrom"; + +} \ No newline at end of file diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php new file mode 100644 index 0000000000..73d46f81a4 --- /dev/null +++ b/includes/specials/SpecialRecentchangeslinked.php @@ -0,0 +1,191 @@ +getInt( 'days' ); + $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); + $hideminor = $wgRequest->getBool( 'hideminor' ) ? 1 : 0; + $showlinkedto = $wgRequest->getBool( 'showlinkedto' ) ? 1 : 0; + + $title = Title::newFromURL( $target ); + $target = $title ? $title->getPrefixedText() : ''; + + $wgOut->setPagetitle( wfMsg( 'recentchangeslinked' ) ); + $sk = $wgUser->getSkin(); + + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', array(), wfMsg( 'recentchangeslinked' ) ) . "\n" . + Xml::inputLabel( wfMsg( 'recentchangeslinked-page' ), 'target', 'recentchangeslinked-target', 40, $target ) . + "   " . + Xml::check( 'showlinkedto', $showlinkedto, array('id' => 'showlinkedto') ) . ' ' . + Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) . + "
    \n" . + Xml::hidden( 'title', $wgTitle->getPrefixedText() ). "\n" . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n" + ); + + if ( !$target ) { + return; + } + $nt = Title::newFromURL( $target ); + if( !$nt ) { + $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); + return; + } + $id = $nt->getArticleId(); + + $wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $nt->getPrefixedText() ) ); + $wgOut->setSyndicated(); + $wgOut->setFeedAppendQuery( "target=" . urlencode( $target ) ); + + if ( !$days ) { + $days = (int)$wgUser->getOption( 'rcdays', 7 ); + } + list( $limit, /* offset */ ) = wfCheckLimits( 100, 'rclimit' ); + + $dbr = wfGetDB( DB_SLAVE,'recentchangeslinked' ); + $cutoff = $dbr->timestamp( time() - ( $days * 86400 ) ); + + $hideminor = ($hideminor ? 1 : 0); + if ( $hideminor ) { + $mlink = $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchangeslinked' ), + wfMsg( 'show' ), 'target=' . htmlspecialchars( $nt->getPrefixedURL() ) . + "&days={$days}&limit={$limit}&hideminor=0&showlinkedto={$showlinkedto}" ); + } else { + $mlink = $sk->makeKnownLink( $wgContLang->specialPage( "Recentchangeslinked" ), + wfMsg( "hide" ), "target=" . htmlspecialchars( $nt->getPrefixedURL() ) . + "&days={$days}&limit={$limit}&hideminor=1&showlinkedto={$showlinkedto}" ); + } + if ( $hideminor ) { + $cmq = 'AND rc_minor=0'; + } else { $cmq = ''; } + + list($recentchanges, $categorylinks, $pagelinks, $watchlist) = + $dbr->tableNamesN( 'recentchanges', 'categorylinks', 'pagelinks', "watchlist" ); + + $uid = $wgUser->getId(); + // The fields we are selecting + $fields = "rc_cur_id,rc_namespace,rc_title, + rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,rc_log_type,rc_log_action,rc_params,rc_deleted, + rc_new, rc_id, rc_this_oldid, rc_last_oldid, rc_bot, rc_patrolled, rc_type, rc_old_len, rc_new_len"; + $fields .= $uid ? ",wl_user" : ""; + + // Check if this should be a feed + + $feed = false; + global $wgFeedLimit; + $feedFormat = $wgRequest->getVal( 'feed' ); + if( $feedFormat ) { + $feed = new ChangesFeed( $feedFormat, false ); + # Sanity check + if( $limit > $wgFeedLimit ) { + $limit = $wgFeedLimit; + } + } + + // If target is a Category, use categorylinks and invert from and to + if( $nt->getNamespace() == NS_CATEGORY ) { + $catkey = $dbr->addQuotes( $nt->getDBkey() ); + # The table clauses + $tables = "$categorylinks, $recentchanges"; + $tables .= $uid ? " LEFT JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : ""; + + $sql = "SELECT /* wfSpecialRecentchangeslinked */ $fields FROM $tables + WHERE rc_timestamp > '{$cutoff}' {$cmq} + AND cl_from=rc_cur_id + AND cl_to=$catkey + GROUP BY $fields ORDER BY rc_timestamp DESC LIMIT {$limit}"; // Shitty-ass GROUP BY by for postgres apparently + } else { + if( $showlinkedto ) { + $ns = $dbr->addQuotes( $nt->getNamespace() ); + $dbkey = $dbr->addQuotes( $nt->getDBkey() ); + $joinConds = "AND pl_namespace={$ns} AND pl_title={$dbkey} AND pl_from=rc_cur_id"; + } else { + $joinConds = "AND pl_namespace=rc_namespace AND pl_title=rc_title AND pl_from=$id"; + } + # The table clauses + $tables = "$pagelinks, $recentchanges"; + $tables .= $uid ? " LEFT JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : ""; + + $sql = "SELECT /* wfSpecialRecentchangeslinked */ $fields FROM $tables + WHERE rc_timestamp > '{$cutoff}' {$cmq} + {$joinConds} + GROUP BY $fields ORDER BY rc_timestamp DESC LIMIT {$limit}"; // Shitty-ass GROUP BY by for postgres apparently + } + # Actually do the query + $res = $dbr->query( $sql, __METHOD__ ); + $count = $dbr->numRows( $res ); + $rchanges = array(); + # Output feeds now and be done with it! + if( $feed ) { + if( $count ) { + $counter = 1; + while ( $limit ) { + if ( 0 == $count ) { break; } + $obj = $dbr->fetchObject( $res ); + --$count; + $rc = RecentChange::newFromRow( $obj ); + $rc->counter = $counter++; + --$limit; + $rchanges[] = $obj; + } + } + $wgOut->disable(); + + $feedObj = $feed->getFeedObject( + wfMsgForContent( 'recentchangeslinked-title', $nt->getPrefixedText() ), + wfMsgForContent( 'recentchangeslinked' ) + ); + ChangesFeed::generateFeed( $rchanges, $feedObj ); + return; + } + + # Otherwise, carry on with regular output... + $wgOut->addHTML("< ".$sk->makeLinkObj($nt, "", "redirect=no" )."
    \n"); + $note = wfMsgExt( "rcnote", array ( 'parseinline' ), $limit, $days, $wgLang->timeAndDate( wfTimestampNow(), true ) ); + $wgOut->addHTML( "
    \n{$note}\n
    " ); + + $note = rcDayLimitlinks( $days, $limit, "Recentchangeslinked", + "target=" . $nt->getPrefixedURL() . "&hideminor={$hideminor}&showlinkedto={$showlinkedto}", + false, $mlink ); + + $wgOut->addHTML( $note."\n" ); + + $list = ChangesList::newFromUser( $wgUser ); + $s = $list->beginRecentChangesList(); + + if ( $count ) { + $counter = 1; + while ( $limit ) { + if ( 0 == $count ) { break; } + $obj = $dbr->fetchObject( $res ); + --$count; + $rc = RecentChange::newFromRow( $obj ); + $rc->counter = $counter++; + $s .= $list->recentChangesLine( $rc , !empty( $obj->wl_user) ); + --$limit; + } + } else { + $wgOut->addWikiMsg('recentchangeslinked-noresult'); + } + $s .= $list->endRecentChangesList(); + + $dbr->freeResult( $res ); + $wgOut->addHTML( $s ); +} diff --git a/includes/specials/SpecialResetpass.php b/includes/specials/SpecialResetpass.php new file mode 100644 index 0000000000..707b941dd4 --- /dev/null +++ b/includes/specials/SpecialResetpass.php @@ -0,0 +1,167 @@ +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' ); + } + } + + /** + * Main execution point + */ + function execute( $par ) { + global $wgUser, $wgAuth, $wgOut, $wgRequest; + + if( !$wgAuth->allowPasswordChange() ) { + $this->error( wfMsg( 'resetpass_forbidden' ) ); + return; + } + + if( $this->mName === null && !$wgRequest->wasPosted() ) { + $this->error( wfMsg( 'resetpass_missing' ) ); + return; + } + + if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) { + $newpass = $wgRequest->getVal( 'wpNewPassword' ); + $retype = $wgRequest->getVal( 'wpRetype' ); + try { + $this->attemptReset( $newpass, $retype ); + $wgOut->addWikiMsg( 'resetpass_success' ); + + $data = array( + 'action' => 'submitlogin', + 'wpName' => $this->mName, + 'wpPassword' => $newpass, + 'returnto' => $wgRequest->getVal( 'returnto' ), + ); + if( $wgRequest->getCheck( 'wpRemember' ) ) { + $data['wpRemember'] = 1; + } + $login = new LoginForm( new FauxRequest( $data, true ) ); + $login->execute(); + + return; + } catch( PasswordError $e ) { + $this->error( $e->getMessage() ); + } + } + $this->showForm(); + } + + function error( $msg ) { + global $wgOut; + $wgOut->addHtml( '
    ' . + htmlspecialchars( $msg ) . + '
    ' ); + } + + function showForm() { + global $wgOut, $wgUser, $wgRequest; + + $wgOut->disallowUserJs(); + + $self = SpecialPage::getTitleFor( 'Resetpass' ); + $form = + '
    ' . + wfOpenElement( 'form', + array( + 'method' => 'post', + 'action' => $self->getLocalUrl() ) ) . + '

    ' . wfMsgHtml( 'resetpass_header' ) . '

    ' . + '
    ' . + wfMsgExt( 'resetpass_text', array( 'parse' ) ) . + '
    ' . + '' . + wfHidden( 'token', $wgUser->editToken() ) . + wfHidden( 'wpName', $this->mName ) . + wfHidden( 'wpPassword', $this->mTemporaryPassword ) . + wfHidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . + $this->pretty( array( + array( 'wpName', 'username', 'text', $this->mName ), + array( 'wpNewPassword', 'newpassword', 'password', '' ), + array( 'wpRetype', 'yourpasswordagain', 'password', '' ), + ) ) . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '
    ' . + Xml::checkLabel( wfMsg( 'remembermypassword' ), + 'wpRemember', 'wpRemember', + $wgRequest->getCheck( 'wpRemember' ) ) . + '
    ' . + wfSubmitButton( wfMsgHtml( 'resetpass_submit' ) ) . + '
    ' . + wfCloseElement( 'form' ) . + '
    '; + $wgOut->addHtml( $form ); + } + + function pretty( $fields ) { + $out = ''; + foreach( $fields as $list ) { + list( $name, $label, $type, $value ) = $list; + if( $type == 'text' ) { + $field = '' . htmlspecialchars( $value ) . ''; + } else { + $field = Xml::input( $name, 20, $value, + array( 'id' => $name, 'type' => $type ) ); + } + $out .= ''; + $out .= ''; + $out .= Xml::label( wfMsg( $label ), $name ); + $out .= ''; + $out .= ''; + $out .= $field; + $out .= ''; + $out .= ''; + } + return $out; + } + + /** + * @throws PasswordError when cannot set the new password because requirements not met. + */ + function attemptReset( $newpass, $retype ) { + $user = User::newFromName( $this->mName ); + if( $user->isAnon() ) { + throw new PasswordError( 'no such user' ); + } + + if( !$user->checkTemporaryPassword( $this->mTemporaryPassword ) ) { + throw new PasswordError( wfMsg( 'resetpass_bad_temporary' ) ); + } + + if( $newpass !== $retype ) { + throw new PasswordError( wfMsg( 'badretype' ) ); + } + + $user->setPassword( $newpass ); + $user->saveSettings(); + } +} diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php new file mode 100644 index 0000000000..a3d4b7ee82 --- /dev/null +++ b/includes/specials/SpecialRevisiondelete.php @@ -0,0 +1,1470 @@ +getText( 'target' ); + $oldid = $wgRequest->getArray( 'oldid' ); + $artimestamp = $wgRequest->getArray( 'artimestamp' ); + $logid = $wgRequest->getArray( 'logid' ); + $img = $wgRequest->getArray( 'oldimage' ); + $fileid = $wgRequest->getArray( 'fileid' ); + # For reviewing deleted files... + $file = $wgRequest->getVal( 'file' ); + # If this is a revision, then we need a target page + $page = Title::newFromUrl( $target ); + if( is_null($page) ) { + $wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) ); + return; + } + # Only one target set at a time please! + $i = (bool)$file + (bool)$oldid + (bool)$logid + (bool)$artimestamp + (bool)$fileid + (bool)$img; + if( $i !== 1 ) { + $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + return; + } + # Logs must have a type given + if( $logid && !strpos($page->getDBKey(),'/') ) { + $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + return; + } + # Either submit or create our form + $form = new RevisionDeleteForm( $page, $oldid, $logid, $artimestamp, $fileid, $img, $file ); + if( $wgRequest->wasPosted() ) { + $form->submit( $wgRequest ); + } else if( $oldid || $artimestamp ) { + $form->showRevs(); + } else if( $fileid || $img ) { + $form->showImages(); + } else if( $logid ) { + $form->showLogItems(); + } + # Show relevant lines from the deletion log. This will show even if said ID + # does not exist...might be helpful + $wgOut->addHTML( "

    " . htmlspecialchars( LogPage::logName( 'delete' ) ) . "

    \n" ); + LogEventsList::showLogExtract( $wgOut, 'delete', $page->getPrefixedText() ); + if( $wgUser->isAllowed( 'suppressionlog' ) ){ + $wgOut->addHTML( "

    " . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "

    \n" ); + LogEventsList::showLogExtract( $wgOut, 'suppress', $page->getPrefixedText() ); + } +} + +/** + * Implements the GUI for Revision Deletion. + * @ingroup SpecialPage + */ +class RevisionDeleteForm { + /** + * @param Title $page + * @param array $oldids + * @param array $logids + * @param array $artimestamps + * @param array $fileids + * @param array $img + * @param string $file + */ + function __construct( $page, $oldids, $logids, $artimestamps, $fileids, $img, $file ) { + global $wgUser, $wgOut; + + $this->page = $page; + # For reviewing deleted files... + if( $file ) { + $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $page, $file ); + $oimage->load(); + // Check if user is allowed to see this file + if( !$oimage->userCan(File::DELETED_FILE) ) { + $wgOut->permissionRequired( 'suppressrevision' ); + } else { + $this->showFile( $file ); + } + return; + } + $this->skin = $wgUser->getSkin(); + # Give a link to the log for this page + if( !is_null($this->page) && $this->page->getNamespace() > -1 ) { + $links = array(); + + $logtitle = SpecialPage::getTitleFor( 'Log' ); + $links[] = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'viewpagelogs' ), + wfArrayToCGI( array( 'page' => $this->page->getPrefixedUrl() ) ) ); + # Give a link to the page history + $links[] = $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml( 'pagehist' ), + wfArrayToCGI( array( 'action' => 'history' ) ) ); + # Link to deleted edits + if( $wgUser->isAllowed('undelete') ) { + $undelete = SpecialPage::getTitleFor( 'Undelete' ); + $links[] = $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml( 'deletedhist' ), + wfArrayToCGI( array( 'target' => $this->page->getPrefixedUrl() ) ) ); + } + # Logs themselves don't have histories or archived revisions + $wgOut->setSubtitle( '

    '.implode($links,' / ').'

    ' ); + } + // At this point, we should only have one of these + if( $oldids ) { + $this->revisions = $oldids; + $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ); + $this->deleteKey='oldid'; + } else if( $artimestamps ) { + $this->archrevs = $artimestamps; + $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ); + $this->deleteKey='artimestamp'; + } else if( $img ) { + $this->ofiles = $img; + $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE ); + $this->deleteKey='oldimage'; + } else if( $fileids ) { + $this->afiles = $fileids; + $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE ); + $this->deleteKey='fileid'; + } else if( $logids ) { + $this->events = $logids; + $hide_content_name = array( 'revdelete-hide-name', 'wpHideName', LogPage::DELETED_ACTION ); + $this->deleteKey='logid'; + } + // Our checkbox messages depends one what we are doing, + // e.g. we don't hide "text" for logs or images + $this->checks = array( + $hide_content_name, + array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ), + array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ) ); + if( $wgUser->isAllowed('suppressrevision') ) { + $this->checks[] = array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED ); + } + } + + /** + * Show a deleted file version requested by the visitor. + */ + private function showFile( $key ) { + global $wgOut, $wgRequest; + $wgOut->disable(); + + # We mustn't allow the output to be Squid cached, otherwise + # if an admin previews a deleted image, and it's cached, then + # a user without appropriate permissions can toddle off and + # nab the image, and Squid will serve it + $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); + $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); + $wgRequest->response()->header( 'Pragma: no-cache' ); + + $store = FileStore::get( 'deleted' ); + $store->stream( $key ); + } + + /** + * This lets a user set restrictions for live and archived revisions + */ + function showRevs() { + global $wgOut, $wgUser, $action; + + $UserAllowed = true; + + $count = ($this->deleteKey=='oldid') ? + count($this->revisions) : count($this->archrevs); + $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(), $count ); + + $bitfields = 0; + $wgOut->addHtml( "
      " ); + + $where = $revObjs = array(); + $dbr = wfGetDB( DB_SLAVE ); + + $revisions = 0; + // Live revisions... + if( $this->deleteKey=='oldid' ) { + // Run through and pull all our data in one query + foreach( $this->revisions as $revid ) { + $where[] = intval($revid); + } + $whereClause = 'rev_id IN(' . implode(',',$where) . ')'; + $result = $dbr->select( array('revision','page'), '*', + array( 'rev_page' => $this->page->getArticleID(), + $whereClause, 'rev_page = page_id' ), + __METHOD__ ); + while( $row = $dbr->fetchObject( $result ) ) { + $revObjs[$row->rev_id] = new Revision( $row ); + } + foreach( $this->revisions as $revid ) { + // Hiding top revisison is bad + if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) { + continue; + } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) { + // If a rev is hidden from sysops + if( $action != 'submit') { + $wgOut->permissionRequired( 'suppressrevision' ); + return; + } + $UserAllowed = false; + } + $revisions++; + $wgOut->addHtml( $this->historyLine( $revObjs[$revid] ) ); + $bitfields |= $revObjs[$revid]->mDeleted; + } + // The archives... + } else { + // Run through and pull all our data in one query + foreach( $this->archrevs as $timestamp ) { + $where[] = $dbr->addQuotes( $timestamp ); + } + $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')'; + $result = $dbr->select( 'archive', '*', + array( 'ar_namespace' => $this->page->getNamespace(), + 'ar_title' => $this->page->getDBKey(), + $whereClause ), + __METHOD__ ); + while( $row = $dbr->fetchObject( $result ) ) { + $revObjs[$row->ar_timestamp] = new Revision( array( + 'page' => $this->page->getArticleId(), + 'id' => $row->ar_rev_id, + 'text' => $row->ar_text_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, + 'text_id' => $row->ar_text_id, + 'deleted' => $row->ar_deleted, + 'len' => $row->ar_len) ); + } + foreach( $this->archrevs as $timestamp ) { + if( !isset($revObjs[$timestamp]) ) { + continue; + } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) { + // If a rev is hidden from sysops + if( $action != 'submit') { + $wgOut->permissionRequired( 'suppressrevision' ); + return; + } + $UserAllowed = false; + } + $revisions++; + $wgOut->addHtml( $this->historyLine( $revObjs[$timestamp] ) ); + $bitfields |= $revObjs[$timestamp]->mDeleted; + } + } + if( !$revisions ) { + $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + return; + } + + $wgOut->addHtml( "
    " ); + + $wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) ); + + // Normal sysops can always see what they did, but can't always change it + if( !$UserAllowed ) return; + + $items = array( + Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), + Xml::submitButton( wfMsg( 'revdelete-submit' ) ) + ); + $hidden = array( + Xml::hidden( 'wpEditToken', $wgUser->editToken() ), + Xml::hidden( 'target', $this->page->getPrefixedText() ), + Xml::hidden( 'type', $this->deleteKey ) + ); + if( $this->deleteKey=='oldid' ) { + foreach( $revObjs as $rev ) + $hidden[] = wfHidden( 'oldid[]', $rev->getId() ); + } else { + foreach( $revObjs as $rev ) + $hidden[] = wfHidden( 'artimestamp[]', $rev->getTimestamp() ); + } + $special = SpecialPage::getTitleFor( 'Revisiondelete' ); + $wgOut->addHtml( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), + 'id' => 'mw-revdel-form-revisions' ) ) . + Xml::openElement( 'fieldset' ) . + xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) ) + ); + // 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 ) ) ); + } + foreach( $items as $item ) { + $wgOut->addHtml( Xml::tags( 'p', null, $item ) ); + } + foreach( $hidden as $item ) { + $wgOut->addHtml( $item ); + } + $wgOut->addHtml( + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n" + ); + + } + + /** + * This lets a user set restrictions for archived images + */ + function showImages() { + global $wgOut, $wgUser, $action; + + $UserAllowed = true; + + $count = ($this->deleteKey=='oldimage') ? count($this->ofiles) : count($this->afiles); + $wgOut->addWikiText( wfMsgExt( 'revdelete-selected', array('parsemag'), + $this->page->getPrefixedText(), $count ) ); + + $bitfields = 0; + $wgOut->addHtml( "
      " ); + + $where = $filesObjs = array(); + $dbr = wfGetDB( DB_SLAVE ); + // Live old revisions... + $revisions = 0; + 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() ); + } + $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; + $result = $dbr->select( 'oldimage', '*', + array( 'oi_name' => $this->page->getDbKey(), + $whereClause ), + __METHOD__ ); + while( $row = $dbr->fetchObject( $result ) ) { + $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row ); + $filesObjs[$row->oi_archive_name]->user = $row->oi_user; + $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text; + } + // Check through our images + foreach( $this->ofiles as $timestamp ) { + $archivename = $timestamp.'!'.$this->page->getDbKey(); + if( !isset($filesObjs[$archivename]) ) { + continue; + } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) { + // If a rev is hidden from sysops + if( $action != 'submit' ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return; + } + $UserAllowed = false; + } + $revisions++; + // Inject history info + $wgOut->addHtml( $this->fileLine( $filesObjs[$archivename] ) ); + $bitfields |= $filesObjs[$archivename]->deleted; + } + // Archived files... + } else { + // Run through and pull all our data in one query + foreach( $this->afiles as $id ) { + $where[] = intval($id); + } + $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; + $result = $dbr->select( 'filearchive', '*', + array( 'fa_name' => $this->page->getDbKey(), + $whereClause ), + __METHOD__ ); + while( $row = $dbr->fetchObject( $result ) ) { + $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row ); + } + + foreach( $this->afiles as $fileid ) { + if( !isset($filesObjs[$fileid]) ) { + continue; + } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) { + // If a rev is hidden from sysops + if( $action != 'submit' ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return; + } + $UserAllowed = false; + } + $revisions++; + // Inject history info + $wgOut->addHtml( $this->archivedfileLine( $filesObjs[$fileid] ) ); + $bitfields |= $filesObjs[$fileid]->deleted; + } + } + if( !$revisions ) { + $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + return; + } + + $wgOut->addHtml( "
    " ); + + $wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) ); + //Normal sysops can always see what they did, but can't always change it + if( !$UserAllowed ) return; + + $items = array( + Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), + Xml::submitButton( wfMsg( 'revdelete-submit' ) ) + ); + $hidden = array( + Xml::hidden( 'wpEditToken', $wgUser->editToken() ), + Xml::hidden( 'target', $this->page->getPrefixedText() ), + Xml::hidden( 'type', $this->deleteKey ) + ); + if( $this->deleteKey=='oldimage' ) { + foreach( $this->ofiles as $filename ) + $hidden[] = wfHidden( 'oldimage[]', $filename ); + } else { + foreach( $this->afiles as $fileid ) + $hidden[] = wfHidden( 'fileid[]', $fileid ); + } + $special = SpecialPage::getTitleFor( 'Revisiondelete' ); + $wgOut->addHtml( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), + 'id' => 'mw-revdel-form-filerevisions' ) ) . + Xml::openElement( 'fieldset' ) . + xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) ) + ); + // 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 ) ) ); + } + foreach( $items as $item ) { + $wgOut->addHtml( Xml::tags( 'p', null, $item ) ); + } + foreach( $hidden as $item ) { + $wgOut->addHtml( $item ); + } + + $wgOut->addHtml( + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n" + ); + } + + /** + * This lets a user set restrictions for log items + */ + function showLogItems() { + global $wgOut, $wgUser, $action, $wgMessageCache; + + $UserAllowed = true; + $wgOut->addWikiText( wfMsgExt( 'logdelete-selected', array('parsemag'), count($this->events) ) ); + + $bitfields = 0; + $wgOut->addHtml( "
      " ); + + $where = $logRows = array(); + $dbr = wfGetDB( DB_SLAVE ); + // Run through and pull all our data in one query + $logItems = 0; + foreach( $this->events as $logid ) { + $where[] = intval($logid); + } + list($log,$logtype) = explode( '/',$this->page->getDBKey(), 2 ); + $whereClause = "log_type = '$logtype' AND log_id IN(" . implode(',',$where) . ")"; + $result = $dbr->select( 'logging', '*', + array( $whereClause ), + __METHOD__ ); + while( $row = $dbr->fetchObject( $result ) ) { + $logRows[$row->log_id] = $row; + } + $wgMessageCache->loadAllMessages(); + foreach( $this->events as $logid ) { + // Don't hide from oversight log!!! + if( !isset( $logRows[$logid] ) || $logRows[$logid]->log_type=='suppress' ) { + continue; + } else if( !LogEventsList::userCan( $logRows[$logid],Revision::DELETED_RESTRICTED) ) { + // If an event is hidden from sysops + if( $action != 'submit') { + $wgOut->permissionRequired( 'suppressrevision' ); + return; + } + $UserAllowed = false; + } + $logItems++; + $wgOut->addHtml( $this->logLine( $logRows[$logid] ) ); + $bitfields |= $logRows[$logid]->log_deleted; + } + if( !$logItems ) { + $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + return; + } + + $wgOut->addHtml( "
    " ); + + $wgOut->addWikiMsg( 'revdelete-text' ); + // Normal sysops can always see what they did, but can't always change it + if( !$UserAllowed ) return; + + $items = array( + Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), + Xml::submitButton( wfMsg( 'revdelete-submit' ) ) ); + $hidden = array( + Xml::hidden( 'wpEditToken', $wgUser->editToken() ), + Xml::hidden( 'target', $this->page->getPrefixedText() ), + Xml::hidden( 'type', $this->deleteKey ) ); + foreach( $this->events as $logid ) { + $hidden[] = Xml::hidden( 'logid[]', $logid ); + } + + $special = SpecialPage::getTitleFor( 'Revisiondelete' ); + $wgOut->addHtml( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), + 'id' => 'mw-revdel-form-logs' ) ) . + Xml::openElement( 'fieldset' ) . + xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) ) + ); + // 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 ) ) ); + } + foreach( $items as $item ) { + $wgOut->addHtml( Xml::tags( 'p', null, $item ) ); + } + foreach( $hidden as $item ) { + $wgOut->addHtml( $item ); + } + + $wgOut->addHtml( + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n" + ); + } + + /** + * @param Revision $rev + * @returns string + */ + private function historyLine( $rev ) { + global $wgContLang; + + $date = $wgContLang->timeanddate( $rev->getTimestamp() ); + $difflink = $del = ''; + // Live revisions + if( $this->deleteKey=='oldid' ) { + $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ); + $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'), + 'diff=' . $rev->getId() . '&oldid=prev' ) . ')'; + // Archived revisions + } else { + $undelete = SpecialPage::getTitleFor( 'Undelete' ); + $target = $this->page->getPrefixedText(); + $revlink = $this->skin->makeLinkObj( $undelete, $date, + "target=$target×tamp=" . $rev->getTimestamp() ); + $difflink = '(' . $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml('diff'), + "target=$target&diff=prev×tamp=" . $rev->getTimestamp() ) . ')'; + } + + if( $rev->isDeleted(Revision::DELETED_TEXT) ) { + $revlink = ''.$revlink.''; + $del = ' ' . wfMsgHtml( 'deletedrev' ) . ''; + if( !$rev->userCan(Revision::DELETED_TEXT) ) { + $revlink = ''.$date.''; + $difflink = '(' . wfMsgHtml('diff') . ')'; + } + } + + return "
  • $difflink $revlink ".$this->skin->revUserLink( $rev )." ".$this->skin->revComment( $rev )."$del
  • "; + } + + /** + * @param File $file + * @returns string + */ + private function fileLine( $file ) { + global $wgContLang, $wgTitle; + + $target = $this->page->getPrefixedText(); + $date = $wgContLang->timeanddate( $file->getTimestamp(), true ); + + $del = ''; + # Hidden files... + if( $file->isDeleted(File::DELETED_FILE) ) { + $del = ' ' . wfMsgHtml( 'deletedrev' ) . ''; + if( !$file->userCan(File::DELETED_FILE) ) { + $pageLink = $date; + } else { + $pageLink = $this->skin->makeKnownLinkObj( $wgTitle, $date, + "target=$target&file=$file->sha1.".$file->getExtension() ); + } + $pageLink = '' . $pageLink . ''; + # Regular files... + } else { + $url = $file->getUrlRel(); + $pageLink = "{$date}"; + } + + $data = wfMsgHtml( 'widthheight', + $wgContLang->formatNum( $file->getWidth() ), + $wgContLang->formatNum( $file->getHeight() ) ) . + ' (' . wfMsgHtml( 'nbytes', $wgContLang->formatNum( $file->getSize() ) ) . ')'; + + return "
  • $pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del
  • "; + } + + /** + * @param ArchivedFile $file + * @returns string + */ + private function archivedfileLine( $file ) { + global $wgContLang, $wgTitle; + + $target = $this->page->getPrefixedText(); + $date = $wgContLang->timeanddate( $file->getTimestamp(), true ); + + $undelete = SpecialPage::getTitleFor( 'Undelete' ); + $pageLink = $this->skin->makeKnownLinkObj( $undelete, $date, "target=$target&file={$file->getKey()}" ); + + $del = ''; + if( $file->isDeleted(File::DELETED_FILE) ) { + $del = ' ' . wfMsgHtml( 'deletedrev' ) . ''; + } + + $data = wfMsgHtml( 'widthheight', + $wgContLang->formatNum( $file->getWidth() ), + $wgContLang->formatNum( $file->getHeight() ) ) . + ' (' . wfMsgHtml( 'nbytes', $wgContLang->formatNum( $file->getSize() ) ) . ')'; + + return "
  • $pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del
  • "; + } + + /** + * @param Array $row row + * @returns string + */ + private function logLine( $row ) { + global $wgContLang; + + $date = $wgContLang->timeanddate( $row->log_timestamp ); + $paramArray = LogPage::extractParams( $row->log_params ); + $title = Title::makeTitle( $row->log_namespace, $row->log_title ); + + $logtitle = SpecialPage::getTitleFor( 'Log' ); + $loglink = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'log' ), + wfArrayToCGI( array( 'page' => $title->getPrefixedUrl() ) ) ); + // Action text + if( !LogEventsList::userCan($row,LogPage::DELETED_ACTION) ) { + $action = '' . wfMsgHtml('rev-deleted-event') . ''; + } else { + $action = LogPage::actionText( $row->log_type, $row->log_action, $title, + $this->skin, $paramArray, true, true ); + if( $row->log_deleted & LogPage::DELETED_ACTION ) + $action = '' . $action . ''; + } + // User links + $userLink = $this->skin->userLink( $row->log_user, User::WhoIs($row->log_user) ); + if( LogEventsList::isDeleted($row,LogPage::DELETED_USER) ) { + $userLink = '' . $userLink . ''; + } + // Comment + $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $row->log_comment ); + if( LogEventsList::isDeleted($row,LogPage::DELETED_COMMENT) ) { + $comment = '' . $comment . ''; + } + return "
  • ($loglink) $date $userLink $action $comment
  • "; + } + + /** + * Generate a user tool link cluster if the current user is allowed to view it + * @param ArchivedFile $file + * @return string HTML + */ + private function fileUserTools( $file ) { + if( $file->userCan( Revision::DELETED_USER ) ) { + $link = $this->skin->userLink( $file->user, $file->user_text ) . + $this->skin->userToolLinks( $file->user, $file->user_text ); + } else { + $link = wfMsgHtml( 'rev-deleted-user' ); + } + if( $file->isDeleted( Revision::DELETED_USER ) ) { + return '' . $link . ''; + } + return $link; + } + + /** + * Wrap and format the given file's comment block, if the current + * user is allowed to view it. + * + * @param ArchivedFile $file + * @return string HTML + */ + private function fileComment( $file ) { + if( $file->userCan( File::DELETED_COMMENT ) ) { + $block = $this->skin->commentBlock( $file->description ); + } else { + $block = ' ' . wfMsgHtml( 'rev-deleted-comment' ); + } + if( $file->isDeleted( File::DELETED_COMMENT ) ) { + return "$block"; + } + return $block; + } + + /** + * @param WebRequest $request + */ + function submit( $request ) { + global $wgUser, $wgOut; + + $bitfield = $this->extractBitfield( $request ); + $comment = $request->getText( 'wpReason' ); + # Can the user set this field? + if( $bitfield & Revision::DELETED_RESTRICTED && !$wgUser->isAllowed('suppressrevision') ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return false; + } + # If the save went through, go to success message. Otherwise + # bounce back to form... + if( $this->save( $bitfield, $comment, $this->page ) ) { + $this->success(); + } else if( $request->getCheck( 'oldid' ) || $request->getCheck( 'artimestamp' ) ) { + return $this->showRevs(); + } else if( $request->getCheck( 'logid' ) ) { + return $this->showLogs(); + } else if( $request->getCheck( 'oldimage' ) || $request->getCheck( 'fileid' ) ) { + return $this->showImages(); + } + } + + private function success() { + global $wgOut; + + $wgOut->setPagetitle( wfMsgHtml( 'actioncomplete' ) ); + + if( $this->deleteKey=='logid' ) { + $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'logdelete-success' ) ), false ); + $this->showLogItems(); + } else if( $this->deleteKey=='oldid' || $this->deleteKey=='artimestamp' ) { + $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'revdelete-success' ) ), false ); + $this->showRevs(); + } else if( $this->deleteKey=='fileid' ) { + $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'revdelete-success' ) ), false ); + $this->showImages(); + } else if( $this->deleteKey=='oldimage' ) { + $wgOut->addWikiText( Xml::element( 'span', array( 'class' => 'success' ), wfMsg( 'revdelete-success' ) ), false ); + $this->showImages(); + } + } + + /** + * Put together a rev_deleted bitfield from the submitted checkboxes + * @param WebRequest $request + * @return int + */ + private function extractBitfield( $request ) { + $bitfield = 0; + foreach( $this->checks as $item ) { + list( /* message */ , $name, $field ) = $item; + if( $request->getCheck( $name ) ) { + $bitfield |= $field; + } + } + return $bitfield; + } + + private function save( $bitfield, $reason, $title ) { + $dbw = wfGetDB( DB_MASTER ); + // Don't allow simply locking the interface for no reason + if( $bitfield == Revision::DELETED_RESTRICTED ) { + $bitfield = 0; + } + $deleter = new RevisionDeleter( $dbw ); + // By this point, only one of the below should be set + if( isset($this->revisions) ) { + return $deleter->setRevVisibility( $title, $this->revisions, $bitfield, $reason ); + } else if( isset($this->archrevs) ) { + return $deleter->setArchiveVisibility( $title, $this->archrevs, $bitfield, $reason ); + } else if( isset($this->ofiles) ) { + return $deleter->setOldImgVisibility( $title, $this->ofiles, $bitfield, $reason ); + } else if( isset($this->afiles) ) { + return $deleter->setArchFileVisibility( $title, $this->afiles, $bitfield, $reason ); + } else if( isset($this->events) ) { + return $deleter->setEventVisibility( $title, $this->events, $bitfield, $reason ); + } + } +} + +/** + * Implements the actions for Revision Deletion. + * @ingroup SpecialPage + */ +class RevisionDeleter { + function __construct( $db ) { + $this->dbw = $db; + } + + /** + * @param $title, the page these events apply to + * @param array $items list of revision ID numbers + * @param int $bitfield new rev_deleted value + * @param string $comment Comment for log records + */ + function setRevVisibility( $title, $items, $bitfield, $comment ) { + global $wgOut; + + $userAllowedAll = $success = true; + $revIDs = array(); + $revCount = 0; + // Run through and pull all our data in one query + foreach( $items as $revid ) { + $where[] = intval($revid); + } + $whereClause = 'rev_id IN(' . implode(',',$where) . ')'; + $result = $this->dbw->select( 'revision', '*', + array( 'rev_page' => $title->getArticleID(), + $whereClause ), + __METHOD__ ); + while( $row = $this->dbw->fetchObject( $result ) ) { + $revObjs[$row->rev_id] = new Revision( $row ); + } + // To work! + foreach( $items as $revid ) { + if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) { + $success = false; + continue; // Must exist + } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) { + $userAllowedAll=false; + continue; + } + // For logging, maintain a count of revisions + if( $revObjs[$revid]->mDeleted != $bitfield ) { + $revCount++; + $revIDs[]=$revid; + + $this->updateRevision( $revObjs[$revid], $bitfield ); + $this->updateRecentChangesEdits( $revObjs[$revid], $bitfield, false ); + } + } + // Clear caches... + // Don't log or touch if nothing changed + if( $revCount > 0 ) { + $this->updateLog( $title, $revCount, $bitfield, $revObjs[$revid]->mDeleted, + $comment, $title, 'oldid', $revIDs ); + $this->updatePage( $title ); + } + // Where all revs allowed to be set? + if( !$userAllowedAll ) { + //FIXME: still might be confusing??? + $wgOut->permissionRequired( 'suppressrevision' ); + return false; + } + + return $success; + } + + /** + * @param $title, the page these events apply to + * @param array $items list of revision ID numbers + * @param int $bitfield new rev_deleted value + * @param string $comment Comment for log records + */ + function setArchiveVisibility( $title, $items, $bitfield, $comment ) { + global $wgOut; + + $userAllowedAll = $success = true; + $count = 0; + $Id_set = array(); + // Run through and pull all our data in one query + foreach( $items as $timestamp ) { + $where[] = $this->dbw->addQuotes( $timestamp ); + } + $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')'; + $result = $this->dbw->select( 'archive', '*', + array( 'ar_namespace' => $title->getNamespace(), + 'ar_title' => $title->getDBKey(), + $whereClause ), + __METHOD__ ); + while( $row = $this->dbw->fetchObject( $result ) ) { + $revObjs[$row->ar_timestamp] = new Revision( array( + 'page' => $title->getArticleId(), + 'id' => $row->ar_rev_id, + 'text' => $row->ar_text_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, + 'text_id' => $row->ar_text_id, + 'deleted' => $row->ar_deleted, + 'len' => $row->ar_len) ); + } + // To work! + foreach( $items as $timestamp ) { + // This will only select the first revision with this timestamp. + // Since they are all selected/deleted at once, we can just check the + // permissions of one. UPDATE is done via timestamp, so all revs are set. + if( !is_object($revObjs[$timestamp]) ) { + $success = false; + continue; // Must exist + } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) { + $userAllowedAll=false; + continue; + } + // Which revisions did we change anything about? + if( $revObjs[$timestamp]->mDeleted != $bitfield ) { + $Id_set[]=$timestamp; + $count++; + + $this->updateArchive( $revObjs[$timestamp], $title, $bitfield ); + } + } + // For logging, maintain a count of revisions + if( $count > 0 ) { + $this->updateLog( $title, $count, $bitfield, $revObjs[$timestamp]->mDeleted, + $comment, $title, 'artimestamp', $Id_set ); + } + // Where all revs allowed to be set? + if( !$userAllowedAll ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return false; + } + + return $success; + } + + /** + * @param $title, the page these events apply to + * @param array $items list of revision ID numbers + * @param int $bitfield new rev_deleted value + * @param string $comment Comment for log records + */ + function setOldImgVisibility( $title, $items, $bitfield, $comment ) { + global $wgOut; + + $userAllowedAll = $success = true; + $count = 0; + $set = array(); + // Run through and pull all our data in one query + foreach( $items as $timestamp ) { + $where[] = $this->dbw->addQuotes( $timestamp.'!'.$title->getDbKey() ); + } + $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; + $result = $this->dbw->select( 'oldimage', '*', + array( 'oi_name' => $title->getDbKey(), + $whereClause ), + __METHOD__ ); + while( $row = $this->dbw->fetchObject( $result ) ) { + $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row ); + $filesObjs[$row->oi_archive_name]->user = $row->oi_user; + $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text; + } + // To work! + foreach( $items as $timestamp ) { + $archivename = $timestamp.'!'.$title->getDbKey(); + if( !isset($filesObjs[$archivename]) ) { + $success = false; + continue; // Must exist + } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) { + $userAllowedAll=false; + continue; + } + + $transaction = true; + // Which revisions did we change anything about? + if( $filesObjs[$archivename]->deleted != $bitfield ) { + $count++; + + $this->dbw->begin(); + $this->updateOldFiles( $filesObjs[$archivename], $bitfield ); + // If this image is currently hidden... + if( $filesObjs[$archivename]->deleted & File::DELETED_FILE ) { + if( $bitfield & File::DELETED_FILE ) { + # Leave it alone if we are not changing this... + $set[]=$archivename; + $transaction = true; + } else { + # We are moving this out + $transaction = $this->makeOldImagePublic( $filesObjs[$archivename] ); + $set[]=$transaction; + } + // Is it just now becoming hidden? + } else if( $bitfield & File::DELETED_FILE ) { + $transaction = $this->makeOldImagePrivate( $filesObjs[$archivename] ); + $set[]=$transaction; + } else { + $set[]=$timestamp; + } + // If our file operations fail, then revert back the db + if( $transaction==false ) { + $this->dbw->rollback(); + return false; + } + $this->dbw->commit(); + } + } + + // Log if something was changed + if( $count > 0 ) { + $this->updateLog( $title, $count, $bitfield, $filesObjs[$archivename]->deleted, + $comment, $title, 'oldimage', $set ); + # Purge page/history + $file = wfLocalFile( $title ); + $file->purgeCache(); + $file->purgeHistory(); + # Invalidate cache for all pages using this file + $update = new HTMLCacheUpdate( $title, 'imagelinks' ); + $update->doUpdate(); + } + // Where all revs allowed to be set? + if( !$userAllowedAll ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return false; + } + + return $success; + } + + /** + * @param $title, the page these events apply to + * @param array $items list of revision ID numbers + * @param int $bitfield new rev_deleted value + * @param string $comment Comment for log records + */ + function setArchFileVisibility( $title, $items, $bitfield, $comment ) { + global $wgOut; + + $userAllowedAll = $success = true; + $count = 0; + $Id_set = array(); + + // Run through and pull all our data in one query + foreach( $items as $id ) { + $where[] = intval($id); + } + $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; + $result = $this->dbw->select( 'filearchive', '*', + array( 'fa_name' => $title->getDbKey(), + $whereClause ), + __METHOD__ ); + while( $row = $this->dbw->fetchObject( $result ) ) { + $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row ); + } + // To work! + foreach( $items as $fileid ) { + if( !isset($filesObjs[$fileid]) ) { + $success = false; + continue; // Must exist + } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) { + $userAllowedAll=false; + continue; + } + // Which revisions did we change anything about? + if( $filesObjs[$fileid]->deleted != $bitfield ) { + $Id_set[]=$fileid; + $count++; + + $this->updateArchFiles( $filesObjs[$fileid], $bitfield ); + } + } + // Log if something was changed + if( $count > 0 ) { + $this->updateLog( $title, $count, $bitfield, $comment, + $filesObjs[$fileid]->deleted, $title, 'fileid', $Id_set ); + } + // Where all revs allowed to be set? + if( !$userAllowedAll ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return false; + } + + return $success; + } + + /** + * @param $title, the log page these events apply to + * @param array $items list of log ID numbers + * @param int $bitfield new log_deleted value + * @param string $comment Comment for log records + */ + function setEventVisibility( $title, $items, $bitfield, $comment ) { + global $wgOut; + + $userAllowedAll = $success = true; + $count = 0; + $log_Ids = array(); + + // Run through and pull all our data in one query + foreach( $items as $logid ) { + $where[] = intval($logid); + } + list($log,$logtype) = explode( '/',$title->getDBKey(), 2 ); + $whereClause = "log_type ='$logtype' AND log_id IN(" . implode(',',$where) . ")"; + $result = $this->dbw->select( 'logging', '*', + array( $whereClause ), + __METHOD__ ); + while( $row = $this->dbw->fetchObject( $result ) ) { + $logRows[$row->log_id] = $row; + } + // To work! + foreach( $items as $logid ) { + if( !isset($logRows[$logid]) ) { + $success = false; + continue; // Must exist + } else if( !LogEventsList::userCan($logRows[$logid], LogPage::DELETED_RESTRICTED) + || $logRows[$logid]->log_type == 'suppress' ) { + // Don't hide from oversight log!!! + $userAllowedAll=false; + continue; + } + // Which logs did we change anything about? + if( $logRows[$logid]->log_deleted != $bitfield ) { + $log_Ids[]=$logid; + $count++; + + $this->updateLogs( $logRows[$logid], $bitfield ); + $this->updateRecentChangesLog( $logRows[$logid], $bitfield, true ); + } + } + // Don't log or touch if nothing changed + if( $count > 0 ) { + $this->updateLog( $title, $count, $bitfield, $logRows[$logid]->log_deleted, + $comment, $title, 'logid', $log_Ids ); + } + // Were all revs allowed to be set? + if( !$userAllowedAll ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return false; + } + + return $success; + } + + /** + * Moves an image to a safe private location + * Caller is responsible for clearing caches + * @param File $oimage + * @returns mixed, timestamp string on success, false on failure + */ + function makeOldImagePrivate( $oimage ) { + $transaction = new FSTransaction(); + if( !FileStore::lock() ) { + wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); + return false; + } + $oldpath = $oimage->getArchivePath() . DIRECTORY_SEPARATOR . $oimage->archive_name; + // Dupe the file into the file store + if( file_exists( $oldpath ) ) { + // Is our directory configured? + if( $store = FileStore::get( 'deleted' ) ) { + if( !$oimage->sha1 ) { + $oimage->upgradeRow(); // sha1 may be missing + } + $key = $oimage->sha1 . '.' . $oimage->getExtension(); + $transaction->add( $store->insert( $key, $oldpath, FileStore::DELETE_ORIGINAL ) ); + } else { + $group = null; + $key = null; + $transaction = false; // Return an error and do nothing + } + } else { + wfDebug( __METHOD__." deleting already-missing '$oldpath'; moving on to database\n" ); + $group = null; + $key = ''; + $transaction = new FSTransaction(); // empty + } + + if( $transaction === false ) { + // Fail to restore? + wfDebug( __METHOD__.": import to file store failed, aborting\n" ); + throw new MWException( "Could not archive and delete file $oldpath" ); + return false; + } + + wfDebug( __METHOD__.": set db items, applying file transactions\n" ); + $transaction->commit(); + FileStore::unlock(); + + $m = explode('!',$oimage->archive_name,2); + $timestamp = $m[0]; + + return $timestamp; + } + + /** + * Moves an image from a safe private location + * Caller is responsible for clearing caches + * @param File $oimage + * @returns mixed, string timestamp on success, false on failure + */ + function makeOldImagePublic( $oimage ) { + $transaction = new FSTransaction(); + if( !FileStore::lock() ) { + wfDebug( __METHOD__." could not acquire filestore lock\n" ); + return false; + } + + $store = FileStore::get( 'deleted' ); + if( !$store ) { + wfDebug( __METHOD__.": skipping row with no file.\n" ); + return false; + } + + $key = $oimage->sha1.'.'.$oimage->getExtension(); + $destDir = $oimage->getArchivePath(); + if( !is_dir( $destDir ) ) { + wfMkdirParents( $destDir ); + } + $destPath = $destDir . DIRECTORY_SEPARATOR . $oimage->archive_name; + // Check if any other stored revisions use this file; + // if so, we shouldn't remove the file from the hidden + // archives so they will still work. Check hidden files first. + $useCount = $this->dbw->selectField( 'oldimage', '1', + array( 'oi_sha1' => $oimage->sha1, + 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ), + __METHOD__, array( 'FOR UPDATE' ) ); + // Check the rest of the deleted archives too. + // (these are the ones that don't show in the image history) + if( !$useCount ) { + $useCount = $this->dbw->selectField( 'filearchive', '1', + array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ), + __METHOD__, array( 'FOR UPDATE' ) ); + } + + if( $useCount == 0 ) { + wfDebug( __METHOD__.": nothing else using {$oimage->sha1}, will deleting after\n" ); + $flags = FileStore::DELETE_ORIGINAL; + } else { + $flags = 0; + } + $transaction->add( $store->export( $key, $destPath, $flags ) ); + + wfDebug( __METHOD__.": set db items, applying file transactions\n" ); + $transaction->commit(); + FileStore::unlock(); + + $m = explode('!',$oimage->archive_name,2); + $timestamp = $m[0]; + + return $timestamp; + } + + /** + * Update the revision's rev_deleted field + * @param Revision $rev + * @param int $bitfield new rev_deleted bitfield value + */ + function updateRevision( $rev, $bitfield ) { + $this->dbw->update( 'revision', + array( 'rev_deleted' => $bitfield ), + array( 'rev_id' => $rev->getId(), + 'rev_page' => $rev->getPage() ), + __METHOD__ ); + } + + /** + * Update the revision's rev_deleted field + * @param Revision $rev + * @param Title $title + * @param int $bitfield new rev_deleted bitfield value + */ + function updateArchive( $rev, $title, $bitfield ) { + $this->dbw->update( 'archive', + array( 'ar_deleted' => $bitfield ), + array( 'ar_namespace' => $title->getNamespace(), + 'ar_title' => $title->getDBKey(), + 'ar_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ), + 'ar_rev_id' => $rev->getId() ), + __METHOD__ ); + } + + /** + * Update the images's oi_deleted field + * @param File $file + * @param int $bitfield new rev_deleted bitfield value + */ + function updateOldFiles( $file, $bitfield ) { + $this->dbw->update( 'oldimage', + array( 'oi_deleted' => $bitfield ), + array( 'oi_name' => $file->getName(), + 'oi_timestamp' => $this->dbw->timestamp( $file->getTimestamp() ) ), + __METHOD__ ); + } + + /** + * Update the images's fa_deleted field + * @param ArchivedFile $file + * @param int $bitfield new rev_deleted bitfield value + */ + function updateArchFiles( $file, $bitfield ) { + $this->dbw->update( 'filearchive', + array( 'fa_deleted' => $bitfield ), + array( 'fa_id' => $file->getId() ), + __METHOD__ ); + } + + /** + * Update the logging log_deleted field + * @param Row $row + * @param int $bitfield new rev_deleted bitfield value + */ + function updateLogs( $row, $bitfield ) { + $this->dbw->update( 'logging', + array( 'log_deleted' => $bitfield ), + array( 'log_id' => $row->log_id ), + __METHOD__ ); + } + + /** + * Update the revision's recentchanges record if fields have been hidden + * @param Revision $rev + * @param int $bitfield new rev_deleted bitfield value + */ + function updateRecentChangesEdits( $rev, $bitfield ) { + $this->dbw->update( 'recentchanges', + array( 'rc_deleted' => $bitfield, + 'rc_patrolled' => 1 ), + array( 'rc_this_oldid' => $rev->getId(), + 'rc_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ) ), + __METHOD__ ); + } + + /** + * Update the revision's recentchanges record if fields have been hidden + * @param Row $row + * @param int $bitfield new rev_deleted bitfield value + */ + function updateRecentChangesLog( $row, $bitfield ) { + $this->dbw->update( 'recentchanges', + array( 'rc_deleted' => $bitfield, + 'rc_patrolled' => 1 ), + array( 'rc_logid' => $row->log_id, + 'rc_timestamp' => $row->log_timestamp ), + __METHOD__ ); + } + + /** + * Touch the page's cache invalidation timestamp; this forces cached + * history views to refresh, so any newly hidden or shown fields will + * update properly. + * @param Title $title + */ + function updatePage( $title ) { + $title->invalidateCache(); + $title->purgeSquid(); + + // Extensions that require referencing previous revisions may need this + wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$title ) ); + } + + /** + * Checks for a change in the bitfield for a certain option and updates the + * provided array accordingly. + * + * @param String $desc Description to add to the array if the option was + * enabled / disabled. + * @param int $field The bitmask describing the single option. + * @param int $diff The xor of the old and new bitfields. + * @param array $arr The array to update. + */ + function checkItem ( $desc, $field, $diff, $new, &$arr ) { + if ( $diff & $field ) { + $arr [ ( $new & $field ) ? 0 : 1 ][] = $desc; + } + } + + /** + * Gets an array describing the changes made to the visibilit of the revision. + * If the resulting array is $arr, then $arr[0] will contain an array of strings + * describing the items that were hidden, $arr[2] will contain an array of strings + * describing the items that were unhidden, and $arr[3] will contain an array with + * a single string, which can be one of "applied restrictions to sysops", + * "removed restrictions from sysops", or null. + * + * @param int $n The new bitfield. + * @param int $o The old bitfield. + * @return An array as described above. + */ + function getChanges ( $n, $o ) { + $diff = $n ^ $o; + $ret = array ( 0 => array(), 1 => array(), 2 => array() ); + + $this->checkItem ( wfMsgForContent ( 'revdelete-content' ), + Revision::DELETED_TEXT, $diff, $n, $ret ); + $this->checkItem ( wfMsgForContent ( 'revdelete-summary' ), + Revision::DELETED_COMMENT, $diff, $n, $ret ); + $this->checkItem ( wfMsgForContent ( 'revdelete-uname' ), + Revision::DELETED_USER, $diff, $n, $ret ); + + // Restriction application to sysops + if ( $diff & Revision::DELETED_RESTRICTED ) { + if ( $n & Revision::DELETED_RESTRICTED ) + $ret[2][] = wfMsgForContent ( 'revdelete-restricted' ); + else + $ret[2][] = wfMsgForContent ( 'revdelete-unrestricted' ); + } + + return $ret; + } + + /** + * Gets a log message to describe the given revision visibility change. This + * message will be of the form "[hid {content, edit summary, username}]; + * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment". + * + * @param int $count The number of effected revisions. + * @param int $nbitfield The new bitfield for the revision. + * @param int $obitfield The old bitfield for the revision. + * @param string $comment The comment associated with the change. + * @param bool $isForLog + */ + function getLogMessage ( $count, $nbitfield, $obitfield, $comment, $isForLog = false ) { + global $wgContLang; + + $s = ''; + $changes = $this->getChanges( $nbitfield, $obitfield ); + + if ( count ( $changes[0] ) ) { + $s .= wfMsgForContent ( 'revdelete-hid', implode ( ', ', $changes[0] ) ); + } + + if ( count ( $changes[1] ) ) { + if ($s) $s .= '; '; + + $s .= wfMsgForContent ( 'revdelete-unhid', implode ( ', ', $changes[1] ) ); + } + + if ( count ( $changes[2] )) { + if ($s) + $s .= ' (' . $changes[2][0] . ')'; + else + $s = $changes[2][0]; + } + + $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message'; + $ret = wfMsgExt ( $msg, array( 'parsemag', 'content' ), + $s, $wgContLang->formatNum( $count ) ); + + if ( $comment ) + $ret .= ": $comment"; + + return $ret; + + } + + /** + * Record a log entry on the action + * @param Title $title, page where item was removed from + * @param int $count the number of revisions altered for this page + * @param int $nbitfield the new _deleted value + * @param int $obitfield the old _deleted value + * @param string $comment + * @param Title $target, the relevant page + * @param string $param, URL param + * @param Array $items + */ + function updateLog( $title, $count, $nbitfield, $obitfield, $comment, $target, $param, $items = array() ) { + // Put things hidden from sysops in the oversight log + $logtype = ( ($nbitfield | $obitfield) & Revision::DELETED_RESTRICTED ) ? 'suppress' : 'delete'; + $log = new LogPage( $logtype ); + + $reason = $this->getLogMessage ( $count, $nbitfield, $obitfield, $comment, $param == 'logid' ); + + if( $param == 'logid' ) { + $params = array( implode( ',', $items) ); + $log->addEntry( 'event', $title, $reason, $params ); + } else { + // Add params for effected page and ids + $params = array( $param, implode( ',', $items) ); + $log->addEntry( 'revision', $title, $reason, $params ); + } + } +} diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php new file mode 100644 index 0000000000..0a483af52f --- /dev/null +++ b/includes/specials/SpecialSearch.php @@ -0,0 +1,651 @@ + +# http://www.mediawiki.org/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# http://www.gnu.org/copyleft/gpl.html + +/** + * Run text & title search and display the output + * @file + * @ingroup SpecialPage + */ + +/** + * Entry point + * + * @param $par String: (default '') + */ +function wfSpecialSearch( $par = '' ) { + global $wgRequest, $wgUser; + + $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $par ) ); + $searchPage = new SpecialSearch( $wgRequest, $wgUser ); + if( $wgRequest->getVal( 'fulltext' ) + || !is_null( $wgRequest->getVal( 'offset' )) + || !is_null( $wgRequest->getVal( 'searchx' ))) { + $searchPage->showResults( $search, 'search' ); + } else { + $searchPage->goResult( $search ); + } +} + +/** + * implements Special:Search - Run text & title search and display the output + * @ingroup SpecialPage + */ +class SpecialSearch { + + /** + * 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 SpecialSearch( &$request, &$user ) { + list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); + + $this->namespaces = $this->powerSearch( $request ); + if( empty( $this->namespaces ) ) { + $this->namespaces = SearchEngine::userNamespaces( $user ); + } + + $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; + } + + /** + * If an exact title match can be found, jump straight ahead to it. + * @param string $term + * @public + */ + function goResult( $term ) { + global $wgOut; + global $wgGoToEdit; + + $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 ) ) { + wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) ); + # If the feature is enabled, go straight to the edit page + if ( $wgGoToEdit ) { + $wgOut->redirect( $t->getFullURL( 'action=edit' ) ); + return; + } + } + + $wgOut->wrapWikiMsg( "==$1==\n", 'notitlematches' ); + if( $t->quickUserCan( 'create' ) && $t->quickUserCan( 'edit' ) ) { + $wgOut->addWikiMsg( 'noexactmatch', wfEscapeWikiText( $term ) ); + } else { + $wgOut->addWikiMsg( 'noexactmatch-nocreate', wfEscapeWikiText( $term ) ); + } + + return $this->showResults( $term ); + } + + /** + * @param string $term + * @public + */ + function showResults( $term ) { + $fname = 'SpecialSearch::showResults'; + wfProfileIn( $fname ); + global $wgOut, $wgUser; + $sk = $wgUser->getSkin(); + + $this->setupPage( $term ); + + $wgOut->addWikiMsg( 'searchresulttext' ); + + if( '' === trim( $term ) ) { + // Empty query -- straight view of search form + $wgOut->setSubtitle( '' ); + $wgOut->addHTML( $this->powerSearchBox( $term ) ); + $wgOut->addHTML( $this->powerSearchFocus() ); + wfProfileOut( $fname ); + return; + } + + global $wgDisableTextSearch; + if ( $wgDisableTextSearch ) { + global $wgForwardSearchUrl; + if( $wgForwardSearchUrl ) { + $url = str_replace( '$1', urlencode( $term ), $wgForwardSearchUrl ); + $wgOut->redirect( $url ); + 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( $fname ); + 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 ); + + // 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 ); + 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 = ''. + $textMatches->getSuggestionSnippet().''; + + $wgOut->addHTML('
    '.wfMsg('search-suggest',$suggestLink).'
    '); + } + + // show number of results + $num = ( $titleMatches ? $titleMatches->numRows() : 0 ) + + ( $textMatches ? $textMatches->numRows() : 0); + $totalNum = 0; + if($titleMatches && !is_null($titleMatches->getTotalHits())) + $totalNum += $titleMatches->getTotalHits(); + if($textMatches && !is_null($textMatches->getTotalHits())) + $totalNum += $textMatches->getTotalHits(); + if ( $num > 0 ) { + if ( $totalNum > 0 ){ + $top = wfMsgExt('showingresultstotal', array( 'parseinline' ), + $this->offset+1, $this->offset+$num, $totalNum ); + } elseif ( $num >= $this->limit ) { + $top = wfShowingResults( $this->offset, $this->limit ); + } else { + $top = wfShowingResultsNum( $this->offset, $this->limit, $num ); + } + $wgOut->addHTML( "

    {$top}

    \n" ); + } + + // prev/next links + if( $num || $this->offset ) { + $prevnext = wfViewPrevNext( $this->offset, $this->limit, + SpecialPage::getTitleFor( 'Search' ), + wfArrayToCGI( + $this->powerSearchOptions(), + array( 'search' => $term ) ), + ($num < $this->limit) ); + $wgOut->addHTML( "

    {$prevnext}

    \n" ); + wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) ); + } else { + wfRunHooks( 'SpecialSearchNoResults', array( $term ) ); + } + + if( $titleMatches ) { + if( $titleMatches->numRows() ) { + $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' ); + $wgOut->addHTML( $this->showMatches( $titleMatches ) ); + } + $titleMatches->free(); + } + + if( $textMatches ) { + // output appropriate heading + if( $textMatches->numRows() ) { + if($titleMatches) + $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' ); + else // if no title matches the heading is redundant + $wgOut->addHTML("
    "); + } elseif( $num == 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( $textMatches->numRows() ) + $wgOut->addHTML( $this->showMatches( $textMatches ) ); + + $textMatches->free(); + } + + if ( $num == 0 ) { + $wgOut->addWikiMsg( 'nonefound' ); + } + if( $num || $this->offset ) { + $wgOut->addHTML( "

    {$prevnext}

    \n" ); + } + $wgOut->addHTML( $this->powerSearchBox( $term ) ); + wfProfileOut( $fname ); + } + + #------------------------------------------------------------------ + # Private methods below this line + + /** + * + */ + function setupPage( $term ) { + global $wgOut; + if( !empty( $term ) ) + $wgOut->setPageTitle( wfMsg( 'searchresults' ) ); + $subtitlemsg = ( Title::newFromText( $term ) ? 'searchsubtitle' : 'searchsubtitleinvalid' ); + $wgOut->setSubtitle( $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($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 + * @private + */ + 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 + * @private + */ + function powerSearchOptions() { + $opt = array(); + foreach( $this->namespaces as $n ) { + $opt['ns' . $n] = 1; + } + $opt['redirs'] = $this->searchRedirects ? 1 : 0; + return $opt; + } + + /** + * Show whole set of results + * + * @param SearchResultSet $matches + */ + function showMatches( &$matches ) { + $fname = 'SpecialSearch::showMatches'; + wfProfileIn( $fname ); + + global $wgContLang; + $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); + + $out = ""; + + $infoLine = $matches->getInfo(); + if( !is_null($infoLine) ) + $out .= "\n\n"; + + + $off = $this->offset + 1; + $out .= "
      \n"; + + while( $result = $matches->next() ) { + $out .= $this->showHit( $result, $terms ); + } + $out .= "
    \n"; + + // convert the whole thing to desired language variant + global $wgContLang; + $out = $wgContLang->convert( $out ); + wfProfileOut( $fname ); + return $out; + } + + /** + * Format a single hit result + * @param SearchResult $result + * @param array $terms terms to highlight + */ + function showHit( $result, $terms ) { + $fname = 'SpecialSearch::showHit'; + wfProfileIn( $fname ); + global $wgUser, $wgContLang, $wgLang; + + if( $result->isBrokenTitle() ) { + wfProfileOut( $fname ); + return "\n"; + } + + $t = $result->getTitle(); + $sk = $wgUser->getSkin(); + + $link = $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( $fname ); + return "
  • {$link}
  • \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( $fname ); + return "\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 = "" + .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText)) + .""; + $section = ''; + if( !is_null($sectionTitle) ) + $section = "" + .wfMsg('search-section', $sk->makeKnownLinkObj( $sectionTitle, $sectionText)) + .""; + + // format text extract + $extract = "
    ".$result->getTextSnippet($terms)."
    "; + + // 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' ), + $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 = ' -- '. + wfMsg('search-relatedarticle').''; + } + + // Include a thumbnail for media files... + if( $t->getNamespace() == NS_IMAGE ) { + $img = wfFindFile( $t ); + if( $img ) { + $thumb = $img->getThumbnail( 120, 120 ); + if( $thumb ) { + $desc = $img->getShortDesc(); + wfProfileOut( $fname ); + // Ugly table. :D + // Float doesn't seem to interact well with the bullets. + // Table messes up vertical alignment of the bullet, but I'm + // not sure what more I can do about that. :( + return "
  • " . + '' . + '' . + '' . + '' . + '' . + '
    ' . + $thumb->toHtml( array( 'desc-link' => true ) ) . + '' . + $link . + $extract . + "
    {$score}{$desc} - {$date}{$related}
    " . + '
    ' . + "
  • \n"; + } + } + } + + wfProfileOut( $fname ); + return "
  • {$link} {$redirect} {$section} {$extract}\n" . + "
    {$score}{$size} - {$date}{$related}
    " . + "
  • \n"; + + } + + /** + * Show results from other wikis + * + * @param SearchResultSet $matches + */ + function showInterwiki( &$matches, $query ) { + $fname = 'SpecialSearch::showInterwiki'; + wfProfileIn( $fname ); + + global $wgContLang; + $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); + + $out = "
    ".wfMsg('search-interwiki-caption')."
    \n"; + $off = $this->offset + 1; + $out .= "
      \n"; + + // work out custom project captions + $customCaptions = array(); + $customLines = explode("\n",wfMsg('search-interwiki-custom')); // format per line : + 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 .= "
    \n"; + + // convert the whole thing to desired language variant + global $wgContLang; + $out = $wgContLang->convert( $out ); + wfProfileOut( $fname ); + return $out; + } + + /** + * Show single interwiki link + * + * @param SearchResult $result + * @param string $lastInterwiki + * @param array $terms + * @param string $query + * @param array $customCaptions iw prefix -> caption + */ + function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions){ + $fname = 'SpecialSearch::showInterwikiHit'; + wfProfileIn( $fname ); + global $wgUser, $wgContLang, $wgLang; + + if( $result->isBrokenTitle() ) { + wfProfileOut( $fname ); + return "\n"; + } + + $t = $result->getTitle(); + $sk = $wgUser->getSkin(); + + $link = $sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms)); + + // format redirect if any + $redirectTitle = $result->getRedirectTitle(); + $redirectText = $result->getRedirectSnippet($terms); + $redirect = ''; + if( !is_null($redirectTitle) ) + $redirect = "" + .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText)) + .""; + + $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 = $sk->makeKnownLinkObj( $searchTitle, wfMsg('search-interwiki-more'), + wfArrayToCGI(array('search' => $query, 'fulltext' => 'Search'))); + $out .= "
    {$searchLink}{$caption}
    \n
      "; + } + + $out .= "
    • {$link} {$redirect}
    • \n"; + wfProfileOut( $fname ); + return $out; + } + + + /** + * Generates the power search box at bottom of [[Special:Search]] + * @param $term string: search term + * @return $out string: HTML form + */ + function powerSearchBox( $term ) { + global $wgScript; + + $namespaces = ''; + foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { + $name = str_replace( '_', ' ', $name ); + if( '' == $name ) { + $name = wfMsg( 'blanknamespace' ); + } + $namespaces .= Xml::openElement( 'span', array( 'style' => 'white-space: nowrap' ) ) . + Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $this->namespaces ) ) . + Xml::closeElement( 'span' ) . "\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"; + + $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) . + Xml::fieldset( wfMsg( 'powersearch-legend' ), + Xml::hidden( 'title', 'Special:Search' ) . + "

      " . + wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) . + "
      " . + $namespaces . + "

      " . + "

      " . + $redirect . " " . $redirectLabel . + "

      " . + wfMsgExt( 'powersearch-field', array( 'parseinline' ) ) . + " " . + $searchField . + " " . + $searchButton ) . + ""; + + return $out; + } + + function powerSearchFocus() { + global $wgJsMimeType; + return ""; + } + + function shortDialog($term) { + global $wgScript; + + $out = Xml::openElement( 'form', array( + 'id' => 'search', + 'method' => 'get', + 'action' => $wgScript + )); + $out .= Xml::hidden( 'title', 'Special:Search' ); + $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . ' '; + foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { + if( in_array( $ns, $this->namespaces ) ) { + $out .= Xml::hidden( "ns{$ns}", '1' ); + } + } + $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) ); + $out .= Xml::closeElement( 'form' ); + + return $out; + } +} diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php new file mode 100644 index 0000000000..2e7d24a5a0 --- /dev/null +++ b/includes/specials/SpecialShortpages.php @@ -0,0 +1,98 @@ +tableName( 'page' ); + $name = $dbr->addQuotes( $this->getName() ); + + $forceindex = $dbr->useIndexClause("page_len"); + + if ($wgContentNamespaces) + $nsclause = "page_namespace IN (" . $dbr->makeList($wgContentNamespaces) . ")"; + else + $nsclause = "page_namespace = " . NS_MAIN; + + return + "SELECT $name as type, + page_namespace as namespace, + page_title as title, + page_len AS value + FROM $page $forceindex + WHERE $nsclause AND page_is_redirect=0"; + } + + function preprocessResults( $db, $res ) { + # There's no point doing a batch check if we aren't caching results; + # the page must exist for it to have been pulled out of the table + if( $this->isCached() ) { + $batch = new LinkBatch(); + while( $row = $db->fetchObject( $res ) ) + $batch->add( $row->namespace, $row->title ); + $batch->execute(); + if( $db->numRows( $res ) > 0 ) + $db->dataSeek( $res, 0 ); + } + } + + function sortDescending() { + return false; + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + $dm = $wgContLang->getDirMark(); + + $title = Title::makeTitleSafe( $result->namespace, $result->title ); + if ( !$title ) { + return ''; + } + $hlink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' ); + $plink = $this->isCached() + ? $skin->makeLinkObj( $title ) + : $skin->makeKnownLinkObj( $title ); + $size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->value ) ) ); + + return $title->exists() + ? "({$hlink}) {$dm}{$plink} {$dm}[{$size}]" + : "({$hlink}) {$dm}{$plink} {$dm}[{$size}]"; + } +} + +/** + * constructor + */ +function wfSpecialShortpages() { + list( $limit, $offset ) = wfCheckLimits(); + + $spp = new ShortPagesPage(); + + return $spp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php new file mode 100644 index 0000000000..ca91ad514b --- /dev/null +++ b/includes/specials/SpecialSpecialpages.php @@ -0,0 +1,82 @@ +loadAllMessages(); + + $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Is this really needed? + $sk = $wgUser->getSkin(); + + $pages = SpecialPage::getUsablePages(); + + if( count( $pages ) == 0 ) { + # Yeah, that was pointless. Thanks for coming. + return; + } + + /** Put them into a sortable array */ + $groups = array(); + foreach ( $pages as $page ) { + if ( $page->isListed() ) { + $group = SpecialPage::getGroup( $page ); + if( !isset($groups[$group]) ) { + $groups[$group] = array(); + } + $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted() ); + } + } + + /** Sort */ + if ( $wgSortSpecialPages ) { + foreach( $groups as $group => $sortedPages ) { + ksort( $groups[$group] ); + } + } + + /** Always move "other" to end */ + if( array_key_exists('other',$groups) ) { + $other = $groups['other']; + unset( $groups['other'] ); + $groups['other'] = $other; + } + + /** Now output the HTML */ + foreach ( $groups as $group => $sortedPages ) { + $middle = ceil( count($sortedPages)/2 ); + $total = count($sortedPages); + $count = 0; + + $wgOut->addHTML( "

      ".wfMsgHtml("specialpages-group-$group")."

      \n" ); + $wgOut->addHTML( "" ); + $wgOut->addHTML( "
        \n" ); + foreach( $sortedPages as $desc => $specialpage ) { + list( $title, $restricted ) = $specialpage; + $link = $sk->makeKnownLinkObj( $title , htmlspecialchars( $desc ) ); + if( $restricted ) { + $wgOut->addHTML( "
      • {$link}
      • \n" ); + } else { + $wgOut->addHTML( "
      • {$link}
      • \n" ); + } + + # Split up the larger groups + $count++; + if( $total > 3 && $count == $middle ) { + $wgOut->addHTML( "
        " ); + } + } + $wgOut->addHTML( "
      \n" ); + } + $wgOut->addHTML( + Xml::openElement('div', array( 'class' => 'mw-specialpages-notes' )). + wfMsgWikiHtml('specialpages-note'). + Xml::closeElement('div') + ); +} diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php new file mode 100644 index 0000000000..570a21c602 --- /dev/null +++ b/includes/specials/SpecialStatistics.php @@ -0,0 +1,93 @@ +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"; + + $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"; + + global $wgDisableCounters, $wgMiserMode, $wgUser, $wgLang, $wgContLang; + if( !$wgDisableCounters && !$wgMiserMode ) { + $res = $dbr->select( + 'page', + array( + 'page_namespace', + 'page_title', + 'page_counter', + ), + array( + 'page_is_redirect' => 0, + 'page_counter > 0', + ), + __METHOD__, + array( + 'ORDER BY' => 'page_counter DESC', + 'LIMIT' => 10, + ) + ); + if( $res->numRows() > 0 ) { + $text .= "==" . wfMsgNoTrans( 'statistics-mostpopular' ) . "==\n"; + 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"; + } + $res->free(); + } + } + + $footer = wfMsgNoTrans( 'statistics-footer' ); + if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) + $text .= "\n" . $footer; + + $wgOut->addWikiText( $text ); + } +} diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php new file mode 100644 index 0000000000..f23e89ce8b --- /dev/null +++ b/includes/specials/SpecialUncategorizedcategories.php @@ -0,0 +1,30 @@ +requestedNamespace = NS_CATEGORY; + } + + function getName() { + return "Uncategorizedcategories"; + } +} + +/** + * constructor + */ +function wfSpecialUncategorizedcategories() { + list( $limit, $offset ) = wfCheckLimits(); + + $lpp = new UncategorizedCategoriesPage(); + + return $lpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php new file mode 100644 index 0000000000..986ec967e6 --- /dev/null +++ b/includes/specials/SpecialUncategorizedimages.php @@ -0,0 +1,48 @@ + + */ + +/** + * @ingroup SpecialPage + */ +class UncategorizedImagesPage extends ImageQueryPage { + + function getName() { + return 'Uncategorizedimages'; + } + + function sortDescending() { + return false; + } + + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); + $ns = NS_IMAGE; + + return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace, + page_title AS title, page_title AS value + FROM {$page} LEFT JOIN {$categorylinks} ON page_id = cl_from + WHERE cl_from IS NULL AND page_namespace = {$ns} AND page_is_redirect = 0"; + } + +} + +function wfSpecialUncategorizedimages() { + $uip = new UncategorizedImagesPage(); + list( $limit, $offset ) = wfCheckLimits(); + return $uip->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php new file mode 100644 index 0000000000..e7f0aacacf --- /dev/null +++ b/includes/specials/SpecialUncategorizedpages.php @@ -0,0 +1,55 @@ +tableNamesN( 'page', 'categorylinks' ); + $name = $dbr->addQuotes( $this->getName() ); + + return + " + SELECT + $name as type, + page_namespace AS namespace, + page_title AS title, + page_title AS value + FROM $page + LEFT JOIN $categorylinks ON page_id=cl_from + WHERE cl_from IS NULL AND page_namespace={$this->requestedNamespace} AND page_is_redirect=0 + "; + } +} + +/** + * constructor + */ +function wfSpecialUncategorizedpages() { + list( $limit, $offset ) = wfCheckLimits(); + + $lpp = new UncategorizedPagesPage(); + + return $lpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUncategorizedtemplates.php b/includes/specials/SpecialUncategorizedtemplates.php new file mode 100644 index 0000000000..cb2a6d40f3 --- /dev/null +++ b/includes/specials/SpecialUncategorizedtemplates.php @@ -0,0 +1,33 @@ + + */ +class UncategorizedTemplatesPage extends UncategorizedPagesPage { + + var $requestedNamespace = NS_TEMPLATE; + + public function getName() { + return 'Uncategorizedtemplates'; + } + +} + +/** + * Main execution point + * + * @param mixed $par Parameter passed to the page + */ +function wfSpecialUncategorizedtemplates() { + list( $limit, $offset ) = wfCheckLimits(); + $utp = new UncategorizedTemplatesPage(); + $utp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php new file mode 100644 index 0000000000..33d9476013 --- /dev/null +++ b/includes/specials/SpecialUndelete.php @@ -0,0 +1,1278 @@ +execute(); +} + +/** + * Used to show archived pages and eventually restore them. + * @ingroup SpecialPage + */ +class PageArchive { + protected $title; + var $fileStatus; + + function __construct( $title ) { + if( is_null( $title ) ) { + throw new MWException( 'Archiver() given a null title.'); + } + $this->title = $title; + } + + /** + * List all deleted pages recorded in the archive table. Returns result + * wrapper with (ar_namespace, ar_title, count) fields, ordered by page + * namespace/title. + * + * @return ResultWrapper + */ + public static function listAllPages() { + $dbr = wfGetDB( DB_SLAVE ); + return self::listPages( $dbr, '' ); + } + + /** + * List deleted pages recorded in the archive table matching the + * given title prefix. + * Returns result wrapper with (ar_namespace, ar_title, count) fields. + * + * @return ResultWrapper + */ + public static function listPagesByPrefix( $prefix ) { + $dbr = wfGetDB( DB_SLAVE ); + + $title = Title::newFromText( $prefix ); + if( $title ) { + $ns = $title->getNamespace(); + $encPrefix = $dbr->escapeLike( $title->getDBkey() ); + } else { + // Prolly won't work too good + // @todo handle bare namespace names cleanly? + $ns = 0; + $encPrefix = $dbr->escapeLike( $prefix ); + } + $conds = array( + 'ar_namespace' => $ns, + "ar_title LIKE '$encPrefix%'", + ); + return self::listPages( $dbr, $conds ); + } + + protected static function listPages( $dbr, $condition ) { + return $dbr->resultObject( + $dbr->select( + array( 'archive' ), + array( + 'ar_namespace', + 'ar_title', + 'COUNT(*) AS count' + ), + $condition, + __METHOD__, + array( + 'GROUP BY' => 'ar_namespace,ar_title', + 'ORDER BY' => 'ar_namespace,ar_title', + 'LIMIT' => 100, + ) + ) + ); + } + + /** + * List the revisions of the given page. Returns result wrapper with + * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields. + * + * @return ResultWrapper + */ + function listRevisions() { + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'archive', + array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len', 'ar_deleted' ), + array( 'ar_namespace' => $this->title->getNamespace(), + 'ar_title' => $this->title->getDBkey() ), + 'PageArchive::listRevisions', + array( 'ORDER BY' => 'ar_timestamp DESC' ) ); + $ret = $dbr->resultObject( $res ); + return $ret; + } + + /** + * List the deleted file revisions for this page, if it's a file page. + * Returns a result wrapper with various filearchive fields, or null + * if not a file page. + * + * @return ResultWrapper + * @todo Does this belong in Image for fuller encapsulation? + */ + function listFiles() { + if( $this->title->getNamespace() == NS_IMAGE ) { + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'filearchive', + array( + 'fa_id', + 'fa_name', + 'fa_archive_name', + 'fa_storage_key', + 'fa_storage_group', + 'fa_size', + 'fa_width', + 'fa_height', + 'fa_bits', + 'fa_metadata', + 'fa_media_type', + 'fa_major_mime', + 'fa_minor_mime', + 'fa_description', + 'fa_user', + 'fa_user_text', + 'fa_timestamp', + 'fa_deleted' ), + array( 'fa_name' => $this->title->getDBkey() ), + __METHOD__, + array( 'ORDER BY' => 'fa_timestamp DESC' ) ); + $ret = $dbr->resultObject( $res ); + return $ret; + } + return null; + } + + /** + * Fetch (and decompress if necessary) the stored text for the deleted + * revision of the page with the given timestamp. + * + * @return string + * @deprecated Use getRevision() for more flexible information + */ + function getRevisionText( $timestamp ) { + $rev = $this->getRevision( $timestamp ); + return $rev ? $rev->getText() : null; + } + + /** + * Return a Revision object containing data for the deleted revision. + * Note that the result *may* or *may not* have a null page ID. + * @param string $timestamp + * @return Revision + */ + function getRevision( $timestamp ) { + $dbr = wfGetDB( DB_SLAVE ); + $row = $dbr->selectRow( 'archive', + array( + 'ar_rev_id', + 'ar_text', + 'ar_comment', + 'ar_user', + 'ar_user_text', + 'ar_timestamp', + 'ar_minor_edit', + 'ar_flags', + 'ar_text_id', + 'ar_deleted', + 'ar_len' ), + array( 'ar_namespace' => $this->title->getNamespace(), + 'ar_title' => $this->title->getDBkey(), + 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), + __METHOD__ ); + if( $row ) { + return new Revision( array( + 'page' => $this->title->getArticleId(), + 'id' => $row->ar_rev_id, + 'text' => ($row->ar_text_id + ? null + : Revision::getRevisionText( $row, 'ar_' ) ), + 'comment' => $row->ar_comment, + 'user' => $row->ar_user, + 'user_text' => $row->ar_user_text, + 'timestamp' => $row->ar_timestamp, + 'minor_edit' => $row->ar_minor_edit, + 'text_id' => $row->ar_text_id, + 'deleted' => $row->ar_deleted, + 'len' => $row->ar_len) ); + } else { + return null; + } + } + + /** + * Return the most-previous revision, either live or deleted, against + * the deleted revision given by timestamp. + * + * May produce unexpected results in case of history merges or other + * unusual time issues. + * + * @param string $timestamp + * @return Revision or null + */ + function getPreviousRevision( $timestamp ) { + $dbr = wfGetDB( DB_SLAVE ); + + // Check the previous deleted revision... + $row = $dbr->selectRow( 'archive', + 'ar_timestamp', + array( 'ar_namespace' => $this->title->getNamespace(), + 'ar_title' => $this->title->getDBkey(), + 'ar_timestamp < ' . + $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), + __METHOD__, + array( + 'ORDER BY' => 'ar_timestamp DESC', + 'LIMIT' => 1 ) ); + $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false; + + $row = $dbr->selectRow( array( 'page', 'revision' ), + array( 'rev_id', 'rev_timestamp' ), + array( + 'page_namespace' => $this->title->getNamespace(), + 'page_title' => $this->title->getDBkey(), + 'page_id = rev_page', + 'rev_timestamp < ' . + $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), + __METHOD__, + array( + 'ORDER BY' => 'rev_timestamp DESC', + 'LIMIT' => 1 ) ); + $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false; + $prevLiveId = $row ? intval( $row->rev_id ) : null; + + if( $prevLive && $prevLive > $prevDeleted ) { + // Most prior revision was live + return Revision::newFromId( $prevLiveId ); + } elseif( $prevDeleted ) { + // Most prior revision was deleted + return $this->getRevision( $prevDeleted ); + } else { + // No prior revision on this page. + return null; + } + } + + /** + * Get the text from an archive row containing ar_text, ar_flags and ar_text_id + */ + function getTextFromRow( $row ) { + if( is_null( $row->ar_text_id ) ) { + // An old row from MediaWiki 1.4 or previous. + // Text is embedded in this row in classic compression format. + return Revision::getRevisionText( $row, "ar_" ); + } else { + // New-style: keyed to the text storage backend. + $dbr = wfGetDB( DB_SLAVE ); + $text = $dbr->selectRow( 'text', + array( 'old_text', 'old_flags' ), + array( 'old_id' => $row->ar_text_id ), + __METHOD__ ); + return Revision::getRevisionText( $text ); + } + } + + + /** + * Fetch (and decompress if necessary) the stored text of the most + * recently edited deleted revision of the page. + * + * If there are no archived revisions for the page, returns NULL. + * + * @return string + */ + function getLastRevisionText() { + $dbr = wfGetDB( DB_SLAVE ); + $row = $dbr->selectRow( 'archive', + array( 'ar_text', 'ar_flags', 'ar_text_id' ), + array( 'ar_namespace' => $this->title->getNamespace(), + 'ar_title' => $this->title->getDBkey() ), + 'PageArchive::getLastRevisionText', + array( 'ORDER BY' => 'ar_timestamp DESC' ) ); + if( $row ) { + return $this->getTextFromRow( $row ); + } else { + return NULL; + } + } + + /** + * Quick check if any archived revisions are present for the page. + * @return bool + */ + function isDeleted() { + $dbr = wfGetDB( DB_SLAVE ); + $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', + array( 'ar_namespace' => $this->title->getNamespace(), + 'ar_title' => $this->title->getDBkey() ) ); + return ($n > 0); + } + + /** + * Restore the given (or all) text and file revisions for the page. + * Once restored, the items will be removed from the archive tables. + * The deletion log will be updated with an undeletion notice. + * + * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @param string $comment + * @param array $fileVersions + * @param bool $unsuppress + * + * @return array(number of file revisions restored, number of image revisions restored, log message) + * on success, false on failure + */ + function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false ) { + // If both the set of text revisions and file revisions are empty, + // restore everything. Otherwise, just restore the requested items. + $restoreAll = empty( $timestamps ) && empty( $fileVersions ); + + $restoreText = $restoreAll || !empty( $timestamps ); + $restoreFiles = $restoreAll || !empty( $fileVersions ); + + if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) { + $img = wfLocalFile( $this->title ); + $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); + $filesRestored = $this->fileStatus->successCount; + } else { + $filesRestored = 0; + } + + if( $restoreText ) { + $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress ); + if($textRestored === false) // It must be one of UNDELETE_* + return false; + } else { + $textRestored = 0; + } + + // Touch the log! + global $wgContLang; + $log = new LogPage( 'delete' ); + + if( $textRestored && $filesRestored ) { + $reason = wfMsgExt( 'undeletedrevisions-files', array( 'content', 'parsemag' ), + $wgContLang->formatNum( $textRestored ), + $wgContLang->formatNum( $filesRestored ) ); + } elseif( $textRestored ) { + $reason = wfMsgExt( 'undeletedrevisions', array( 'content', 'parsemag' ), + $wgContLang->formatNum( $textRestored ) ); + } elseif( $filesRestored ) { + $reason = wfMsgExt( 'undeletedfiles', array( 'content', 'parsemag' ), + $wgContLang->formatNum( $filesRestored ) ); + } else { + wfDebug( "Undelete: nothing undeleted...\n" ); + return false; + } + + if( trim( $comment ) != '' ) + $reason .= ": {$comment}"; + $log->addEntry( 'restore', $this->title, $reason ); + + return array($textRestored, $filesRestored, $reason); + } + + /** + * This is the meaty bit -- restores archived revisions of the given page + * to the cur/old tables. If the page currently exists, all revisions will + * be stuffed into old, otherwise the most recent will go into cur. + * + * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @param string $comment + * @param array $fileVersions + * @param bool $unsuppress, remove all ar_deleted/fa_deleted restrictions of seletected revs + * + * @return mixed number of revisions restored or false on failure + */ + private function undeleteRevisions( $timestamps, $unsuppress = false ) { + if ( wfReadOnly() ) + return false; + $restoreAll = empty( $timestamps ); + + $dbw = wfGetDB( DB_MASTER ); + + # Does this page already exist? We'll have to update it... + $article = new Article( $this->title ); + $options = 'FOR UPDATE'; + $page = $dbw->selectRow( 'page', + array( 'page_id', 'page_latest' ), + array( 'page_namespace' => $this->title->getNamespace(), + 'page_title' => $this->title->getDBkey() ), + __METHOD__, + $options ); + if( $page ) { + $makepage = false; + # Page already exists. Import the history, and if necessary + # we'll update the latest revision field in the record. + $newid = 0; + $pageId = $page->page_id; + $previousRevId = $page->page_latest; + # Get the time span of this page + $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp', + array( 'rev_id' => $previousRevId ), + __METHOD__ ); + if( $previousTimestamp === false ) { + wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" ); + return 0; + } + } else { + # Have to create a new article... + $makepage = true; + $previousRevId = 0; + $previousTimestamp = 0; + } + + if( $restoreAll ) { + $oldones = '1 = 1'; # All revisions... + } else { + $oldts = implode( ',', + array_map( array( &$dbw, 'addQuotes' ), + array_map( array( &$dbw, 'timestamp' ), + $timestamps ) ) ); + + $oldones = "ar_timestamp IN ( {$oldts} )"; + } + + /** + * Select each archived revision... + */ + $result = $dbw->select( 'archive', + /* fields */ array( + 'ar_rev_id', + 'ar_text', + 'ar_comment', + 'ar_user', + 'ar_user_text', + 'ar_timestamp', + 'ar_minor_edit', + 'ar_flags', + 'ar_text_id', + 'ar_deleted', + 'ar_page_id', + 'ar_len' ), + /* WHERE */ array( + 'ar_namespace' => $this->title->getNamespace(), + 'ar_title' => $this->title->getDBkey(), + $oldones ), + __METHOD__, + /* 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 ); + $pageId = $newid; + } + + $revision = null; + $restored = 0; + + while( $row = $ret->fetchObject() ) { + if( $row->ar_text_id ) { + // Revision was deleted in 1.5+; text is in + // the regular text table, use the reference. + // Specify null here so the so the text is + // dereferenced for page length info if needed. + $revText = null; + } else { + // Revision was deleted in 1.4 or earlier. + // Text is squashed into the archive row, and + // a new text table entry will be created for it. + $revText = Revision::getRevisionText( $row, 'ar_' ); + } + $revision = new Revision( array( + 'page' => $pageId, + 'id' => $row->ar_rev_id, + 'text' => $revText, + 'comment' => $row->ar_comment, + 'user' => $row->ar_user, + 'user_text' => $row->ar_user_text, + 'timestamp' => $row->ar_timestamp, + 'minor_edit' => $row->ar_minor_edit, + 'text_id' => $row->ar_text_id, + 'deleted' => $unsuppress ? 0 : $row->ar_deleted, + 'len' => $row->ar_len + ) ); + $revision->insertOn( $dbw ); + $restored++; + + wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) ); + } + // Was anything restored at all? + 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 ); + } + + if( $newid ) { + wfRunHooks( 'ArticleUndelete', array( &$this->title, true ) ); + Article::onArticleCreate( $this->title ); + } else { + wfRunHooks( 'ArticleUndelete', array( &$this->title, false ) ); + Article::onArticleEdit( $this->title ); + } + + if( $this->title->getNamespace() == NS_IMAGE ) { + $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); + $update->doUpdate(); + } + } else { + // Revision couldn't be created. This is very weird + 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; + } + + function getFileStatus() { return $this->fileStatus; } +} + +/** + * The HTML form for Special:Undelete, which allows users with the appropriate + * permissions to view and restore deleted content. + * @ingroup SpecialPage + */ +class UndeleteForm { + var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj; + var $mTargetTimestamp, $mAllowed, $mComment; + + function UndeleteForm( $request, $par = "" ) { + global $wgUser; + $this->mAction = $request->getVal( 'action' ); + $this->mTarget = $request->getVal( 'target' ); + $this->mSearchPrefix = $request->getText( 'prefix' ); + $time = $request->getVal( 'timestamp' ); + $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; + $this->mFile = $request->getVal( 'file' ); + + $posted = $request->wasPosted() && + $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); + $this->mRestore = $request->getCheck( 'restore' ) && $posted; + $this->mPreview = $request->getCheck( 'preview' ) && $posted; + $this->mDiff = $request->getCheck( 'diff' ); + $this->mComment = $request->getText( 'wpComment' ); + $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); + + if( $par != "" ) { + $this->mTarget = $par; + } + if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) { + $this->mAllowed = true; + } else { + $this->mAllowed = false; + $this->mTimestamp = ''; + $this->mRestore = false; + } + if ( $this->mTarget !== "" ) { + $this->mTargetObj = Title::newFromURL( $this->mTarget ); + } else { + $this->mTargetObj = NULL; + } + if( $this->mRestore ) { + $timestamps = array(); + $this->mFileVersions = array(); + foreach( $_REQUEST as $key => $val ) { + $matches = array(); + if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { + array_push( $timestamps, $matches[1] ); + } + + if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { + $this->mFileVersions[] = intval( $matches[1] ); + } + } + rsort( $timestamps ); + $this->mTargetTimestamp = $timestamps; + } + } + + function execute() { + global $wgOut, $wgUser; + if ( $this->mAllowed ) { + $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); + } else { + $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); + } + + if( is_null( $this->mTargetObj ) ) { + # Not all users can just browse every deleted page from the list + if( $wgUser->isAllowed( 'browsearchive' ) ) { + $this->showSearchForm(); + + # List undeletable articles + if( $this->mSearchPrefix ) { + $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix ); + $this->showList( $result ); + } + } else { + $wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) ); + } + return; + } + if( $this->mTimestamp !== '' ) { + return $this->showRevision( $this->mTimestamp ); + } + if( $this->mFile !== null ) { + $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile ); + // Check if user is allowed to see this file + if( !$file->userCan( File::DELETED_FILE ) ) { + $wgOut->permissionRequired( 'suppressrevision' ); + return false; + } else { + return $this->showFile( $this->mFile ); + } + } + if( $this->mRestore && $this->mAction == "submit" ) { + return $this->undelete(); + } + return $this->showHistory(); + } + + function showSearchForm() { + global $wgOut, $wgScript; + $wgOut->addWikiMsg( 'undelete-header' ); + + $wgOut->addHtml( + Xml::openElement( 'form', array( + 'method' => 'get', + 'action' => $wgScript ) ) . + '
      ' . + Xml::element( 'legend', array(), + wfMsg( 'undelete-search-box' ) ) . + Xml::hidden( 'title', + SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) . + Xml::inputLabel( wfMsg( 'undelete-search-prefix' ), + 'prefix', 'prefix', 20, + $this->mSearchPrefix ) . + Xml::submitButton( wfMsg( 'undelete-search-submit' ) ) . + '
      ' . + '' ); + } + + // Generic list of deleted pages + private function showList( $result ) { + global $wgLang, $wgContLang, $wgUser, $wgOut; + + if( $result->numRows() == 0 ) { + $wgOut->addWikiMsg( 'undelete-no-results' ); + return; + } + + $wgOut->addWikiMsg( "undeletepagetext" ); + + $sk = $wgUser->getSkin(); + $undelete = SpecialPage::getTitleFor( 'Undelete' ); + $wgOut->addHTML( "
        \n" ); + while( $row = $result->fetchObject() ) { + $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( "
      • {$link} ({$revs})
      • \n" ); + } + $result->free(); + $wgOut->addHTML( "
      \n" ); + + return true; + } + + private function showRevision( $timestamp ) { + global $wgLang, $wgUser, $wgOut; + $self = SpecialPage::getTitleFor( 'Undelete' ); + $skin = $wgUser->getSkin(); + + if(!preg_match("/[0-9]{14}/",$timestamp)) return 0; + + $archive = new PageArchive( $this->mTargetObj ); + $rev = $archive->getRevision( $timestamp ); + + if( !$rev ) { + $wgOut->addWikiMsg( 'undeleterevision-missing' ); + return; + } + + if( $rev->isDeleted(Revision::DELETED_TEXT) ) { + if( !$rev->userCan(Revision::DELETED_TEXT) ) { + $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) ); + return; + } else { + $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) ); + $wgOut->addHTML( '
      ' ); + // and we are allowed to see... + } + } + + $wgOut->setPageTitle( wfMsg( 'undeletepage' ) ); + + $link = $skin->makeKnownLinkObj( + 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 ); + if( $previousRev ) { + $this->showDiff( $previousRev, $rev ); + if( $wgUser->getOption( 'diffonly' ) ) { + return; + } else { + $wgOut->addHtml( '
      ' ); + } + } else { + $wgOut->addHtml( wfMsgHtml( 'undelete-nodiff' ) ); + } + } + + $wgOut->addHtml( '

      ' . wfMsgHtml( 'undelete-revision', $link, $time, $user ) . '

      ' ); + + wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ); + + if( $this->mPreview ) { + $wgOut->addHtml( "
      \n" ); + + //Hide [edit]s + $popts = $wgOut->parserOptions(); + $popts->setEditSection( false ); + $wgOut->parserOptions( $popts ); + $wgOut->addWikiTextTitleTidy( $rev->revText(), $this->mTargetObj, true ); + } + + $wgOut->addHtml( + wfElement( 'textarea', array( + 'readonly' => 'readonly', + 'cols' => intval( $wgUser->getOption( 'cols' ) ), + 'rows' => intval( $wgUser->getOption( 'rows' ) ) ), + $rev->revText() . "\n" ) . + wfOpenElement( 'div' ) . + wfOpenElement( 'form', array( + 'method' => 'post', + 'action' => $self->getLocalURL( "action=submit" ) ) ) . + wfElement( 'input', array( + 'type' => 'hidden', + 'name' => 'target', + 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) . + wfElement( 'input', array( + 'type' => 'hidden', + 'name' => 'timestamp', + 'value' => $timestamp ) ) . + wfElement( 'input', array( + 'type' => 'hidden', + 'name' => 'wpEditToken', + 'value' => $wgUser->editToken() ) ) . + wfElement( 'input', array( + 'type' => 'submit', + 'name' => 'preview', + 'value' => wfMsg( 'showpreview' ) ) ) . + wfElement( 'input', array( + 'name' => 'diff', + 'type' => 'submit', + 'value' => wfMsg( 'showdiff' ) ) ) . + wfCloseElement( 'form' ) . + wfCloseElement( 'div' ) ); + } + + /** + * Build a diff display between this and the previous either deleted + * or non-deleted edit. + * @param Revision $previousRev + * @param Revision $currentRev + * @return string HTML + */ + function showDiff( $previousRev, $currentRev ) { + global $wgOut, $wgUser; + + $diffEngine = new DifferenceEngine(); + $diffEngine->showDiffStyle(); + $wgOut->addHtml( + "
      " . + "" . + "" . + "" . + "" . + "" . + "" . + "" . + "" . + "" . + $diffEngine->generateDiffBody( + $previousRev->getText(), $currentRev->getText() ) . + "
      " . + $this->diffHeader( $previousRev ) . + "" . + $this->diffHeader( $currentRev ) . + "
      " . + "
      \n" ); + + } + + private function diffHeader( $rev ) { + global $wgUser, $wgLang, $wgLang; + $sk = $wgUser->getSkin(); + $isDeleted = !( $rev->getId() && $rev->getTitle() ); + if( $isDeleted ) { + /// @fixme $rev->getTitle() is null for deleted revs...? + $targetPage = SpecialPage::getTitleFor( 'Undelete' ); + $targetQuery = 'target=' . + $this->mTargetObj->getPrefixedUrl() . + '×tamp=' . + wfTimestamp( TS_MW, $rev->getTimestamp() ); + } else { + /// @fixme getId() may return non-zero for deleted revs... + $targetPage = $rev->getTitle(); + $targetQuery = 'oldid=' . $rev->getId(); + } + return + '
      ' . + $sk->makeLinkObj( $targetPage, + wfMsgHtml( 'revisionasof', + $wgLang->timeanddate( $rev->getTimestamp(), true ) ), + $targetQuery ) . + ( $isDeleted ? ' ' . wfMsgHtml( 'deletedrev' ) : '' ) . + '
      ' . + '
      ' . + $sk->revUserTools( $rev ) . '
      ' . + '
      ' . + '
      ' . + $sk->revComment( $rev ) . '
      ' . + '
      '; + } + + /** + * Show a deleted file version requested by the visitor. + */ + private function showFile( $key ) { + global $wgOut, $wgRequest; + $wgOut->disable(); + + # We mustn't allow the output to be Squid cached, otherwise + # if an admin previews a deleted image, and it's cached, then + # a user without appropriate permissions can toddle off and + # nab the image, and Squid will serve it + $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); + $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); + $wgRequest->response()->header( 'Pragma: no-cache' ); + + $store = FileStore::get( 'deleted' ); + $store->stream( $key ); + } + + private function showHistory() { + global $wgLang, $wgUser, $wgOut; + + $sk = $wgUser->getSkin(); + if( $this->mAllowed ) { + $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); + } else { + $wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) ); + } + + $wgOut->addWikiText( wfMsgHtml( 'undeletepagetitle', $this->mTargetObj->getPrefixedText()) ); + + $archive = new PageArchive( $this->mTargetObj ); + /* + $text = $archive->getLastRevisionText(); + if( is_null( $text ) ) { + $wgOut->addWikiMsg( "nohistory" ); + return; + } + */ + if ( $this->mAllowed ) { + $wgOut->addWikiMsg( "undeletehistory" ); + $wgOut->addWikiMsg( "undeleterevdel" ); + } else { + $wgOut->addWikiMsg( "undeletehistorynoadmin" ); + } + + # List all stored revisions + $revisions = $archive->listRevisions(); + $files = $archive->listFiles(); + + $haveRevisions = $revisions && $revisions->numRows() > 0; + $haveFiles = $files && $files->numRows() > 0; + + # Batch existence check on user and talk pages + if( $haveRevisions ) { + $batch = new LinkBatch(); + while( $row = $revisions->fetchObject() ) { + $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); + $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) ); + } + $batch->execute(); + $revisions->seek( 0 ); + } + if( $haveFiles ) { + $batch = new LinkBatch(); + while( $row = $files->fetchObject() ) { + $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); + $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) ); + } + $batch->execute(); + $files->seek( 0 ); + } + + if ( $this->mAllowed ) { + $titleObj = SpecialPage::getTitleFor( "Undelete" ); + $action = $titleObj->getLocalURL( "action=submit" ); + # Start the form here + $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) ); + $wgOut->addHtml( $top ); + } + + # Show relevant lines from the deletion log: + $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" ); + LogEventsList::showLogExtract( $wgOut, 'delete', $this->mTargetObj->getPrefixedText() ); + + if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { + # Format the user-visible controls (comment field, submission button) + # in a nice little table + if( $wgUser->isAllowed( 'suppressrevision' ) ) { + $unsuppressBox = + " +   + " . + Xml::checkLabel( wfMsg('revdelete-unsuppress'), 'wpUnsuppress', + 'mw-undelete-unsuppress', $this->mUnsuppress ). + " + "; + } else { + $unsuppressBox = ""; + } + $table = + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'undelete') ). + Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . + " + " . + wfMsgWikiHtml( 'undeleteextrahelp' ) . + " + + + " . + Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) . + " + " . + Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) . + " + + +   + " . + Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . + Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . + " + " . + $unsuppressBox . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ); + + $wgOut->addHtml( $table ); + } + + $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" ); + + if( $haveRevisions ) { + # The page's stored (deleted) history: + $wgOut->addHTML("
        "); + $target = urlencode( $this->mTarget ); + $remaining = $revisions->numRows(); + $earliestLiveTime = $this->getEarliestTime( $this->mTargetObj ); + + while( $row = $revisions->fetchObject() ) { + $remaining--; + $wgOut->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) ); + } + $revisions->free(); + $wgOut->addHTML("
      "); + } else { + $wgOut->addWikiMsg( "nohistory" ); + } + + if( $haveFiles ) { + $wgOut->addHtml( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" ); + $wgOut->addHtml( "
        " ); + while( $row = $files->fetchObject() ) { + $wgOut->addHTML( $this->formatFileRow( $row, $sk ) ); + } + $files->free(); + $wgOut->addHTML( "
      " ); + } + + if ( $this->mAllowed ) { + # Slip in the hidden controls here + $misc = Xml::hidden( 'target', $this->mTarget ); + $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); + $misc .= Xml::closeElement( 'form' ); + $wgOut->addHtml( $misc ); + } + + return true; + } + + private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) { + global $wgUser, $wgLang; + + $rev = new Revision( array( + 'page' => $this->mTargetObj->getArticleId(), + 'comment' => $row->ar_comment, + 'user' => $row->ar_user, + 'user_text' => $row->ar_user_text, + 'timestamp' => $row->ar_timestamp, + 'minor_edit' => $row->ar_minor_edit, + 'deleted' => $row->ar_deleted, + 'len' => $row->ar_len ) ); + + $stxt = ''; + $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); + if( $this->mAllowed ) { + $checkBox = Xml::check( "ts$ts" ); + $titleObj = SpecialPage::getTitleFor( "Undelete" ); + $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk ); + # Last link + if( !$rev->userCan( Revision::DELETED_TEXT ) ) { + $last = wfMsgHtml('diff'); + } else if( $remaining > 0 || ($earliestLiveTime && $ts > $earliestLiveTime) ) { + $last = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml('diff'), + "target=" . $this->mTargetObj->getPrefixedUrl() . "×tamp=$ts&diff=prev" ); + } else { + $last = wfMsgHtml('diff'); + } + } else { + $checkBox = ''; + $pageLink = $wgLang->timeanddate( $ts, true ); + $last = wfMsgHtml('diff'); + } + $userLink = $sk->revUserTools( $rev ); + + if(!is_null($size = $row->ar_len)) { + if($size == 0) + $stxt = wfMsgHtml('historyempty'); + else + $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); + } + $comment = $sk->revComment( $rev ); + $revdlink = ''; + if( $wgUser->isAllowed( 'deleterevision' ) ) { + $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); + if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { + // 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" ); + // Bolden oversighted content + if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) + $del = "$del"; + } + $revdlink = "($del)"; + } + + return "
    • $checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment
    • "; + } + + private function formatFileRow( $row, $sk ) { + global $wgUser, $wgLang; + + $file = ArchivedFile::newFromRow( $row ); + + $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); + if( $this->mAllowed && $row->fa_storage_key ) { + $checkBox = Xml::check( "fileid" . $row->fa_id ); + $key = urlencode( $row->fa_storage_key ); + $target = urlencode( $this->mTarget ); + $titleObj = SpecialPage::getTitleFor( "Undelete" ); + $pageLink = $this->getFileLink( $file, $titleObj, $ts, $key, $sk ); + } else { + $checkBox = ''; + $pageLink = $wgLang->timeanddate( $ts, true ); + } + $userLink = $this->getFileUser( $file, $sk ); + $data = + wfMsgHtml( 'widthheight', + $wgLang->formatNum( $row->fa_width ), + $wgLang->formatNum( $row->fa_height ) ) . + ' (' . + wfMsgHtml( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) . + ')'; + $comment = $this->getFileComment( $file, $sk ); + $revdlink = ''; + if( $wgUser->isAllowed( 'deleterevision' ) ) { + $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); + if( !$file->userCan(File::DELETED_RESTRICTED ) ) { + // If revision was hidden from sysops + $del = wfMsgHtml('rev-delundel'); + } else { + $del = $sk->makeKnownLinkObj( $revdel, + wfMsgHtml('rev-delundel'), + 'target=' . $this->mTargetObj->getPrefixedUrl() . + '&fileid=' . $row->fa_id ); + // Bolden oversighted content + if( $file->isDeleted( File::DELETED_RESTRICTED ) ) + $del = "$del"; + } + $revdlink = "($del)"; + } + return "
    • $checkBox $revdlink $pageLink . . $userLink $data $comment
    • \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 + */ + function getPageLink( $rev, $titleObj, $ts, $sk ) { + global $wgLang; + + if( !$rev->userCan(Revision::DELETED_TEXT) ) { + return '' . $wgLang->timeanddate( $ts, true ) . ''; + } else { + $link = $sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), + "target=".$this->mTargetObj->getPrefixedUrl()."×tamp=$ts" ); + if( $rev->isDeleted(Revision::DELETED_TEXT) ) + $link = '' . $link . ''; + return $link; + } + } + + /** + * Fetch image view link if it's available to all users + * @return string + */ + function getFileLink( $file, $titleObj, $ts, $key, $sk ) { + global $wgLang; + + if( !$file->userCan(File::DELETED_FILE) ) { + return '' . $wgLang->timeanddate( $ts, true ) . ''; + } else { + $link = $sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), + "target=".$this->mTargetObj->getPrefixedUrl()."&file=$key" ); + if( $file->isDeleted(File::DELETED_FILE) ) + $link = '' . $link . ''; + return $link; + } + } + + /** + * Fetch file's user id if it's available to this user + * @return string + */ + function getFileUser( $file, $sk ) { + if( !$file->userCan(File::DELETED_USER) ) { + return '' . wfMsgHtml( 'rev-deleted-user' ) . ''; + } else { + $link = $sk->userLink( $file->getRawUser(), $file->getRawUserText() ) . + $sk->userToolLinks( $file->getRawUser(), $file->getRawUserText() ); + if( $file->isDeleted(File::DELETED_USER) ) + $link = '' . $link . ''; + return $link; + } + } + + /** + * Fetch file upload comment if it's available to this user + * @return string + */ + function getFileComment( $file, $sk ) { + if( !$file->userCan(File::DELETED_COMMENT) ) { + return '' . wfMsgHtml( 'rev-deleted-comment' ) . ''; + } else { + $link = $sk->commentBlock( $file->getRawDescription() ); + if( $file->isDeleted(File::DELETED_COMMENT) ) + $link = '' . $link . ''; + return $link; + } + } + + function undelete() { + global $wgOut, $wgUser; + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + if( !is_null( $this->mTargetObj ) ) { + $archive = new PageArchive( $this->mTargetObj ); + $ok = $archive->undelete( + $this->mTargetTimestamp, + $this->mComment, + $this->mFileVersions, + $this->mUnsuppress ); + + if( is_array($ok) ) { + if ( $ok[1] ) // Undeleted file count + wfRunHooks( 'FileUndeleteComplete', array( + $this->mTargetObj, $this->mFileVersions, + $wgUser, $this->mComment) ); + + $skin = $wgUser->getSkin(); + $link = $skin->makeKnownLinkObj( $this->mTargetObj ); + $wgOut->addHtml( wfMsgWikiHtml( 'undeletedpage', $link ) ); + } else { + $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); + $wgOut->addHtml( '

      ' . wfMsgHtml( "undeleterevdel" ) . '

      ' ); + } + + // Show file deletion warnings and errors + $status = $archive->getFileStatus(); + if( $status && !$status->isGood() ) { + $wgOut->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) ); + } + } else { + $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); + } + return false; + } +} diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php new file mode 100644 index 0000000000..0bf7e5aa5a --- /dev/null +++ b/includes/specials/SpecialUnlockdb.php @@ -0,0 +1,107 @@ +isAllowed( 'siteadmin' ) ) { + $wgOut->permissionRequired( 'siteadmin' ); + return; + } + + $action = $wgRequest->getVal( 'action' ); + $f = new DBUnlockForm(); + + if ( "success" == $action ) { + $f->showSuccess(); + } else if ( "submit" == $action && $wgRequest->wasPosted() && + $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $f->doSubmit(); + } else { + $f->showForm( "" ); + } +} + +/** + * @ingroup SpecialPage + */ +class DBUnlockForm { + function showForm( $err ) + { + global $wgOut, $wgUser; + + global $wgReadOnlyFile; + if( !file_exists( $wgReadOnlyFile ) ) { + $wgOut->addWikiMsg( 'databasenotlocked' ); + return; + } + + $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); + $wgOut->addWikiMsg( "unlockdbtext" ); + + if ( "" != $err ) { + $wgOut->setSubtitle( wfMsg( "formerror" ) ); + $wgOut->addHTML( '

      ' . htmlspecialchars( $err ) . "

      \n" ); + } + $lc = htmlspecialchars( wfMsg( "unlockconfirm" ) ); + $lb = htmlspecialchars( wfMsg( "unlockbtn" ) ); + $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); + $action = $titleObj->escapeLocalURL( "action=submit" ); + $token = htmlspecialchars( $wgUser->editToken() ); + + $wgOut->addHTML( << + + + + + + + + + +
      + + {$lc}
        + +
      + + +END +); + + } + + function doSubmit() { + global $wgOut, $wgRequest, $wgReadOnlyFile; + + $wpLockConfirm = $wgRequest->getCheck( 'wpLockConfirm' ); + if ( ! $wpLockConfirm ) { + $this->showForm( wfMsg( "locknoconfirm" ) ); + return; + } + if ( @! unlink( $wgReadOnlyFile ) ) { + $wgOut->showFileDeleteError( $wgReadOnlyFile ); + return; + } + $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); + $success = $titleObj->getFullURL( "action=success" ); + $wgOut->redirect( $success ); + } + + function showSuccess() { + global $wgOut; + global $ip; + + $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); + $wgOut->setSubtitle( wfMsg( "unlockdbsuccesssub" ) ); + $wgOut->addWikiMsg( "unlockdbsuccesstext", $ip ); + } +} diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php new file mode 100644 index 0000000000..406f794409 --- /dev/null +++ b/includes/specials/SpecialUnusedcategories.php @@ -0,0 +1,46 @@ +tableNamesN( 'categorylinks', 'page' ); + return "SELECT 'Unusedcategories' as type, + {$NScat} as namespace, page_title as title, page_title as value + FROM $page + LEFT JOIN $categorylinks ON page_title=cl_to + WHERE cl_from IS NULL + AND page_namespace = {$NScat} + AND page_is_redirect = 0"; + } + + function formatResult( $skin, $result ) { + $title = Title::makeTitle( NS_CATEGORY, $result->title ); + return $skin->makeLinkObj( $title, $title->getText() ); + } +} + +/** constructor */ +function wfSpecialUnusedCategories() { + list( $limit, $offset ) = wfCheckLimits(); + $uc = new UnusedCategoriesPage(); + return $uc->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php new file mode 100644 index 0000000000..d71b638fa5 --- /dev/null +++ b/includes/specials/SpecialUnusedimages.php @@ -0,0 +1,60 @@ +tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' ); + + return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, + img_user, img_user_text, img_description + 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"; + } else { + list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' ); + + return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, + img_user, img_user_text, img_description + FROM $image LEFT JOIN $imagelinks ON img_name=il_to WHERE il_to IS NULL "; + } + } + + function getPageHeader() { + return wfMsgExt( 'unusedimagestext', array( 'parse') ); + } + +} + +/** + * Entry point + */ +function wfSpecialUnusedimages() { + list( $limit, $offset ) = wfCheckLimits(); + $uip = new UnusedimagesPage(); + + return $uip->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php new file mode 100644 index 0000000000..89acd09c75 --- /dev/null +++ b/includes/specials/SpecialUnusedtemplates.php @@ -0,0 +1,54 @@ + + * @copyright © 2006 Rob Church + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + * @ingroup SpecialPage + */ +class UnusedtemplatesPage extends QueryPage { + + function getName() { return( 'Unusedtemplates' ); } + function isExpensive() { return true; } + function isSyndicated() { return false; } + function sortDescending() { return false; } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' ); + $sql = "SELECT 'Unusedtemplates' AS type, page_title AS title, + page_namespace AS namespace, 0 AS value + FROM $page + LEFT JOIN $templatelinks + ON page_namespace = tl_namespace AND page_title = tl_title + WHERE page_namespace = 10 AND tl_from IS NULL + AND page_is_redirect = 0"; + return $sql; + } + + function formatResult( $skin, $result ) { + $title = Title::makeTitle( NS_TEMPLATE, $result->title ); + $pageLink = $skin->makeKnownLinkObj( $title, '', 'redirect=no' ); + $wlhLink = $skin->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Whatlinkshere' ), + wfMsgHtml( 'unusedtemplateswlh' ), + 'target=' . $title->getPrefixedUrl() ); + return wfSpecialList( $pageLink, $wlhLink ); + } + + function getPageHeader() { + return wfMsgExt( 'unusedtemplatestext', array( 'parse' ) ); + } + +} + +function wfSpecialUnusedtemplates() { + list( $limit, $offset ) = wfCheckLimits(); + $utp = new UnusedtemplatesPage(); + $utp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php new file mode 100644 index 0000000000..64ab372995 --- /dev/null +++ b/includes/specials/SpecialUnwatchedpages.php @@ -0,0 +1,68 @@ + + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class UnwatchedpagesPage extends QueryPage { + + function getName() { return 'Unwatchedpages'; } + function isExpensive() { return true; } + function isSyndicated() { return false; } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' ); + $mwns = NS_MEDIAWIKI; + return + " + SELECT + 'Unwatchedpages' as type, + page_namespace as namespace, + page_title as title, + page_namespace as value + FROM $page + LEFT JOIN $watchlist ON wl_namespace = page_namespace AND page_title = wl_title + WHERE wl_title IS NULL AND page_is_redirect = 0 AND page_namespace<>$mwns + "; + } + + function sortDescending() { return false; } + + function formatResult( $skin, $result ) { + global $wgContLang; + + $nt = Title::makeTitle( $result->namespace, $result->title ); + $text = $wgContLang->convert( $nt->getPrefixedText() ); + + $plink = $skin->makeKnownLinkObj( $nt, htmlspecialchars( $text ) ); + $wlink = $skin->makeKnownLinkObj( $nt, wfMsgHtml( 'watch' ), 'action=watch' ); + + return wfSpecialList( $plink, $wlink ); + } +} + +/** + * constructor + */ +function wfSpecialUnwatchedpages() { + global $wgUser, $wgOut; + + if ( ! $wgUser->isAllowed( 'unwatchedpages' ) ) + return $wgOut->permissionRequired( 'unwatchedpages' ); + + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new UnwatchedpagesPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php new file mode 100644 index 0000000000..0f37f503a5 --- /dev/null +++ b/includes/specials/SpecialUpload.php @@ -0,0 +1,1755 @@ +execute(); +} + +/** + * implements Special:Upload + * @ingroup SpecialPage + */ +class UploadForm { + const SUCCESS = 0; + const BEFORE_PROCESSING = 1; + const LARGE_FILE_SERVER = 2; + const EMPTY_FILE = 3; + const MIN_LENGHT_PARTNAME = 4; + const ILLEGAL_FILENAME = 5; + const PROTECTED_PAGE = 6; + const OVERWRITE_EXISTING_FILE = 7; + const FILETYPE_MISSING = 8; + const FILETYPE_BADTYPE = 9; + const VERIFICATION_ERROR = 10; + const UPLOAD_VERIFICATION_ERROR = 11; + const UPLOAD_WARNING = 12; + const INTERNAL_ERROR = 13; + + /**#@+ + * @access private + */ + var $mComment, $mLicense, $mIgnoreWarning, $mCurlError; + var $mDestName, $mTempPath, $mFileSize, $mFileProps; + var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked; + var $mSrcName, $mSessionKey, $mStashed, $mDesiredDestName, $mRemoveTempFile, $mSourceType; + var $mDestWarningAck, $mCurlDestHandle; + var $mLocalFile; + + # Placeholders for text injection by hooks (must be HTML) + # extensions should take care to _append_ to the present value + var $uploadFormTextTop; + var $uploadFormTextAfterSummary; + + const SESSION_VERSION = 1; + /**#@-*/ + + /** + * Constructor : initialise object + * Get data POSTed through the form and assign them to the object + * @param $request Data posted. + */ + function UploadForm( &$request ) { + global $wgAllowCopyUploads; + $this->mDesiredDestName = $request->getText( 'wpDestFile' ); + $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ); + $this->mComment = $request->getText( 'wpUploadDescription' ); + + if( !$request->wasPosted() ) { + # GET requests just give the main form; no data except destination + # filename and description + return; + } + + # Placeholders for text injection by hooks (empty per default) + $this->uploadFormTextTop = ""; + $this->uploadFormTextAfterSummary = ""; + + $this->mReUpload = $request->getCheck( 'wpReUpload' ); + $this->mUploadClicked = $request->getCheck( 'wpUpload' ); + + $this->mLicense = $request->getText( 'wpLicense' ); + $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); + $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); + $this->mWatchthis = $request->getBool( 'wpWatchthis' ); + $this->mSourceType = $request->getText( 'wpSourceType' ); + $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); + + $this->mAction = $request->getVal( 'action' ); + + $this->mSessionKey = $request->getInt( 'wpSessionKey' ); + if( !empty( $this->mSessionKey ) && + isset( $_SESSION['wsUploadData'][$this->mSessionKey]['version'] ) && + $_SESSION['wsUploadData'][$this->mSessionKey]['version'] == self::SESSION_VERSION ) { + /** + * Confirming a temporarily stashed upload. + * We don't want path names to be forged, so we keep + * them in the session on the server and just give + * an opaque key to the user agent. + */ + $data = $_SESSION['wsUploadData'][$this->mSessionKey]; + $this->mTempPath = $data['mTempPath']; + $this->mFileSize = $data['mFileSize']; + $this->mSrcName = $data['mSrcName']; + $this->mFileProps = $data['mFileProps']; + $this->mCurlError = 0/*UPLOAD_ERR_OK*/; + $this->mStashed = true; + $this->mRemoveTempFile = false; + } else { + /** + *Check for a newly uploaded file. + */ + if( $wgAllowCopyUploads && $this->mSourceType == 'web' ) { + $this->initializeFromUrl( $request ); + } else { + $this->initializeFromUpload( $request ); + } + } + } + + /** + * Initialize the uploaded file from PHP data + * @access private + */ + function initializeFromUpload( $request ) { + $this->mTempPath = $request->getFileTempName( 'wpUploadFile' ); + $this->mFileSize = $request->getFileSize( 'wpUploadFile' ); + $this->mSrcName = $request->getFileName( 'wpUploadFile' ); + $this->mCurlError = $request->getUploadError( 'wpUploadFile' ); + $this->mSessionKey = false; + $this->mStashed = false; + $this->mRemoveTempFile = false; // PHP will handle this + } + + /** + * Copy a web file to a temporary file + * @access private + */ + function initializeFromUrl( $request ) { + global $wgTmpDirectory; + $url = $request->getText( 'wpUploadFileURL' ); + $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); + + $this->mTempPath = $local_file; + $this->mFileSize = 0; # Will be set by curlCopy + $this->mCurlError = $this->curlCopy( $url, $local_file ); + $pathParts = explode( '/', $url ); + $this->mSrcName = array_pop( $pathParts ); + $this->mSessionKey = false; + $this->mStashed = false; + + // PHP won't auto-cleanup the file + $this->mRemoveTempFile = file_exists( $local_file ); + } + + /** + * Safe copy from URL + * Returns true if there was an error, false otherwise + */ + private function curlCopy( $url, $dest ) { + global $wgUser, $wgOut; + + if( !$wgUser->isAllowed( 'upload_by_url' ) ) { + $wgOut->permissionRequired( 'upload_by_url' ); + return true; + } + + # Maybe remove some pasting blanks :-) + $url = trim( $url ); + if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) { + # Only HTTP or FTP URLs + $wgOut->showErrorPage( 'upload-proto-error', 'upload-proto-error-text' ); + return true; + } + + # Open temporary file + $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" ); + if( $this->mCurlDestHandle === false ) { + # Could not open temporary file to write in + $wgOut->showErrorPage( 'upload-file-error', 'upload-file-error-text'); + return true; + } + + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug + curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout + curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed + curl_setopt( $ch, CURLOPT_URL, $url); + curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) ); + curl_exec( $ch ); + $error = curl_errno( $ch ) ? true : false; + $errornum = curl_errno( $ch ); + // if ( $error ) print curl_error ( $ch ) ; # Debugging output + curl_close( $ch ); + + fclose( $this->mCurlDestHandle ); + unset( $this->mCurlDestHandle ); + if( $error ) { + unlink( $dest ); + if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) ) + $wgOut->showErrorPage( 'upload-misc-error', 'upload-misc-error-text' ); + else + $wgOut->showErrorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" ); + } + + return $error; + } + + /** + * Callback function for CURL-based web transfer + * Write data to file unless we've passed the length limit; + * if so, abort immediately. + * @access private + */ + function uploadCurlCallback( $ch, $data ) { + global $wgMaxUploadSize; + $length = strlen( $data ); + $this->mFileSize += $length; + if( $this->mFileSize > $wgMaxUploadSize ) { + return 0; + } + fwrite( $this->mCurlDestHandle, $data ); + return $length; + } + + /** + * Start doing stuff + * @access public + */ + function execute() { + global $wgUser, $wgOut; + global $wgEnableUploads; + + # Check uploading enabled + if( !$wgEnableUploads ) { + $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext', array( $this->mDesiredDestName ) ); + return; + } + + # Check permissions + if( !$wgUser->isAllowed( 'upload' ) ) { + if( !$wgUser->isLoggedIn() ) { + $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' ); + } else { + $wgOut->permissionRequired( 'upload' ); + } + return; + } + + # Check blocks + if( $wgUser->isBlocked() ) { + $wgOut->blockedPage(); + return; + } + + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + + if( $this->mReUpload ) { + if( !$this->unsaveUploadedFile() ) { + return; + } + # Because it is probably checked and shouldn't be + $this->mIgnoreWarning = false; + + $this->mainUploadForm(); + } else if( 'submit' == $this->mAction || $this->mUploadClicked ) { + $this->processUpload(); + } else { + $this->mainUploadForm(); + } + + $this->cleanupTempFile(); + } + + /** + * Do the upload + * Checks are made in SpecialUpload::execute() + * + * @access private + */ + function processUpload(){ + global $wgUser, $wgOut, $wgFileExtensions; + $details = null; + $value = null; + $value = $this->internalProcessUpload( $details ); + + switch($value) { + case self::SUCCESS: + $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() ); + break; + + case self::BEFORE_PROCESSING: + break; + + case self::LARGE_FILE_SERVER: + $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) ); + break; + + case self::EMPTY_FILE: + $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) ); + break; + + case self::MIN_LENGHT_PARTNAME: + $this->mainUploadForm( wfMsgHtml( 'minlength1' ) ); + break; + + case self::ILLEGAL_FILENAME: + $filtered = $details['filtered']; + $this->uploadError( wfMsgWikiHtml( 'illegalfilename', htmlspecialchars( $filtered ) ) ); + break; + + case self::PROTECTED_PAGE: + $wgOut->showPermissionsErrorPage( $details['permissionserrors'] ); + break; + + case self::OVERWRITE_EXISTING_FILE: + $errorText = $details['overwrite']; + $overwrite = new WikiError( $wgOut->parse( $errorText ) ); + $this->uploadError( $overwrite->toString() ); + break; + + case self::FILETYPE_MISSING: + $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) ); + break; + + case self::FILETYPE_BADTYPE: + $finalExt = $details['finalExt']; + $this->uploadError( + wfMsgExt( 'filetype-banned-type', + array( 'parseinline' ), + htmlspecialchars( $finalExt ), + implode( + wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), + $wgFileExtensions + ) + ) + ); + break; + + case self::VERIFICATION_ERROR: + $veri = $details['veri']; + $this->uploadError( $veri->toString() ); + break; + + case self::UPLOAD_VERIFICATION_ERROR: + $error = $details['error']; + $this->uploadError( $error ); + break; + + case self::UPLOAD_WARNING: + $warning = $details['warning']; + $this->uploadWarning( $warning ); + break; + + case self::INTERNAL_ERROR: + $internal = $details['internal']; + $this->showError( $internal ); + break; + + default: + throw new MWException( __METHOD__ . ": Unknown value `{$value}`" ); + } + } + + /** + * Really do the upload + * Checks are made in SpecialUpload::execute() + * + * @param array $resultDetails contains result-specific dict of additional values + * + * @access private + */ + function internalProcessUpload( &$resultDetails ) { + global $wgUser; + + if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) + { + wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file." ); + return self::BEFORE_PROCESSING; + } + + /** + * If there was no filename or a zero size given, give up quick. + */ + if( trim( $this->mSrcName ) == '' || empty( $this->mFileSize ) ) { + return self::EMPTY_FILE; + } + + /* Check for curl error */ + if( $this->mCurlError ) { + return self::BEFORE_PROCESSING; + } + + # Chop off any directories in the given filename + if( $this->mDesiredDestName ) { + $basename = $this->mDesiredDestName; + } else { + $basename = $this->mSrcName; + } + $filtered = wfBaseName( $basename ); + + /** + * We'll want to blacklist against *any* 'extension', and use + * only the final one for the whitelist. + */ + list( $partname, $ext ) = $this->splitExtensions( $filtered ); + + if( count( $ext ) ) { + $finalExt = $ext[count( $ext ) - 1]; + } else { + $finalExt = ''; + } + + # If there was more than one "extension", reassemble the base + # filename to prevent bogus complaints about length + if( count( $ext ) > 1 ) { + for( $i = 0; $i < count( $ext ) - 1; $i++ ) + $partname .= '.' . $ext[$i]; + } + + if( strlen( $partname ) < 1 ) { + return self::MIN_LENGHT_PARTNAME; + } + + /** + * Filter out illegal characters, and try to make a legible name + * out of it. We'll strip some silently that Title would die on. + */ + $filtered = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $filtered ); + $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(); + + /** + * If the image is protected, non-sysop users won't be able + * to modify it by uploading a new revision. + */ + $permErrors = $nt->getUserPermissionsErrors( 'edit', $wgUser ); + $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $wgUser ); + $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $wgUser ) ); + + if( $permErrors || $permErrorsUpload || $permErrorsCreate ) { + // merge all the problems into one list, avoiding duplicates + $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) ); + $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) ); + $resultDetails = array( 'permissionserrors' => $permErrors ); + return self::PROTECTED_PAGE; + } + + /** + * In some cases we may forbid overwriting of existing files. + */ + $overwrite = $this->checkOverwrite( $this->mDestName ); + if( $overwrite !== true ) { + $resultDetails = array( 'overwrite' => $overwrite ); + return self::OVERWRITE_EXISTING_FILE; + } + + /* Don't allow users to override the blacklist (check file extension) */ + global $wgCheckFileExtensions, $wgStrictFileExtensions; + global $wgFileExtensions, $wgFileBlacklist; + if ($finalExt == '') { + return self::FILETYPE_MISSING; + } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || + ($wgCheckFileExtensions && $wgStrictFileExtensions && + !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) { + $resultDetails = array( 'finalExt' => $finalExt ); + return self::FILETYPE_BADTYPE; + } + + /** + * Look at the contents of the file; if we can recognize the + * type but it's corrupt or data of the wrong type, we should + * probably not accept it. + */ + if( !$this->mStashed ) { + $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $finalExt ); + $this->checkMacBinary(); + $veri = $this->verify( $this->mTempPath, $finalExt ); + + if( $veri !== true ) { //it's a wiki error... + $resultDetails = array( 'veri' => $veri ); + return self::VERIFICATION_ERROR; + } + + /** + * Provide an opportunity for extensions to add further checks + */ + $error = ''; + if( !wfRunHooks( 'UploadVerification', + array( $this->mDestName, $this->mTempPath, &$error ) ) ) { + $resultDetails = array( 'error' => $error ); + return self::UPLOAD_VERIFICATION_ERROR; + } + } + + + /** + * Check for non-fatal conditions + */ + if ( ! $this->mIgnoreWarning ) { + $warning = ''; + + global $wgCapitalLinks; + if( $wgCapitalLinks ) { + $filtered = ucfirst( $filtered ); + } + if( $basename != $filtered ) { + $warning .= '
    • '.wfMsgHtml( 'badfilename', htmlspecialchars( $this->mDestName ) ).'
    • '; + } + + global $wgCheckFileExtensions; + if ( $wgCheckFileExtensions ) { + if ( !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) { + $warning .= '
    • ' . + wfMsgExt( 'filetype-unwanted-type', + array( 'parseinline' ), + htmlspecialchars( $finalExt ), + implode( + wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), + $wgFileExtensions + ) + ) . '
    • '; + } + } + + global $wgUploadSizeWarning; + if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) { + $skin = $wgUser->getSkin(); + $wsize = $skin->formatSize( $wgUploadSizeWarning ); + $asize = $skin->formatSize( $this->mFileSize ); + $warning .= '
    • ' . wfMsgHtml( 'large-file', $wsize, $asize ) . '
    • '; + } + if ( $this->mFileSize == 0 ) { + $warning .= '
    • '.wfMsgHtml( 'emptyfile' ).'
    • '; + } + + if ( !$this->mDestWarningAck ) { + $warning .= self::getExistsWarning( $this->mLocalFile ); + } + + $warning .= $this->getDupeWarning( $this->mTempPath ); + + if( $warning != '' ) { + /** + * Stash the file in a temporary location; the user can choose + * to let it through and we'll complete the upload then. + */ + $resultDetails = array( 'warning' => $warning ); + return self::UPLOAD_WARNING; + } + } + + /** + * Try actually saving the thing... + * It will show an error form on failure. + */ + $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, + $this->mCopyrightStatus, $this->mCopyrightSource ); + + $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText, + File::DELETE_SOURCE, $this->mFileProps ); + if ( !$status->isGood() ) { + $resultDetails = array( 'internal' => $status->getWikiText() ); + return self::INTERNAL_ERROR; + } else { + if ( $this->mWatchthis ) { + global $wgUser; + $wgUser->addWatch( $this->mLocalFile->getTitle() ); + } + // Success, redirect to description page + $img = null; // @todo: added to avoid passing a ref to null - should this be defined somewhere? + wfRunHooks( 'UploadComplete', array( &$this ) ); + return self::SUCCESS; + } + } + + /** + * Do existence checks on a file and produce a warning + * This check is static and can be done pre-upload via AJAX + * Returns an HTML fragment consisting of one or more LI elements if there is a warning + * Returns an empty string if there is no warning + */ + static function getExistsWarning( $file ) { + global $wgUser, $wgContLang; + // Check for uppercase extension. We allow these filenames but check if an image + // with lowercase extension exists already + $warning = ''; + $align = $wgContLang->isRtl() ? 'left' : 'right'; + + if( strpos( $file->getName(), '.' ) == false ) { + $partname = $file->getName(); + $rawExtension = ''; + } else { + $n = strrpos( $file->getName(), '.' ); + $rawExtension = substr( $file->getName(), $n + 1 ); + $partname = substr( $file->getName(), 0, $n ); + } + + $sk = $wgUser->getSkin(); + + if ( $rawExtension != $file->getExtension() ) { + // We're not using the normalized form of the extension. + // Normal form is lowercase, using most common of alternate + // extensions (eg 'jpg' rather than 'JPEG'). + // + // Check for another file using the normalized form... + $nt_lc = Title::makeTitle( NS_IMAGE, $partname . '.' . $file->getExtension() ); + $file_lc = wfLocalFile( $nt_lc ); + } else { + $file_lc = false; + } + + if( $file->exists() ) { + $dlink = $sk->makeKnownLinkObj( $file->getTitle() ); + if ( $file->allowInlineDisplay() ) { + $dlink2 = $sk->makeImageLinkObj( $file->getTitle(), wfMsgExt( 'fileexists-thumb', 'parseinline' ), + $file->getName(), $align, array(), false, true ); + } elseif ( !$file->allowInlineDisplay() && $file->isSafeFile() ) { + $icon = $file->iconThumb(); + $dlink2 = '
      ' . + $icon->toHtml( array( 'desc-link' => true ) ) . '
      ' . $dlink . '
      '; + } else { + $dlink2 = ''; + } + + $warning .= '
    • ' . wfMsgExt( 'fileexists', array('parseinline','replaceafter'), $dlink ) . '
    • ' . $dlink2; + + } elseif( $file->getTitle()->getArticleID() ) { + $lnk = $sk->makeKnownLinkObj( $file->getTitle(), '', 'redirect=no' ); + $warning .= '
    • ' . wfMsgExt( 'filepageexists', array( 'parseinline', 'replaceafter' ), $lnk ) . '
    • '; + } elseif ( $file_lc && $file_lc->exists() ) { + # Check if image with lowercase extension exists. + # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension + $dlink = $sk->makeKnownLinkObj( $nt_lc ); + if ( $file_lc->allowInlineDisplay() ) { + $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline' ), + $nt_lc->getText(), $align, array(), false, true ); + } elseif ( !$file_lc->allowInlineDisplay() && $file_lc->isSafeFile() ) { + $icon = $file_lc->iconThumb(); + $dlink2 = '
      ' . + $icon->toHtml( array( 'desc-link' => true ) ) . '
      ' . $dlink . '
      '; + } else { + $dlink2 = ''; + } + + $warning .= '
    • ' . + wfMsgExt( 'fileexists-extension', 'parsemag', + $file->getTitle()->getPrefixedText(), $dlink ) . + '
    • ' . $dlink2; + + } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' ) + && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) ) + { + # Check for filenames like 50px- or 180px-, these are mostly thumbnails + $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension ); + $file_thb = wfLocalFile( $nt_thb ); + if ($file_thb->exists() ) { + # Check if an image without leading '180px-' (or similiar) exists + $dlink = $sk->makeKnownLinkObj( $nt_thb); + if ( $file_thb->allowInlineDisplay() ) { + $dlink2 = $sk->makeImageLinkObj( $nt_thb, + wfMsgExt( 'fileexists-thumb', 'parseinline' ), + $nt_thb->getText(), $align, array(), false, true ); + } elseif ( !$file_thb->allowInlineDisplay() && $file_thb->isSafeFile() ) { + $icon = $file_thb->iconThumb(); + $dlink2 = '
      ' . + $icon->toHtml( array( 'desc-link' => true ) ) . '
      ' . + $dlink . '
      '; + } else { + $dlink2 = ''; + } + + $warning .= '
    • ' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) . + '
    • ' . $dlink2; + } else { + # Image w/o '180px-' does not exists, but we do not like these filenames + $warning .= '
    • ' . wfMsgExt( 'file-thumbnail-no', 'parseinline' , + substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '
    • '; + } + } + + $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist(); + # Do the match + foreach( $filenamePrefixBlacklist as $prefix ) { + if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { + $warning .= '
    • ' . wfMsgExt( 'filename-bad-prefix', 'parseinline', $prefix ) . '
    • '; + break; + } + } + + if ( $file->wasDeleted() && !$file->exists() ) { + # If the file existed before and was deleted, warn the user of this + # Don't bother doing so if the file exists now, however + $ltitle = SpecialPage::getTitleFor( 'Log' ); + $llink = $sk->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ), + 'type=delete&page=' . $file->getTitle()->getPrefixedUrl() ); + $warning .= '
    • ' . wfMsgWikiHtml( 'filewasdeleted', $llink ) . '
    • '; + } + return $warning; + } + + /** + * Get a list of warnings + * + * @param string local filename, e.g. 'file exists', 'non-descriptive filename' + * @return array list of warning messages + */ + static function ajaxGetExistsWarning( $filename ) { + $file = wfFindFile( $filename ); + if( !$file ) { + // Force local file so we have an object to do further checks against + // if there isn't an exact match... + $file = wfLocalFile( $filename ); + } + $s = ' '; + if ( $file ) { + $warning = self::getExistsWarning( $file ); + if ( $warning !== '' ) { + $s = "
        $warning
      "; + } + } + return $s; + } + + /** + * Render a preview of a given license for the AJAX preview on upload + * + * @param string $license + * @return string + */ + public static function ajaxGetLicensePreview( $license ) { + global $wgParser, $wgUser; + $text = '{{' . $license . '}}'; + $title = Title::makeTitle( NS_IMAGE, 'Sample.jpg' ); + $options = ParserOptions::newFromUser( $wgUser ); + + // Expand subst: first, then live templates... + $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options ); + $output = $wgParser->parse( $text, $title, $options ); + + return $output->getText(); + } + + /** + * Check for duplicate files and throw up a warning before the upload + * completes. + */ + function getDupeWarning( $tempfile ) { + $hash = File::sha1Base36( $tempfile ); + $dupes = RepoGroup::singleton()->findBySha1( $hash ); + if( $dupes ) { + global $wgOut; + $msg = ""; + foreach( $dupes as $file ) { + $title = $file->getTitle(); + $msg .= $title->getPrefixedText() . + "|" . $title->getText() . "\n"; + } + $msg .= ""; + return "
    • " . + wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) . + $wgOut->parse( $msg ) . + "
    • \n"; + } else { + return ''; + } + } + + /** + * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]] + * + * @return array list of prefixes + */ + public static function getFilenamePrefixBlacklist() { + $blacklist = array(); + $message = wfMsgForContent( 'filename-prefix-blacklist' ); + if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) { + $lines = explode( "\n", $message ); + foreach( $lines as $line ) { + // Remove comment lines + $comment = substr( trim( $line ), 0, 1 ); + if ( $comment == '#' || $comment == '' ) { + continue; + } + // Remove additional comments after a prefix + $comment = strpos( $line, '#' ); + if ( $comment > 0 ) { + $line = substr( $line, 0, $comment-1 ); + } + $blacklist[] = trim( $line ); + } + } + return $blacklist; + } + + /** + * Stash a file in a temporary directory for later processing + * after the user has confirmed it. + * + * If the user doesn't explicitly cancel or accept, these files + * can accumulate in the temp directory. + * + * @param string $saveName - the destination filename + * @param string $tempName - the source temporary file to save + * @return string - full path the stashed file, or false on failure + * @access private + */ + function saveTempUploadedFile( $saveName, $tempName ) { + global $wgOut; + $repo = RepoGroup::singleton()->getLocalRepo(); + $status = $repo->storeTemp( $saveName, $tempName ); + if ( !$status->isGood() ) { + $this->showError( $status->getWikiText() ); + return false; + } else { + return $status->value; + } + } + + /** + * Stash a file in a temporary directory for later processing, + * and save the necessary descriptive info into the session. + * Returns a key value which will be passed through a form + * to pick up the path info on a later invocation. + * + * @return int + * @access private + */ + function stashSession() { + $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); + + if( !$stash ) { + # Couldn't save the file. + return false; + } + + $key = mt_rand( 0, 0x7fffffff ); + $_SESSION['wsUploadData'][$key] = array( + 'mTempPath' => $stash, + 'mFileSize' => $this->mFileSize, + 'mSrcName' => $this->mSrcName, + 'mFileProps' => $this->mFileProps, + 'version' => self::SESSION_VERSION, + ); + return $key; + } + + /** + * Remove a temporarily kept file stashed by saveTempUploadedFile(). + * @access private + * @return success + */ + function unsaveUploadedFile() { + global $wgOut; + $repo = RepoGroup::singleton()->getLocalRepo(); + $success = $repo->freeTemp( $this->mTempPath ); + if ( ! $success ) { + $wgOut->showFileDeleteError( $this->mTempPath ); + return false; + } else { + return true; + } + } + + /* -------------------------------------------------------------- */ + + /** + * @param string $error as HTML + * @access private + */ + function uploadError( $error ) { + global $wgOut; + $wgOut->addHTML( '

      ' . wfMsgHtml( 'uploadwarning' ) . "

      \n" ); + $wgOut->addHTML( '' . $error . '' ); + } + + /** + * There's something wrong with this file, not enough to reject it + * totally but we require manual intervention to save it for real. + * Stash it away, then present a form asking to confirm or cancel. + * + * @param string $warning as HTML + * @access private + */ + function uploadWarning( $warning ) { + global $wgOut; + global $wgUseCopyrightUpload; + + $this->mSessionKey = $this->stashSession(); + if( !$this->mSessionKey ) { + # Couldn't save file; an error has been displayed so let's go. + return; + } + + $wgOut->addHTML( '

      ' . wfMsgHtml( 'uploadwarning' ) . "

      \n" ); + $wgOut->addHTML( '
        ' . $warning . "
      \n" ); + + $titleObj = SpecialPage::getTitleFor( 'Upload' ); + + if ( $wgUseCopyrightUpload ) { + $copyright = Xml::hidden( 'wpUploadCopyStatus', $this->mCopyrightStatus ) . "\n" . + Xml::hidden( 'wpUploadSource', $this->mCopyrightSource ) . "\n"; + } else { + $copyright = ''; + } + + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), + 'enctype' => 'multipart/form-data', 'id' => 'uploadwarning' ) ) . "\n" . + Xml::hidden( 'wpIgnoreWarning', '1' ) . "\n" . + Xml::hidden( 'wpSessionKey', $this->mSessionKey ) . "\n" . + Xml::hidden( 'wpUploadDescription', $this->mComment ) . "\n" . + Xml::hidden( 'wpLicense', $this->mLicense ) . "\n" . + Xml::hidden( 'wpDestFile', $this->mDesiredDestName ) . "\n" . + Xml::hidden( 'wpWatchthis', $this->mWatchthis ) . "\n" . + "{$copyright}
      " . + Xml::submitButton( wfMsg( 'ignorewarning' ), array ( 'name' => 'wpUpload', 'id' => 'wpUpload', 'checked' => 'checked' ) ) . ' ' . + Xml::submitButton( wfMsg( 'reuploaddesc' ), array ( 'name' => 'wpReUpload', 'id' => 'wpReUpload' ) ) . + Xml::closeElement( 'form' ) . "\n" + ); + } + + /** + * Displays the main upload form, optionally with a highlighted + * error message up at the top. + * + * @param string $msg as HTML + * @access private + */ + function mainUploadForm( $msg='' ) { + global $wgOut, $wgUser, $wgLang, $wgMaxUploadSize; + global $wgUseCopyrightUpload, $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview; + global $wgRequest, $wgAllowCopyUploads; + global $wgStylePath, $wgStyleVersion; + + $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck; + $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview; + + $adc = wfBoolToStr( $useAjaxDestCheck ); + $alp = wfBoolToStr( $useAjaxLicensePreview ); + $autofill = wfBoolToStr( $this->mDesiredDestName == '' ); + + $wgOut->addScript( "" ); + $wgOut->addScriptFile( 'upload.js' ); + $wgOut->addScriptFile( 'edit.js' ); // For support + + if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) + { + wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); + return false; + } + + if( $this->mDesiredDestName ) { + $title = Title::makeTitleSafe( NS_IMAGE, $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( + $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted', + array( 'parse', 'replaceafter' ), + $wgUser->getSkin()->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), + wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count ) + ) + ); + $wgOut->addHtml( "
      {$link}
      " ); + } + + // Show the relevant lines from deletion log (for still deleted files only) + if( $title instanceof Title && $title->isDeleted() > 0 && !$title->exists() ) { + $this->showDeletionLog( $wgOut, $title->getPrefixedText() ); + } + } + + $cols = intval($wgUser->getOption( 'cols' )); + + if( $wgUser->getOption( 'editwidth' ) ) { + $width = " style=\"width:100%\""; + } else { + $width = ''; + } + + if ( '' != $msg ) { + $sub = wfMsgHtml( 'uploaderror' ); + $wgOut->addHTML( "

      {$sub}

      \n" . + "{$msg}\n" ); + } + $wgOut->addHTML( '
      ' ); + $wgOut->addWikiMsg( 'uploadtext', $this->mDesiredDestName ); + $wgOut->addHTML( "
      \n" ); + + # Print a list of allowed file extensions, if so configured. We ignore + # MIME type here, it's incomprehensible to most people and too long. + global $wgCheckFileExtensions, $wgStrictFileExtensions, + $wgFileExtensions, $wgFileBlacklist; + + $allowedExtensions = ''; + if( $wgCheckFileExtensions ) { + $delim = wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ); + if( $wgStrictFileExtensions ) { + # Everything not permitted is banned + $extensionsList = + '
      ' . + wfMsgWikiHtml( 'upload-permitted', implode( $wgFileExtensions, $delim ) ) . + "
      \n"; + } else { + # We have to list both preferred and prohibited + $extensionsList = + '
      ' . + wfMsgWikiHtml( 'upload-preferred', implode( $wgFileExtensions, $delim ) ) . + "
      \n" . + '
      ' . + wfMsgWikiHtml( 'upload-prohibited', implode( $wgFileBlacklist, $delim ) ) . + "
      \n"; + } + } else { + # Everything is permitted. + $extensionsList = ''; + } + + # Get the maximum file size from php.ini as $wgMaxUploadSize works for uploads from URL via CURL only + # See http://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize for possible values of upload_max_filesize + $val = trim( ini_get( 'upload_max_filesize' ) ); + $last = strtoupper( ( substr( $val, -1 ) ) ); + switch( $last ) { + case 'G': + $val2 = substr( $val, 0, -1 ) * 1024 * 1024 * 1024; + break; + case 'M': + $val2 = substr( $val, 0, -1 ) * 1024 * 1024; + break; + case 'K': + $val2 = substr( $val, 0, -1 ) * 1024; + break; + default: + $val2 = $val; + } + $val2 = $wgAllowCopyUploads ? min( $wgMaxUploadSize, $val2 ) : $val2; + $maxUploadSize = '
      ' . + wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ), + $wgLang->formatSize( $val2 ) ) . + "
      \n"; + + $sourcefilename = wfMsgExt( 'sourcefilename', array( 'parseinline', 'escapenoentities' ) ); + $destfilename = wfMsgExt( 'destfilename', array( 'parseinline', 'escapenoentities' ) ); + + $summary = wfMsgExt( 'fileuploadsummary', 'parseinline' ); + + $licenses = new Licenses(); + $license = wfMsgExt( 'license', array( 'parseinline' ) ); + $nolicense = wfMsgHtml( 'nolicense' ); + $licenseshtml = $licenses->getHtml(); + + $ulb = wfMsgHtml( 'uploadbtn' ); + + + $titleObj = SpecialPage::getTitleFor( 'Upload' ); + + $encDestName = htmlspecialchars( $this->mDesiredDestName ); + + $watchChecked = $this->watchCheck() + ? 'checked="checked"' + : ''; + $warningChecked = $this->mIgnoreWarning ? 'checked' : ''; + + // Prepare form for upload or upload/copy + if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) { + $filename_form = + "" . + "" . + wfMsgHTML( 'upload_source_file' ) . "
      " . + "" . + "" . + wfMsgHtml( 'upload_source_url' ) ; + } else { + $filename_form = + "mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . + "size='60' />" . + "" ; + } + if ( $useAjaxDestCheck ) { + $warningRow = " "; + $destOnkeyup = 'onkeyup="wgUploadWarningObj.keypress();"'; + } else { + $warningRow = ''; + $destOnkeyup = ''; + } + + $encComment = htmlspecialchars( $this->mComment ); + + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL(), + 'enctype' => 'multipart/form-data', 'id' => 'mw-upload-form' ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'upload' ) ) . + Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-upload-table' ) ) . + " + {$this->uploadFormTextTop} + + + + + {$filename_form} + + + + + + {$maxUploadSize} + {$extensionsList} + + + + + + + + + + + + + + + + + {$this->uploadFormTextAfterSummary} + + + " + ); + + if ( $licenseshtml != '' ) { + global $wgStylePath; + $wgOut->addHTML( " + + + + + + + + " + ); + if( $useAjaxLicensePreview ) { + $wgOut->addHtml( " + + + + " + ); + } + } + + if ( $wgUseCopyrightUpload ) { + $filestatus = wfMsgExt( 'filestatus', 'escapenoentities' ); + $copystatus = htmlspecialchars( $this->mCopyrightStatus ); + $filesource = wfMsgExt( 'filesource', 'escapenoentities' ); + $uploadsource = htmlspecialchars( $this->mCopyrightSource ); + + $wgOut->addHTML( " + + + + + + + + + + + + + + + " + ); + } + + $wgOut->addHtml( " + + + + + + + + + $warningRow + + + + getSkin()->tooltipAndAccesskey( 'upload' ) . " /> + + + + + " + ); + $wgOut->addWikiText( wfMsgForContent( 'edittools' ) ); + $wgOut->addHTML( " + + " . + Xml::closeElement( 'table' ) . + Xml::hidden( 'wpDestFileWarningAck', '', array( 'id' => 'wpDestFileWarningAck' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); + $uploadfooter = wfMsgNoTrans( 'uploadfooter' ); + if( $uploadfooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadfooter ) ){ + $wgOut->addWikiText( '' ); + } + } + + /* -------------------------------------------------------------- */ + + /** + * See if we should check the 'watch this page' checkbox on the form + * based on the user's preferences and whether we're being asked + * to create a new file or update an existing one. + * + * In the case where 'watch edits' is off but 'watch creations' is on, + * we'll leave the box unchecked. + * + * Note that the page target can be changed *on the form*, so our check + * state can get out of sync. + */ + function watchCheck() { + global $wgUser; + if( $wgUser->getOption( 'watchdefault' ) ) { + // Watch all edits! + return true; + } + + $local = wfLocalFile( $this->mDesiredDestName ); + if( $local && $local->exists() ) { + // We're uploading a new version of an existing file. + // No creation, so don't watch it if we're not already. + return $local->getTitle()->userIsWatching(); + } else { + // New page should get watched if that's our option. + return $wgUser->getOption( 'watchcreations' ); + } + } + + /** + * Split a file into a base name and all dot-delimited 'extensions' + * on the end. Some web server configurations will fall back to + * earlier pseudo-'extensions' to determine type and execute + * scripts, so the blacklist needs to check them all. + * + * @return array + */ + function splitExtensions( $filename ) { + $bits = explode( '.', $filename ); + $basename = array_shift( $bits ); + return array( $basename, $bits ); + } + + /** + * Perform case-insensitive match against a list of file extensions. + * Returns true if the extension is in the list. + * + * @param string $ext + * @param array $list + * @return bool + */ + function checkFileExtension( $ext, $list ) { + return in_array( strtolower( $ext ), $list ); + } + + /** + * Perform case-insensitive match against a list of file extensions. + * Returns true if any of the extensions are in the list. + * + * @param array $ext + * @param array $list + * @return bool + */ + function checkFileExtensionList( $ext, $list ) { + foreach( $ext as $e ) { + if( in_array( strtolower( $e ), $list ) ) { + return true; + } + } + return false; + } + + /** + * Verifies that it's ok to include the uploaded file + * + * @param string $tmpfile the full path of the temporary file to verify + * @param string $extension The filename extension that the file is to be served with + * @return mixed true of the file is verified, a WikiError object otherwise. + */ + function verify( $tmpfile, $extension ) { + #magically determine mime type + $magic = MimeMagic::singleton(); + $mime = $magic->guessMimeType($tmpfile,false); + + #check mime type, if desired + global $wgVerifyMimeType; + if ($wgVerifyMimeType) { + + wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n"); + #check mime type against file extension + if( !$this->verifyExtension( $mime, $extension ) ) { + return new WikiErrorMsg( 'uploadcorrupt' ); + } + + #check mime type blacklist + global $wgMimeTypeBlacklist; + if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) + && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { + return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) ); + } + } + + #check for htmlish code and javascript + if( $this->detectScript ( $tmpfile, $mime, $extension ) ) { + return new WikiErrorMsg( 'uploadscripted' ); + } + + /** + * Scan the uploaded file for viruses + */ + $virus= $this->detectVirus($tmpfile); + if ( $virus ) { + return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) ); + } + + wfDebug( __METHOD__.": all clear; passing.\n" ); + return true; + } + + /** + * Checks if the mime type of the uploaded file matches the file extension. + * + * @param string $mime the mime type of the uploaded file + * @param string $extension The filename extension that the file is to be served with + * @return bool + */ + function verifyExtension( $mime, $extension ) { + $magic = MimeMagic::singleton(); + + if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) + if ( ! $magic->isRecognizableExtension( $extension ) ) { + wfDebug( __METHOD__.": passing file with unknown detected mime type; " . + "unrecognized extension '$extension', can't verify\n" ); + return true; + } else { + wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ". + "recognized extension '$extension', so probably invalid file\n" ); + return false; + } + + $match= $magic->isMatchingExtension($extension,$mime); + + if ($match===NULL) { + wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" ); + return true; + } elseif ($match===true) { + wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" ); + + #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it! + return true; + + } else { + wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" ); + return false; + } + } + + /** + * Heuristic for detecting files that *could* contain JavaScript instructions or + * things that may look like HTML to a browser and are thus + * potentially harmful. The present implementation will produce false positives in some situations. + * + * @param string $file Pathname to the temporary upload file + * @param string $mime The mime type of the file + * @param string $extension The extension of the file + * @return bool true if the file contains something looking like embedded scripts + */ + function detectScript($file, $mime, $extension) { + global $wgAllowTitlesInSVG; + + #ugly hack: for text files, always look at the entire file. + #For binarie field, just check the first K. + + if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file ); + else { + $fp = fopen( $file, 'rb' ); + $chunk = fread( $fp, 1024 ); + fclose( $fp ); + } + + $chunk= strtolower( $chunk ); + + if (!$chunk) return false; + + #decode from UTF-16 if needed (could be used for obfuscation). + if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE"; + elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE"; + else $enc= NULL; + + if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk); + + $chunk= trim($chunk); + + #FIXME: convert from UTF-16 if necessarry! + + wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n"); + + #check for HTML doctype + if (eregi("addHTML( "
      Bad configuration: unknown virus scanner: $wgAntivirus
      \n" ); + return "unknown antivirus: $wgAntivirus"; + } + + # look up scanner configuration + $command = $wgAntivirusSetup[$wgAntivirus]["command"]; + $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"]; + $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ? + $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null; + + if ( strpos( $command,"%f" ) === false ) { + # simple pattern: append file to scan + $command .= " " . wfEscapeShellArg( $file ); + } else { + # complex pattern: replace "%f" with file to scan + $command = str_replace( "%f", wfEscapeShellArg( $file ), $command ); + } + + wfDebug( __METHOD__.": running virus scan: $command \n" ); + + # execute virus scanner + $exitCode = false; + + #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too. + # that does not seem to be worth the pain. + # Ask me (Duesentrieb) about it if it's ever needed. + $output = array(); + if ( wfIsWindows() ) { + exec( "$command", $output, $exitCode ); + } else { + exec( "$command 2>&1", $output, $exitCode ); + } + + # map exit code to AV_xxx constants. + $mappedCode = $exitCode; + if ( $exitCodeMap ) { + if ( isset( $exitCodeMap[$exitCode] ) ) { + $mappedCode = $exitCodeMap[$exitCode]; + } elseif ( isset( $exitCodeMap["*"] ) ) { + $mappedCode = $exitCodeMap["*"]; + } + } + + if ( $mappedCode === AV_SCAN_FAILED ) { + # scan failed (code was mapped to false by $exitCodeMap) + wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" ); + + if ( $wgAntivirusRequired ) { + return "scan failed (code $exitCode)"; + } else { + return NULL; + } + } else if ( $mappedCode === AV_SCAN_ABORTED ) { + # scan failed because filetype is unknown (probably imune) + wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" ); + return NULL; + } else if ( $mappedCode === AV_NO_VIRUS ) { + # no virus found + wfDebug( __METHOD__.": file passed virus scan.\n" ); + return false; + } else { + $output = join( "\n", $output ); + $output = trim( $output ); + + if ( !$output ) { + $output = true; #if there's no output, return true + } elseif ( $msgPattern ) { + $groups = array(); + if ( preg_match( $msgPattern, $output, $groups ) ) { + if ( $groups[1] ) { + $output = $groups[1]; + } + } + } + + wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output" ); + return $output; + } + } + + /** + * Check if the temporary file is MacBinary-encoded, as some uploads + * from Internet Explorer on Mac OS Classic and Mac OS X will be. + * If so, the data fork will be extracted to a second temporary file, + * which will then be checked for validity and either kept or discarded. + * + * @access private + */ + function checkMacBinary() { + $macbin = new MacBinary( $this->mTempPath ); + if( $macbin->isValid() ) { + $dataFile = tempnam( wfTempDir(), "WikiMacBinary" ); + $dataHandle = fopen( $dataFile, 'wb' ); + + wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" ); + $macbin->extractData( $dataHandle ); + + $this->mTempPath = $dataFile; + $this->mFileSize = $macbin->dataForkLength(); + + // We'll have to manually remove the new file if it's not kept. + $this->mRemoveTempFile = true; + } + $macbin->close(); + } + + /** + * If we've modified the upload file we need to manually remove it + * on exit to clean up. + * @access private + */ + function cleanupTempFile() { + if ( $this->mRemoveTempFile && file_exists( $this->mTempPath ) ) { + wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file {$this->mTempPath}\n" ); + unlink( $this->mTempPath ); + } + } + + /** + * Check if there's an overwrite conflict and, if so, if restrictions + * forbid this user from performing the upload. + * + * @return mixed true on success, WikiError on failure + * @access private + */ + function checkOverwrite( $name ) { + $img = wfFindFile( $name ); + + $error = ''; + if( $img ) { + global $wgUser, $wgOut; + if( $img->isLocal() ) { + if( !self::userCanReUpload( $wgUser, $img->name ) ) { + $error = 'fileexists-forbidden'; + } + } else { + if( !$wgUser->isAllowed( 'reupload' ) || + !$wgUser->isAllowed( 'reupload-shared' ) ) { + $error = "fileexists-shared-forbidden"; + } + } + } + + if( $error ) { + $errorText = wfMsg( $error, wfEscapeWikiText( $img->getName() ) ); + return $errorText; + } + + // Rockin', go ahead and upload + return true; + } + + /** + * Check if a user is the last uploader + * + * @param User $user + * @param string $img, image name + * @return bool + */ + public static function userCanReUpload( User $user, $img ) { + if( $user->isAllowed( 'reupload' ) ) + return true; // non-conditional + if( !$user->isAllowed( 'reupload-own' ) ) + return false; + + $dbr = wfGetDB( DB_SLAVE ); + $row = $dbr->selectRow('image', + /* SELECT */ 'img_user', + /* WHERE */ array( 'img_name' => $img ) + ); + if ( !$row ) + return false; + + return $user->getId() == $row->img_user; + } + + /** + * Display an error with a wikitext description + */ + function showError( $description ) { + global $wgOut; + $wgOut->setPageTitle( wfMsg( "internalerror" ) ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->setArticleRelated( false ); + $wgOut->enableClientCache( false ); + $wgOut->addWikiText( $description ); + } + + /** + * Get the initial image page text based on a comment and optional file status information + */ + static function getInitialPageText( $comment, $license, $copyStatus, $source ) { + global $wgUseCopyrightUpload; + if ( $wgUseCopyrightUpload ) { + if ( $license != '' ) { + $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + } + $pageText = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n" . + '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" . + "$licensetxt" . + '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ; + } else { + if ( $license != '' ) { + $filedesc = $comment == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n"; + $pageText = $filedesc . + '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + } else { + $pageText = $comment; + } + } + return $pageText; + } + + /** + * If there are rows in the deletion log for this file, show them, + * along with a nice little note for the user + * + * @param OutputPage $out + * @param string filename + */ + private function showDeletionLog( $out, $filename ) { + global $wgUser; + $loglist = new LogEventsList( $wgUser->getSkin(), $out ); + $pager = new LogPager( $loglist, 'delete', false, $filename ); + if( $pager->getNumRows() > 0 ) { + $out->addHtml( '
      ' ); + $out->addWikiMsg( 'upload-wasdeleted' ); + $out->addHTML( + $loglist->beginLogEventsList() . + $pager->getBody() . + $loglist->endLogEventsList() + ); + $out->addHtml( '
      ' ); + } + } +} diff --git a/includes/specials/SpecialUploadMogile.php b/includes/specials/SpecialUploadMogile.php new file mode 100644 index 0000000000..7ff8fda663 --- /dev/null +++ b/includes/specials/SpecialUploadMogile.php @@ -0,0 +1,135 @@ +execute(); +} + +/** + * Extends Special:Upload with MogileFS. + * @ingroup SpecialPage + */ +class UploadFormMogile extends UploadForm { + /** + * Move the uploaded file from its temporary location to the final + * destination. If a previous version of the file exists, move + * it into the archive subdirectory. + * + * @todo If the later save fails, we may have disappeared the original file. + * + * @param string $saveName + * @param string $tempName full path to the temporary file + * @param bool $useRename Not used in this implementation + */ + function saveUploadedFile( $saveName, $tempName, $useRename = false ) { + global $wgOut; + $mfs = MogileFS::NewMogileFS(); + + $this->mSavedFile = "image!{$saveName}"; + + if( $mfs->getPaths( $this->mSavedFile )) { + $this->mUploadOldVersion = gmdate( 'YmdHis' ) . "!{$saveName}"; + if( !$mfs->rename( $this->mSavedFile, "archive!{$this->mUploadOldVersion}" ) ) { + $wgOut->showFileRenameError( $this->mSavedFile, + "archive!{$this->mUploadOldVersion}" ); + return false; + } + } else { + $this->mUploadOldVersion = ''; + } + + if ( $this->mStashed ) { + if (!$mfs->rename($tempName,$this->mSavedFile)) { + $wgOut->showFileRenameError($tempName, $this->mSavedFile ); + return false; + } + } else { + if ( !$mfs->saveFile($this->mSavedFile,'normal',$tempName )) { + $wgOut->showFileCopyError( $tempName, $this->mSavedFile ); + return false; + } + unlink($tempName); + } + return true; + } + + /** + * Stash a file in a temporary directory for later processing + * after the user has confirmed it. + * + * If the user doesn't explicitly cancel or accept, these files + * can accumulate in the temp directory. + * + * @param string $saveName - the destination filename + * @param string $tempName - the source temporary file to save + * @return string - full path the stashed file, or false on failure + * @access private + */ + function saveTempUploadedFile( $saveName, $tempName ) { + global $wgOut; + + $stash = 'stash!' . gmdate( "YmdHis" ) . '!' . $saveName; + $mfs = MogileFS::NewMogileFS(); + if ( !$mfs->saveFile( $stash, 'normal', $tempName ) ) { + $wgOut->showFileCopyError( $tempName, $stash ); + return false; + } + unlink($tempName); + return $stash; + } + + /** + * Stash a file in a temporary directory for later processing, + * and save the necessary descriptive info into the session. + * Returns a key value which will be passed through a form + * to pick up the path info on a later invocation. + * + * @return int + * @access private + */ + function stashSession() { + $stash = $this->saveTempUploadedFile( + $this->mUploadSaveName, $this->mUploadTempName ); + + if( !$stash ) { + # Couldn't save the file. + return false; + } + + $key = mt_rand( 0, 0x7fffffff ); + $_SESSION['wsUploadData'][$key] = array( + 'mUploadTempName' => $stash, + 'mUploadSize' => $this->mUploadSize, + 'mOname' => $this->mOname ); + return $key; + } + + /** + * Remove a temporarily kept file stashed by saveTempUploadedFile(). + * @access private + * @return success + */ + function unsaveUploadedFile() { + global $wgOut; + $mfs = MogileFS::NewMogileFS(); + if ( ! $mfs->delete( $this->mUploadTempName ) ) { + $wgOut->showFileDeleteError( $this->mUploadTempName ); + return false; + } else { + return true; + } + } +} diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php new file mode 100644 index 0000000000..179ef3f9d2 --- /dev/null +++ b/includes/specials/SpecialUserlogin.php @@ -0,0 +1,928 @@ +execute(); +} + +/** + * implements Special:Login + * @ingroup SpecialPage + */ +class LoginForm { + + const SUCCESS = 0; + const NO_NAME = 1; + const ILLEGAL = 2; + const WRONG_PLUGIN_PASS = 3; + const NOT_EXISTS = 4; + const WRONG_PASS = 5; + const EMPTY_PASS = 6; + const RESET_PASS = 7; + const ABORTED = 8; + const CREATE_BLOCKED = 9; + + var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; + var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; + var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage, $mSkipCookieCheck; + + /** + * Constructor + * @param WebRequest $request A WebRequest object passed by reference + */ + function LoginForm( &$request, $par = '' ) { + global $wgLang, $wgAllowRealName, $wgEnableEmail; + global $wgAuth; + + $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]] + $this->mName = $request->getText( 'wpName' ); + $this->mPassword = $request->getText( 'wpPassword' ); + $this->mRetype = $request->getText( 'wpRetype' ); + $this->mDomain = $request->getText( 'wpDomain' ); + $this->mReturnTo = $request->getVal( 'returnto' ); + $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); + $this->mPosted = $request->wasPosted(); + $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); + $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) + && $wgEnableEmail; + $this->mMailmypassword = $request->getCheck( 'wpMailmypassword' ) + && $wgEnableEmail; + $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); + $this->mAction = $request->getVal( 'action' ); + $this->mRemember = $request->getCheck( 'wpRemember' ); + $this->mLanguage = $request->getText( 'uselang' ); + $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); + + if( $wgEnableEmail ) { + $this->mEmail = $request->getText( 'wpEmail' ); + } else { + $this->mEmail = ''; + } + if( $wgAllowRealName ) { + $this->mRealName = $request->getText( 'wpRealName' ); + } else { + $this->mRealName = ''; + } + + if( !$wgAuth->validDomain( $this->mDomain ) ) { + $this->mDomain = 'invaliddomain'; + } + $wgAuth->setDomain( $this->mDomain ); + + # When switching accounts, it sucks to get automatically logged out + if( $this->mReturnTo == $wgLang->specialPage( 'Userlogout' ) ) { + $this->mReturnTo = ''; + } + } + + function execute() { + if ( !is_null( $this->mCookieCheck ) ) { + $this->onCookieRedirectCheck( $this->mCookieCheck ); + return; + } else if( $this->mPosted ) { + if( $this->mCreateaccount ) { + return $this->addNewAccount(); + } else if ( $this->mCreateaccountMail ) { + return $this->addNewAccountMailPassword(); + } else if ( $this->mMailmypassword ) { + return $this->mailPassword(); + } else if ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { + return $this->processLogin(); + } + } + $this->mainLoginForm( '' ); + } + + /** + * @private + */ + function addNewAccountMailPassword() { + global $wgOut; + + if ('' == $this->mEmail) { + $this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) ); + return; + } + + $u = $this->addNewaccountInternal(); + + if ($u == NULL) { + return; + } + + // Wipe the initial password and mail a temporary one + $u->setPassword( null ); + $u->saveSettings(); + $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); + + wfRunHooks( 'AddNewAccount', array( $u, true ) ); + + $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setArticleRelated( false ); + + if( WikiError::isError( $result ) ) { + $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); + } else { + $wgOut->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); + $wgOut->returnToMain( false ); + } + $u = 0; + } + + + /** + * @private + */ + function addNewAccount() { + global $wgUser, $wgEmailAuthentication; + + # Create the account and abort if there's a problem doing so + $u = $this->addNewAccountInternal(); + if( $u == NULL ) + return; + + # If we showed up language selection links, and one was in use, be + # smart (and sensible) and save that language as the user's preference + global $wgLoginLanguageSelector; + if( $wgLoginLanguageSelector && $this->mLanguage ) + $u->setOption( 'language', $this->mLanguage ); + + # Send out an email authentication message if needed + if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) { + global $wgOut; + $error = $u->sendConfirmationMail(); + if( WikiError::isError( $error ) ) { + $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() ); + } else { + $wgOut->addWikiMsg( 'confirmemail_oncreate' ); + } + } + + # 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( $wgUser->isAnon() ) { + $wgUser = $u; + $wgUser->setCookies(); + wfRunHooks( 'AddNewAccount', array( $wgUser ) ); + if( $this->hasSessionCookie() ) { + return $this->successfulLogin( wfMsg( 'welcomecreation', $wgUser->getName() ), false ); + } else { + return $this->cookieRedirectCheck( 'new' ); + } + } else { + # Confirm that the account was created + global $wgOut; + $self = SpecialPage::getTitleFor( 'Userlogin' ); + $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) ); + $wgOut->setArticleRelated( false ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + $wgOut->addHtml( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); + $wgOut->returnToMain( false, $self ); + wfRunHooks( 'AddNewAccount', array( $u ) ); + return true; + } + } + + /** + * @private + */ + function addNewAccountInternal() { + global $wgUser, $wgOut; + global $wgEnableSorbs, $wgProxyWhitelist; + global $wgMemc, $wgAccountCreationThrottle; + global $wgAuth, $wgMinimalPasswordLength; + global $wgEmailConfirmToEdit; + + // If the user passes an invalid domain, something is fishy + if( !$wgAuth->validDomain( $this->mDomain ) ) { + $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); + 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( 'local' != $this->mDomain && '' != $this->mDomain ) { + if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) { + $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); + return false; + } + } + + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return false; + } + + # Check permissions + if ( !$wgUser->isAllowed( 'createaccount' ) ) { + $this->userNotPrivilegedMessage(); + return false; + } elseif ( $wgUser->isBlockedFromCreateAccount() ) { + $this->userBlockedMessage(); + return false; + } + + $ip = wfGetIP(); + if ( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) && + $wgUser->inSorbsBlacklist( $ip ) ) + { + $this->mainLoginForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' ); + return; + } + + # Now create a dummy user ($u) and check if it is valid + $name = trim( $this->mName ); + $u = User::newFromName( $name, 'creatable' ); + if ( is_null( $u ) ) { + $this->mainLoginForm( wfMsg( 'noname' ) ); + return false; + } + + if ( 0 != $u->idForName() ) { + $this->mainLoginForm( wfMsg( 'userexists' ) ); + return false; + } + + if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) { + $this->mainLoginForm( wfMsg( 'badretype' ) ); + return false; + } + + # check for minimal password length + if ( !$u->isValidPassword( $this->mPassword ) ) { + if ( !$this->mCreateaccountMail ) { + $this->mainLoginForm( wfMsgExt( 'passwordtooshort', array( 'parsemag' ), $wgMinimalPasswordLength ) ); + return false; + } else { + # do not force a password for account creation by email + # set invalid password, it will be replaced later by a random generated password + $this->mPassword = null; + } + } + + # 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; + } + + if( !empty( $this->mEmail ) && !User::isValidEmailAddr( $this->mEmail ) ) { + $this->mainLoginForm( wfMsg( 'invalidemailaddress' ) ); + return false; + } + + # 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 ); + + $abortError = ''; + if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { + // Hook point to add extra creation throttles and blocks + wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); + $this->mainLoginForm( $abortError ); + return false; + } + + if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) { + $key = wfMemcKey( 'acctcreate', 'ip', $ip ); + $value = $wgMemc->incr( $key ); + if ( !$value ) { + $wgMemc->set( $key, 1, 86400 ); + } + if ( $value > $wgAccountCreationThrottle ) { + $this->throttleHit( $wgAccountCreationThrottle ); + return false; + } + } + + if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { + $this->mainLoginForm( wfMsg( 'externaldberror' ) ); + return false; + } + + return $this->initUser( $u, false ); + } + + /** + * Actually add a user to the database. + * Give it a User object that has been initialised with a name. + * + * @param $u User object. + * @param $autocreate boolean -- true if this is an autocreation via auth plugin + * @return User object. + * @private + */ + function initUser( $u, $autocreate ) { + global $wgAuth; + + $u->addToDatabase(); + + if ( $wgAuth->allowPasswordChange() ) { + $u->setPassword( $this->mPassword ); + } + + $u->setEmail( $this->mEmail ); + $u->setRealName( $this->mRealName ); + $u->setToken(); + + $wgAuth->initUser( $u, $autocreate ); + + $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); + $u->saveSettings(); + + # Update user count + $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); + $ssUpdate->doUpdate(); + + return $u; + } + + /** + * Internally authenticate the login request. + * + * This may create a local account as a side effect if the + * authentication plugin allows transparent local account + * creation. + * + * @public + */ + function authenticateUserData() { + global $wgUser, $wgAuth; + if ( '' == $this->mName ) { + return self::NO_NAME; + } + + // 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; + } + $u = User::newFromName( $this->mName ); + if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) { + return self::ILLEGAL; + } + + $isAutoCreated = false; + if ( 0 == $u->getID() ) { + $status = $this->attemptAutoCreate( $u ); + if ( $status !== self::SUCCESS ) { + return $status; + } else { + $isAutoCreated = true; + } + } else { + $u->load(); + } + + // Give general extensions, such as a captcha, a chance to abort logins + $abort = self::ABORTED; + if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort ) ) ) { + return $abort; + } + + 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. + // + // 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. + // + // 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. + // + 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. + // + $retval = self::RESET_PASS; + } else { + $retval = '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS; + } + } else { + $wgAuth->updateUser( $u ); + $wgUser = $u; + + if ( $isAutoCreated ) { + // Must be run after $wgUser is set, for correct new user log + wfRunHooks( 'AuthPluginAutoCreate', array( $wgUser ) ); + } + + $retval = self::SUCCESS; + } + wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) ); + return $retval; + } + + /** + * 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 ( !$wgAuth->autoCreate() ) { + return self::NOT_EXISTS; + } + if ( !$wgAuth->userExists( $user->getName() ) ) { + wfDebug( __METHOD__.": user does not exist\n" ); + return self::NOT_EXISTS; + } + if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { + wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" ); + return self::WRONG_PLUGIN_PASS; + } + if ( $wgUser->isBlockedFromCreateAccount() ) { + wfDebug( __METHOD__.": user is blocked from account creation\n" ); + return self::CREATE_BLOCKED; + } + + wfDebug( __METHOD__.": creating account\n" ); + $user = $this->initUser( $user, true ); + return self::SUCCESS; + } + + function processLogin() { + global $wgUser, $wgAuth; + + switch ($this->authenticateUserData()) + { + case self::SUCCESS: + # We've verified now, update the real record + if( (bool)$this->mRemember != (bool)$wgUser->getOption( 'rememberpassword' ) ) { + $wgUser->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); + $wgUser->saveSettings(); + } else { + $wgUser->invalidateCache(); + } + $wgUser->setCookies(); + + if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { + /* 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( wfMsg( 'loginsuccess', $wgUser->getName() ) ); + } else { + return $this->cookieRedirectCheck( 'login' ); + } + break; + + case self::NO_NAME: + case self::ILLEGAL: + $this->mainLoginForm( wfMsg( 'noname' ) ); + break; + case self::WRONG_PLUGIN_PASS: + $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); + break; + case self::NOT_EXISTS: + if( $wgUser->isAllowed( 'createaccount' ) ){ + $this->mainLoginForm( wfMsg( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); + } else { + $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) ); + } + break; + case self::WRONG_PASS: + $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); + break; + case self::EMPTY_PASS: + $this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) ); + break; + case self::RESET_PASS: + $this->resetLoginForm( wfMsg( 'resetpass_announce' ) ); + break; + case self::CREATE_BLOCKED: + $this->userBlockedMessage(); + break; + default: + throw new MWException( "Unhandled case value" ); + } + } + + function resetLoginForm( $error ) { + global $wgOut; + $wgOut->addWikiText( "
      $error
      " ); + $reset = new PasswordResetForm( $this->mName, $this->mPassword ); + $reset->execute( null ); + } + + /** + * @private + */ + function mailPassword() { + global $wgUser, $wgOut, $wgAuth; + + if( !$wgAuth->allowPasswordChange() ) { + $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); + return; + } + + # Check against blocked IPs + # fixme -- should we not? + if( $wgUser->isBlocked() ) { + $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); + return; + } + + # Check against the rate limiter + if( $wgUser->pingLimiter( 'mailpassword' ) ) { + $wgOut->rateLimited(); + return; + } + + if ( '' == $this->mName ) { + $this->mainLoginForm( wfMsg( 'noname' ) ); + return; + } + $u = User::newFromName( $this->mName ); + if( is_null( $u ) ) { + $this->mainLoginForm( wfMsg( 'noname' ) ); + return; + } + if ( 0 == $u->getID() ) { + $this->mainLoginForm( wfMsg( 'nosuchuser', $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. + $this->mainLoginForm( wfMsgExt( 'throttled-mailpassword', array( 'parsemag' ), + round( $wgPasswordReminderResendTime, 3 ) ) ); + return; + } + + $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' ); + if( WikiError::isError( $result ) ) { + $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); + } else { + $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' ); + } + } + + + /** + * @param object user + * @param bool throttle + * @param string message name of email title + * @param string message name of email text + * @return mixed true on success, WikiError on failure + * @private + */ + function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { + global $wgCookiePath, $wgCookieDomain, $wgCookiePrefix, $wgCookieSecure; + global $wgServer, $wgScript; + + if ( '' == $u->getEmail() ) { + return new WikiError( wfMsg( 'noemail', $u->getName() ) ); + } + + $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 ); + + return $result; + } + + + /** + * @param string $msg Message that will be shown on success + * @param bool $auto Toggle auto-redirect to main page; default true + * @private + */ + function successfulLogin( $msg, $auto = true ) { + global $wgUser; + global $wgOut; + + # Run any hooks; ignore results + + $injected_html = ''; + wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html)); + + $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setArticleRelated( false ); + $wgOut->addWikiText( $msg ); + $wgOut->addHtml( $injected_html ); + if ( !empty( $this->mReturnTo ) ) { + $wgOut->returnToMain( $auto, $this->mReturnTo ); + } else { + $wgOut->returnToMain( $auto ); + } + } + + /** */ + function userNotPrivilegedMessage($errors) { + global $wgOut; + + $wgOut->setPageTitle( wfMsg( 'permissionserrors' ) ); + $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. + $wgOut->addWikiMsg( 'cantcreateaccount-nonblock-text' ); + + $wgOut->returnToMain( false ); + } + + /** */ + function userBlockedMessage() { + global $wgOut, $wgUser; + + # Let's be nice about this, it's likely that this feature will be used + # for blocking large numbers of innocent people, e.g. range blocks on + # schools. Don't blame it on the user. There's a small chance that it + # really is the user's fault, i.e. the username is blocked and they + # haven't bothered to log out before trying to create an account to + # evade it, but we'll leave that to their guilty conscience to figure + # out. + + $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setArticleRelated( false ); + + $ip = wfGetIP(); + $blocker = User::whoIs( $wgUser->mBlock->mBy ); + $block_reason = $wgUser->mBlock->mReason; + + if ( strval( $block_reason ) === '' ) { + $block_reason = wfMsg( 'blockednoreason' ); + } + $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker ); + $wgOut->returnToMain( false ); + } + + /** + * @private + */ + function mainLoginForm( $msg, $msgtype = 'error' ) { + global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail; + global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector; + global $wgAuth, $wgEmailConfirmToEdit; + + $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); + + if ( $this->mType == 'signup' ) { + // Block signup here if in readonly. Keeps user from + // going through the process (filling out data, etc) + // and being informed later. + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } elseif ( $wgUser->isBlockedFromCreateAccount() ) { + $this->userBlockedMessage(); + return; + } elseif ( count( $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) { + $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' ); + return; + } + } + + if ( '' == $this->mName ) { + if ( $wgUser->isLoggedIn() ) { + $this->mName = $wgUser->getName(); + } else { + $this->mName = isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ? $_COOKIE[$wgCookiePrefix.'UserName'] : null; + } + } + + $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); + + if ( $this->mType == 'signup' ) { + $template = new UsercreateTemplate(); + $q = 'action=submitlogin&type=signup'; + $linkq = 'type=login'; + $linkmsg = 'gotaccount'; + } else { + $template = new UserloginTemplate(); + $q = 'action=submitlogin&type=login'; + $linkq = 'type=signup'; + $linkmsg = 'nologin'; + } + + if ( !empty( $this->mReturnTo ) ) { + $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); + $q .= $returnto; + $linkq .= $returnto; + } + + # Pass any language selection on to the mode switch link + if( $wgLoginLanguageSelector && $this->mLanguage ) + $linkq .= '&uselang=' . $this->mLanguage; + + $link = ''; + $link .= wfMsgHtml( $linkmsg . 'link' ); # Calling either 'gotaccountlink' or 'nologinlink' + $link .= ''; + + # Don't show a "create account" link if the user can't + if( $this->showCreateOrLoginLink( $wgUser ) ) + $template->set( 'link', wfMsgHtml( $linkmsg, $link ) ); + else + $template->set( 'link', '' ); + + $template->set( 'header', '' ); + $template->set( 'name', $this->mName ); + $template->set( 'password', $this->mPassword ); + $template->set( 'retype', $this->mRetype ); + $template->set( 'email', $this->mEmail ); + $template->set( 'realname', $this->mRealName ); + $template->set( 'domain', $this->mDomain ); + + $template->set( 'action', $titleObj->getLocalUrl( $q ) ); + $template->set( 'message', $msg ); + $template->set( 'messagetype', $msgtype ); + $template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() ); + $template->set( 'userealname', $wgAllowRealName ); + $template->set( 'useemail', $wgEnableEmail ); + $template->set( 'emailrequired', $wgEmailConfirmToEdit ); + $template->set( 'canreset', $wgAuth->allowPasswordChange() ); + $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); + + # Prepare language selection links as needed + if( $wgLoginLanguageSelector ) { + $template->set( 'languages', $this->makeLanguageSelector() ); + if( $this->mLanguage ) + $template->set( 'uselang', $this->mLanguage ); + } + + // Give authentication and captcha plugins a chance to modify the form + $wgAuth->modifyUITemplate( $template ); + if ( $this->mType == 'signup' ) { + wfRunHooks( 'UserCreateForm', array( &$template ) ); + } else { + wfRunHooks( 'UserLoginForm', array( &$template ) ); + } + + $wgOut->setPageTitle( wfMsg( 'userlogin' ) ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setArticleRelated( false ); + $wgOut->disallowUserJs(); // just in case... + $wgOut->addTemplate( $template ); + } + + /** + * @private + */ + function showCreateOrLoginLink( &$user ) { + if( $this->mType == 'signup' ) { + return( true ); + } elseif( $user->isAllowed( 'createaccount' ) ) { + return( true ); + } else { + return( false ); + } + } + + /** + * 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. + * + * @private + */ + function hasSessionCookie() { + global $wgDisableCookieCheck, $wgRequest; + return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie(); + } + + /** + * @private + */ + function cookieRedirectCheck( $type ) { + global $wgOut; + + $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); + $check = $titleObj->getFullURL( 'wpCookieCheck='.$type ); + + return $wgOut->redirect( $check ); + } + + /** + * @private + */ + function onCookieRedirectCheck( $type ) { + global $wgUser; + + if ( !$this->hasSessionCookie() ) { + if ( $type == 'new' ) { + return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) ); + } else if ( $type == 'login' ) { + return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) ); + } else { + # shouldn't happen + return $this->mainLoginForm( wfMsg( 'error' ) ); + } + } else { + return $this->successfulLogin( wfMsgExt( 'loginsuccess', array( 'parseinline' ), $wgUser->getName() ) ); + } + } + + /** + * @private + */ + function throttleHit( $limit ) { + global $wgOut; + + $wgOut->addWikiMsg( 'acct_creation_throttle_hit', $limit ); + } + + /** + * Produce a bar of links which allow the user to select another language + * during login/registration but retain "returnto" + * + * @return string + */ + function makeLanguageSelector() { + $msg = wfMsgForContent( 'loginlanguagelinks' ); + if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) { + $langs = explode( "\n", $msg ); + $links = array(); + foreach( $langs as $lang ) { + $lang = trim( $lang, '* ' ); + $parts = explode( '|', $lang ); + if (count($parts) >= 2) { + $links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] ); + } + } + return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', implode( ' | ', $links ) ) : ''; + } else { + return ''; + } + } + + /** + * Create a language selector link for a particular language + * Links back to this page preserving type and returnto + * + * @param $text Link text + * @param $lang Language code + */ + function makeLanguageSelectorLink( $text, $lang ) { + global $wgUser; + $self = SpecialPage::getTitleFor( 'Userlogin' ); + $attr[] = 'uselang=' . $lang; + if( $this->mType == 'signup' ) + $attr[] = 'type=signup'; + if( $this->mReturnTo ) + $attr[] = 'returnto=' . $this->mReturnTo; + $skin = $wgUser->getSkin(); + return $skin->makeKnownLinkObj( $self, htmlspecialchars( $text ), implode( '&', $attr ) ); + } +} diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php new file mode 100644 index 0000000000..137eadb4a6 --- /dev/null +++ b/includes/specials/SpecialUserlogout.php @@ -0,0 +1,23 @@ +getName(); + $wgUser->logout(); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + + // Hook. + $injected_html = ''; + wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName) ); + + $wgOut->addHTML( wfMsgExt( 'logouttext', array( 'parse' ) ) . $injected_html ); + $wgOut->returnToMain(); +} diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php new file mode 100644 index 0000000000..bf4d4409a4 --- /dev/null +++ b/includes/specials/SpecialUserrights.php @@ -0,0 +1,576 @@ +changeableGroups(); + return !empty( $available['add'] ) + or !empty( $available['remove'] ) + or ($this->isself and + (!empty( $available['add-self'] ) + or !empty( $available['remove-self'] ))); + } + + /** + * Manage forms to be shown according to posted data. + * Depending on the submit button used, call a form or a save function. + * + * @param $par Mixed: string if any subpage provided, else null + */ + function execute( $par ) { + // If the visitor doesn't have permissions to assign or remove + // any groups, it's a bit silly to give them the user search prompt. + global $wgUser, $wgRequest; + + if( $par ) { + $this->mTarget = $par; + } else { + $this->mTarget = $wgRequest->getVal( 'user' ); + } + + if (!$this->mTarget) { + /* + * If the user specified no target, and they can only + * edit their own groups, automatically set them as the + * target. + */ + $available = $this->changeableGroups(); + if (empty($available['add']) && empty($available['remove'])) + $this->mTarget = $wgUser->getName(); + } + + if ($this->mTarget == $wgUser->getName()) + $this->isself = true; + + if( !$this->userCanExecute( $wgUser ) ) { + // fixme... there may be intermediate groups we can mention. + global $wgOut; + $wgOut->showPermissionsErrorPage( array( + $wgUser->isAnon() + ? 'userrights-nologin' + : 'userrights-notallowed' ) ); + return; + } + + if ( wfReadOnly() ) { + global $wgOut; + $wgOut->readOnlyPage(); + return; + } + + $this->outputHeader(); + + $this->setHeaders(); + + // show the general form + $this->switchForm(); + + if( $wgRequest->wasPosted() ) { + // save settings + if( $wgRequest->getCheck( 'saveusergroups' ) ) { + $reason = $wgRequest->getVal( 'user-reason' ); + if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mTarget ) ) { + $this->saveUserGroups( + $this->mTarget, + $reason + ); + } + } + } + + // show some more forms + if( $this->mTarget ) { + $this->editUserGroupsForm( $this->mTarget ); + } + } + + /** + * Save user groups changes in the database. + * Data comes from the editUserGroupsForm() form function + * + * @param $username String: username to apply changes to. + * @param $reason String: reason for group change + * @return null + */ + function saveUserGroups( $username, $reason = '') { + global $wgRequest, $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + + $user = $this->fetchUser( $username ); + if( !$user ) { + return; + } + + $allgroups = $this->getAllGroups(); + $addgroup = array(); + $removegroup = array(); + + // This could possibly create a highly unlikely race condition if permissions are changed between + // when the form is loaded and when the form is saved. Ignoring it for the moment. + foreach ($allgroups as $group) { + // We'll tell it to remove all unchecked groups, and add all checked groups. + // Later on, this gets filtered for what can actually be removed + if ($wgRequest->getCheck( "wpGroup-$group" )) { + $addgroup[] = $group; + } else { + $removegroup[] = $group; + } + } + + // 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']; + } + + $removegroup = array_unique( + array_intersect( (array)$removegroup, $removable ) ); + $addgroup = array_unique( + array_intersect( (array)$addgroup, $addable ) ); + + $oldGroups = $user->getGroups(); + $newGroups = $oldGroups; + // remove then add groups + if( $removegroup ) { + $newGroups = array_diff($newGroups, $removegroup); + foreach( $removegroup as $group ) { + $user->removeGroup( $group ); + } + } + if( $addgroup ) { + $newGroups = array_merge($newGroups, $addgroup); + foreach( $addgroup as $group ) { + $user->addGroup( $group ); + } + } + $newGroups = array_unique( $newGroups ); + + // Ensure that caches are cleared + $user->invalidateCache(); + + wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) ); + wfDebug( 'newGroups: ' . print_r( $newGroups, true ) ); + if( $user instanceof User ) { + // hmmm + wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) ); + } + + if( $newGroups != $oldGroups ) { + $this->addLogEntry( $user, $oldGroups, $newGroups ); + } + } + + /** + * Add a rights log entry for an action. + */ + function addLogEntry( $user, $oldGroups, $newGroups ) { + global $wgRequest; + $log = new LogPage( 'rights' ); + + $log->addEntry( 'rights', + $user->getUserPage(), + $wgRequest->getText( 'user-reason' ), + array( + $this->makeGroupNameList( $oldGroups ), + $this->makeGroupNameList( $newGroups ) + ) + ); + } + + /** + * Edit user groups membership + * @param $username String: name of the user. + */ + function editUserGroupsForm( $username ) { + global $wgOut; + + $user = $this->fetchUser( $username ); + if( !$user ) { + return; + } + + $groups = $user->getGroups(); + + $this->showEditUserGroupsForm( $user, $groups ); + + // This isn't really ideal logging behavior, but let's not hide the + // interwiki logs if we're using them as is. + $this->showLogFragment( $user, $wgOut ); + } + + /** + * Normalize the input username, which may be local or remote, and + * return a user (or proxy) object for manipulating it. + * + * Side effects: error output for invalid access + * @return mixed User, UserRightsProxy, or null + */ + function fetchUser( $username ) { + global $wgOut, $wgUser; + + $parts = explode( '@', $username ); + if( count( $parts ) < 2 ) { + $name = trim( $username ); + $database = ''; + } else { + list( $name, $database ) = array_map( 'trim', $parts ); + + if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) { + $wgOut->addWikiMsg( 'userrights-no-interwiki' ); + return null; + } + if( !UserRightsProxy::validDatabase( $database ) ) { + $wgOut->addWikiMsg( 'userrights-nodatabase', $database ); + return null; + } + } + + if( $name == '' ) { + $wgOut->addWikiMsg( 'nouserspecified' ); + return false; + } + + if( $name{0} == '#' ) { + // Numeric ID can be specified... + // We'll do a lookup for the name internally. + $id = intval( substr( $name, 1 ) ); + + if( $database == '' ) { + $name = User::whoIs( $id ); + } else { + $name = UserRightsProxy::whoIs( $database, $id ); + } + + if( !$name ) { + $wgOut->addWikiMsg( 'noname' ); + return null; + } + } + + if( $database == '' ) { + $user = User::newFromName( $name ); + } else { + $user = UserRightsProxy::newFromName( $database, $name ); + } + + if( !$user || $user->isAnon() ) { + $wgOut->addWikiMsg( 'nosuchusershort', $username ); + return null; + } + + return $user; + } + + function makeGroupNameList( $ids ) { + return implode( ', ', $ids ); + } + + /** + * Output a form to allow searching for a user + */ + function switchForm() { + global $wgOut, $wgScript; + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) . + Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', array(), wfMsg( 'userrights-lookup-user' ) ) . + Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, $this->mTarget ) . ' ' . + Xml::submitButton( wfMsg( 'editusergroup' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n" + ); + } + + /** + * Go through used and available groups and return the ones that this + * form will be able to manipulate based on the current user's system + * permissions. + * + * @param $groups Array: list of groups the given user is in + * @return Array: Tuple of addable, then removable groups + */ + protected function splitGroups( $groups ) { + global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + list($addable, $removable) = array_values( $this->changeableGroups() ); + + $removable = array_intersect( + array_merge($this->isself ? $wgGroupsRemoveFromSelf : array(), $removable), + $groups ); // Can't remove groups the user doesn't have + $addable = array_diff( + array_merge($this->isself ? $wgGroupsAddToSelf : array(), $addable), + $groups ); // Can't add groups the user does have + + return array( $addable, $removable ); + } + + /** + * Show the form to edit group memberships. + * + * @param $user User or UserRightsProxy you're editing + * @param $groups Array: Array of groups the user is in + */ + protected function showEditUserGroupsForm( $user, $groups ) { + global $wgOut, $wgUser; + + list( $addable, $removable ) = $this->splitGroups( $groups ); + + $list = array(); + foreach( $user->getGroups() as $group ) + $list[] = self::buildGroupLink( $group ); + + $grouplist = ''; + if( count( $list ) > 0 ) { + $grouplist = Xml::tags( 'p', null, wfMsgHtml( 'userrights-groupsmember' ) . ' ' . implode( ', ', $list ) ); + } + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) . + Xml::hidden( 'user', $this->mTarget ) . + Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) . + wfMsgExt( 'editinguser', array( 'parse' ), wfEscapeWikiText( $user->getName() ) ) . + wfMsgExt( 'userrights-groups-help', array( 'parse' ) ) . + $grouplist . + Xml::tags( 'p', null, $this->groupCheckboxes( $groups ) ) . + Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-userrights-table-outer' ) ) . + " + " . + Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) . + " + " . + Xml::input( 'user-reason', 60, false, array( 'id' => 'wpReason', 'maxlength' => 255 ) ) . + " + + + + " . + Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups' ) ) . + " + " . + Xml::closeElement( 'table' ) . "\n" . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n" + ); + } + + /** + * Format a link to a group description page + * + * @param $group string + * @return string + */ + private static function buildGroupLink( $group ) { + static $cache = array(); + if( !isset( $cache[$group] ) ) + $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupName( $group ) ); + return $cache[$group]; + } + + /** + * Returns an array of all groups that may be edited + * @return array Array of groups that may be edited. + */ + protected static function getAllGroups() { + return User::getAllGroups(); + } + + /** + * Adds a table with checkboxes where you can select what groups to add/remove + * + * @param $usergroups Array: groups the user belongs to + * @return string XHTML table element with checkboxes + */ + private function groupCheckboxes( $usergroups ) { + $allgroups = $this->getAllGroups(); + $ret = ''; + + $column = 1; + $settable_col = ''; + $unsettable_col = ''; + + foreach ($allgroups as $group) { + $set = in_array( $group, $usergroups ); + # Should the checkbox be disabled? + $disabled = !( + ( $set && $this->canRemove( $group ) ) || + ( !$set && $this->canAdd( $group ) ) ); + # Do we need to point out that this action is irreversible? + $irreversible = !$disabled && ( + ($set && !$this->canAdd( $group )) || + (!$set && !$this->canRemove( $group ) ) ); + + $attr = $disabled ? array( 'disabled' => 'disabled' ) : array(); + $text = $irreversible + ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) ) + : User::getGroupMember( $group ); + $checkbox = Xml::checkLabel( $text, "wpGroup-$group", + "wpGroup-$group", $set, $attr ); + $checkbox = $disabled ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkbox ) : $checkbox; + + if ($disabled) { + $unsettable_col .= "$checkbox
      \n"; + } else { + $settable_col .= "$checkbox
      \n"; + } + } + + if ($column) { + $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) . + " +"; + if( $settable_col !== '' ) { + $ret .= xml::element( 'th', null, wfMsg( 'userrights-changeable-col' ) ); + } + if( $unsettable_col !== '' ) { + $ret .= xml::element( 'th', null, wfMsg( 'userrights-unchangeable-col' ) ); + } + $ret.= " + +"; + if( $settable_col !== '' ) { + $ret .= +" + $settable_col + +"; + } + if( $unsettable_col !== '' ) { + $ret .= +" + $unsettable_col + +"; + } + $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' ); + } + + return $ret; + } + + /** + * @param $group String: the name of the group to check + * @return bool Can we remove the group? + */ + private function canRemove( $group ) { + // $this->changeableGroups()['remove'] doesn't work, of course. Thanks, + // PHP. + $groups = $this->changeableGroups(); + return in_array( $group, $groups['remove'] ) || ($this->isself && in_array( $group, $groups['remove-self'] )); + } + + /** + * @param $group string: the name of the group to check + * @return bool Can we add the group? + */ + private function canAdd( $group ) { + $groups = $this->changeableGroups(); + return in_array( $group, $groups['add'] ) || ($this->isself && in_array( $group, $groups['add-self'] )); + } + + /** + * Returns an array of the groups that the user can add/remove. + * + * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) ) + */ + function changeableGroups() { + global $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + + if( $wgUser->isAllowed( 'userrights' ) ) { + // This group gives the right to modify everything (reverse- + // compatibility with old "userrights lets you change + // everything") + // Using array_merge to make the groups reindexed + $all = array_merge( User::getAllGroups() ); + return array( + 'add' => $all, + 'remove' => $all, + 'add-self' => array(), + 'remove-self' => array() + ); + } + + // Okay, it's not so simple, we will have to go through the arrays + $groups = array( + 'add' => array(), + 'remove' => array(), + 'add-self' => $wgGroupsAddToSelf, + 'remove-self' => $wgGroupsRemoveFromSelf); + $addergroups = $wgUser->getEffectiveGroups(); + + foreach ($addergroups as $addergroup) { + $groups = array_merge_recursive( + $groups, $this->changeableByGroup($addergroup) + ); + $groups['add'] = array_unique( $groups['add'] ); + $groups['remove'] = array_unique( $groups['remove'] ); + } + return $groups; + } + + /** + * 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 ) ) + */ + private function changeableByGroup( $group ) { + global $wgAddGroups, $wgRemoveGroups; + + $groups = array( 'add' => array(), 'remove' => array() ); + if( empty($wgAddGroups[$group]) ) { + // Don't add anything to $groups + } elseif( $wgAddGroups[$group] === true ) { + // You get everything + $groups['add'] = User::getAllGroups(); + } elseif( is_array($wgAddGroups[$group]) ) { + $groups['add'] = $wgAddGroups[$group]; + } + + // Same thing for remove + if( empty($wgRemoveGroups[$group]) ) { + } elseif($wgRemoveGroups[$group] === true ) { + $groups['remove'] = User::getAllGroups(); + } elseif( is_array($wgRemoveGroups[$group]) ) { + $groups['remove'] = $wgRemoveGroups[$group]; + } + return $groups; + } + + /** + * Show a rights log fragment for the specified user + * + * @param $user User to show log for + * @param $output OutputPage to use + */ + protected function showLogFragment( $user, $output ) { + $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 new file mode 100644 index 0000000000..8771fa1cf1 --- /dev/null +++ b/includes/specials/SpecialVersion.php @@ -0,0 +1,390 @@ + + * @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 { + private $firstExtOpened = true; + + /** + * main() + */ + function execute() { + global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks; + $wgMessageCache->loadAllMessages(); + + $wgOut->addHTML( '
      ' ); + $text = + $this->MediaWikiCredits() . + $this->softwareInformation() . + $this->extensionCredits(); + if ( $wgSpecialVersionShowHooks ) { + $text .= $this->wgHooks(); + } + $wgOut->addWikiText( $text ); + $wgOut->addHTML( $this->IPInfo() ); + $wgOut->addHTML( '
      ' ); + } + + /**#@+ + * @private + */ + + /** + * @return wiki text showing the license information + */ + static function MediaWikiCredits() { + $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) ) . + "__NOTOC__ + This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''', + copyright (C) 2001-2008 Magnus Manske, Brion Vibber, Lee Daniel Crocker, + Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason, + Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan and others. + + MediaWiki is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + MediaWiki is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License] + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online]. + "; + + return str_replace( "\t\t", '', $ret ) . "\n"; + } + + /** + * @return wiki text showing the third party software versions (apache, php, mysql). + */ + static function softwareInformation() { + $dbr = wfGetDB( DB_SLAVE ); + + return Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) . + Xml::openElement( 'table', array( 'id' => 'sv-software' ) ) . + " + " . wfMsg( 'version-software-product' ) . " + " . wfMsg( 'version-software-version' ) . " + \n + + [http://www.mediawiki.org/ MediaWiki] + " . self::getVersionLinked() . " + \n + + [http://www.php.net/ PHP] + " . phpversion() . " (" . php_sapi_name() . ") + \n + + " . $dbr->getSoftwareLink() . " + " . $dbr->getServerVersion() . " + \n" . + Xml::closeElement( 'table' ); + } + + /** + * Return a string of the MediaWiki version with SVN revision if available + * + * @return mixed + */ + public static function getVersion() { + global $wgVersion, $IP; + wfProfileIn( __METHOD__ ); + $svn = self::getSvnRevision( $IP ); + $version = $svn ? "$wgVersion (r$svn)" : $wgVersion; + wfProfileOut( __METHOD__ ); + return $version; + } + + /** + * Return a string of the MediaWiki version with a link to SVN revision if + * available + * + * @return mixed + */ + public static function getVersionLinked() { + global $wgVersion, $IP; + wfProfileIn( __METHOD__ ); + $svn = self::getSvnRevision( $IP ); + $viewvc = 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/?pathrev='; + $version = $svn ? "$wgVersion ([{$viewvc}{$svn} r$svn])" : $wgVersion; + wfProfileOut( __METHOD__ ); + return $version; + } + + /** Generate wikitext showing extensions name, URL, author and description */ + function extensionCredits() { + global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions; + + if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) ) + return ''; + + $extensionTypes = array( + 'specialpage' => wfMsg( 'version-specialpages' ), + 'parserhook' => wfMsg( 'version-parserhooks' ), + 'variable' => wfMsg( 'version-variables' ), + 'media' => wfMsg( 'version-mediahandlers' ), + 'other' => wfMsg( 'version-other' ), + ); + wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) ); + + $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) . + Xml::openElement( 'table', array( 'id' => 'sv-ext' ) ); + + foreach ( $extensionTypes as $type => $text ) { + if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) { + $out .= $this->openExtType( $text ); + + usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); + + foreach ( $wgExtensionCredits[$type] as $extension ) { + if ( isset( $extension['version'] ) ) { + $version = $extension['version']; + } elseif ( isset( $extension['svn-revision'] ) && + preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/', + $extension['svn-revision'], $m ) ) + { + $version = 'r' . $m[1]; + } else { + $version = null; + } + + $out .= $this->formatCredits( + isset ( $extension['name'] ) ? $extension['name'] : '', + $version, + isset ( $extension['author'] ) ? $extension['author'] : '', + isset ( $extension['url'] ) ? $extension['url'] : null, + isset ( $extension['description'] ) ? $extension['description'] : '', + isset ( $extension['descriptionmsg'] ) ? $extension['descriptionmsg'] : '' + ); + } + } + } + + if ( count( $wgExtensionFunctions ) ) { + $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) ); + $out .= '' . $this->listToText( $wgExtensionFunctions ) . "\n"; + } + + if ( $cnt = count( $tags = $wgParser->getTags() ) ) { + for ( $i = 0; $i < $cnt; ++$i ) + $tags[$i] = "<{$tags[$i]}>"; + $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) ); + $out .= '' . $this->listToText( $tags ). "\n"; + } + + if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) { + $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) ); + $out .= '' . $this->listToText( $fhooks ) . "\n"; + } + + if ( count( $wgSkinExtensionFunctions ) ) { + $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ) ); + $out .= '' . $this->listToText( $wgSkinExtensionFunctions ) . "\n"; + } + $out .= Xml::closeElement( 'table' ); + return $out; + } + + /** Callback to sort extensions by type */ + function compare( $a, $b ) { + global $wgLang; + if( $a['name'] === $b['name'] ) { + return 0; + } else { + return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] ) + ? 1 + : -1; + } + } + + function formatCredits( $name, $version = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) { + $extension = isset( $url ) ? "[$url $name]" : $name; + $version = isset( $version ) ? "(" . wfMsg( 'version-version' ) . " $version)" : ''; + + # Look for a localized description + if( isset( $descriptionMsg ) ) { + $msg = wfMsg( $descriptionMsg ); + if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) { + $description = $msg; + } + } + + return " + $extension $version + $description + " . $this->listToText( (array)$author ) . " + \n"; + } + + /** + * @return string + */ + function wgHooks() { + global $wgHooks; + + if ( count( $wgHooks ) ) { + $myWgHooks = $wgHooks; + ksort( $myWgHooks ); + + $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) . + Xml::openElement( 'table', array( 'id' => 'sv-hooks' ) ) . + " + " . wfMsg( 'version-hook-name' ) . " + " . wfMsg( 'version-hook-subscribedby' ) . " + \n"; + + foreach ( $myWgHooks as $hook => $hooks ) + $ret .= " + $hook + " . $this->listToText( $hooks ) . " + \n"; + + $ret .= Xml::closeElement( 'table' ); + return $ret; + } else + return ''; + } + + private function openExtType($text, $name = null) { + $opt = array( 'colspan' => 3 ); + $out = ''; + + if(!$this->firstExtOpened) { + // Insert a spacing line + $out .= '' . Xml::element( 'td', $opt ) . "\n"; + } + $this->firstExtOpened = false; + + if($name) { $opt['id'] = "sv-$name"; } + + $out .= "" . Xml::element( 'th', $opt, $text) . "\n"; + return $out; + } + + /** + * @static + * + * @return string + */ + function IPInfo() { + $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) ); + return "\n" . + "visited from $ip"; + } + + /** + * @param array $list + * @return string + */ + function listToText( $list ) { + $cnt = count( $list ); + + if ( $cnt == 1 ) { + // Enforce always returning a string + return (string)$this->arrayToString( $list[0] ); + } elseif ( $cnt == 0 ) { + return ''; + } else { + 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"; + } + } + + /** + * @static + * + * @param mixed $list Will convert an array to string if given and return + * the paramater unaltered otherwise + * @return mixed + */ + function arrayToString( $list ) { + if( is_object( $list ) ) { + $class = get_class( $list ); + return "($class)"; + } elseif ( ! is_array( $list ) ) { + return $list; + } else { + $class = get_class( $list[0] ); + return "($class, {$list[1]})"; + } + } + + /** + * Retrieve the revision number of a Subversion working directory. + * + * @param string $dir + * @return mixed revision number as int, or false if not a SVN checkout + */ + public static function getSvnRevision( $dir ) { + // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html + $entries = $dir . '/.svn/entries'; + + if( !file_exists( $entries ) ) { + return false; + } + + $content = file( $entries ); + + // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4) + if( preg_match( '/^<\?xml/', $content[0] ) ) { + // subversion is release <= 1.3 + if( !function_exists( 'simplexml_load_file' ) ) { + // We could fall back to expat... YUCK + return false; + } + + // SimpleXml whines about the xmlns... + wfSuppressWarnings(); + $xml = simplexml_load_file( $entries ); + wfRestoreWarnings(); + + if( $xml ) { + foreach( $xml->entry as $entry ) { + if( $xml->entry[0]['name'] == '' ) { + // The directory entry should always have a revision marker. + if( $entry['revision'] ) { + return intval( $entry['revision'] ); + } + } + } + } + return false; + } else { + // subversion is release 1.4 + return intval( $content[3] ); + } + } + + /**#@-*/ +} + +/**#@-*/ diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php new file mode 100644 index 0000000000..969a8d8184 --- /dev/null +++ b/includes/specials/SpecialWantedcategories.php @@ -0,0 +1,90 @@ + + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class WantedCategoriesPage extends QueryPage { + + function getName() { + return 'Wantedcategories'; + } + + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); + $name = $dbr->addQuotes( $this->getName() ); + return + " + SELECT + $name as type, + " . NS_CATEGORY . " as namespace, + cl_to as title, + COUNT(*) as value + FROM $categorylinks + LEFT JOIN $page ON cl_to = page_title AND page_namespace = ". NS_CATEGORY ." + WHERE page_title IS NULL + GROUP BY 1,2,3 + "; + } + + 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 wfSpecialWantedCategories() { + list( $limit, $offset ) = wfCheckLimits(); + + $wpp = new WantedCategoriesPage(); + + $wpp->doQuery( $offset, $limit ); +} diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php new file mode 100644 index 0000000000..650e04f912 --- /dev/null +++ b/includes/specials/SpecialWantedpages.php @@ -0,0 +1,131 @@ +setListoutput( $inc ); + $this->nlinks = $nlinks; + } + + function getName() { + return 'Wantedpages'; + } + + function isExpensive() { + return true; + } + function isSyndicated() { return false; } + + function getSQL() { + global $wgWantedPagesThreshold; + $count = $wgWantedPagesThreshold - 1; + $dbr = wfGetDB( DB_SLAVE ); + $pagelinks = $dbr->tableName( 'pagelinks' ); + $page = $dbr->tableName( 'page' ); + return + "SELECT 'Wantedpages' AS type, + pl_namespace AS namespace, + pl_title AS title, + COUNT(*) AS value + FROM $pagelinks + LEFT JOIN $page AS pg1 + ON pl_namespace = pg1.page_namespace AND pl_title = pg1.page_title + LEFT JOIN $page AS pg2 + ON pl_from = pg2.page_id + WHERE pg1.page_namespace IS NULL + AND pl_namespace NOT IN ( 2, 3 ) + AND pg2.page_namespace != 8 + GROUP BY 1,2,3 + HAVING COUNT(*) > $count"; + } + + /** + * Cache page existence for performance + */ + 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 ); + } + + /** + * Format an individual result + * + * @param $skin Skin to use for UI elements + * @param $result Result row + * @return string + */ + public function formatResult( $skin, $result ) { + $title = Title::makeTitleSafe( $result->namespace, $result->title ); + if( $title instanceof Title ) { + if( $this->isCached() ) { + $pageLink = $title->exists() + ? '' . $skin->makeLinkObj( $title ) . '' + : $skin->makeBrokenLinkObj( $title ); + } else { + $pageLink = $skin->makeBrokenLinkObj( $title ); + } + return wfSpecialList( $pageLink, $this->makeWlhLink( $title, $skin, $result ) ); + } else { + $tsafe = htmlspecialchars( $result->title ); + return "Invalid title in result set; {$tsafe}"; + } + } + + /** + * Make a "what links here" link for a specified result if required + * + * @param $title Title to make the link for + * @param $skin Skin to use + * @param $result Result row + * @return string + */ + private function makeWlhLink( $title, $skin, $result ) { + global $wgLang; + if( $this->nlinks ) { + $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); + $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $result->value ) ); + return $skin->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() ); + } else { + return null; + } + } + +} + +/** + * constructor + */ +function wfSpecialWantedpages( $par = null, $specialPage ) { + $inc = $specialPage->including(); + + if ( $inc ) { + @list( $limit, $nlinks ) = explode( '/', $par, 2 ); + $limit = (int)$limit; + $nlinks = $nlinks === 'nlinks'; + $offset = 0; + } else { + list( $limit, $offset ) = wfCheckLimits(); + $nlinks = true; + } + + $wpp = new WantedPagesPage( $inc, $nlinks ); + + $wpp->doQuery( $offset, $limit, !$inc ); +} diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php new file mode 100644 index 0000000000..4af22c7dff --- /dev/null +++ b/includes/specials/SpecialWatchlist.php @@ -0,0 +1,372 @@ +getSkin(); + $specialTitle = SpecialPage::getTitleFor( 'Watchlist' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + + # 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 ) ); + return; + } + + $wgOut->setPageTitle( wfMsg( 'watchlist' ) ); + + $sub = wfMsgExt( 'watchlistfor', 'parseinline', $wgUser->getName() ); + $sub .= '
      ' . WatchlistEditor::buildTools( $wgUser->getSkin() ); + $wgOut->setSubtitle( $sub ); + + if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) { + $editor = new WatchlistEditor(); + $editor->execute( $wgUser, $wgOut, $wgRequest, $mode ); + return; + } + + $uid = $wgUser->getId(); + 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' ), + /* ? */ 'namespace' => 'all', + ); + + 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['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' ); + + # Get query variables + $days = $wgRequest->getVal( 'days', $prefs['days'] ); + $hideOwn = $wgRequest->getBool( 'hideOwn', $prefs['hideown'] ); + $hideBots = $wgRequest->getBool( 'hideBots', $prefs['hidebots'] ); + $hideMinor = $wgRequest->getBool( 'hideMinor', $prefs['hideminor'] ); + + # Get namespace value, if supplied, and prepare a WHERE fragment + $nameSpace = $wgRequest->getIntOrNull( 'namespace' ); + if( !is_null( $nameSpace ) ) { + $nameSpace = intval( $nameSpace ); + $nameSpaceClause = " AND rc_namespace = $nameSpace"; + } else { + $nameSpace = ''; + $nameSpaceClause = ''; + } + + $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); + list( $page, $watchlist, $recentchanges ) = $dbr->tableNamesN( 'page', 'watchlist', 'recentchanges' ); + + $watchlistCount = $dbr->selectField( 'watchlist', 'COUNT(*)', + array( 'wl_user' => $uid ), __METHOD__ ); + // Adjust for page X, talk:page X, which are both stored separately, + // but treated together + $nitems = floor($watchlistCount / 2); + + if( is_null($days) || !is_numeric($days) ) { + $big = 1000; /* The magical big */ + if($nitems > $big) { + # Set default cutoff shorter + $days = $defaults['days'] = (12.0 / 24.0); # 12 hours... + } else { + $days = $defaults['days']; # default cutoff for shortlisters + } + } else { + $days = floatval($days); + } + + // Dump everything here + $nondefaults = array(); + + wfAppendToArrayIfNotDefault('days' , $days , $defaults, $nondefaults); + wfAppendToArrayIfNotDefault('hideOwn' , (int)$hideOwn , $defaults, $nondefaults); + wfAppendToArrayIfNotDefault('hideBots' , (int)$hideBots, $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) { + $wgOut->addWikiMsg( 'nowatchlist' ); + return; + } + + 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; + */ + } + + # If the watchlist is relatively short, it's simplest to zip + # down its entirety and then sort the results. + + # If it's relatively long, it may be worth our while to zip + # through the time-sorted page list checking for watched items. + + # 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) + if( $wgUser->getOption( 'extendwatchlist' )) { + $andLatest=''; + $limitWatchlist = 'LIMIT ' . 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 = ''; + } + + $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( '
      ' . + "\n\n" ); + } + if ( $wgShowUpdatedMarker ) { + $wltsfield = ", ${watchlist}.wl_notificationtimestamp "; + } else { + $wltsfield = ''; + } + $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 ); + $numRows = $dbr->numRows( $res ); + + /* Start bottom header */ + $wgOut->addHTML( "
      \n" ); + + if($days >= 1) { + $wgOut->addWikiText( wfMsgExt( 'rcnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), + $wgLang->formatNum( $days ), $wgLang->timeAndDate( wfTimestampNow(), true ) ) . '
      ' , false ); + } elseif($days > 0) { + $wgOut->addWikiText( wfMsgExt( 'wlnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), + $wgLang->formatNum( round($days*24) ) ) . '
      ' , false ); + } + + $wgOut->addHTML( "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "
      \n" ); + + # Spit out some control panel links + $thisTitle = SpecialPage::getTitleFor( 'Watchlist' ); + $skin = $wgUser->getSkin(); + + # Hide/show bot edits + $label = $hideBots ? wfMsgHtml( 'watchlist-show-bots' ) : wfMsgHtml( 'watchlist-hide-bots' ); + $linkBits = wfArrayToCGI( array( 'hideBots' => 1 - (int)$hideBots ), $nondefaults ); + $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); + + # Hide/show own edits + $label = $hideOwn ? wfMsgHtml( 'watchlist-show-own' ) : wfMsgHtml( 'watchlist-hide-own' ); + $linkBits = wfArrayToCGI( array( 'hideOwn' => 1 - (int)$hideOwn ), $nondefaults ); + $links[] = $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 ) ); + + # Form for namespace filtering + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) ); + $form .= '

      '; + $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; + $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; + $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '

      '; + $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 ); + $form .= Xml::closeElement( 'form' ); + $wgOut->addHtml( $form ); + + # If there's nothing to show, stop here + if( $numRows == 0 ) { + $wgOut->addWikiMsg( 'watchnochange' ); + return; + } + + /* End bottom header */ + + /* Do link batch query */ + $linkBatch = new LinkBatch; + while ( $row = $dbr->fetchObject( $res ) ) { + $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); + if ( $row->rc_user != 0 ) { + $linkBatch->add( NS_USER, $userNameUnderscored ); + } + $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); + } + $linkBatch->execute(); + $dbr->dataSeek( $res, 0 ); + + $list = ChangesList::newFromUser( $wgUser ); + + $s = $list->beginRecentChangesList(); + $counter = 1; + while ( $obj = $dbr->fetchObject( $res ) ) { + # Make RC entry + $rc = RecentChange::newFromRow( $obj ); + $rc->counter = $counter++; + + if ( $wgShowUpdatedMarker ) { + $updated = $obj->wl_notificationtimestamp; + } else { + $updated = false; + } + + if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) { + $rc->numberofWatchingusers = $dbr->selectField( 'watchlist', + 'COUNT(*)', + array( + 'wl_namespace' => $obj->rc_namespace, + 'wl_title' => $obj->rc_title, + ), + __METHOD__ ); + } else { + $rc->numberofWatchingusers = 0; + } + + $s .= $list->recentChangesLine( $rc, $updated ); + } + $s .= $list->endRecentChangesList(); + + $dbr->freeResult( $res ); + $wgOut->addHTML( $s ); + +} + +function wlHoursLink( $h, $page, $options = array() ) { + global $wgUser, $wgLang, $wgContLang; + $sk = $wgUser->getSkin(); + $s = $sk->makeKnownLink( + $wgContLang->specialPage( $page ), + $wgLang->formatNum( $h ), + wfArrayToCGI( array('days' => ($h / 24.0)), $options ) ); + return $s; +} + +function wlDaysLink( $d, $page, $options = array() ) { + global $wgUser, $wgLang, $wgContLang; + $sk = $wgUser->getSkin(); + $s = $sk->makeKnownLink( + $wgContLang->specialPage( $page ), + ($d ? $wgLang->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) ), + wfArrayToCGI( array('days' => $d), $options ) ); + return $s; +} + +/** + * Returns html + */ +function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { + $hours = array( 1, 2, 6, 12 ); + $days = array( 1, 3, 7 ); + $i = 0; + foreach( $hours as $h ) { + $hours[$i++] = wlHoursLink( $h, $page, $options ); + } + $i = 0; + foreach( $days as $d ) { + $days[$i++] = wlDaysLink( $d, $page, $options ); + } + return wfMsgExt('wlshowlast', + array('parseinline', 'replaceafter'), + implode(' | ', $hours), + implode(' | ', $days), + wlDaysLink( 0, $page, $options ) ); +} + +/** + * Count the number of items on a user's watchlist + * + * @param $talk Include talk pages + * @return integer + */ +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' ); + $row = $dbr->fetchObject( $res ); + $count = $row->count; + $dbr->freeResult( $res ); + + # Halve to remove talk pages if needed + if( !$talk ) + $count = floor( $count / 2 ); + + return( $count ); +} diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php new file mode 100644 index 0000000000..a57df5e03f --- /dev/null +++ b/includes/specials/SpecialWhatlinkshere.php @@ -0,0 +1,408 @@ +execute(); +} + +/** + * implements Special:Whatlinkshere + * @ingroup SpecialPage + */ +class WhatLinksHerePage { + // Stored data + protected $par; + + // Stored objects + protected $opts, $target, $selfTitle; + + // Stored globals + protected $skin, $request; + + protected $limits = array( 20, 50, 100, 250, 500 ); + + function WhatLinksHerePage( $request, $par = null ) { + global $wgUser; + $this->request = $request; + $this->skin = $wgUser->getSkin(); + $this->par = $par; + } + + function execute() { + global $wgOut; + + $opts = new FormOptions(); + + $opts->add( 'target', '' ); + $opts->add( 'namespace', '', FormOptions::INTNULL ); + $opts->add( 'limit', 50 ); + $opts->add( 'from', 0 ); + $opts->add( 'back', 0 ); + $opts->add( 'hideredirs', false ); + $opts->add( 'hidetrans', false ); + $opts->add( 'hidelinks', false ); + $opts->add( 'hideimages', false ); + + $opts->fetchValuesFromRequest( $this->request ); + $opts->validateIntBounds( 'limit', 0, 5000 ); + + // Give precedence to subpage syntax + if ( isset($this->par) ) { + $opts->setValue( 'target', $this->par ); + } + + // Bind to member variable + $this->opts = $opts; + + $this->target = Title::newFromURL( $opts->getValue( 'target' ) ); + if( !$this->target ) { + $wgOut->addHTML( $this->whatlinkshereForm() ); + return; + } + + $this->selfTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->target->getPrefixedDBkey() ); + + $wgOut->setPageTitle( wfMsgExt( 'whatlinkshere-title', 'escape', $this->target->getPrefixedText() ) ); + $wgOut->setSubtitle( wfMsgHtml( 'linklistsub' ) ); + + $wgOut->addHTML( wfMsgExt( 'whatlinkshere-barrow', array( 'escapenoentities') ) . ' ' .$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."
      \n"); + + $this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ), + $opts->getValue( 'from' ), $opts->getValue( 'back' ) ); + } + + /** + * @param $level int Recursion level + * @param $target Title Target title + * @param $limit int Number of entries to display + * @param $from Title Display from this article ID + * @param $back Title Display from this article ID at backwards scrolling + * @private + */ + function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) { + global $wgOut, $wgMaxRedirectLinksRetrieved; + $dbr = wfGetDB( DB_SLAVE ); + $options = array(); + + $hidelinks = $this->opts->getValue( 'hidelinks' ); + $hideredirs = $this->opts->getValue( 'hideredirs' ); + $hidetrans = $this->opts->getValue( 'hidetrans' ); + $hideimages = $target->getNamespace() != NS_IMAGE || $this->opts->getValue( 'hideimages' ); + + $fetchlinks = (!$hidelinks || !$hideredirs); + + // Make the query + $plConds = array( + 'page_id=pl_from', + 'pl_namespace' => $target->getNamespace(), + 'pl_title' => $target->getDBkey(), + ); + if( $hideredirs ) { + $plConds['page_is_redirect'] = 0; + } elseif( $hidelinks ) { + $plConds['page_is_redirect'] = 1; + } + + $tlConds = array( + 'page_id=tl_from', + 'tl_namespace' => $target->getNamespace(), + 'tl_title' => $target->getDBkey(), + ); + + $ilConds = array( + 'page_id=il_from', + 'il_to' => $target->getDBkey(), + ); + + $namespace = $this->opts->getValue( 'namespace' ); + if ( is_int($namespace) ) { + $plConds['page_namespace'] = $namespace; + $tlConds['page_namespace'] = $namespace; + $ilConds['page_namespace'] = $namespace; + } + + if ( $from ) { + $tlConds[] = "tl_from >= $from"; + $plConds[] = "pl_from >= $from"; + $ilConds[] = "il_from >= $from"; + } + + // Read an extra row as an at-end check + $queryLimit = $limit + 1; + + // Enforce join order, sometimes namespace selector may + // trigger filesorts which are far less efficient than scanning many entries + $options[] = 'STRAIGHT_JOIN'; + + $options['LIMIT'] = $queryLimit; + $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ); + + if( $fetchlinks ) { + $options['ORDER BY'] = 'pl_from'; + $plRes = $dbr->select( array( 'pagelinks', 'page' ), $fields, + $plConds, __METHOD__, $options ); + } + + if( !$hidetrans ) { + $options['ORDER BY'] = 'tl_from'; + $tlRes = $dbr->select( array( 'templatelinks', 'page' ), $fields, + $tlConds, __METHOD__, $options ); + } + + if( !$hideimages ) { + $options['ORDER BY'] = 'il_from'; + $ilRes = $dbr->select( array( 'imagelinks', 'page' ), $fields, + $ilConds, __METHOD__, $options ); + } + + 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() ); + } + return; + } + + // Read the rows into an array and remove duplicates + // templatelinks comes second so that the templatelinks row overwrites the + // pagelinks row, so we get (inclusion) rather than nothing + if( $fetchlinks ) { + while ( $row = $dbr->fetchObject( $plRes ) ) { + $row->is_template = 0; + $row->is_image = 0; + $rows[$row->page_id] = $row; + } + $dbr->freeResult( $plRes ); + + } + if( !$hidetrans ) { + while ( $row = $dbr->fetchObject( $tlRes ) ) { + $row->is_template = 1; + $row->is_image = 0; + $rows[$row->page_id] = $row; + } + $dbr->freeResult( $tlRes ); + } + if( !$hideimages ) { + while ( $row = $dbr->fetchObject( $ilRes ) ) { + $row->is_template = 0; + $row->is_image = 1; + $rows[$row->page_id] = $row; + } + $dbr->freeResult( $ilRes ); + } + + // Sort by key and then change the keys to 0-based indices + ksort( $rows ); + $rows = array_values( $rows ); + + $numRows = count( $rows ); + + // Work out the start and end IDs, for prev/next links + if ( $numRows > $limit ) { + // More rows available after these ones + // Get the ID from the last row in the result set + $nextId = $rows[$limit]->page_id; + // Remove undisplayed rows + $rows = array_slice( $rows, 0, $limit ); + } else { + // No more rows after + $nextId = false; + } + $prevId = $from; + + if ( $level == 0 ) { + $wgOut->addHTML( $this->whatlinkshereForm() ); + $wgOut->addHTML( $this->getFilterPanel() ); + $wgOut->addWikiMsg( 'linkshere', $this->target->getPrefixedText() ); + + $prevnext = $this->getPrevNext( $prevId, $nextId ); + $wgOut->addHTML( $prevnext ); + } + + $wgOut->addHTML( $this->listStart() ); + foreach ( $rows as $row ) { + $nt = Title::makeTitle( $row->page_namespace, $row->page_title ); + + if ( $row->page_is_redirect && $level < 2 ) { + $wgOut->addHTML( $this->listItem( $row, $nt, true ) ); + $this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved ); + $wgOut->addHTML( Xml::closeElement( 'li' ) ); + } else { + $wgOut->addHTML( $this->listItem( $row, $nt ) ); + } + } + + $wgOut->addHTML( $this->listEnd() ); + + if( $level == 0 ) { + $wgOut->addHTML( $prevnext ); + } + } + + protected function listStart() { + return Xml::openElement( 'ul' ); + } + + protected function listItem( $row, $nt, $notClose = false ) { + # local message cache + static $msgcache = null; + if ( $msgcache === null ) { + static $msgs = array( 'isredirect', 'istemplate', 'semicolon-separator', + 'whatlinkshere-links', 'isimage' ); + $msgcache = array(); + foreach ( $msgs as $msg ) { + $msgcache[$msg] = wfMsgHtml( $msg ); + } + } + + $suppressRedirect = $row->page_is_redirect ? 'redirect=no' : ''; + $link = $this->skin->makeKnownLinkObj( $nt, '', $suppressRedirect ); + + // Display properties (redirect or template) + $propsText = ''; + $props = array(); + if ( $row->page_is_redirect ) + $props[] = $msgcache['isredirect']; + if ( $row->is_template ) + $props[] = $msgcache['istemplate']; + if( $row->is_image ) + $props[] = $msgcache['isimage']; + + if ( count( $props ) ) { + $propsText = '(' . implode( $msgcache['semicolon-separator'], $props ) . ')'; + } + + # Space for utilities links, with a what-links-here link provided + $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'] ); + $wlh = Xml::wrapClass( "($wlhLink)", 'mw-whatlinkshere-tools' ); + + return $notClose ? + Xml::openElement( 'li' ) . "$link $propsText $wlh\n" : + Xml::tags( 'li', null, "$link $propsText $wlh" ) . "\n"; + } + + protected function listEnd() { + return Xml::closeElement( 'ul' ); + } + + protected function wlhLink( Title $target, $text ) { + static $title = null; + if ( $title === null ) + $title = SpecialPage::getTitleFor( 'Whatlinkshere' ); + + $targetText = $target->getPrefixedUrl(); + return $this->skin->makeKnownLinkObj( $title, $text, 'target=' . $targetText ); + } + + function makeSelfLink( $text, $query ) { + return $this->skin->makeKnownLinkObj( $this->selfTitle, $text, $query ); + } + + function getPrevNext( $prevId, $nextId ) { + global $wgLang; + $currentLimit = $this->opts->getValue( 'limit' ); + $fmtLimit = $wgLang->formatNum( $currentLimit ); + $prev = wfMsgExt( 'whatlinkshere-prev', array( 'parsemag', 'escape' ), $fmtLimit ); + $next = wfMsgExt( 'whatlinkshere-next', array( 'parsemag', 'escape' ), $fmtLimit ); + + $changed = $this->opts->getChangedValues(); + unset($changed['target']); // Already in the request title + + if ( 0 != $prevId ) { + $overrides = array( 'from' => $this->opts->getValue( 'back' ) ); + $prev = $this->makeSelfLink( $prev, wfArrayToCGI( $overrides, $changed ) ); + } + if ( 0 != $nextId ) { + $overrides = array( 'from' => $nextId, 'back' => $prevId ); + $next = $this->makeSelfLink( $next, wfArrayToCGI( $overrides, $changed ) ); + } + + $limitLinks = array(); + foreach ( $this->limits as $limit ) { + $prettyLimit = $wgLang->formatNum( $limit ); + $overrides = array( 'limit' => $limit ); + $limitLinks[] = $this->makeSelfLink( $prettyLimit, wfArrayToCGI( $overrides, $changed ) ); + } + + $nums = implode ( ' | ', $limitLinks ); + + return wfMsgHtml( 'viewprevnext', $prev, $next, $nums ); + } + + function whatlinkshereForm() { + global $wgScript, $wgTitle; + + // We get nicer value from the title object + $this->opts->consumeValue( 'target' ); + // Reset these for new requests + $this->opts->consumeValues( array( 'back', 'from' ) ); + + $target = $this->target ? $this->target->getPrefixedText() : ''; + $namespace = $this->opts->consumeValue( 'namespace' ); + + # Build up the form + $f = Xml::openElement( 'form', array( 'action' => $wgScript ) ); + + # Values that should not be forgotten + $f .= Xml::hidden( 'title', $wgTitle->getPrefixedText() ); + foreach ( $this->opts->getUnconsumedValues() as $name => $value ) { + $f .= Xml::hidden( $name, $value ); + } + + $f .= Xml::fieldset( wfMsg( 'whatlinkshere' ) ); + + # Target input + $f .= Xml::inputLabel( wfMsg( 'whatlinkshere-page' ), 'target', + 'mw-whatlinkshere-target', 40, $target ); + + $f .= ' '; + + # Namespace selector + $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $namespace, '' ); + + # Submit + $f .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); + + # Close + $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n"; + + return $f; + } + + function getFilterPanel() { + $show = wfMsgHtml( 'show' ); + $hide = wfMsgHtml( 'hide' ); + + $changed = $this->opts->getChangedValues(); + unset($changed['target']); // Already in the request title + + $links = array(); + $types = array( 'hidetrans', 'hidelinks', 'hideredirs' ); + if( $this->target->getNamespace() == NS_IMAGE ) + $types[] = 'hideimages'; + foreach( $types as $type ) { + $chosen = $this->opts->getValue( $type ); + $msg = wfMsgHtml( "whatlinkshere-{$type}", $chosen ? $show : $hide ); + $overrides = array( $type => !$chosen ); + $links[] = $this->makeSelfLink( $msg, wfArrayToCGI( $overrides, $changed ) ); + } + return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), implode( ' | ', $links ) ); + } +} diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php new file mode 100644 index 0000000000..2092e43b52 --- /dev/null +++ b/includes/specials/SpecialWithoutinterwiki.php @@ -0,0 +1,88 @@ + + */ +class WithoutInterwikiPage extends PageQueryPage { + private $prefix = ''; + + function getName() { + return 'Withoutinterwiki'; + } + + function getPageHeader() { + global $wgScript, $wgMiserMode; + + # Do not show useless input form if wiki is running in misermode + if( $wgMiserMode ) { + return ''; + } + + $prefix = $this->prefix; + $t = SpecialPage::getTitleFor( $this->getName() ); + + return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'withoutinterwiki-legend' ) ) . + Xml::hidden( 'title', $t->getPrefixedText() ) . + Xml::inputLabel( wfMsg( 'allpagesprefix' ), 'prefix', 'wiprefix', 20, $prefix ) . ' ' . + Xml::submitButton( wfMsg( 'withoutinterwiki-submit' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ); + } + + function sortDescending() { + return false; + } + + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' ); + $prefix = $this->prefix ? "AND page_title LIKE '" . $dbr->escapeLike( $this->prefix ) . "%'" : ''; + return + "SELECT 'Withoutinterwiki' AS type, + page_namespace AS namespace, + page_title AS title, + page_title AS value + FROM $page + LEFT JOIN $langlinks + ON ll_from = page_id + WHERE ll_title IS NULL + AND page_namespace=" . NS_MAIN . " + AND page_is_redirect = 0 + {$prefix}"; + } + + function setPrefix( $prefix = '' ) { + $this->prefix = $prefix; + } + +} + +function wfSpecialWithoutinterwiki() { + global $wgRequest, $wgContLang, $wgCapitalLinks; + list( $limit, $offset ) = wfCheckLimits(); + if( $wgCapitalLinks ) { + $prefix = $wgContLang->ucfirst( $wgRequest->getVal( 'prefix' ) ); + } else { + $prefix = $wgRequest->getVal( 'prefix' ); + } + $wip = new WithoutInterwikiPage(); + $wip->setPrefix( $prefix ); + $wip->doQuery( $offset, $limit ); +} diff --git a/includes/specials/Specialpages.php b/includes/specials/Specialpages.php deleted file mode 100644 index ca91ad514b..0000000000 --- a/includes/specials/Specialpages.php +++ /dev/null @@ -1,82 +0,0 @@ -loadAllMessages(); - - $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Is this really needed? - $sk = $wgUser->getSkin(); - - $pages = SpecialPage::getUsablePages(); - - if( count( $pages ) == 0 ) { - # Yeah, that was pointless. Thanks for coming. - return; - } - - /** Put them into a sortable array */ - $groups = array(); - foreach ( $pages as $page ) { - if ( $page->isListed() ) { - $group = SpecialPage::getGroup( $page ); - if( !isset($groups[$group]) ) { - $groups[$group] = array(); - } - $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted() ); - } - } - - /** Sort */ - if ( $wgSortSpecialPages ) { - foreach( $groups as $group => $sortedPages ) { - ksort( $groups[$group] ); - } - } - - /** Always move "other" to end */ - if( array_key_exists('other',$groups) ) { - $other = $groups['other']; - unset( $groups['other'] ); - $groups['other'] = $other; - } - - /** Now output the HTML */ - foreach ( $groups as $group => $sortedPages ) { - $middle = ceil( count($sortedPages)/2 ); - $total = count($sortedPages); - $count = 0; - - $wgOut->addHTML( "

      ".wfMsgHtml("specialpages-group-$group")."

      \n" ); - $wgOut->addHTML( "" ); - $wgOut->addHTML( "
        \n" ); - foreach( $sortedPages as $desc => $specialpage ) { - list( $title, $restricted ) = $specialpage; - $link = $sk->makeKnownLinkObj( $title , htmlspecialchars( $desc ) ); - if( $restricted ) { - $wgOut->addHTML( "
      • {$link}
      • \n" ); - } else { - $wgOut->addHTML( "
      • {$link}
      • \n" ); - } - - # Split up the larger groups - $count++; - if( $total > 3 && $count == $middle ) { - $wgOut->addHTML( "
        " ); - } - } - $wgOut->addHTML( "
      \n" ); - } - $wgOut->addHTML( - Xml::openElement('div', array( 'class' => 'mw-specialpages-notes' )). - wfMsgWikiHtml('specialpages-note'). - Xml::closeElement('div') - ); -} diff --git a/includes/specials/Statistics.php b/includes/specials/Statistics.php deleted file mode 100644 index 570a21c602..0000000000 --- a/includes/specials/Statistics.php +++ /dev/null @@ -1,93 +0,0 @@ -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"; - - $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"; - - global $wgDisableCounters, $wgMiserMode, $wgUser, $wgLang, $wgContLang; - if( !$wgDisableCounters && !$wgMiserMode ) { - $res = $dbr->select( - 'page', - array( - 'page_namespace', - 'page_title', - 'page_counter', - ), - array( - 'page_is_redirect' => 0, - 'page_counter > 0', - ), - __METHOD__, - array( - 'ORDER BY' => 'page_counter DESC', - 'LIMIT' => 10, - ) - ); - if( $res->numRows() > 0 ) { - $text .= "==" . wfMsgNoTrans( 'statistics-mostpopular' ) . "==\n"; - 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"; - } - $res->free(); - } - } - - $footer = wfMsgNoTrans( 'statistics-footer' ); - if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) - $text .= "\n" . $footer; - - $wgOut->addWikiText( $text ); - } -} diff --git a/includes/specials/Uncategorizedcategories.php b/includes/specials/Uncategorizedcategories.php deleted file mode 100644 index f23e89ce8b..0000000000 --- a/includes/specials/Uncategorizedcategories.php +++ /dev/null @@ -1,30 +0,0 @@ -requestedNamespace = NS_CATEGORY; - } - - function getName() { - return "Uncategorizedcategories"; - } -} - -/** - * constructor - */ -function wfSpecialUncategorizedcategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new UncategorizedCategoriesPage(); - - return $lpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Uncategorizedimages.php b/includes/specials/Uncategorizedimages.php deleted file mode 100644 index 986ec967e6..0000000000 --- a/includes/specials/Uncategorizedimages.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ - -/** - * @ingroup SpecialPage - */ -class UncategorizedImagesPage extends ImageQueryPage { - - function getName() { - return 'Uncategorizedimages'; - } - - function sortDescending() { - return false; - } - - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $ns = NS_IMAGE; - - return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace, - page_title AS title, page_title AS value - FROM {$page} LEFT JOIN {$categorylinks} ON page_id = cl_from - WHERE cl_from IS NULL AND page_namespace = {$ns} AND page_is_redirect = 0"; - } - -} - -function wfSpecialUncategorizedimages() { - $uip = new UncategorizedImagesPage(); - list( $limit, $offset ) = wfCheckLimits(); - return $uip->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Uncategorizedpages.php b/includes/specials/Uncategorizedpages.php deleted file mode 100644 index e7f0aacacf..0000000000 --- a/includes/specials/Uncategorizedpages.php +++ /dev/null @@ -1,55 +0,0 @@ -tableNamesN( 'page', 'categorylinks' ); - $name = $dbr->addQuotes( $this->getName() ); - - return - " - SELECT - $name as type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $categorylinks ON page_id=cl_from - WHERE cl_from IS NULL AND page_namespace={$this->requestedNamespace} AND page_is_redirect=0 - "; - } -} - -/** - * constructor - */ -function wfSpecialUncategorizedpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new UncategorizedPagesPage(); - - return $lpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Uncategorizedtemplates.php b/includes/specials/Uncategorizedtemplates.php deleted file mode 100644 index cb2a6d40f3..0000000000 --- a/includes/specials/Uncategorizedtemplates.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ -class UncategorizedTemplatesPage extends UncategorizedPagesPage { - - var $requestedNamespace = NS_TEMPLATE; - - public function getName() { - return 'Uncategorizedtemplates'; - } - -} - -/** - * Main execution point - * - * @param mixed $par Parameter passed to the page - */ -function wfSpecialUncategorizedtemplates() { - list( $limit, $offset ) = wfCheckLimits(); - $utp = new UncategorizedTemplatesPage(); - $utp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Undelete.php b/includes/specials/Undelete.php deleted file mode 100644 index 33d9476013..0000000000 --- a/includes/specials/Undelete.php +++ /dev/null @@ -1,1278 +0,0 @@ -execute(); -} - -/** - * Used to show archived pages and eventually restore them. - * @ingroup SpecialPage - */ -class PageArchive { - protected $title; - var $fileStatus; - - function __construct( $title ) { - if( is_null( $title ) ) { - throw new MWException( 'Archiver() given a null title.'); - } - $this->title = $title; - } - - /** - * List all deleted pages recorded in the archive table. Returns result - * wrapper with (ar_namespace, ar_title, count) fields, ordered by page - * namespace/title. - * - * @return ResultWrapper - */ - public static function listAllPages() { - $dbr = wfGetDB( DB_SLAVE ); - return self::listPages( $dbr, '' ); - } - - /** - * List deleted pages recorded in the archive table matching the - * given title prefix. - * Returns result wrapper with (ar_namespace, ar_title, count) fields. - * - * @return ResultWrapper - */ - public static function listPagesByPrefix( $prefix ) { - $dbr = wfGetDB( DB_SLAVE ); - - $title = Title::newFromText( $prefix ); - if( $title ) { - $ns = $title->getNamespace(); - $encPrefix = $dbr->escapeLike( $title->getDBkey() ); - } else { - // Prolly won't work too good - // @todo handle bare namespace names cleanly? - $ns = 0; - $encPrefix = $dbr->escapeLike( $prefix ); - } - $conds = array( - 'ar_namespace' => $ns, - "ar_title LIKE '$encPrefix%'", - ); - return self::listPages( $dbr, $conds ); - } - - protected static function listPages( $dbr, $condition ) { - return $dbr->resultObject( - $dbr->select( - array( 'archive' ), - array( - 'ar_namespace', - 'ar_title', - 'COUNT(*) AS count' - ), - $condition, - __METHOD__, - array( - 'GROUP BY' => 'ar_namespace,ar_title', - 'ORDER BY' => 'ar_namespace,ar_title', - 'LIMIT' => 100, - ) - ) - ); - } - - /** - * List the revisions of the given page. Returns result wrapper with - * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields. - * - * @return ResultWrapper - */ - function listRevisions() { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'archive', - array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len', 'ar_deleted' ), - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), - 'PageArchive::listRevisions', - array( 'ORDER BY' => 'ar_timestamp DESC' ) ); - $ret = $dbr->resultObject( $res ); - return $ret; - } - - /** - * List the deleted file revisions for this page, if it's a file page. - * Returns a result wrapper with various filearchive fields, or null - * if not a file page. - * - * @return ResultWrapper - * @todo Does this belong in Image for fuller encapsulation? - */ - function listFiles() { - if( $this->title->getNamespace() == NS_IMAGE ) { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'filearchive', - array( - 'fa_id', - 'fa_name', - 'fa_archive_name', - 'fa_storage_key', - 'fa_storage_group', - 'fa_size', - 'fa_width', - 'fa_height', - 'fa_bits', - 'fa_metadata', - 'fa_media_type', - 'fa_major_mime', - 'fa_minor_mime', - 'fa_description', - 'fa_user', - 'fa_user_text', - 'fa_timestamp', - 'fa_deleted' ), - array( 'fa_name' => $this->title->getDBkey() ), - __METHOD__, - array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - $ret = $dbr->resultObject( $res ); - return $ret; - } - return null; - } - - /** - * Fetch (and decompress if necessary) the stored text for the deleted - * revision of the page with the given timestamp. - * - * @return string - * @deprecated Use getRevision() for more flexible information - */ - function getRevisionText( $timestamp ) { - $rev = $this->getRevision( $timestamp ); - return $rev ? $rev->getText() : null; - } - - /** - * Return a Revision object containing data for the deleted revision. - * Note that the result *may* or *may not* have a null page ID. - * @param string $timestamp - * @return Revision - */ - function getRevision( $timestamp ) { - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'archive', - array( - 'ar_rev_id', - 'ar_text', - 'ar_comment', - 'ar_user', - 'ar_user_text', - 'ar_timestamp', - 'ar_minor_edit', - 'ar_flags', - 'ar_text_id', - 'ar_deleted', - 'ar_len' ), - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), - __METHOD__ ); - if( $row ) { - return new Revision( array( - 'page' => $this->title->getArticleId(), - 'id' => $row->ar_rev_id, - 'text' => ($row->ar_text_id - ? null - : Revision::getRevisionText( $row, 'ar_' ) ), - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id, - 'deleted' => $row->ar_deleted, - 'len' => $row->ar_len) ); - } else { - return null; - } - } - - /** - * Return the most-previous revision, either live or deleted, against - * the deleted revision given by timestamp. - * - * May produce unexpected results in case of history merges or other - * unusual time issues. - * - * @param string $timestamp - * @return Revision or null - */ - function getPreviousRevision( $timestamp ) { - $dbr = wfGetDB( DB_SLAVE ); - - // Check the previous deleted revision... - $row = $dbr->selectRow( 'archive', - 'ar_timestamp', - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp < ' . - $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), - __METHOD__, - array( - 'ORDER BY' => 'ar_timestamp DESC', - 'LIMIT' => 1 ) ); - $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false; - - $row = $dbr->selectRow( array( 'page', 'revision' ), - array( 'rev_id', 'rev_timestamp' ), - array( - 'page_namespace' => $this->title->getNamespace(), - 'page_title' => $this->title->getDBkey(), - 'page_id = rev_page', - 'rev_timestamp < ' . - $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), - __METHOD__, - array( - 'ORDER BY' => 'rev_timestamp DESC', - 'LIMIT' => 1 ) ); - $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false; - $prevLiveId = $row ? intval( $row->rev_id ) : null; - - if( $prevLive && $prevLive > $prevDeleted ) { - // Most prior revision was live - return Revision::newFromId( $prevLiveId ); - } elseif( $prevDeleted ) { - // Most prior revision was deleted - return $this->getRevision( $prevDeleted ); - } else { - // No prior revision on this page. - return null; - } - } - - /** - * Get the text from an archive row containing ar_text, ar_flags and ar_text_id - */ - function getTextFromRow( $row ) { - if( is_null( $row->ar_text_id ) ) { - // An old row from MediaWiki 1.4 or previous. - // Text is embedded in this row in classic compression format. - return Revision::getRevisionText( $row, "ar_" ); - } else { - // New-style: keyed to the text storage backend. - $dbr = wfGetDB( DB_SLAVE ); - $text = $dbr->selectRow( 'text', - array( 'old_text', 'old_flags' ), - array( 'old_id' => $row->ar_text_id ), - __METHOD__ ); - return Revision::getRevisionText( $text ); - } - } - - - /** - * Fetch (and decompress if necessary) the stored text of the most - * recently edited deleted revision of the page. - * - * If there are no archived revisions for the page, returns NULL. - * - * @return string - */ - function getLastRevisionText() { - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'archive', - array( 'ar_text', 'ar_flags', 'ar_text_id' ), - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), - 'PageArchive::getLastRevisionText', - array( 'ORDER BY' => 'ar_timestamp DESC' ) ); - if( $row ) { - return $this->getTextFromRow( $row ); - } else { - return NULL; - } - } - - /** - * Quick check if any archived revisions are present for the page. - * @return bool - */ - function isDeleted() { - $dbr = wfGetDB( DB_SLAVE ); - $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ) ); - return ($n > 0); - } - - /** - * Restore the given (or all) text and file revisions for the page. - * Once restored, the items will be removed from the archive tables. - * The deletion log will be updated with an undeletion notice. - * - * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param string $comment - * @param array $fileVersions - * @param bool $unsuppress - * - * @return array(number of file revisions restored, number of image revisions restored, log message) - * on success, false on failure - */ - function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false ) { - // If both the set of text revisions and file revisions are empty, - // restore everything. Otherwise, just restore the requested items. - $restoreAll = empty( $timestamps ) && empty( $fileVersions ); - - $restoreText = $restoreAll || !empty( $timestamps ); - $restoreFiles = $restoreAll || !empty( $fileVersions ); - - if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) { - $img = wfLocalFile( $this->title ); - $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); - $filesRestored = $this->fileStatus->successCount; - } else { - $filesRestored = 0; - } - - if( $restoreText ) { - $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress ); - if($textRestored === false) // It must be one of UNDELETE_* - return false; - } else { - $textRestored = 0; - } - - // Touch the log! - global $wgContLang; - $log = new LogPage( 'delete' ); - - if( $textRestored && $filesRestored ) { - $reason = wfMsgExt( 'undeletedrevisions-files', array( 'content', 'parsemag' ), - $wgContLang->formatNum( $textRestored ), - $wgContLang->formatNum( $filesRestored ) ); - } elseif( $textRestored ) { - $reason = wfMsgExt( 'undeletedrevisions', array( 'content', 'parsemag' ), - $wgContLang->formatNum( $textRestored ) ); - } elseif( $filesRestored ) { - $reason = wfMsgExt( 'undeletedfiles', array( 'content', 'parsemag' ), - $wgContLang->formatNum( $filesRestored ) ); - } else { - wfDebug( "Undelete: nothing undeleted...\n" ); - return false; - } - - if( trim( $comment ) != '' ) - $reason .= ": {$comment}"; - $log->addEntry( 'restore', $this->title, $reason ); - - return array($textRestored, $filesRestored, $reason); - } - - /** - * This is the meaty bit -- restores archived revisions of the given page - * to the cur/old tables. If the page currently exists, all revisions will - * be stuffed into old, otherwise the most recent will go into cur. - * - * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param string $comment - * @param array $fileVersions - * @param bool $unsuppress, remove all ar_deleted/fa_deleted restrictions of seletected revs - * - * @return mixed number of revisions restored or false on failure - */ - private function undeleteRevisions( $timestamps, $unsuppress = false ) { - if ( wfReadOnly() ) - return false; - $restoreAll = empty( $timestamps ); - - $dbw = wfGetDB( DB_MASTER ); - - # Does this page already exist? We'll have to update it... - $article = new Article( $this->title ); - $options = 'FOR UPDATE'; - $page = $dbw->selectRow( 'page', - array( 'page_id', 'page_latest' ), - array( 'page_namespace' => $this->title->getNamespace(), - 'page_title' => $this->title->getDBkey() ), - __METHOD__, - $options ); - if( $page ) { - $makepage = false; - # Page already exists. Import the history, and if necessary - # we'll update the latest revision field in the record. - $newid = 0; - $pageId = $page->page_id; - $previousRevId = $page->page_latest; - # Get the time span of this page - $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp', - array( 'rev_id' => $previousRevId ), - __METHOD__ ); - if( $previousTimestamp === false ) { - wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" ); - return 0; - } - } else { - # Have to create a new article... - $makepage = true; - $previousRevId = 0; - $previousTimestamp = 0; - } - - if( $restoreAll ) { - $oldones = '1 = 1'; # All revisions... - } else { - $oldts = implode( ',', - array_map( array( &$dbw, 'addQuotes' ), - array_map( array( &$dbw, 'timestamp' ), - $timestamps ) ) ); - - $oldones = "ar_timestamp IN ( {$oldts} )"; - } - - /** - * Select each archived revision... - */ - $result = $dbw->select( 'archive', - /* fields */ array( - 'ar_rev_id', - 'ar_text', - 'ar_comment', - 'ar_user', - 'ar_user_text', - 'ar_timestamp', - 'ar_minor_edit', - 'ar_flags', - 'ar_text_id', - 'ar_deleted', - 'ar_page_id', - 'ar_len' ), - /* WHERE */ array( - 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - $oldones ), - __METHOD__, - /* 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 ); - $pageId = $newid; - } - - $revision = null; - $restored = 0; - - while( $row = $ret->fetchObject() ) { - if( $row->ar_text_id ) { - // Revision was deleted in 1.5+; text is in - // the regular text table, use the reference. - // Specify null here so the so the text is - // dereferenced for page length info if needed. - $revText = null; - } else { - // Revision was deleted in 1.4 or earlier. - // Text is squashed into the archive row, and - // a new text table entry will be created for it. - $revText = Revision::getRevisionText( $row, 'ar_' ); - } - $revision = new Revision( array( - 'page' => $pageId, - 'id' => $row->ar_rev_id, - 'text' => $revText, - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id, - 'deleted' => $unsuppress ? 0 : $row->ar_deleted, - 'len' => $row->ar_len - ) ); - $revision->insertOn( $dbw ); - $restored++; - - wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) ); - } - // Was anything restored at all? - 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 ); - } - - if( $newid ) { - wfRunHooks( 'ArticleUndelete', array( &$this->title, true ) ); - Article::onArticleCreate( $this->title ); - } else { - wfRunHooks( 'ArticleUndelete', array( &$this->title, false ) ); - Article::onArticleEdit( $this->title ); - } - - if( $this->title->getNamespace() == NS_IMAGE ) { - $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); - $update->doUpdate(); - } - } else { - // Revision couldn't be created. This is very weird - 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; - } - - function getFileStatus() { return $this->fileStatus; } -} - -/** - * The HTML form for Special:Undelete, which allows users with the appropriate - * permissions to view and restore deleted content. - * @ingroup SpecialPage - */ -class UndeleteForm { - var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj; - var $mTargetTimestamp, $mAllowed, $mComment; - - function UndeleteForm( $request, $par = "" ) { - global $wgUser; - $this->mAction = $request->getVal( 'action' ); - $this->mTarget = $request->getVal( 'target' ); - $this->mSearchPrefix = $request->getText( 'prefix' ); - $time = $request->getVal( 'timestamp' ); - $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; - $this->mFile = $request->getVal( 'file' ); - - $posted = $request->wasPosted() && - $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - $this->mRestore = $request->getCheck( 'restore' ) && $posted; - $this->mPreview = $request->getCheck( 'preview' ) && $posted; - $this->mDiff = $request->getCheck( 'diff' ); - $this->mComment = $request->getText( 'wpComment' ); - $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); - - if( $par != "" ) { - $this->mTarget = $par; - } - if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) { - $this->mAllowed = true; - } else { - $this->mAllowed = false; - $this->mTimestamp = ''; - $this->mRestore = false; - } - if ( $this->mTarget !== "" ) { - $this->mTargetObj = Title::newFromURL( $this->mTarget ); - } else { - $this->mTargetObj = NULL; - } - if( $this->mRestore ) { - $timestamps = array(); - $this->mFileVersions = array(); - foreach( $_REQUEST as $key => $val ) { - $matches = array(); - if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { - array_push( $timestamps, $matches[1] ); - } - - if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { - $this->mFileVersions[] = intval( $matches[1] ); - } - } - rsort( $timestamps ); - $this->mTargetTimestamp = $timestamps; - } - } - - function execute() { - global $wgOut, $wgUser; - if ( $this->mAllowed ) { - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); - } else { - $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); - } - - if( is_null( $this->mTargetObj ) ) { - # Not all users can just browse every deleted page from the list - if( $wgUser->isAllowed( 'browsearchive' ) ) { - $this->showSearchForm(); - - # List undeletable articles - if( $this->mSearchPrefix ) { - $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix ); - $this->showList( $result ); - } - } else { - $wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) ); - } - return; - } - if( $this->mTimestamp !== '' ) { - return $this->showRevision( $this->mTimestamp ); - } - if( $this->mFile !== null ) { - $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile ); - // Check if user is allowed to see this file - if( !$file->userCan( File::DELETED_FILE ) ) { - $wgOut->permissionRequired( 'suppressrevision' ); - return false; - } else { - return $this->showFile( $this->mFile ); - } - } - if( $this->mRestore && $this->mAction == "submit" ) { - return $this->undelete(); - } - return $this->showHistory(); - } - - function showSearchForm() { - global $wgOut, $wgScript; - $wgOut->addWikiMsg( 'undelete-header' ); - - $wgOut->addHtml( - Xml::openElement( 'form', array( - 'method' => 'get', - 'action' => $wgScript ) ) . - '
      ' . - Xml::element( 'legend', array(), - wfMsg( 'undelete-search-box' ) ) . - Xml::hidden( 'title', - SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) . - Xml::inputLabel( wfMsg( 'undelete-search-prefix' ), - 'prefix', 'prefix', 20, - $this->mSearchPrefix ) . - Xml::submitButton( wfMsg( 'undelete-search-submit' ) ) . - '
      ' . - '' ); - } - - // Generic list of deleted pages - private function showList( $result ) { - global $wgLang, $wgContLang, $wgUser, $wgOut; - - if( $result->numRows() == 0 ) { - $wgOut->addWikiMsg( 'undelete-no-results' ); - return; - } - - $wgOut->addWikiMsg( "undeletepagetext" ); - - $sk = $wgUser->getSkin(); - $undelete = SpecialPage::getTitleFor( 'Undelete' ); - $wgOut->addHTML( "
        \n" ); - while( $row = $result->fetchObject() ) { - $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( "
      • {$link} ({$revs})
      • \n" ); - } - $result->free(); - $wgOut->addHTML( "
      \n" ); - - return true; - } - - private function showRevision( $timestamp ) { - global $wgLang, $wgUser, $wgOut; - $self = SpecialPage::getTitleFor( 'Undelete' ); - $skin = $wgUser->getSkin(); - - if(!preg_match("/[0-9]{14}/",$timestamp)) return 0; - - $archive = new PageArchive( $this->mTargetObj ); - $rev = $archive->getRevision( $timestamp ); - - if( !$rev ) { - $wgOut->addWikiMsg( 'undeleterevision-missing' ); - return; - } - - if( $rev->isDeleted(Revision::DELETED_TEXT) ) { - if( !$rev->userCan(Revision::DELETED_TEXT) ) { - $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) ); - return; - } else { - $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) ); - $wgOut->addHTML( '
      ' ); - // and we are allowed to see... - } - } - - $wgOut->setPageTitle( wfMsg( 'undeletepage' ) ); - - $link = $skin->makeKnownLinkObj( - 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 ); - if( $previousRev ) { - $this->showDiff( $previousRev, $rev ); - if( $wgUser->getOption( 'diffonly' ) ) { - return; - } else { - $wgOut->addHtml( '
      ' ); - } - } else { - $wgOut->addHtml( wfMsgHtml( 'undelete-nodiff' ) ); - } - } - - $wgOut->addHtml( '

      ' . wfMsgHtml( 'undelete-revision', $link, $time, $user ) . '

      ' ); - - wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ); - - if( $this->mPreview ) { - $wgOut->addHtml( "
      \n" ); - - //Hide [edit]s - $popts = $wgOut->parserOptions(); - $popts->setEditSection( false ); - $wgOut->parserOptions( $popts ); - $wgOut->addWikiTextTitleTidy( $rev->revText(), $this->mTargetObj, true ); - } - - $wgOut->addHtml( - wfElement( 'textarea', array( - 'readonly' => 'readonly', - 'cols' => intval( $wgUser->getOption( 'cols' ) ), - 'rows' => intval( $wgUser->getOption( 'rows' ) ) ), - $rev->revText() . "\n" ) . - wfOpenElement( 'div' ) . - wfOpenElement( 'form', array( - 'method' => 'post', - 'action' => $self->getLocalURL( "action=submit" ) ) ) . - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'target', - 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) . - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'timestamp', - 'value' => $timestamp ) ) . - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'wpEditToken', - 'value' => $wgUser->editToken() ) ) . - wfElement( 'input', array( - 'type' => 'submit', - 'name' => 'preview', - 'value' => wfMsg( 'showpreview' ) ) ) . - wfElement( 'input', array( - 'name' => 'diff', - 'type' => 'submit', - 'value' => wfMsg( 'showdiff' ) ) ) . - wfCloseElement( 'form' ) . - wfCloseElement( 'div' ) ); - } - - /** - * Build a diff display between this and the previous either deleted - * or non-deleted edit. - * @param Revision $previousRev - * @param Revision $currentRev - * @return string HTML - */ - function showDiff( $previousRev, $currentRev ) { - global $wgOut, $wgUser; - - $diffEngine = new DifferenceEngine(); - $diffEngine->showDiffStyle(); - $wgOut->addHtml( - "
      " . - "" . - "" . - "" . - "" . - "" . - "" . - "" . - "" . - "" . - $diffEngine->generateDiffBody( - $previousRev->getText(), $currentRev->getText() ) . - "
      " . - $this->diffHeader( $previousRev ) . - "" . - $this->diffHeader( $currentRev ) . - "
      " . - "
      \n" ); - - } - - private function diffHeader( $rev ) { - global $wgUser, $wgLang, $wgLang; - $sk = $wgUser->getSkin(); - $isDeleted = !( $rev->getId() && $rev->getTitle() ); - if( $isDeleted ) { - /// @fixme $rev->getTitle() is null for deleted revs...? - $targetPage = SpecialPage::getTitleFor( 'Undelete' ); - $targetQuery = 'target=' . - $this->mTargetObj->getPrefixedUrl() . - '×tamp=' . - wfTimestamp( TS_MW, $rev->getTimestamp() ); - } else { - /// @fixme getId() may return non-zero for deleted revs... - $targetPage = $rev->getTitle(); - $targetQuery = 'oldid=' . $rev->getId(); - } - return - '
      ' . - $sk->makeLinkObj( $targetPage, - wfMsgHtml( 'revisionasof', - $wgLang->timeanddate( $rev->getTimestamp(), true ) ), - $targetQuery ) . - ( $isDeleted ? ' ' . wfMsgHtml( 'deletedrev' ) : '' ) . - '
      ' . - '
      ' . - $sk->revUserTools( $rev ) . '
      ' . - '
      ' . - '
      ' . - $sk->revComment( $rev ) . '
      ' . - '
      '; - } - - /** - * Show a deleted file version requested by the visitor. - */ - private function showFile( $key ) { - global $wgOut, $wgRequest; - $wgOut->disable(); - - # We mustn't allow the output to be Squid cached, otherwise - # if an admin previews a deleted image, and it's cached, then - # a user without appropriate permissions can toddle off and - # nab the image, and Squid will serve it - $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); - $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); - $wgRequest->response()->header( 'Pragma: no-cache' ); - - $store = FileStore::get( 'deleted' ); - $store->stream( $key ); - } - - private function showHistory() { - global $wgLang, $wgUser, $wgOut; - - $sk = $wgUser->getSkin(); - if( $this->mAllowed ) { - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); - } else { - $wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) ); - } - - $wgOut->addWikiText( wfMsgHtml( 'undeletepagetitle', $this->mTargetObj->getPrefixedText()) ); - - $archive = new PageArchive( $this->mTargetObj ); - /* - $text = $archive->getLastRevisionText(); - if( is_null( $text ) ) { - $wgOut->addWikiMsg( "nohistory" ); - return; - } - */ - if ( $this->mAllowed ) { - $wgOut->addWikiMsg( "undeletehistory" ); - $wgOut->addWikiMsg( "undeleterevdel" ); - } else { - $wgOut->addWikiMsg( "undeletehistorynoadmin" ); - } - - # List all stored revisions - $revisions = $archive->listRevisions(); - $files = $archive->listFiles(); - - $haveRevisions = $revisions && $revisions->numRows() > 0; - $haveFiles = $files && $files->numRows() > 0; - - # Batch existence check on user and talk pages - if( $haveRevisions ) { - $batch = new LinkBatch(); - while( $row = $revisions->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) ); - } - $batch->execute(); - $revisions->seek( 0 ); - } - if( $haveFiles ) { - $batch = new LinkBatch(); - while( $row = $files->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) ); - } - $batch->execute(); - $files->seek( 0 ); - } - - if ( $this->mAllowed ) { - $titleObj = SpecialPage::getTitleFor( "Undelete" ); - $action = $titleObj->getLocalURL( "action=submit" ); - # Start the form here - $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) ); - $wgOut->addHtml( $top ); - } - - # Show relevant lines from the deletion log: - $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" ); - LogEventsList::showLogExtract( $wgOut, 'delete', $this->mTargetObj->getPrefixedText() ); - - if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { - # Format the user-visible controls (comment field, submission button) - # in a nice little table - if( $wgUser->isAllowed( 'suppressrevision' ) ) { - $unsuppressBox = - " -   - " . - Xml::checkLabel( wfMsg('revdelete-unsuppress'), 'wpUnsuppress', - 'mw-undelete-unsuppress', $this->mUnsuppress ). - " - "; - } else { - $unsuppressBox = ""; - } - $table = - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'undelete') ). - Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . - " - " . - wfMsgWikiHtml( 'undeleteextrahelp' ) . - " - - - " . - Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) . - " - " . - Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) . - " - - -   - " . - Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . - Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . - " - " . - $unsuppressBox . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ); - - $wgOut->addHtml( $table ); - } - - $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" ); - - if( $haveRevisions ) { - # The page's stored (deleted) history: - $wgOut->addHTML("
        "); - $target = urlencode( $this->mTarget ); - $remaining = $revisions->numRows(); - $earliestLiveTime = $this->getEarliestTime( $this->mTargetObj ); - - while( $row = $revisions->fetchObject() ) { - $remaining--; - $wgOut->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) ); - } - $revisions->free(); - $wgOut->addHTML("
      "); - } else { - $wgOut->addWikiMsg( "nohistory" ); - } - - if( $haveFiles ) { - $wgOut->addHtml( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" ); - $wgOut->addHtml( "
        " ); - while( $row = $files->fetchObject() ) { - $wgOut->addHTML( $this->formatFileRow( $row, $sk ) ); - } - $files->free(); - $wgOut->addHTML( "
      " ); - } - - if ( $this->mAllowed ) { - # Slip in the hidden controls here - $misc = Xml::hidden( 'target', $this->mTarget ); - $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); - $misc .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $misc ); - } - - return true; - } - - private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) { - global $wgUser, $wgLang; - - $rev = new Revision( array( - 'page' => $this->mTargetObj->getArticleId(), - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'deleted' => $row->ar_deleted, - 'len' => $row->ar_len ) ); - - $stxt = ''; - $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); - if( $this->mAllowed ) { - $checkBox = Xml::check( "ts$ts" ); - $titleObj = SpecialPage::getTitleFor( "Undelete" ); - $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk ); - # Last link - if( !$rev->userCan( Revision::DELETED_TEXT ) ) { - $last = wfMsgHtml('diff'); - } else if( $remaining > 0 || ($earliestLiveTime && $ts > $earliestLiveTime) ) { - $last = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml('diff'), - "target=" . $this->mTargetObj->getPrefixedUrl() . "×tamp=$ts&diff=prev" ); - } else { - $last = wfMsgHtml('diff'); - } - } else { - $checkBox = ''; - $pageLink = $wgLang->timeanddate( $ts, true ); - $last = wfMsgHtml('diff'); - } - $userLink = $sk->revUserTools( $rev ); - - if(!is_null($size = $row->ar_len)) { - if($size == 0) - $stxt = wfMsgHtml('historyempty'); - else - $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); - } - $comment = $sk->revComment( $rev ); - $revdlink = ''; - if( $wgUser->isAllowed( 'deleterevision' ) ) { - $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); - if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { - // 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" ); - // Bolden oversighted content - if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) - $del = "$del"; - } - $revdlink = "($del)"; - } - - return "
    • $checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment
    • "; - } - - private function formatFileRow( $row, $sk ) { - global $wgUser, $wgLang; - - $file = ArchivedFile::newFromRow( $row ); - - $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); - if( $this->mAllowed && $row->fa_storage_key ) { - $checkBox = Xml::check( "fileid" . $row->fa_id ); - $key = urlencode( $row->fa_storage_key ); - $target = urlencode( $this->mTarget ); - $titleObj = SpecialPage::getTitleFor( "Undelete" ); - $pageLink = $this->getFileLink( $file, $titleObj, $ts, $key, $sk ); - } else { - $checkBox = ''; - $pageLink = $wgLang->timeanddate( $ts, true ); - } - $userLink = $this->getFileUser( $file, $sk ); - $data = - wfMsgHtml( 'widthheight', - $wgLang->formatNum( $row->fa_width ), - $wgLang->formatNum( $row->fa_height ) ) . - ' (' . - wfMsgHtml( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) . - ')'; - $comment = $this->getFileComment( $file, $sk ); - $revdlink = ''; - if( $wgUser->isAllowed( 'deleterevision' ) ) { - $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); - if( !$file->userCan(File::DELETED_RESTRICTED ) ) { - // If revision was hidden from sysops - $del = wfMsgHtml('rev-delundel'); - } else { - $del = $sk->makeKnownLinkObj( $revdel, - wfMsgHtml('rev-delundel'), - 'target=' . $this->mTargetObj->getPrefixedUrl() . - '&fileid=' . $row->fa_id ); - // Bolden oversighted content - if( $file->isDeleted( File::DELETED_RESTRICTED ) ) - $del = "$del"; - } - $revdlink = "($del)"; - } - return "
    • $checkBox $revdlink $pageLink . . $userLink $data $comment
    • \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 - */ - function getPageLink( $rev, $titleObj, $ts, $sk ) { - global $wgLang; - - if( !$rev->userCan(Revision::DELETED_TEXT) ) { - return '' . $wgLang->timeanddate( $ts, true ) . ''; - } else { - $link = $sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), - "target=".$this->mTargetObj->getPrefixedUrl()."×tamp=$ts" ); - if( $rev->isDeleted(Revision::DELETED_TEXT) ) - $link = '' . $link . ''; - return $link; - } - } - - /** - * Fetch image view link if it's available to all users - * @return string - */ - function getFileLink( $file, $titleObj, $ts, $key, $sk ) { - global $wgLang; - - if( !$file->userCan(File::DELETED_FILE) ) { - return '' . $wgLang->timeanddate( $ts, true ) . ''; - } else { - $link = $sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), - "target=".$this->mTargetObj->getPrefixedUrl()."&file=$key" ); - if( $file->isDeleted(File::DELETED_FILE) ) - $link = '' . $link . ''; - return $link; - } - } - - /** - * Fetch file's user id if it's available to this user - * @return string - */ - function getFileUser( $file, $sk ) { - if( !$file->userCan(File::DELETED_USER) ) { - return '' . wfMsgHtml( 'rev-deleted-user' ) . ''; - } else { - $link = $sk->userLink( $file->getRawUser(), $file->getRawUserText() ) . - $sk->userToolLinks( $file->getRawUser(), $file->getRawUserText() ); - if( $file->isDeleted(File::DELETED_USER) ) - $link = '' . $link . ''; - return $link; - } - } - - /** - * Fetch file upload comment if it's available to this user - * @return string - */ - function getFileComment( $file, $sk ) { - if( !$file->userCan(File::DELETED_COMMENT) ) { - return '' . wfMsgHtml( 'rev-deleted-comment' ) . ''; - } else { - $link = $sk->commentBlock( $file->getRawDescription() ); - if( $file->isDeleted(File::DELETED_COMMENT) ) - $link = '' . $link . ''; - return $link; - } - } - - function undelete() { - global $wgOut, $wgUser; - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - if( !is_null( $this->mTargetObj ) ) { - $archive = new PageArchive( $this->mTargetObj ); - $ok = $archive->undelete( - $this->mTargetTimestamp, - $this->mComment, - $this->mFileVersions, - $this->mUnsuppress ); - - if( is_array($ok) ) { - if ( $ok[1] ) // Undeleted file count - wfRunHooks( 'FileUndeleteComplete', array( - $this->mTargetObj, $this->mFileVersions, - $wgUser, $this->mComment) ); - - $skin = $wgUser->getSkin(); - $link = $skin->makeKnownLinkObj( $this->mTargetObj ); - $wgOut->addHtml( wfMsgWikiHtml( 'undeletedpage', $link ) ); - } else { - $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); - $wgOut->addHtml( '

      ' . wfMsgHtml( "undeleterevdel" ) . '

      ' ); - } - - // Show file deletion warnings and errors - $status = $archive->getFileStatus(); - if( $status && !$status->isGood() ) { - $wgOut->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) ); - } - } else { - $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); - } - return false; - } -} diff --git a/includes/specials/Unlockdb.php b/includes/specials/Unlockdb.php deleted file mode 100644 index 0bf7e5aa5a..0000000000 --- a/includes/specials/Unlockdb.php +++ /dev/null @@ -1,107 +0,0 @@ -isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); - return; - } - - $action = $wgRequest->getVal( 'action' ); - $f = new DBUnlockForm(); - - if ( "success" == $action ) { - $f->showSuccess(); - } else if ( "submit" == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( "" ); - } -} - -/** - * @ingroup SpecialPage - */ -class DBUnlockForm { - function showForm( $err ) - { - global $wgOut, $wgUser; - - global $wgReadOnlyFile; - if( !file_exists( $wgReadOnlyFile ) ) { - $wgOut->addWikiMsg( 'databasenotlocked' ); - return; - } - - $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); - $wgOut->addWikiMsg( "unlockdbtext" ); - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsg( "formerror" ) ); - $wgOut->addHTML( '

      ' . htmlspecialchars( $err ) . "

      \n" ); - } - $lc = htmlspecialchars( wfMsg( "unlockconfirm" ) ); - $lb = htmlspecialchars( wfMsg( "unlockbtn" ) ); - $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); - $action = $titleObj->escapeLocalURL( "action=submit" ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( << - - - - - - - - - -
      - - {$lc}
        - -
      - - -END -); - - } - - function doSubmit() { - global $wgOut, $wgRequest, $wgReadOnlyFile; - - $wpLockConfirm = $wgRequest->getCheck( 'wpLockConfirm' ); - if ( ! $wpLockConfirm ) { - $this->showForm( wfMsg( "locknoconfirm" ) ); - return; - } - if ( @! unlink( $wgReadOnlyFile ) ) { - $wgOut->showFileDeleteError( $wgReadOnlyFile ); - return; - } - $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); - $success = $titleObj->getFullURL( "action=success" ); - $wgOut->redirect( $success ); - } - - function showSuccess() { - global $wgOut; - global $ip; - - $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); - $wgOut->setSubtitle( wfMsg( "unlockdbsuccesssub" ) ); - $wgOut->addWikiMsg( "unlockdbsuccesstext", $ip ); - } -} diff --git a/includes/specials/Unusedcategories.php b/includes/specials/Unusedcategories.php deleted file mode 100644 index 406f794409..0000000000 --- a/includes/specials/Unusedcategories.php +++ /dev/null @@ -1,46 +0,0 @@ -tableNamesN( 'categorylinks', 'page' ); - return "SELECT 'Unusedcategories' as type, - {$NScat} as namespace, page_title as title, page_title as value - FROM $page - LEFT JOIN $categorylinks ON page_title=cl_to - WHERE cl_from IS NULL - AND page_namespace = {$NScat} - AND page_is_redirect = 0"; - } - - function formatResult( $skin, $result ) { - $title = Title::makeTitle( NS_CATEGORY, $result->title ); - return $skin->makeLinkObj( $title, $title->getText() ); - } -} - -/** constructor */ -function wfSpecialUnusedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - $uc = new UnusedCategoriesPage(); - return $uc->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Unusedimages.php b/includes/specials/Unusedimages.php deleted file mode 100644 index d71b638fa5..0000000000 --- a/includes/specials/Unusedimages.php +++ /dev/null @@ -1,60 +0,0 @@ -tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' ); - - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, - img_user, img_user_text, img_description - 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"; - } else { - list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' ); - - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, - img_user, img_user_text, img_description - FROM $image LEFT JOIN $imagelinks ON img_name=il_to WHERE il_to IS NULL "; - } - } - - function getPageHeader() { - return wfMsgExt( 'unusedimagestext', array( 'parse') ); - } - -} - -/** - * Entry point - */ -function wfSpecialUnusedimages() { - list( $limit, $offset ) = wfCheckLimits(); - $uip = new UnusedimagesPage(); - - return $uip->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Unusedtemplates.php b/includes/specials/Unusedtemplates.php deleted file mode 100644 index 89acd09c75..0000000000 --- a/includes/specials/Unusedtemplates.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - * @ingroup SpecialPage - */ -class UnusedtemplatesPage extends QueryPage { - - function getName() { return( 'Unusedtemplates' ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } - function sortDescending() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' ); - $sql = "SELECT 'Unusedtemplates' AS type, page_title AS title, - page_namespace AS namespace, 0 AS value - FROM $page - LEFT JOIN $templatelinks - ON page_namespace = tl_namespace AND page_title = tl_title - WHERE page_namespace = 10 AND tl_from IS NULL - AND page_is_redirect = 0"; - return $sql; - } - - function formatResult( $skin, $result ) { - $title = Title::makeTitle( NS_TEMPLATE, $result->title ); - $pageLink = $skin->makeKnownLinkObj( $title, '', 'redirect=no' ); - $wlhLink = $skin->makeKnownLinkObj( - SpecialPage::getTitleFor( 'Whatlinkshere' ), - wfMsgHtml( 'unusedtemplateswlh' ), - 'target=' . $title->getPrefixedUrl() ); - return wfSpecialList( $pageLink, $wlhLink ); - } - - function getPageHeader() { - return wfMsgExt( 'unusedtemplatestext', array( 'parse' ) ); - } - -} - -function wfSpecialUnusedtemplates() { - list( $limit, $offset ) = wfCheckLimits(); - $utp = new UnusedtemplatesPage(); - $utp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Unwatchedpages.php b/includes/specials/Unwatchedpages.php deleted file mode 100644 index 64ab372995..0000000000 --- a/includes/specials/Unwatchedpages.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class UnwatchedpagesPage extends QueryPage { - - function getName() { return 'Unwatchedpages'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' ); - $mwns = NS_MEDIAWIKI; - return - " - SELECT - 'Unwatchedpages' as type, - page_namespace as namespace, - page_title as title, - page_namespace as value - FROM $page - LEFT JOIN $watchlist ON wl_namespace = page_namespace AND page_title = wl_title - WHERE wl_title IS NULL AND page_is_redirect = 0 AND page_namespace<>$mwns - "; - } - - function sortDescending() { return false; } - - function formatResult( $skin, $result ) { - global $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->makeKnownLinkObj( $nt, htmlspecialchars( $text ) ); - $wlink = $skin->makeKnownLinkObj( $nt, wfMsgHtml( 'watch' ), 'action=watch' ); - - return wfSpecialList( $plink, $wlink ); - } -} - -/** - * constructor - */ -function wfSpecialUnwatchedpages() { - global $wgUser, $wgOut; - - if ( ! $wgUser->isAllowed( 'unwatchedpages' ) ) - return $wgOut->permissionRequired( 'unwatchedpages' ); - - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new UnwatchedpagesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Upload.php b/includes/specials/Upload.php deleted file mode 100644 index 0f37f503a5..0000000000 --- a/includes/specials/Upload.php +++ /dev/null @@ -1,1755 +0,0 @@ -execute(); -} - -/** - * implements Special:Upload - * @ingroup SpecialPage - */ -class UploadForm { - const SUCCESS = 0; - const BEFORE_PROCESSING = 1; - const LARGE_FILE_SERVER = 2; - const EMPTY_FILE = 3; - const MIN_LENGHT_PARTNAME = 4; - const ILLEGAL_FILENAME = 5; - const PROTECTED_PAGE = 6; - const OVERWRITE_EXISTING_FILE = 7; - const FILETYPE_MISSING = 8; - const FILETYPE_BADTYPE = 9; - const VERIFICATION_ERROR = 10; - const UPLOAD_VERIFICATION_ERROR = 11; - const UPLOAD_WARNING = 12; - const INTERNAL_ERROR = 13; - - /**#@+ - * @access private - */ - var $mComment, $mLicense, $mIgnoreWarning, $mCurlError; - var $mDestName, $mTempPath, $mFileSize, $mFileProps; - var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked; - var $mSrcName, $mSessionKey, $mStashed, $mDesiredDestName, $mRemoveTempFile, $mSourceType; - var $mDestWarningAck, $mCurlDestHandle; - var $mLocalFile; - - # Placeholders for text injection by hooks (must be HTML) - # extensions should take care to _append_ to the present value - var $uploadFormTextTop; - var $uploadFormTextAfterSummary; - - const SESSION_VERSION = 1; - /**#@-*/ - - /** - * Constructor : initialise object - * Get data POSTed through the form and assign them to the object - * @param $request Data posted. - */ - function UploadForm( &$request ) { - global $wgAllowCopyUploads; - $this->mDesiredDestName = $request->getText( 'wpDestFile' ); - $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ); - $this->mComment = $request->getText( 'wpUploadDescription' ); - - if( !$request->wasPosted() ) { - # GET requests just give the main form; no data except destination - # filename and description - return; - } - - # Placeholders for text injection by hooks (empty per default) - $this->uploadFormTextTop = ""; - $this->uploadFormTextAfterSummary = ""; - - $this->mReUpload = $request->getCheck( 'wpReUpload' ); - $this->mUploadClicked = $request->getCheck( 'wpUpload' ); - - $this->mLicense = $request->getText( 'wpLicense' ); - $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); - $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); - $this->mWatchthis = $request->getBool( 'wpWatchthis' ); - $this->mSourceType = $request->getText( 'wpSourceType' ); - $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); - - $this->mAction = $request->getVal( 'action' ); - - $this->mSessionKey = $request->getInt( 'wpSessionKey' ); - if( !empty( $this->mSessionKey ) && - isset( $_SESSION['wsUploadData'][$this->mSessionKey]['version'] ) && - $_SESSION['wsUploadData'][$this->mSessionKey]['version'] == self::SESSION_VERSION ) { - /** - * Confirming a temporarily stashed upload. - * We don't want path names to be forged, so we keep - * them in the session on the server and just give - * an opaque key to the user agent. - */ - $data = $_SESSION['wsUploadData'][$this->mSessionKey]; - $this->mTempPath = $data['mTempPath']; - $this->mFileSize = $data['mFileSize']; - $this->mSrcName = $data['mSrcName']; - $this->mFileProps = $data['mFileProps']; - $this->mCurlError = 0/*UPLOAD_ERR_OK*/; - $this->mStashed = true; - $this->mRemoveTempFile = false; - } else { - /** - *Check for a newly uploaded file. - */ - if( $wgAllowCopyUploads && $this->mSourceType == 'web' ) { - $this->initializeFromUrl( $request ); - } else { - $this->initializeFromUpload( $request ); - } - } - } - - /** - * Initialize the uploaded file from PHP data - * @access private - */ - function initializeFromUpload( $request ) { - $this->mTempPath = $request->getFileTempName( 'wpUploadFile' ); - $this->mFileSize = $request->getFileSize( 'wpUploadFile' ); - $this->mSrcName = $request->getFileName( 'wpUploadFile' ); - $this->mCurlError = $request->getUploadError( 'wpUploadFile' ); - $this->mSessionKey = false; - $this->mStashed = false; - $this->mRemoveTempFile = false; // PHP will handle this - } - - /** - * Copy a web file to a temporary file - * @access private - */ - function initializeFromUrl( $request ) { - global $wgTmpDirectory; - $url = $request->getText( 'wpUploadFileURL' ); - $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); - - $this->mTempPath = $local_file; - $this->mFileSize = 0; # Will be set by curlCopy - $this->mCurlError = $this->curlCopy( $url, $local_file ); - $pathParts = explode( '/', $url ); - $this->mSrcName = array_pop( $pathParts ); - $this->mSessionKey = false; - $this->mStashed = false; - - // PHP won't auto-cleanup the file - $this->mRemoveTempFile = file_exists( $local_file ); - } - - /** - * Safe copy from URL - * Returns true if there was an error, false otherwise - */ - private function curlCopy( $url, $dest ) { - global $wgUser, $wgOut; - - if( !$wgUser->isAllowed( 'upload_by_url' ) ) { - $wgOut->permissionRequired( 'upload_by_url' ); - return true; - } - - # Maybe remove some pasting blanks :-) - $url = trim( $url ); - if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) { - # Only HTTP or FTP URLs - $wgOut->showErrorPage( 'upload-proto-error', 'upload-proto-error-text' ); - return true; - } - - # Open temporary file - $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" ); - if( $this->mCurlDestHandle === false ) { - # Could not open temporary file to write in - $wgOut->showErrorPage( 'upload-file-error', 'upload-file-error-text'); - return true; - } - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug - curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout - curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed - curl_setopt( $ch, CURLOPT_URL, $url); - curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) ); - curl_exec( $ch ); - $error = curl_errno( $ch ) ? true : false; - $errornum = curl_errno( $ch ); - // if ( $error ) print curl_error ( $ch ) ; # Debugging output - curl_close( $ch ); - - fclose( $this->mCurlDestHandle ); - unset( $this->mCurlDestHandle ); - if( $error ) { - unlink( $dest ); - if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) ) - $wgOut->showErrorPage( 'upload-misc-error', 'upload-misc-error-text' ); - else - $wgOut->showErrorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" ); - } - - return $error; - } - - /** - * Callback function for CURL-based web transfer - * Write data to file unless we've passed the length limit; - * if so, abort immediately. - * @access private - */ - function uploadCurlCallback( $ch, $data ) { - global $wgMaxUploadSize; - $length = strlen( $data ); - $this->mFileSize += $length; - if( $this->mFileSize > $wgMaxUploadSize ) { - return 0; - } - fwrite( $this->mCurlDestHandle, $data ); - return $length; - } - - /** - * Start doing stuff - * @access public - */ - function execute() { - global $wgUser, $wgOut; - global $wgEnableUploads; - - # Check uploading enabled - if( !$wgEnableUploads ) { - $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext', array( $this->mDesiredDestName ) ); - return; - } - - # Check permissions - if( !$wgUser->isAllowed( 'upload' ) ) { - if( !$wgUser->isLoggedIn() ) { - $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' ); - } else { - $wgOut->permissionRequired( 'upload' ); - } - return; - } - - # Check blocks - if( $wgUser->isBlocked() ) { - $wgOut->blockedPage(); - return; - } - - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - if( $this->mReUpload ) { - if( !$this->unsaveUploadedFile() ) { - return; - } - # Because it is probably checked and shouldn't be - $this->mIgnoreWarning = false; - - $this->mainUploadForm(); - } else if( 'submit' == $this->mAction || $this->mUploadClicked ) { - $this->processUpload(); - } else { - $this->mainUploadForm(); - } - - $this->cleanupTempFile(); - } - - /** - * Do the upload - * Checks are made in SpecialUpload::execute() - * - * @access private - */ - function processUpload(){ - global $wgUser, $wgOut, $wgFileExtensions; - $details = null; - $value = null; - $value = $this->internalProcessUpload( $details ); - - switch($value) { - case self::SUCCESS: - $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() ); - break; - - case self::BEFORE_PROCESSING: - break; - - case self::LARGE_FILE_SERVER: - $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) ); - break; - - case self::EMPTY_FILE: - $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) ); - break; - - case self::MIN_LENGHT_PARTNAME: - $this->mainUploadForm( wfMsgHtml( 'minlength1' ) ); - break; - - case self::ILLEGAL_FILENAME: - $filtered = $details['filtered']; - $this->uploadError( wfMsgWikiHtml( 'illegalfilename', htmlspecialchars( $filtered ) ) ); - break; - - case self::PROTECTED_PAGE: - $wgOut->showPermissionsErrorPage( $details['permissionserrors'] ); - break; - - case self::OVERWRITE_EXISTING_FILE: - $errorText = $details['overwrite']; - $overwrite = new WikiError( $wgOut->parse( $errorText ) ); - $this->uploadError( $overwrite->toString() ); - break; - - case self::FILETYPE_MISSING: - $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) ); - break; - - case self::FILETYPE_BADTYPE: - $finalExt = $details['finalExt']; - $this->uploadError( - wfMsgExt( 'filetype-banned-type', - array( 'parseinline' ), - htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ) - ) - ); - break; - - case self::VERIFICATION_ERROR: - $veri = $details['veri']; - $this->uploadError( $veri->toString() ); - break; - - case self::UPLOAD_VERIFICATION_ERROR: - $error = $details['error']; - $this->uploadError( $error ); - break; - - case self::UPLOAD_WARNING: - $warning = $details['warning']; - $this->uploadWarning( $warning ); - break; - - case self::INTERNAL_ERROR: - $internal = $details['internal']; - $this->showError( $internal ); - break; - - default: - throw new MWException( __METHOD__ . ": Unknown value `{$value}`" ); - } - } - - /** - * Really do the upload - * Checks are made in SpecialUpload::execute() - * - * @param array $resultDetails contains result-specific dict of additional values - * - * @access private - */ - function internalProcessUpload( &$resultDetails ) { - global $wgUser; - - if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) - { - wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file." ); - return self::BEFORE_PROCESSING; - } - - /** - * If there was no filename or a zero size given, give up quick. - */ - if( trim( $this->mSrcName ) == '' || empty( $this->mFileSize ) ) { - return self::EMPTY_FILE; - } - - /* Check for curl error */ - if( $this->mCurlError ) { - return self::BEFORE_PROCESSING; - } - - # Chop off any directories in the given filename - if( $this->mDesiredDestName ) { - $basename = $this->mDesiredDestName; - } else { - $basename = $this->mSrcName; - } - $filtered = wfBaseName( $basename ); - - /** - * We'll want to blacklist against *any* 'extension', and use - * only the final one for the whitelist. - */ - list( $partname, $ext ) = $this->splitExtensions( $filtered ); - - if( count( $ext ) ) { - $finalExt = $ext[count( $ext ) - 1]; - } else { - $finalExt = ''; - } - - # If there was more than one "extension", reassemble the base - # filename to prevent bogus complaints about length - if( count( $ext ) > 1 ) { - for( $i = 0; $i < count( $ext ) - 1; $i++ ) - $partname .= '.' . $ext[$i]; - } - - if( strlen( $partname ) < 1 ) { - return self::MIN_LENGHT_PARTNAME; - } - - /** - * Filter out illegal characters, and try to make a legible name - * out of it. We'll strip some silently that Title would die on. - */ - $filtered = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $filtered ); - $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(); - - /** - * If the image is protected, non-sysop users won't be able - * to modify it by uploading a new revision. - */ - $permErrors = $nt->getUserPermissionsErrors( 'edit', $wgUser ); - $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $wgUser ); - $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $wgUser ) ); - - if( $permErrors || $permErrorsUpload || $permErrorsCreate ) { - // merge all the problems into one list, avoiding duplicates - $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) ); - $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) ); - $resultDetails = array( 'permissionserrors' => $permErrors ); - return self::PROTECTED_PAGE; - } - - /** - * In some cases we may forbid overwriting of existing files. - */ - $overwrite = $this->checkOverwrite( $this->mDestName ); - if( $overwrite !== true ) { - $resultDetails = array( 'overwrite' => $overwrite ); - return self::OVERWRITE_EXISTING_FILE; - } - - /* Don't allow users to override the blacklist (check file extension) */ - global $wgCheckFileExtensions, $wgStrictFileExtensions; - global $wgFileExtensions, $wgFileBlacklist; - if ($finalExt == '') { - return self::FILETYPE_MISSING; - } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || - ($wgCheckFileExtensions && $wgStrictFileExtensions && - !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) { - $resultDetails = array( 'finalExt' => $finalExt ); - return self::FILETYPE_BADTYPE; - } - - /** - * Look at the contents of the file; if we can recognize the - * type but it's corrupt or data of the wrong type, we should - * probably not accept it. - */ - if( !$this->mStashed ) { - $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $finalExt ); - $this->checkMacBinary(); - $veri = $this->verify( $this->mTempPath, $finalExt ); - - if( $veri !== true ) { //it's a wiki error... - $resultDetails = array( 'veri' => $veri ); - return self::VERIFICATION_ERROR; - } - - /** - * Provide an opportunity for extensions to add further checks - */ - $error = ''; - if( !wfRunHooks( 'UploadVerification', - array( $this->mDestName, $this->mTempPath, &$error ) ) ) { - $resultDetails = array( 'error' => $error ); - return self::UPLOAD_VERIFICATION_ERROR; - } - } - - - /** - * Check for non-fatal conditions - */ - if ( ! $this->mIgnoreWarning ) { - $warning = ''; - - global $wgCapitalLinks; - if( $wgCapitalLinks ) { - $filtered = ucfirst( $filtered ); - } - if( $basename != $filtered ) { - $warning .= '
    • '.wfMsgHtml( 'badfilename', htmlspecialchars( $this->mDestName ) ).'
    • '; - } - - global $wgCheckFileExtensions; - if ( $wgCheckFileExtensions ) { - if ( !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) { - $warning .= '
    • ' . - wfMsgExt( 'filetype-unwanted-type', - array( 'parseinline' ), - htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ) - ) . '
    • '; - } - } - - global $wgUploadSizeWarning; - if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) { - $skin = $wgUser->getSkin(); - $wsize = $skin->formatSize( $wgUploadSizeWarning ); - $asize = $skin->formatSize( $this->mFileSize ); - $warning .= '
    • ' . wfMsgHtml( 'large-file', $wsize, $asize ) . '
    • '; - } - if ( $this->mFileSize == 0 ) { - $warning .= '
    • '.wfMsgHtml( 'emptyfile' ).'
    • '; - } - - if ( !$this->mDestWarningAck ) { - $warning .= self::getExistsWarning( $this->mLocalFile ); - } - - $warning .= $this->getDupeWarning( $this->mTempPath ); - - if( $warning != '' ) { - /** - * Stash the file in a temporary location; the user can choose - * to let it through and we'll complete the upload then. - */ - $resultDetails = array( 'warning' => $warning ); - return self::UPLOAD_WARNING; - } - } - - /** - * Try actually saving the thing... - * It will show an error form on failure. - */ - $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, - $this->mCopyrightStatus, $this->mCopyrightSource ); - - $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText, - File::DELETE_SOURCE, $this->mFileProps ); - if ( !$status->isGood() ) { - $resultDetails = array( 'internal' => $status->getWikiText() ); - return self::INTERNAL_ERROR; - } else { - if ( $this->mWatchthis ) { - global $wgUser; - $wgUser->addWatch( $this->mLocalFile->getTitle() ); - } - // Success, redirect to description page - $img = null; // @todo: added to avoid passing a ref to null - should this be defined somewhere? - wfRunHooks( 'UploadComplete', array( &$this ) ); - return self::SUCCESS; - } - } - - /** - * Do existence checks on a file and produce a warning - * This check is static and can be done pre-upload via AJAX - * Returns an HTML fragment consisting of one or more LI elements if there is a warning - * Returns an empty string if there is no warning - */ - static function getExistsWarning( $file ) { - global $wgUser, $wgContLang; - // Check for uppercase extension. We allow these filenames but check if an image - // with lowercase extension exists already - $warning = ''; - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - if( strpos( $file->getName(), '.' ) == false ) { - $partname = $file->getName(); - $rawExtension = ''; - } else { - $n = strrpos( $file->getName(), '.' ); - $rawExtension = substr( $file->getName(), $n + 1 ); - $partname = substr( $file->getName(), 0, $n ); - } - - $sk = $wgUser->getSkin(); - - if ( $rawExtension != $file->getExtension() ) { - // We're not using the normalized form of the extension. - // Normal form is lowercase, using most common of alternate - // extensions (eg 'jpg' rather than 'JPEG'). - // - // Check for another file using the normalized form... - $nt_lc = Title::makeTitle( NS_IMAGE, $partname . '.' . $file->getExtension() ); - $file_lc = wfLocalFile( $nt_lc ); - } else { - $file_lc = false; - } - - if( $file->exists() ) { - $dlink = $sk->makeKnownLinkObj( $file->getTitle() ); - if ( $file->allowInlineDisplay() ) { - $dlink2 = $sk->makeImageLinkObj( $file->getTitle(), wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $file->getName(), $align, array(), false, true ); - } elseif ( !$file->allowInlineDisplay() && $file->isSafeFile() ) { - $icon = $file->iconThumb(); - $dlink2 = '
      ' . - $icon->toHtml( array( 'desc-link' => true ) ) . '
      ' . $dlink . '
      '; - } else { - $dlink2 = ''; - } - - $warning .= '
    • ' . wfMsgExt( 'fileexists', array('parseinline','replaceafter'), $dlink ) . '
    • ' . $dlink2; - - } elseif( $file->getTitle()->getArticleID() ) { - $lnk = $sk->makeKnownLinkObj( $file->getTitle(), '', 'redirect=no' ); - $warning .= '
    • ' . wfMsgExt( 'filepageexists', array( 'parseinline', 'replaceafter' ), $lnk ) . '
    • '; - } elseif ( $file_lc && $file_lc->exists() ) { - # Check if image with lowercase extension exists. - # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension - $dlink = $sk->makeKnownLinkObj( $nt_lc ); - if ( $file_lc->allowInlineDisplay() ) { - $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $nt_lc->getText(), $align, array(), false, true ); - } elseif ( !$file_lc->allowInlineDisplay() && $file_lc->isSafeFile() ) { - $icon = $file_lc->iconThumb(); - $dlink2 = '
      ' . - $icon->toHtml( array( 'desc-link' => true ) ) . '
      ' . $dlink . '
      '; - } else { - $dlink2 = ''; - } - - $warning .= '
    • ' . - wfMsgExt( 'fileexists-extension', 'parsemag', - $file->getTitle()->getPrefixedText(), $dlink ) . - '
    • ' . $dlink2; - - } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' ) - && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) ) - { - # Check for filenames like 50px- or 180px-, these are mostly thumbnails - $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension ); - $file_thb = wfLocalFile( $nt_thb ); - if ($file_thb->exists() ) { - # Check if an image without leading '180px-' (or similiar) exists - $dlink = $sk->makeKnownLinkObj( $nt_thb); - if ( $file_thb->allowInlineDisplay() ) { - $dlink2 = $sk->makeImageLinkObj( $nt_thb, - wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $nt_thb->getText(), $align, array(), false, true ); - } elseif ( !$file_thb->allowInlineDisplay() && $file_thb->isSafeFile() ) { - $icon = $file_thb->iconThumb(); - $dlink2 = '
      ' . - $icon->toHtml( array( 'desc-link' => true ) ) . '
      ' . - $dlink . '
      '; - } else { - $dlink2 = ''; - } - - $warning .= '
    • ' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) . - '
    • ' . $dlink2; - } else { - # Image w/o '180px-' does not exists, but we do not like these filenames - $warning .= '
    • ' . wfMsgExt( 'file-thumbnail-no', 'parseinline' , - substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '
    • '; - } - } - - $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist(); - # Do the match - foreach( $filenamePrefixBlacklist as $prefix ) { - if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { - $warning .= '
    • ' . wfMsgExt( 'filename-bad-prefix', 'parseinline', $prefix ) . '
    • '; - break; - } - } - - if ( $file->wasDeleted() && !$file->exists() ) { - # If the file existed before and was deleted, warn the user of this - # Don't bother doing so if the file exists now, however - $ltitle = SpecialPage::getTitleFor( 'Log' ); - $llink = $sk->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ), - 'type=delete&page=' . $file->getTitle()->getPrefixedUrl() ); - $warning .= '
    • ' . wfMsgWikiHtml( 'filewasdeleted', $llink ) . '
    • '; - } - return $warning; - } - - /** - * Get a list of warnings - * - * @param string local filename, e.g. 'file exists', 'non-descriptive filename' - * @return array list of warning messages - */ - static function ajaxGetExistsWarning( $filename ) { - $file = wfFindFile( $filename ); - if( !$file ) { - // Force local file so we have an object to do further checks against - // if there isn't an exact match... - $file = wfLocalFile( $filename ); - } - $s = ' '; - if ( $file ) { - $warning = self::getExistsWarning( $file ); - if ( $warning !== '' ) { - $s = "
        $warning
      "; - } - } - return $s; - } - - /** - * Render a preview of a given license for the AJAX preview on upload - * - * @param string $license - * @return string - */ - public static function ajaxGetLicensePreview( $license ) { - global $wgParser, $wgUser; - $text = '{{' . $license . '}}'; - $title = Title::makeTitle( NS_IMAGE, 'Sample.jpg' ); - $options = ParserOptions::newFromUser( $wgUser ); - - // Expand subst: first, then live templates... - $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options ); - $output = $wgParser->parse( $text, $title, $options ); - - return $output->getText(); - } - - /** - * Check for duplicate files and throw up a warning before the upload - * completes. - */ - function getDupeWarning( $tempfile ) { - $hash = File::sha1Base36( $tempfile ); - $dupes = RepoGroup::singleton()->findBySha1( $hash ); - if( $dupes ) { - global $wgOut; - $msg = ""; - foreach( $dupes as $file ) { - $title = $file->getTitle(); - $msg .= $title->getPrefixedText() . - "|" . $title->getText() . "\n"; - } - $msg .= ""; - return "
    • " . - wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) . - $wgOut->parse( $msg ) . - "
    • \n"; - } else { - return ''; - } - } - - /** - * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]] - * - * @return array list of prefixes - */ - public static function getFilenamePrefixBlacklist() { - $blacklist = array(); - $message = wfMsgForContent( 'filename-prefix-blacklist' ); - if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) { - $lines = explode( "\n", $message ); - foreach( $lines as $line ) { - // Remove comment lines - $comment = substr( trim( $line ), 0, 1 ); - if ( $comment == '#' || $comment == '' ) { - continue; - } - // Remove additional comments after a prefix - $comment = strpos( $line, '#' ); - if ( $comment > 0 ) { - $line = substr( $line, 0, $comment-1 ); - } - $blacklist[] = trim( $line ); - } - } - return $blacklist; - } - - /** - * Stash a file in a temporary directory for later processing - * after the user has confirmed it. - * - * If the user doesn't explicitly cancel or accept, these files - * can accumulate in the temp directory. - * - * @param string $saveName - the destination filename - * @param string $tempName - the source temporary file to save - * @return string - full path the stashed file, or false on failure - * @access private - */ - function saveTempUploadedFile( $saveName, $tempName ) { - global $wgOut; - $repo = RepoGroup::singleton()->getLocalRepo(); - $status = $repo->storeTemp( $saveName, $tempName ); - if ( !$status->isGood() ) { - $this->showError( $status->getWikiText() ); - return false; - } else { - return $status->value; - } - } - - /** - * Stash a file in a temporary directory for later processing, - * and save the necessary descriptive info into the session. - * Returns a key value which will be passed through a form - * to pick up the path info on a later invocation. - * - * @return int - * @access private - */ - function stashSession() { - $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); - - if( !$stash ) { - # Couldn't save the file. - return false; - } - - $key = mt_rand( 0, 0x7fffffff ); - $_SESSION['wsUploadData'][$key] = array( - 'mTempPath' => $stash, - 'mFileSize' => $this->mFileSize, - 'mSrcName' => $this->mSrcName, - 'mFileProps' => $this->mFileProps, - 'version' => self::SESSION_VERSION, - ); - return $key; - } - - /** - * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @access private - * @return success - */ - function unsaveUploadedFile() { - global $wgOut; - $repo = RepoGroup::singleton()->getLocalRepo(); - $success = $repo->freeTemp( $this->mTempPath ); - if ( ! $success ) { - $wgOut->showFileDeleteError( $this->mTempPath ); - return false; - } else { - return true; - } - } - - /* -------------------------------------------------------------- */ - - /** - * @param string $error as HTML - * @access private - */ - function uploadError( $error ) { - global $wgOut; - $wgOut->addHTML( '

      ' . wfMsgHtml( 'uploadwarning' ) . "

      \n" ); - $wgOut->addHTML( '' . $error . '' ); - } - - /** - * There's something wrong with this file, not enough to reject it - * totally but we require manual intervention to save it for real. - * Stash it away, then present a form asking to confirm or cancel. - * - * @param string $warning as HTML - * @access private - */ - function uploadWarning( $warning ) { - global $wgOut; - global $wgUseCopyrightUpload; - - $this->mSessionKey = $this->stashSession(); - if( !$this->mSessionKey ) { - # Couldn't save file; an error has been displayed so let's go. - return; - } - - $wgOut->addHTML( '

      ' . wfMsgHtml( 'uploadwarning' ) . "

      \n" ); - $wgOut->addHTML( '
        ' . $warning . "
      \n" ); - - $titleObj = SpecialPage::getTitleFor( 'Upload' ); - - if ( $wgUseCopyrightUpload ) { - $copyright = Xml::hidden( 'wpUploadCopyStatus', $this->mCopyrightStatus ) . "\n" . - Xml::hidden( 'wpUploadSource', $this->mCopyrightSource ) . "\n"; - } else { - $copyright = ''; - } - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), - 'enctype' => 'multipart/form-data', 'id' => 'uploadwarning' ) ) . "\n" . - Xml::hidden( 'wpIgnoreWarning', '1' ) . "\n" . - Xml::hidden( 'wpSessionKey', $this->mSessionKey ) . "\n" . - Xml::hidden( 'wpUploadDescription', $this->mComment ) . "\n" . - Xml::hidden( 'wpLicense', $this->mLicense ) . "\n" . - Xml::hidden( 'wpDestFile', $this->mDesiredDestName ) . "\n" . - Xml::hidden( 'wpWatchthis', $this->mWatchthis ) . "\n" . - "{$copyright}
      " . - Xml::submitButton( wfMsg( 'ignorewarning' ), array ( 'name' => 'wpUpload', 'id' => 'wpUpload', 'checked' => 'checked' ) ) . ' ' . - Xml::submitButton( wfMsg( 'reuploaddesc' ), array ( 'name' => 'wpReUpload', 'id' => 'wpReUpload' ) ) . - Xml::closeElement( 'form' ) . "\n" - ); - } - - /** - * Displays the main upload form, optionally with a highlighted - * error message up at the top. - * - * @param string $msg as HTML - * @access private - */ - function mainUploadForm( $msg='' ) { - global $wgOut, $wgUser, $wgLang, $wgMaxUploadSize; - global $wgUseCopyrightUpload, $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview; - global $wgRequest, $wgAllowCopyUploads; - global $wgStylePath, $wgStyleVersion; - - $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck; - $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview; - - $adc = wfBoolToStr( $useAjaxDestCheck ); - $alp = wfBoolToStr( $useAjaxLicensePreview ); - $autofill = wfBoolToStr( $this->mDesiredDestName == '' ); - - $wgOut->addScript( "" ); - $wgOut->addScriptFile( 'upload.js' ); - $wgOut->addScriptFile( 'edit.js' ); // For support - - if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) - { - wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); - return false; - } - - if( $this->mDesiredDestName ) { - $title = Title::makeTitleSafe( NS_IMAGE, $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( - $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted', - array( 'parse', 'replaceafter' ), - $wgUser->getSkin()->makeKnownLinkObj( - SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), - wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count ) - ) - ); - $wgOut->addHtml( "
      {$link}
      " ); - } - - // Show the relevant lines from deletion log (for still deleted files only) - if( $title instanceof Title && $title->isDeleted() > 0 && !$title->exists() ) { - $this->showDeletionLog( $wgOut, $title->getPrefixedText() ); - } - } - - $cols = intval($wgUser->getOption( 'cols' )); - - if( $wgUser->getOption( 'editwidth' ) ) { - $width = " style=\"width:100%\""; - } else { - $width = ''; - } - - if ( '' != $msg ) { - $sub = wfMsgHtml( 'uploaderror' ); - $wgOut->addHTML( "

      {$sub}

      \n" . - "{$msg}\n" ); - } - $wgOut->addHTML( '
      ' ); - $wgOut->addWikiMsg( 'uploadtext', $this->mDesiredDestName ); - $wgOut->addHTML( "
      \n" ); - - # Print a list of allowed file extensions, if so configured. We ignore - # MIME type here, it's incomprehensible to most people and too long. - global $wgCheckFileExtensions, $wgStrictFileExtensions, - $wgFileExtensions, $wgFileBlacklist; - - $allowedExtensions = ''; - if( $wgCheckFileExtensions ) { - $delim = wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ); - if( $wgStrictFileExtensions ) { - # Everything not permitted is banned - $extensionsList = - '
      ' . - wfMsgWikiHtml( 'upload-permitted', implode( $wgFileExtensions, $delim ) ) . - "
      \n"; - } else { - # We have to list both preferred and prohibited - $extensionsList = - '
      ' . - wfMsgWikiHtml( 'upload-preferred', implode( $wgFileExtensions, $delim ) ) . - "
      \n" . - '
      ' . - wfMsgWikiHtml( 'upload-prohibited', implode( $wgFileBlacklist, $delim ) ) . - "
      \n"; - } - } else { - # Everything is permitted. - $extensionsList = ''; - } - - # Get the maximum file size from php.ini as $wgMaxUploadSize works for uploads from URL via CURL only - # See http://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize for possible values of upload_max_filesize - $val = trim( ini_get( 'upload_max_filesize' ) ); - $last = strtoupper( ( substr( $val, -1 ) ) ); - switch( $last ) { - case 'G': - $val2 = substr( $val, 0, -1 ) * 1024 * 1024 * 1024; - break; - case 'M': - $val2 = substr( $val, 0, -1 ) * 1024 * 1024; - break; - case 'K': - $val2 = substr( $val, 0, -1 ) * 1024; - break; - default: - $val2 = $val; - } - $val2 = $wgAllowCopyUploads ? min( $wgMaxUploadSize, $val2 ) : $val2; - $maxUploadSize = '
      ' . - wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ), - $wgLang->formatSize( $val2 ) ) . - "
      \n"; - - $sourcefilename = wfMsgExt( 'sourcefilename', array( 'parseinline', 'escapenoentities' ) ); - $destfilename = wfMsgExt( 'destfilename', array( 'parseinline', 'escapenoentities' ) ); - - $summary = wfMsgExt( 'fileuploadsummary', 'parseinline' ); - - $licenses = new Licenses(); - $license = wfMsgExt( 'license', array( 'parseinline' ) ); - $nolicense = wfMsgHtml( 'nolicense' ); - $licenseshtml = $licenses->getHtml(); - - $ulb = wfMsgHtml( 'uploadbtn' ); - - - $titleObj = SpecialPage::getTitleFor( 'Upload' ); - - $encDestName = htmlspecialchars( $this->mDesiredDestName ); - - $watchChecked = $this->watchCheck() - ? 'checked="checked"' - : ''; - $warningChecked = $this->mIgnoreWarning ? 'checked' : ''; - - // Prepare form for upload or upload/copy - if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) { - $filename_form = - "" . - "" . - wfMsgHTML( 'upload_source_file' ) . "
      " . - "" . - "" . - wfMsgHtml( 'upload_source_url' ) ; - } else { - $filename_form = - "mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . - "size='60' />" . - "" ; - } - if ( $useAjaxDestCheck ) { - $warningRow = " "; - $destOnkeyup = 'onkeyup="wgUploadWarningObj.keypress();"'; - } else { - $warningRow = ''; - $destOnkeyup = ''; - } - - $encComment = htmlspecialchars( $this->mComment ); - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL(), - 'enctype' => 'multipart/form-data', 'id' => 'mw-upload-form' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'upload' ) ) . - Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-upload-table' ) ) . - " - {$this->uploadFormTextTop} - - - - - {$filename_form} - - - - - - {$maxUploadSize} - {$extensionsList} - - - - - - - - - - - - - - - - - {$this->uploadFormTextAfterSummary} - - - " - ); - - if ( $licenseshtml != '' ) { - global $wgStylePath; - $wgOut->addHTML( " - - - - - - - - " - ); - if( $useAjaxLicensePreview ) { - $wgOut->addHtml( " - - - - " - ); - } - } - - if ( $wgUseCopyrightUpload ) { - $filestatus = wfMsgExt( 'filestatus', 'escapenoentities' ); - $copystatus = htmlspecialchars( $this->mCopyrightStatus ); - $filesource = wfMsgExt( 'filesource', 'escapenoentities' ); - $uploadsource = htmlspecialchars( $this->mCopyrightSource ); - - $wgOut->addHTML( " - - - - - - - - - - - - - - - " - ); - } - - $wgOut->addHtml( " - - - - - - - - - $warningRow - - - - getSkin()->tooltipAndAccesskey( 'upload' ) . " /> - - - - - " - ); - $wgOut->addWikiText( wfMsgForContent( 'edittools' ) ); - $wgOut->addHTML( " - - " . - Xml::closeElement( 'table' ) . - Xml::hidden( 'wpDestFileWarningAck', '', array( 'id' => 'wpDestFileWarningAck' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - $uploadfooter = wfMsgNoTrans( 'uploadfooter' ); - if( $uploadfooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadfooter ) ){ - $wgOut->addWikiText( '' ); - } - } - - /* -------------------------------------------------------------- */ - - /** - * See if we should check the 'watch this page' checkbox on the form - * based on the user's preferences and whether we're being asked - * to create a new file or update an existing one. - * - * In the case where 'watch edits' is off but 'watch creations' is on, - * we'll leave the box unchecked. - * - * Note that the page target can be changed *on the form*, so our check - * state can get out of sync. - */ - function watchCheck() { - global $wgUser; - if( $wgUser->getOption( 'watchdefault' ) ) { - // Watch all edits! - return true; - } - - $local = wfLocalFile( $this->mDesiredDestName ); - if( $local && $local->exists() ) { - // We're uploading a new version of an existing file. - // No creation, so don't watch it if we're not already. - return $local->getTitle()->userIsWatching(); - } else { - // New page should get watched if that's our option. - return $wgUser->getOption( 'watchcreations' ); - } - } - - /** - * Split a file into a base name and all dot-delimited 'extensions' - * on the end. Some web server configurations will fall back to - * earlier pseudo-'extensions' to determine type and execute - * scripts, so the blacklist needs to check them all. - * - * @return array - */ - function splitExtensions( $filename ) { - $bits = explode( '.', $filename ); - $basename = array_shift( $bits ); - return array( $basename, $bits ); - } - - /** - * Perform case-insensitive match against a list of file extensions. - * Returns true if the extension is in the list. - * - * @param string $ext - * @param array $list - * @return bool - */ - function checkFileExtension( $ext, $list ) { - return in_array( strtolower( $ext ), $list ); - } - - /** - * Perform case-insensitive match against a list of file extensions. - * Returns true if any of the extensions are in the list. - * - * @param array $ext - * @param array $list - * @return bool - */ - function checkFileExtensionList( $ext, $list ) { - foreach( $ext as $e ) { - if( in_array( strtolower( $e ), $list ) ) { - return true; - } - } - return false; - } - - /** - * Verifies that it's ok to include the uploaded file - * - * @param string $tmpfile the full path of the temporary file to verify - * @param string $extension The filename extension that the file is to be served with - * @return mixed true of the file is verified, a WikiError object otherwise. - */ - function verify( $tmpfile, $extension ) { - #magically determine mime type - $magic = MimeMagic::singleton(); - $mime = $magic->guessMimeType($tmpfile,false); - - #check mime type, if desired - global $wgVerifyMimeType; - if ($wgVerifyMimeType) { - - wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n"); - #check mime type against file extension - if( !$this->verifyExtension( $mime, $extension ) ) { - return new WikiErrorMsg( 'uploadcorrupt' ); - } - - #check mime type blacklist - global $wgMimeTypeBlacklist; - if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) - && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { - return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) ); - } - } - - #check for htmlish code and javascript - if( $this->detectScript ( $tmpfile, $mime, $extension ) ) { - return new WikiErrorMsg( 'uploadscripted' ); - } - - /** - * Scan the uploaded file for viruses - */ - $virus= $this->detectVirus($tmpfile); - if ( $virus ) { - return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) ); - } - - wfDebug( __METHOD__.": all clear; passing.\n" ); - return true; - } - - /** - * Checks if the mime type of the uploaded file matches the file extension. - * - * @param string $mime the mime type of the uploaded file - * @param string $extension The filename extension that the file is to be served with - * @return bool - */ - function verifyExtension( $mime, $extension ) { - $magic = MimeMagic::singleton(); - - if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) - if ( ! $magic->isRecognizableExtension( $extension ) ) { - wfDebug( __METHOD__.": passing file with unknown detected mime type; " . - "unrecognized extension '$extension', can't verify\n" ); - return true; - } else { - wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ". - "recognized extension '$extension', so probably invalid file\n" ); - return false; - } - - $match= $magic->isMatchingExtension($extension,$mime); - - if ($match===NULL) { - wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" ); - return true; - } elseif ($match===true) { - wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" ); - - #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it! - return true; - - } else { - wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" ); - return false; - } - } - - /** - * Heuristic for detecting files that *could* contain JavaScript instructions or - * things that may look like HTML to a browser and are thus - * potentially harmful. The present implementation will produce false positives in some situations. - * - * @param string $file Pathname to the temporary upload file - * @param string $mime The mime type of the file - * @param string $extension The extension of the file - * @return bool true if the file contains something looking like embedded scripts - */ - function detectScript($file, $mime, $extension) { - global $wgAllowTitlesInSVG; - - #ugly hack: for text files, always look at the entire file. - #For binarie field, just check the first K. - - if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file ); - else { - $fp = fopen( $file, 'rb' ); - $chunk = fread( $fp, 1024 ); - fclose( $fp ); - } - - $chunk= strtolower( $chunk ); - - if (!$chunk) return false; - - #decode from UTF-16 if needed (could be used for obfuscation). - if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE"; - elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE"; - else $enc= NULL; - - if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk); - - $chunk= trim($chunk); - - #FIXME: convert from UTF-16 if necessarry! - - wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n"); - - #check for HTML doctype - if (eregi("addHTML( "
      Bad configuration: unknown virus scanner: $wgAntivirus
      \n" ); - return "unknown antivirus: $wgAntivirus"; - } - - # look up scanner configuration - $command = $wgAntivirusSetup[$wgAntivirus]["command"]; - $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"]; - $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ? - $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null; - - if ( strpos( $command,"%f" ) === false ) { - # simple pattern: append file to scan - $command .= " " . wfEscapeShellArg( $file ); - } else { - # complex pattern: replace "%f" with file to scan - $command = str_replace( "%f", wfEscapeShellArg( $file ), $command ); - } - - wfDebug( __METHOD__.": running virus scan: $command \n" ); - - # execute virus scanner - $exitCode = false; - - #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too. - # that does not seem to be worth the pain. - # Ask me (Duesentrieb) about it if it's ever needed. - $output = array(); - if ( wfIsWindows() ) { - exec( "$command", $output, $exitCode ); - } else { - exec( "$command 2>&1", $output, $exitCode ); - } - - # map exit code to AV_xxx constants. - $mappedCode = $exitCode; - if ( $exitCodeMap ) { - if ( isset( $exitCodeMap[$exitCode] ) ) { - $mappedCode = $exitCodeMap[$exitCode]; - } elseif ( isset( $exitCodeMap["*"] ) ) { - $mappedCode = $exitCodeMap["*"]; - } - } - - if ( $mappedCode === AV_SCAN_FAILED ) { - # scan failed (code was mapped to false by $exitCodeMap) - wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" ); - - if ( $wgAntivirusRequired ) { - return "scan failed (code $exitCode)"; - } else { - return NULL; - } - } else if ( $mappedCode === AV_SCAN_ABORTED ) { - # scan failed because filetype is unknown (probably imune) - wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" ); - return NULL; - } else if ( $mappedCode === AV_NO_VIRUS ) { - # no virus found - wfDebug( __METHOD__.": file passed virus scan.\n" ); - return false; - } else { - $output = join( "\n", $output ); - $output = trim( $output ); - - if ( !$output ) { - $output = true; #if there's no output, return true - } elseif ( $msgPattern ) { - $groups = array(); - if ( preg_match( $msgPattern, $output, $groups ) ) { - if ( $groups[1] ) { - $output = $groups[1]; - } - } - } - - wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output" ); - return $output; - } - } - - /** - * Check if the temporary file is MacBinary-encoded, as some uploads - * from Internet Explorer on Mac OS Classic and Mac OS X will be. - * If so, the data fork will be extracted to a second temporary file, - * which will then be checked for validity and either kept or discarded. - * - * @access private - */ - function checkMacBinary() { - $macbin = new MacBinary( $this->mTempPath ); - if( $macbin->isValid() ) { - $dataFile = tempnam( wfTempDir(), "WikiMacBinary" ); - $dataHandle = fopen( $dataFile, 'wb' ); - - wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" ); - $macbin->extractData( $dataHandle ); - - $this->mTempPath = $dataFile; - $this->mFileSize = $macbin->dataForkLength(); - - // We'll have to manually remove the new file if it's not kept. - $this->mRemoveTempFile = true; - } - $macbin->close(); - } - - /** - * If we've modified the upload file we need to manually remove it - * on exit to clean up. - * @access private - */ - function cleanupTempFile() { - if ( $this->mRemoveTempFile && file_exists( $this->mTempPath ) ) { - wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file {$this->mTempPath}\n" ); - unlink( $this->mTempPath ); - } - } - - /** - * Check if there's an overwrite conflict and, if so, if restrictions - * forbid this user from performing the upload. - * - * @return mixed true on success, WikiError on failure - * @access private - */ - function checkOverwrite( $name ) { - $img = wfFindFile( $name ); - - $error = ''; - if( $img ) { - global $wgUser, $wgOut; - if( $img->isLocal() ) { - if( !self::userCanReUpload( $wgUser, $img->name ) ) { - $error = 'fileexists-forbidden'; - } - } else { - if( !$wgUser->isAllowed( 'reupload' ) || - !$wgUser->isAllowed( 'reupload-shared' ) ) { - $error = "fileexists-shared-forbidden"; - } - } - } - - if( $error ) { - $errorText = wfMsg( $error, wfEscapeWikiText( $img->getName() ) ); - return $errorText; - } - - // Rockin', go ahead and upload - return true; - } - - /** - * Check if a user is the last uploader - * - * @param User $user - * @param string $img, image name - * @return bool - */ - public static function userCanReUpload( User $user, $img ) { - if( $user->isAllowed( 'reupload' ) ) - return true; // non-conditional - if( !$user->isAllowed( 'reupload-own' ) ) - return false; - - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow('image', - /* SELECT */ 'img_user', - /* WHERE */ array( 'img_name' => $img ) - ); - if ( !$row ) - return false; - - return $user->getId() == $row->img_user; - } - - /** - * Display an error with a wikitext description - */ - function showError( $description ) { - global $wgOut; - $wgOut->setPageTitle( wfMsg( "internalerror" ) ); - $wgOut->setRobotpolicy( "noindex,nofollow" ); - $wgOut->setArticleRelated( false ); - $wgOut->enableClientCache( false ); - $wgOut->addWikiText( $description ); - } - - /** - * Get the initial image page text based on a comment and optional file status information - */ - static function getInitialPageText( $comment, $license, $copyStatus, $source ) { - global $wgUseCopyrightUpload; - if ( $wgUseCopyrightUpload ) { - if ( $license != '' ) { - $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; - } - $pageText = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n" . - '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" . - "$licensetxt" . - '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ; - } else { - if ( $license != '' ) { - $filedesc = $comment == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n"; - $pageText = $filedesc . - '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; - } else { - $pageText = $comment; - } - } - return $pageText; - } - - /** - * If there are rows in the deletion log for this file, show them, - * along with a nice little note for the user - * - * @param OutputPage $out - * @param string filename - */ - private function showDeletionLog( $out, $filename ) { - global $wgUser; - $loglist = new LogEventsList( $wgUser->getSkin(), $out ); - $pager = new LogPager( $loglist, 'delete', false, $filename ); - if( $pager->getNumRows() > 0 ) { - $out->addHtml( '
      ' ); - $out->addWikiMsg( 'upload-wasdeleted' ); - $out->addHTML( - $loglist->beginLogEventsList() . - $pager->getBody() . - $loglist->endLogEventsList() - ); - $out->addHtml( '
      ' ); - } - } -} diff --git a/includes/specials/UploadMogile.php b/includes/specials/UploadMogile.php deleted file mode 100644 index 7ff8fda663..0000000000 --- a/includes/specials/UploadMogile.php +++ /dev/null @@ -1,135 +0,0 @@ -execute(); -} - -/** - * Extends Special:Upload with MogileFS. - * @ingroup SpecialPage - */ -class UploadFormMogile extends UploadForm { - /** - * Move the uploaded file from its temporary location to the final - * destination. If a previous version of the file exists, move - * it into the archive subdirectory. - * - * @todo If the later save fails, we may have disappeared the original file. - * - * @param string $saveName - * @param string $tempName full path to the temporary file - * @param bool $useRename Not used in this implementation - */ - function saveUploadedFile( $saveName, $tempName, $useRename = false ) { - global $wgOut; - $mfs = MogileFS::NewMogileFS(); - - $this->mSavedFile = "image!{$saveName}"; - - if( $mfs->getPaths( $this->mSavedFile )) { - $this->mUploadOldVersion = gmdate( 'YmdHis' ) . "!{$saveName}"; - if( !$mfs->rename( $this->mSavedFile, "archive!{$this->mUploadOldVersion}" ) ) { - $wgOut->showFileRenameError( $this->mSavedFile, - "archive!{$this->mUploadOldVersion}" ); - return false; - } - } else { - $this->mUploadOldVersion = ''; - } - - if ( $this->mStashed ) { - if (!$mfs->rename($tempName,$this->mSavedFile)) { - $wgOut->showFileRenameError($tempName, $this->mSavedFile ); - return false; - } - } else { - if ( !$mfs->saveFile($this->mSavedFile,'normal',$tempName )) { - $wgOut->showFileCopyError( $tempName, $this->mSavedFile ); - return false; - } - unlink($tempName); - } - return true; - } - - /** - * Stash a file in a temporary directory for later processing - * after the user has confirmed it. - * - * If the user doesn't explicitly cancel or accept, these files - * can accumulate in the temp directory. - * - * @param string $saveName - the destination filename - * @param string $tempName - the source temporary file to save - * @return string - full path the stashed file, or false on failure - * @access private - */ - function saveTempUploadedFile( $saveName, $tempName ) { - global $wgOut; - - $stash = 'stash!' . gmdate( "YmdHis" ) . '!' . $saveName; - $mfs = MogileFS::NewMogileFS(); - if ( !$mfs->saveFile( $stash, 'normal', $tempName ) ) { - $wgOut->showFileCopyError( $tempName, $stash ); - return false; - } - unlink($tempName); - return $stash; - } - - /** - * Stash a file in a temporary directory for later processing, - * and save the necessary descriptive info into the session. - * Returns a key value which will be passed through a form - * to pick up the path info on a later invocation. - * - * @return int - * @access private - */ - function stashSession() { - $stash = $this->saveTempUploadedFile( - $this->mUploadSaveName, $this->mUploadTempName ); - - if( !$stash ) { - # Couldn't save the file. - return false; - } - - $key = mt_rand( 0, 0x7fffffff ); - $_SESSION['wsUploadData'][$key] = array( - 'mUploadTempName' => $stash, - 'mUploadSize' => $this->mUploadSize, - 'mOname' => $this->mOname ); - return $key; - } - - /** - * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @access private - * @return success - */ - function unsaveUploadedFile() { - global $wgOut; - $mfs = MogileFS::NewMogileFS(); - if ( ! $mfs->delete( $this->mUploadTempName ) ) { - $wgOut->showFileDeleteError( $this->mUploadTempName ); - return false; - } else { - return true; - } - } -} diff --git a/includes/specials/Userlogin.php b/includes/specials/Userlogin.php deleted file mode 100644 index 179ef3f9d2..0000000000 --- a/includes/specials/Userlogin.php +++ /dev/null @@ -1,928 +0,0 @@ -execute(); -} - -/** - * implements Special:Login - * @ingroup SpecialPage - */ -class LoginForm { - - const SUCCESS = 0; - const NO_NAME = 1; - const ILLEGAL = 2; - const WRONG_PLUGIN_PASS = 3; - const NOT_EXISTS = 4; - const WRONG_PASS = 5; - const EMPTY_PASS = 6; - const RESET_PASS = 7; - const ABORTED = 8; - const CREATE_BLOCKED = 9; - - var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; - var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; - var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage, $mSkipCookieCheck; - - /** - * Constructor - * @param WebRequest $request A WebRequest object passed by reference - */ - function LoginForm( &$request, $par = '' ) { - global $wgLang, $wgAllowRealName, $wgEnableEmail; - global $wgAuth; - - $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]] - $this->mName = $request->getText( 'wpName' ); - $this->mPassword = $request->getText( 'wpPassword' ); - $this->mRetype = $request->getText( 'wpRetype' ); - $this->mDomain = $request->getText( 'wpDomain' ); - $this->mReturnTo = $request->getVal( 'returnto' ); - $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); - $this->mPosted = $request->wasPosted(); - $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); - $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) - && $wgEnableEmail; - $this->mMailmypassword = $request->getCheck( 'wpMailmypassword' ) - && $wgEnableEmail; - $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); - $this->mAction = $request->getVal( 'action' ); - $this->mRemember = $request->getCheck( 'wpRemember' ); - $this->mLanguage = $request->getText( 'uselang' ); - $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); - - if( $wgEnableEmail ) { - $this->mEmail = $request->getText( 'wpEmail' ); - } else { - $this->mEmail = ''; - } - if( $wgAllowRealName ) { - $this->mRealName = $request->getText( 'wpRealName' ); - } else { - $this->mRealName = ''; - } - - if( !$wgAuth->validDomain( $this->mDomain ) ) { - $this->mDomain = 'invaliddomain'; - } - $wgAuth->setDomain( $this->mDomain ); - - # When switching accounts, it sucks to get automatically logged out - if( $this->mReturnTo == $wgLang->specialPage( 'Userlogout' ) ) { - $this->mReturnTo = ''; - } - } - - function execute() { - if ( !is_null( $this->mCookieCheck ) ) { - $this->onCookieRedirectCheck( $this->mCookieCheck ); - return; - } else if( $this->mPosted ) { - if( $this->mCreateaccount ) { - return $this->addNewAccount(); - } else if ( $this->mCreateaccountMail ) { - return $this->addNewAccountMailPassword(); - } else if ( $this->mMailmypassword ) { - return $this->mailPassword(); - } else if ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { - return $this->processLogin(); - } - } - $this->mainLoginForm( '' ); - } - - /** - * @private - */ - function addNewAccountMailPassword() { - global $wgOut; - - if ('' == $this->mEmail) { - $this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) ); - return; - } - - $u = $this->addNewaccountInternal(); - - if ($u == NULL) { - return; - } - - // Wipe the initial password and mail a temporary one - $u->setPassword( null ); - $u->saveSettings(); - $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); - - wfRunHooks( 'AddNewAccount', array( $u, true ) ); - - $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - if( WikiError::isError( $result ) ) { - $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); - } else { - $wgOut->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); - $wgOut->returnToMain( false ); - } - $u = 0; - } - - - /** - * @private - */ - function addNewAccount() { - global $wgUser, $wgEmailAuthentication; - - # Create the account and abort if there's a problem doing so - $u = $this->addNewAccountInternal(); - if( $u == NULL ) - return; - - # If we showed up language selection links, and one was in use, be - # smart (and sensible) and save that language as the user's preference - global $wgLoginLanguageSelector; - if( $wgLoginLanguageSelector && $this->mLanguage ) - $u->setOption( 'language', $this->mLanguage ); - - # Send out an email authentication message if needed - if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) { - global $wgOut; - $error = $u->sendConfirmationMail(); - if( WikiError::isError( $error ) ) { - $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() ); - } else { - $wgOut->addWikiMsg( 'confirmemail_oncreate' ); - } - } - - # 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( $wgUser->isAnon() ) { - $wgUser = $u; - $wgUser->setCookies(); - wfRunHooks( 'AddNewAccount', array( $wgUser ) ); - if( $this->hasSessionCookie() ) { - return $this->successfulLogin( wfMsg( 'welcomecreation', $wgUser->getName() ), false ); - } else { - return $this->cookieRedirectCheck( 'new' ); - } - } else { - # Confirm that the account was created - global $wgOut; - $self = SpecialPage::getTitleFor( 'Userlogin' ); - $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) ); - $wgOut->setArticleRelated( false ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->addHtml( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); - $wgOut->returnToMain( false, $self ); - wfRunHooks( 'AddNewAccount', array( $u ) ); - return true; - } - } - - /** - * @private - */ - function addNewAccountInternal() { - global $wgUser, $wgOut; - global $wgEnableSorbs, $wgProxyWhitelist; - global $wgMemc, $wgAccountCreationThrottle; - global $wgAuth, $wgMinimalPasswordLength; - global $wgEmailConfirmToEdit; - - // If the user passes an invalid domain, something is fishy - if( !$wgAuth->validDomain( $this->mDomain ) ) { - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - 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( 'local' != $this->mDomain && '' != $this->mDomain ) { - if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) { - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - return false; - } - } - - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return false; - } - - # Check permissions - if ( !$wgUser->isAllowed( 'createaccount' ) ) { - $this->userNotPrivilegedMessage(); - return false; - } elseif ( $wgUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage(); - return false; - } - - $ip = wfGetIP(); - if ( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) && - $wgUser->inSorbsBlacklist( $ip ) ) - { - $this->mainLoginForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' ); - return; - } - - # Now create a dummy user ($u) and check if it is valid - $name = trim( $this->mName ); - $u = User::newFromName( $name, 'creatable' ); - if ( is_null( $u ) ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return false; - } - - if ( 0 != $u->idForName() ) { - $this->mainLoginForm( wfMsg( 'userexists' ) ); - return false; - } - - if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) { - $this->mainLoginForm( wfMsg( 'badretype' ) ); - return false; - } - - # check for minimal password length - if ( !$u->isValidPassword( $this->mPassword ) ) { - if ( !$this->mCreateaccountMail ) { - $this->mainLoginForm( wfMsgExt( 'passwordtooshort', array( 'parsemag' ), $wgMinimalPasswordLength ) ); - return false; - } else { - # do not force a password for account creation by email - # set invalid password, it will be replaced later by a random generated password - $this->mPassword = null; - } - } - - # 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; - } - - if( !empty( $this->mEmail ) && !User::isValidEmailAddr( $this->mEmail ) ) { - $this->mainLoginForm( wfMsg( 'invalidemailaddress' ) ); - return false; - } - - # 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 ); - - $abortError = ''; - if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { - // Hook point to add extra creation throttles and blocks - wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); - $this->mainLoginForm( $abortError ); - return false; - } - - if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) { - $key = wfMemcKey( 'acctcreate', 'ip', $ip ); - $value = $wgMemc->incr( $key ); - if ( !$value ) { - $wgMemc->set( $key, 1, 86400 ); - } - if ( $value > $wgAccountCreationThrottle ) { - $this->throttleHit( $wgAccountCreationThrottle ); - return false; - } - } - - if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { - $this->mainLoginForm( wfMsg( 'externaldberror' ) ); - return false; - } - - return $this->initUser( $u, false ); - } - - /** - * Actually add a user to the database. - * Give it a User object that has been initialised with a name. - * - * @param $u User object. - * @param $autocreate boolean -- true if this is an autocreation via auth plugin - * @return User object. - * @private - */ - function initUser( $u, $autocreate ) { - global $wgAuth; - - $u->addToDatabase(); - - if ( $wgAuth->allowPasswordChange() ) { - $u->setPassword( $this->mPassword ); - } - - $u->setEmail( $this->mEmail ); - $u->setRealName( $this->mRealName ); - $u->setToken(); - - $wgAuth->initUser( $u, $autocreate ); - - $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); - $u->saveSettings(); - - # Update user count - $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); - $ssUpdate->doUpdate(); - - return $u; - } - - /** - * Internally authenticate the login request. - * - * This may create a local account as a side effect if the - * authentication plugin allows transparent local account - * creation. - * - * @public - */ - function authenticateUserData() { - global $wgUser, $wgAuth; - if ( '' == $this->mName ) { - return self::NO_NAME; - } - - // 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; - } - $u = User::newFromName( $this->mName ); - if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) { - return self::ILLEGAL; - } - - $isAutoCreated = false; - if ( 0 == $u->getID() ) { - $status = $this->attemptAutoCreate( $u ); - if ( $status !== self::SUCCESS ) { - return $status; - } else { - $isAutoCreated = true; - } - } else { - $u->load(); - } - - // Give general extensions, such as a captcha, a chance to abort logins - $abort = self::ABORTED; - if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort ) ) ) { - return $abort; - } - - 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. - // - // 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. - // - // 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. - // - 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. - // - $retval = self::RESET_PASS; - } else { - $retval = '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS; - } - } else { - $wgAuth->updateUser( $u ); - $wgUser = $u; - - if ( $isAutoCreated ) { - // Must be run after $wgUser is set, for correct new user log - wfRunHooks( 'AuthPluginAutoCreate', array( $wgUser ) ); - } - - $retval = self::SUCCESS; - } - wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) ); - return $retval; - } - - /** - * 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 ( !$wgAuth->autoCreate() ) { - return self::NOT_EXISTS; - } - if ( !$wgAuth->userExists( $user->getName() ) ) { - wfDebug( __METHOD__.": user does not exist\n" ); - return self::NOT_EXISTS; - } - if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { - wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" ); - return self::WRONG_PLUGIN_PASS; - } - if ( $wgUser->isBlockedFromCreateAccount() ) { - wfDebug( __METHOD__.": user is blocked from account creation\n" ); - return self::CREATE_BLOCKED; - } - - wfDebug( __METHOD__.": creating account\n" ); - $user = $this->initUser( $user, true ); - return self::SUCCESS; - } - - function processLogin() { - global $wgUser, $wgAuth; - - switch ($this->authenticateUserData()) - { - case self::SUCCESS: - # We've verified now, update the real record - if( (bool)$this->mRemember != (bool)$wgUser->getOption( 'rememberpassword' ) ) { - $wgUser->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); - $wgUser->saveSettings(); - } else { - $wgUser->invalidateCache(); - } - $wgUser->setCookies(); - - if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { - /* 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( wfMsg( 'loginsuccess', $wgUser->getName() ) ); - } else { - return $this->cookieRedirectCheck( 'login' ); - } - break; - - case self::NO_NAME: - case self::ILLEGAL: - $this->mainLoginForm( wfMsg( 'noname' ) ); - break; - case self::WRONG_PLUGIN_PASS: - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - break; - case self::NOT_EXISTS: - if( $wgUser->isAllowed( 'createaccount' ) ){ - $this->mainLoginForm( wfMsg( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); - } else { - $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) ); - } - break; - case self::WRONG_PASS: - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - break; - case self::EMPTY_PASS: - $this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) ); - break; - case self::RESET_PASS: - $this->resetLoginForm( wfMsg( 'resetpass_announce' ) ); - break; - case self::CREATE_BLOCKED: - $this->userBlockedMessage(); - break; - default: - throw new MWException( "Unhandled case value" ); - } - } - - function resetLoginForm( $error ) { - global $wgOut; - $wgOut->addWikiText( "
      $error
      " ); - $reset = new PasswordResetForm( $this->mName, $this->mPassword ); - $reset->execute( null ); - } - - /** - * @private - */ - function mailPassword() { - global $wgUser, $wgOut, $wgAuth; - - if( !$wgAuth->allowPasswordChange() ) { - $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); - return; - } - - # Check against blocked IPs - # fixme -- should we not? - if( $wgUser->isBlocked() ) { - $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); - return; - } - - # Check against the rate limiter - if( $wgUser->pingLimiter( 'mailpassword' ) ) { - $wgOut->rateLimited(); - return; - } - - if ( '' == $this->mName ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return; - } - $u = User::newFromName( $this->mName ); - if( is_null( $u ) ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return; - } - if ( 0 == $u->getID() ) { - $this->mainLoginForm( wfMsg( 'nosuchuser', $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. - $this->mainLoginForm( wfMsgExt( 'throttled-mailpassword', array( 'parsemag' ), - round( $wgPasswordReminderResendTime, 3 ) ) ); - return; - } - - $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' ); - if( WikiError::isError( $result ) ) { - $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); - } else { - $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' ); - } - } - - - /** - * @param object user - * @param bool throttle - * @param string message name of email title - * @param string message name of email text - * @return mixed true on success, WikiError on failure - * @private - */ - function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { - global $wgCookiePath, $wgCookieDomain, $wgCookiePrefix, $wgCookieSecure; - global $wgServer, $wgScript; - - if ( '' == $u->getEmail() ) { - return new WikiError( wfMsg( 'noemail', $u->getName() ) ); - } - - $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 ); - - return $result; - } - - - /** - * @param string $msg Message that will be shown on success - * @param bool $auto Toggle auto-redirect to main page; default true - * @private - */ - function successfulLogin( $msg, $auto = true ) { - global $wgUser; - global $wgOut; - - # Run any hooks; ignore results - - $injected_html = ''; - wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html)); - - $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - $wgOut->addWikiText( $msg ); - $wgOut->addHtml( $injected_html ); - if ( !empty( $this->mReturnTo ) ) { - $wgOut->returnToMain( $auto, $this->mReturnTo ); - } else { - $wgOut->returnToMain( $auto ); - } - } - - /** */ - function userNotPrivilegedMessage($errors) { - global $wgOut; - - $wgOut->setPageTitle( wfMsg( 'permissionserrors' ) ); - $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. - $wgOut->addWikiMsg( 'cantcreateaccount-nonblock-text' ); - - $wgOut->returnToMain( false ); - } - - /** */ - function userBlockedMessage() { - global $wgOut, $wgUser; - - # Let's be nice about this, it's likely that this feature will be used - # for blocking large numbers of innocent people, e.g. range blocks on - # schools. Don't blame it on the user. There's a small chance that it - # really is the user's fault, i.e. the username is blocked and they - # haven't bothered to log out before trying to create an account to - # evade it, but we'll leave that to their guilty conscience to figure - # out. - - $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - $ip = wfGetIP(); - $blocker = User::whoIs( $wgUser->mBlock->mBy ); - $block_reason = $wgUser->mBlock->mReason; - - if ( strval( $block_reason ) === '' ) { - $block_reason = wfMsg( 'blockednoreason' ); - } - $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker ); - $wgOut->returnToMain( false ); - } - - /** - * @private - */ - function mainLoginForm( $msg, $msgtype = 'error' ) { - global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail; - global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector; - global $wgAuth, $wgEmailConfirmToEdit; - - $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - - if ( $this->mType == 'signup' ) { - // Block signup here if in readonly. Keeps user from - // going through the process (filling out data, etc) - // and being informed later. - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } elseif ( $wgUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage(); - return; - } elseif ( count( $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) { - $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' ); - return; - } - } - - if ( '' == $this->mName ) { - if ( $wgUser->isLoggedIn() ) { - $this->mName = $wgUser->getName(); - } else { - $this->mName = isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ? $_COOKIE[$wgCookiePrefix.'UserName'] : null; - } - } - - $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - - if ( $this->mType == 'signup' ) { - $template = new UsercreateTemplate(); - $q = 'action=submitlogin&type=signup'; - $linkq = 'type=login'; - $linkmsg = 'gotaccount'; - } else { - $template = new UserloginTemplate(); - $q = 'action=submitlogin&type=login'; - $linkq = 'type=signup'; - $linkmsg = 'nologin'; - } - - if ( !empty( $this->mReturnTo ) ) { - $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); - $q .= $returnto; - $linkq .= $returnto; - } - - # Pass any language selection on to the mode switch link - if( $wgLoginLanguageSelector && $this->mLanguage ) - $linkq .= '&uselang=' . $this->mLanguage; - - $link = ''; - $link .= wfMsgHtml( $linkmsg . 'link' ); # Calling either 'gotaccountlink' or 'nologinlink' - $link .= ''; - - # Don't show a "create account" link if the user can't - if( $this->showCreateOrLoginLink( $wgUser ) ) - $template->set( 'link', wfMsgHtml( $linkmsg, $link ) ); - else - $template->set( 'link', '' ); - - $template->set( 'header', '' ); - $template->set( 'name', $this->mName ); - $template->set( 'password', $this->mPassword ); - $template->set( 'retype', $this->mRetype ); - $template->set( 'email', $this->mEmail ); - $template->set( 'realname', $this->mRealName ); - $template->set( 'domain', $this->mDomain ); - - $template->set( 'action', $titleObj->getLocalUrl( $q ) ); - $template->set( 'message', $msg ); - $template->set( 'messagetype', $msgtype ); - $template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() ); - $template->set( 'userealname', $wgAllowRealName ); - $template->set( 'useemail', $wgEnableEmail ); - $template->set( 'emailrequired', $wgEmailConfirmToEdit ); - $template->set( 'canreset', $wgAuth->allowPasswordChange() ); - $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); - - # Prepare language selection links as needed - if( $wgLoginLanguageSelector ) { - $template->set( 'languages', $this->makeLanguageSelector() ); - if( $this->mLanguage ) - $template->set( 'uselang', $this->mLanguage ); - } - - // Give authentication and captcha plugins a chance to modify the form - $wgAuth->modifyUITemplate( $template ); - if ( $this->mType == 'signup' ) { - wfRunHooks( 'UserCreateForm', array( &$template ) ); - } else { - wfRunHooks( 'UserLoginForm', array( &$template ) ); - } - - $wgOut->setPageTitle( wfMsg( 'userlogin' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - $wgOut->disallowUserJs(); // just in case... - $wgOut->addTemplate( $template ); - } - - /** - * @private - */ - function showCreateOrLoginLink( &$user ) { - if( $this->mType == 'signup' ) { - return( true ); - } elseif( $user->isAllowed( 'createaccount' ) ) { - return( true ); - } else { - return( false ); - } - } - - /** - * 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. - * - * @private - */ - function hasSessionCookie() { - global $wgDisableCookieCheck, $wgRequest; - return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie(); - } - - /** - * @private - */ - function cookieRedirectCheck( $type ) { - global $wgOut; - - $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - $check = $titleObj->getFullURL( 'wpCookieCheck='.$type ); - - return $wgOut->redirect( $check ); - } - - /** - * @private - */ - function onCookieRedirectCheck( $type ) { - global $wgUser; - - if ( !$this->hasSessionCookie() ) { - if ( $type == 'new' ) { - return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) ); - } else if ( $type == 'login' ) { - return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) ); - } else { - # shouldn't happen - return $this->mainLoginForm( wfMsg( 'error' ) ); - } - } else { - return $this->successfulLogin( wfMsgExt( 'loginsuccess', array( 'parseinline' ), $wgUser->getName() ) ); - } - } - - /** - * @private - */ - function throttleHit( $limit ) { - global $wgOut; - - $wgOut->addWikiMsg( 'acct_creation_throttle_hit', $limit ); - } - - /** - * Produce a bar of links which allow the user to select another language - * during login/registration but retain "returnto" - * - * @return string - */ - function makeLanguageSelector() { - $msg = wfMsgForContent( 'loginlanguagelinks' ); - if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) { - $langs = explode( "\n", $msg ); - $links = array(); - foreach( $langs as $lang ) { - $lang = trim( $lang, '* ' ); - $parts = explode( '|', $lang ); - if (count($parts) >= 2) { - $links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] ); - } - } - return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', implode( ' | ', $links ) ) : ''; - } else { - return ''; - } - } - - /** - * Create a language selector link for a particular language - * Links back to this page preserving type and returnto - * - * @param $text Link text - * @param $lang Language code - */ - function makeLanguageSelectorLink( $text, $lang ) { - global $wgUser; - $self = SpecialPage::getTitleFor( 'Userlogin' ); - $attr[] = 'uselang=' . $lang; - if( $this->mType == 'signup' ) - $attr[] = 'type=signup'; - if( $this->mReturnTo ) - $attr[] = 'returnto=' . $this->mReturnTo; - $skin = $wgUser->getSkin(); - return $skin->makeKnownLinkObj( $self, htmlspecialchars( $text ), implode( '&', $attr ) ); - } -} diff --git a/includes/specials/Userlogout.php b/includes/specials/Userlogout.php deleted file mode 100644 index 137eadb4a6..0000000000 --- a/includes/specials/Userlogout.php +++ /dev/null @@ -1,23 +0,0 @@ -getName(); - $wgUser->logout(); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - - // Hook. - $injected_html = ''; - wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName) ); - - $wgOut->addHTML( wfMsgExt( 'logouttext', array( 'parse' ) ) . $injected_html ); - $wgOut->returnToMain(); -} diff --git a/includes/specials/Userrights.php b/includes/specials/Userrights.php deleted file mode 100644 index bf4d4409a4..0000000000 --- a/includes/specials/Userrights.php +++ /dev/null @@ -1,576 +0,0 @@ -changeableGroups(); - return !empty( $available['add'] ) - or !empty( $available['remove'] ) - or ($this->isself and - (!empty( $available['add-self'] ) - or !empty( $available['remove-self'] ))); - } - - /** - * Manage forms to be shown according to posted data. - * Depending on the submit button used, call a form or a save function. - * - * @param $par Mixed: string if any subpage provided, else null - */ - function execute( $par ) { - // If the visitor doesn't have permissions to assign or remove - // any groups, it's a bit silly to give them the user search prompt. - global $wgUser, $wgRequest; - - if( $par ) { - $this->mTarget = $par; - } else { - $this->mTarget = $wgRequest->getVal( 'user' ); - } - - if (!$this->mTarget) { - /* - * If the user specified no target, and they can only - * edit their own groups, automatically set them as the - * target. - */ - $available = $this->changeableGroups(); - if (empty($available['add']) && empty($available['remove'])) - $this->mTarget = $wgUser->getName(); - } - - if ($this->mTarget == $wgUser->getName()) - $this->isself = true; - - if( !$this->userCanExecute( $wgUser ) ) { - // fixme... there may be intermediate groups we can mention. - global $wgOut; - $wgOut->showPermissionsErrorPage( array( - $wgUser->isAnon() - ? 'userrights-nologin' - : 'userrights-notallowed' ) ); - return; - } - - if ( wfReadOnly() ) { - global $wgOut; - $wgOut->readOnlyPage(); - return; - } - - $this->outputHeader(); - - $this->setHeaders(); - - // show the general form - $this->switchForm(); - - if( $wgRequest->wasPosted() ) { - // save settings - if( $wgRequest->getCheck( 'saveusergroups' ) ) { - $reason = $wgRequest->getVal( 'user-reason' ); - if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mTarget ) ) { - $this->saveUserGroups( - $this->mTarget, - $reason - ); - } - } - } - - // show some more forms - if( $this->mTarget ) { - $this->editUserGroupsForm( $this->mTarget ); - } - } - - /** - * Save user groups changes in the database. - * Data comes from the editUserGroupsForm() form function - * - * @param $username String: username to apply changes to. - * @param $reason String: reason for group change - * @return null - */ - function saveUserGroups( $username, $reason = '') { - global $wgRequest, $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - - $user = $this->fetchUser( $username ); - if( !$user ) { - return; - } - - $allgroups = $this->getAllGroups(); - $addgroup = array(); - $removegroup = array(); - - // This could possibly create a highly unlikely race condition if permissions are changed between - // when the form is loaded and when the form is saved. Ignoring it for the moment. - foreach ($allgroups as $group) { - // We'll tell it to remove all unchecked groups, and add all checked groups. - // Later on, this gets filtered for what can actually be removed - if ($wgRequest->getCheck( "wpGroup-$group" )) { - $addgroup[] = $group; - } else { - $removegroup[] = $group; - } - } - - // 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']; - } - - $removegroup = array_unique( - array_intersect( (array)$removegroup, $removable ) ); - $addgroup = array_unique( - array_intersect( (array)$addgroup, $addable ) ); - - $oldGroups = $user->getGroups(); - $newGroups = $oldGroups; - // remove then add groups - if( $removegroup ) { - $newGroups = array_diff($newGroups, $removegroup); - foreach( $removegroup as $group ) { - $user->removeGroup( $group ); - } - } - if( $addgroup ) { - $newGroups = array_merge($newGroups, $addgroup); - foreach( $addgroup as $group ) { - $user->addGroup( $group ); - } - } - $newGroups = array_unique( $newGroups ); - - // Ensure that caches are cleared - $user->invalidateCache(); - - wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) ); - wfDebug( 'newGroups: ' . print_r( $newGroups, true ) ); - if( $user instanceof User ) { - // hmmm - wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) ); - } - - if( $newGroups != $oldGroups ) { - $this->addLogEntry( $user, $oldGroups, $newGroups ); - } - } - - /** - * Add a rights log entry for an action. - */ - function addLogEntry( $user, $oldGroups, $newGroups ) { - global $wgRequest; - $log = new LogPage( 'rights' ); - - $log->addEntry( 'rights', - $user->getUserPage(), - $wgRequest->getText( 'user-reason' ), - array( - $this->makeGroupNameList( $oldGroups ), - $this->makeGroupNameList( $newGroups ) - ) - ); - } - - /** - * Edit user groups membership - * @param $username String: name of the user. - */ - function editUserGroupsForm( $username ) { - global $wgOut; - - $user = $this->fetchUser( $username ); - if( !$user ) { - return; - } - - $groups = $user->getGroups(); - - $this->showEditUserGroupsForm( $user, $groups ); - - // This isn't really ideal logging behavior, but let's not hide the - // interwiki logs if we're using them as is. - $this->showLogFragment( $user, $wgOut ); - } - - /** - * Normalize the input username, which may be local or remote, and - * return a user (or proxy) object for manipulating it. - * - * Side effects: error output for invalid access - * @return mixed User, UserRightsProxy, or null - */ - function fetchUser( $username ) { - global $wgOut, $wgUser; - - $parts = explode( '@', $username ); - if( count( $parts ) < 2 ) { - $name = trim( $username ); - $database = ''; - } else { - list( $name, $database ) = array_map( 'trim', $parts ); - - if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) { - $wgOut->addWikiMsg( 'userrights-no-interwiki' ); - return null; - } - if( !UserRightsProxy::validDatabase( $database ) ) { - $wgOut->addWikiMsg( 'userrights-nodatabase', $database ); - return null; - } - } - - if( $name == '' ) { - $wgOut->addWikiMsg( 'nouserspecified' ); - return false; - } - - if( $name{0} == '#' ) { - // Numeric ID can be specified... - // We'll do a lookup for the name internally. - $id = intval( substr( $name, 1 ) ); - - if( $database == '' ) { - $name = User::whoIs( $id ); - } else { - $name = UserRightsProxy::whoIs( $database, $id ); - } - - if( !$name ) { - $wgOut->addWikiMsg( 'noname' ); - return null; - } - } - - if( $database == '' ) { - $user = User::newFromName( $name ); - } else { - $user = UserRightsProxy::newFromName( $database, $name ); - } - - if( !$user || $user->isAnon() ) { - $wgOut->addWikiMsg( 'nosuchusershort', $username ); - return null; - } - - return $user; - } - - function makeGroupNameList( $ids ) { - return implode( ', ', $ids ); - } - - /** - * Output a form to allow searching for a user - */ - function switchForm() { - global $wgOut, $wgScript; - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) . - Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'userrights-lookup-user' ) ) . - Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, $this->mTarget ) . ' ' . - Xml::submitButton( wfMsg( 'editusergroup' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" - ); - } - - /** - * Go through used and available groups and return the ones that this - * form will be able to manipulate based on the current user's system - * permissions. - * - * @param $groups Array: list of groups the given user is in - * @return Array: Tuple of addable, then removable groups - */ - protected function splitGroups( $groups ) { - global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - list($addable, $removable) = array_values( $this->changeableGroups() ); - - $removable = array_intersect( - array_merge($this->isself ? $wgGroupsRemoveFromSelf : array(), $removable), - $groups ); // Can't remove groups the user doesn't have - $addable = array_diff( - array_merge($this->isself ? $wgGroupsAddToSelf : array(), $addable), - $groups ); // Can't add groups the user does have - - return array( $addable, $removable ); - } - - /** - * Show the form to edit group memberships. - * - * @param $user User or UserRightsProxy you're editing - * @param $groups Array: Array of groups the user is in - */ - protected function showEditUserGroupsForm( $user, $groups ) { - global $wgOut, $wgUser; - - list( $addable, $removable ) = $this->splitGroups( $groups ); - - $list = array(); - foreach( $user->getGroups() as $group ) - $list[] = self::buildGroupLink( $group ); - - $grouplist = ''; - if( count( $list ) > 0 ) { - $grouplist = Xml::tags( 'p', null, wfMsgHtml( 'userrights-groupsmember' ) . ' ' . implode( ', ', $list ) ); - } - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) . - Xml::hidden( 'user', $this->mTarget ) . - Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) . - wfMsgExt( 'editinguser', array( 'parse' ), wfEscapeWikiText( $user->getName() ) ) . - wfMsgExt( 'userrights-groups-help', array( 'parse' ) ) . - $grouplist . - Xml::tags( 'p', null, $this->groupCheckboxes( $groups ) ) . - Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-userrights-table-outer' ) ) . - " - " . - Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) . - " - " . - Xml::input( 'user-reason', 60, false, array( 'id' => 'wpReason', 'maxlength' => 255 ) ) . - " - - - - " . - Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups' ) ) . - " - " . - Xml::closeElement( 'table' ) . "\n" . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" - ); - } - - /** - * Format a link to a group description page - * - * @param $group string - * @return string - */ - private static function buildGroupLink( $group ) { - static $cache = array(); - if( !isset( $cache[$group] ) ) - $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupName( $group ) ); - return $cache[$group]; - } - - /** - * Returns an array of all groups that may be edited - * @return array Array of groups that may be edited. - */ - protected static function getAllGroups() { - return User::getAllGroups(); - } - - /** - * Adds a table with checkboxes where you can select what groups to add/remove - * - * @param $usergroups Array: groups the user belongs to - * @return string XHTML table element with checkboxes - */ - private function groupCheckboxes( $usergroups ) { - $allgroups = $this->getAllGroups(); - $ret = ''; - - $column = 1; - $settable_col = ''; - $unsettable_col = ''; - - foreach ($allgroups as $group) { - $set = in_array( $group, $usergroups ); - # Should the checkbox be disabled? - $disabled = !( - ( $set && $this->canRemove( $group ) ) || - ( !$set && $this->canAdd( $group ) ) ); - # Do we need to point out that this action is irreversible? - $irreversible = !$disabled && ( - ($set && !$this->canAdd( $group )) || - (!$set && !$this->canRemove( $group ) ) ); - - $attr = $disabled ? array( 'disabled' => 'disabled' ) : array(); - $text = $irreversible - ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) ) - : User::getGroupMember( $group ); - $checkbox = Xml::checkLabel( $text, "wpGroup-$group", - "wpGroup-$group", $set, $attr ); - $checkbox = $disabled ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkbox ) : $checkbox; - - if ($disabled) { - $unsettable_col .= "$checkbox
      \n"; - } else { - $settable_col .= "$checkbox
      \n"; - } - } - - if ($column) { - $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) . - " -"; - if( $settable_col !== '' ) { - $ret .= xml::element( 'th', null, wfMsg( 'userrights-changeable-col' ) ); - } - if( $unsettable_col !== '' ) { - $ret .= xml::element( 'th', null, wfMsg( 'userrights-unchangeable-col' ) ); - } - $ret.= " - -"; - if( $settable_col !== '' ) { - $ret .= -" - $settable_col - -"; - } - if( $unsettable_col !== '' ) { - $ret .= -" - $unsettable_col - -"; - } - $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' ); - } - - return $ret; - } - - /** - * @param $group String: the name of the group to check - * @return bool Can we remove the group? - */ - private function canRemove( $group ) { - // $this->changeableGroups()['remove'] doesn't work, of course. Thanks, - // PHP. - $groups = $this->changeableGroups(); - return in_array( $group, $groups['remove'] ) || ($this->isself && in_array( $group, $groups['remove-self'] )); - } - - /** - * @param $group string: the name of the group to check - * @return bool Can we add the group? - */ - private function canAdd( $group ) { - $groups = $this->changeableGroups(); - return in_array( $group, $groups['add'] ) || ($this->isself && in_array( $group, $groups['add-self'] )); - } - - /** - * Returns an array of the groups that the user can add/remove. - * - * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) ) - */ - function changeableGroups() { - global $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - - if( $wgUser->isAllowed( 'userrights' ) ) { - // This group gives the right to modify everything (reverse- - // compatibility with old "userrights lets you change - // everything") - // Using array_merge to make the groups reindexed - $all = array_merge( User::getAllGroups() ); - return array( - 'add' => $all, - 'remove' => $all, - 'add-self' => array(), - 'remove-self' => array() - ); - } - - // Okay, it's not so simple, we will have to go through the arrays - $groups = array( - 'add' => array(), - 'remove' => array(), - 'add-self' => $wgGroupsAddToSelf, - 'remove-self' => $wgGroupsRemoveFromSelf); - $addergroups = $wgUser->getEffectiveGroups(); - - foreach ($addergroups as $addergroup) { - $groups = array_merge_recursive( - $groups, $this->changeableByGroup($addergroup) - ); - $groups['add'] = array_unique( $groups['add'] ); - $groups['remove'] = array_unique( $groups['remove'] ); - } - return $groups; - } - - /** - * 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 ) ) - */ - private function changeableByGroup( $group ) { - global $wgAddGroups, $wgRemoveGroups; - - $groups = array( 'add' => array(), 'remove' => array() ); - if( empty($wgAddGroups[$group]) ) { - // Don't add anything to $groups - } elseif( $wgAddGroups[$group] === true ) { - // You get everything - $groups['add'] = User::getAllGroups(); - } elseif( is_array($wgAddGroups[$group]) ) { - $groups['add'] = $wgAddGroups[$group]; - } - - // Same thing for remove - if( empty($wgRemoveGroups[$group]) ) { - } elseif($wgRemoveGroups[$group] === true ) { - $groups['remove'] = User::getAllGroups(); - } elseif( is_array($wgRemoveGroups[$group]) ) { - $groups['remove'] = $wgRemoveGroups[$group]; - } - return $groups; - } - - /** - * Show a rights log fragment for the specified user - * - * @param $user User to show log for - * @param $output OutputPage to use - */ - protected function showLogFragment( $user, $output ) { - $output->addHtml( Xml::element( 'h2', null, LogPage::logName( 'rights' ) . "\n" ) ); - LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage()->getPrefixedText() ); - } -} diff --git a/includes/specials/Version.php b/includes/specials/Version.php deleted file mode 100644 index 8771fa1cf1..0000000000 --- a/includes/specials/Version.php +++ /dev/null @@ -1,390 +0,0 @@ - - * @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 { - private $firstExtOpened = true; - - /** - * main() - */ - function execute() { - global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks; - $wgMessageCache->loadAllMessages(); - - $wgOut->addHTML( '
      ' ); - $text = - $this->MediaWikiCredits() . - $this->softwareInformation() . - $this->extensionCredits(); - if ( $wgSpecialVersionShowHooks ) { - $text .= $this->wgHooks(); - } - $wgOut->addWikiText( $text ); - $wgOut->addHTML( $this->IPInfo() ); - $wgOut->addHTML( '
      ' ); - } - - /**#@+ - * @private - */ - - /** - * @return wiki text showing the license information - */ - static function MediaWikiCredits() { - $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) ) . - "__NOTOC__ - This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''', - copyright (C) 2001-2008 Magnus Manske, Brion Vibber, Lee Daniel Crocker, - Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason, - Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan and others. - - MediaWiki is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MediaWiki is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License] - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online]. - "; - - return str_replace( "\t\t", '', $ret ) . "\n"; - } - - /** - * @return wiki text showing the third party software versions (apache, php, mysql). - */ - static function softwareInformation() { - $dbr = wfGetDB( DB_SLAVE ); - - return Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) . - Xml::openElement( 'table', array( 'id' => 'sv-software' ) ) . - " - " . wfMsg( 'version-software-product' ) . " - " . wfMsg( 'version-software-version' ) . " - \n - - [http://www.mediawiki.org/ MediaWiki] - " . self::getVersionLinked() . " - \n - - [http://www.php.net/ PHP] - " . phpversion() . " (" . php_sapi_name() . ") - \n - - " . $dbr->getSoftwareLink() . " - " . $dbr->getServerVersion() . " - \n" . - Xml::closeElement( 'table' ); - } - - /** - * Return a string of the MediaWiki version with SVN revision if available - * - * @return mixed - */ - public static function getVersion() { - global $wgVersion, $IP; - wfProfileIn( __METHOD__ ); - $svn = self::getSvnRevision( $IP ); - $version = $svn ? "$wgVersion (r$svn)" : $wgVersion; - wfProfileOut( __METHOD__ ); - return $version; - } - - /** - * Return a string of the MediaWiki version with a link to SVN revision if - * available - * - * @return mixed - */ - public static function getVersionLinked() { - global $wgVersion, $IP; - wfProfileIn( __METHOD__ ); - $svn = self::getSvnRevision( $IP ); - $viewvc = 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/?pathrev='; - $version = $svn ? "$wgVersion ([{$viewvc}{$svn} r$svn])" : $wgVersion; - wfProfileOut( __METHOD__ ); - return $version; - } - - /** Generate wikitext showing extensions name, URL, author and description */ - function extensionCredits() { - global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions; - - if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) ) - return ''; - - $extensionTypes = array( - 'specialpage' => wfMsg( 'version-specialpages' ), - 'parserhook' => wfMsg( 'version-parserhooks' ), - 'variable' => wfMsg( 'version-variables' ), - 'media' => wfMsg( 'version-mediahandlers' ), - 'other' => wfMsg( 'version-other' ), - ); - wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) ); - - $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) . - Xml::openElement( 'table', array( 'id' => 'sv-ext' ) ); - - foreach ( $extensionTypes as $type => $text ) { - if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) { - $out .= $this->openExtType( $text ); - - usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); - - foreach ( $wgExtensionCredits[$type] as $extension ) { - if ( isset( $extension['version'] ) ) { - $version = $extension['version']; - } elseif ( isset( $extension['svn-revision'] ) && - preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/', - $extension['svn-revision'], $m ) ) - { - $version = 'r' . $m[1]; - } else { - $version = null; - } - - $out .= $this->formatCredits( - isset ( $extension['name'] ) ? $extension['name'] : '', - $version, - isset ( $extension['author'] ) ? $extension['author'] : '', - isset ( $extension['url'] ) ? $extension['url'] : null, - isset ( $extension['description'] ) ? $extension['description'] : '', - isset ( $extension['descriptionmsg'] ) ? $extension['descriptionmsg'] : '' - ); - } - } - } - - if ( count( $wgExtensionFunctions ) ) { - $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) ); - $out .= '' . $this->listToText( $wgExtensionFunctions ) . "\n"; - } - - if ( $cnt = count( $tags = $wgParser->getTags() ) ) { - for ( $i = 0; $i < $cnt; ++$i ) - $tags[$i] = "<{$tags[$i]}>"; - $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) ); - $out .= '' . $this->listToText( $tags ). "\n"; - } - - if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) { - $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) ); - $out .= '' . $this->listToText( $fhooks ) . "\n"; - } - - if ( count( $wgSkinExtensionFunctions ) ) { - $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ) ); - $out .= '' . $this->listToText( $wgSkinExtensionFunctions ) . "\n"; - } - $out .= Xml::closeElement( 'table' ); - return $out; - } - - /** Callback to sort extensions by type */ - function compare( $a, $b ) { - global $wgLang; - if( $a['name'] === $b['name'] ) { - return 0; - } else { - return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] ) - ? 1 - : -1; - } - } - - function formatCredits( $name, $version = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) { - $extension = isset( $url ) ? "[$url $name]" : $name; - $version = isset( $version ) ? "(" . wfMsg( 'version-version' ) . " $version)" : ''; - - # Look for a localized description - if( isset( $descriptionMsg ) ) { - $msg = wfMsg( $descriptionMsg ); - if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) { - $description = $msg; - } - } - - return " - $extension $version - $description - " . $this->listToText( (array)$author ) . " - \n"; - } - - /** - * @return string - */ - function wgHooks() { - global $wgHooks; - - if ( count( $wgHooks ) ) { - $myWgHooks = $wgHooks; - ksort( $myWgHooks ); - - $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) . - Xml::openElement( 'table', array( 'id' => 'sv-hooks' ) ) . - " - " . wfMsg( 'version-hook-name' ) . " - " . wfMsg( 'version-hook-subscribedby' ) . " - \n"; - - foreach ( $myWgHooks as $hook => $hooks ) - $ret .= " - $hook - " . $this->listToText( $hooks ) . " - \n"; - - $ret .= Xml::closeElement( 'table' ); - return $ret; - } else - return ''; - } - - private function openExtType($text, $name = null) { - $opt = array( 'colspan' => 3 ); - $out = ''; - - if(!$this->firstExtOpened) { - // Insert a spacing line - $out .= '' . Xml::element( 'td', $opt ) . "\n"; - } - $this->firstExtOpened = false; - - if($name) { $opt['id'] = "sv-$name"; } - - $out .= "" . Xml::element( 'th', $opt, $text) . "\n"; - return $out; - } - - /** - * @static - * - * @return string - */ - function IPInfo() { - $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) ); - return "\n" . - "visited from $ip"; - } - - /** - * @param array $list - * @return string - */ - function listToText( $list ) { - $cnt = count( $list ); - - if ( $cnt == 1 ) { - // Enforce always returning a string - return (string)$this->arrayToString( $list[0] ); - } elseif ( $cnt == 0 ) { - return ''; - } else { - 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"; - } - } - - /** - * @static - * - * @param mixed $list Will convert an array to string if given and return - * the paramater unaltered otherwise - * @return mixed - */ - function arrayToString( $list ) { - if( is_object( $list ) ) { - $class = get_class( $list ); - return "($class)"; - } elseif ( ! is_array( $list ) ) { - return $list; - } else { - $class = get_class( $list[0] ); - return "($class, {$list[1]})"; - } - } - - /** - * Retrieve the revision number of a Subversion working directory. - * - * @param string $dir - * @return mixed revision number as int, or false if not a SVN checkout - */ - public static function getSvnRevision( $dir ) { - // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html - $entries = $dir . '/.svn/entries'; - - if( !file_exists( $entries ) ) { - return false; - } - - $content = file( $entries ); - - // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4) - if( preg_match( '/^<\?xml/', $content[0] ) ) { - // subversion is release <= 1.3 - if( !function_exists( 'simplexml_load_file' ) ) { - // We could fall back to expat... YUCK - return false; - } - - // SimpleXml whines about the xmlns... - wfSuppressWarnings(); - $xml = simplexml_load_file( $entries ); - wfRestoreWarnings(); - - if( $xml ) { - foreach( $xml->entry as $entry ) { - if( $xml->entry[0]['name'] == '' ) { - // The directory entry should always have a revision marker. - if( $entry['revision'] ) { - return intval( $entry['revision'] ); - } - } - } - } - return false; - } else { - // subversion is release 1.4 - return intval( $content[3] ); - } - } - - /**#@-*/ -} - -/**#@-*/ diff --git a/includes/specials/Wantedcategories.php b/includes/specials/Wantedcategories.php deleted file mode 100644 index 969a8d8184..0000000000 --- a/includes/specials/Wantedcategories.php +++ /dev/null @@ -1,90 +0,0 @@ - - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class WantedCategoriesPage extends QueryPage { - - function getName() { - return 'Wantedcategories'; - } - - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT - $name as type, - " . NS_CATEGORY . " as namespace, - cl_to as title, - COUNT(*) as value - FROM $categorylinks - LEFT JOIN $page ON cl_to = page_title AND page_namespace = ". NS_CATEGORY ." - WHERE page_title IS NULL - GROUP BY 1,2,3 - "; - } - - 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 wfSpecialWantedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new WantedCategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/Wantedpages.php b/includes/specials/Wantedpages.php deleted file mode 100644 index 650e04f912..0000000000 --- a/includes/specials/Wantedpages.php +++ /dev/null @@ -1,131 +0,0 @@ -setListoutput( $inc ); - $this->nlinks = $nlinks; - } - - function getName() { - return 'Wantedpages'; - } - - function isExpensive() { - return true; - } - function isSyndicated() { return false; } - - function getSQL() { - global $wgWantedPagesThreshold; - $count = $wgWantedPagesThreshold - 1; - $dbr = wfGetDB( DB_SLAVE ); - $pagelinks = $dbr->tableName( 'pagelinks' ); - $page = $dbr->tableName( 'page' ); - return - "SELECT 'Wantedpages' AS type, - pl_namespace AS namespace, - pl_title AS title, - COUNT(*) AS value - FROM $pagelinks - LEFT JOIN $page AS pg1 - ON pl_namespace = pg1.page_namespace AND pl_title = pg1.page_title - LEFT JOIN $page AS pg2 - ON pl_from = pg2.page_id - WHERE pg1.page_namespace IS NULL - AND pl_namespace NOT IN ( 2, 3 ) - AND pg2.page_namespace != 8 - GROUP BY 1,2,3 - HAVING COUNT(*) > $count"; - } - - /** - * Cache page existence for performance - */ - 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 ); - } - - /** - * Format an individual result - * - * @param $skin Skin to use for UI elements - * @param $result Result row - * @return string - */ - public function formatResult( $skin, $result ) { - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if( $title instanceof Title ) { - if( $this->isCached() ) { - $pageLink = $title->exists() - ? '' . $skin->makeLinkObj( $title ) . '' - : $skin->makeBrokenLinkObj( $title ); - } else { - $pageLink = $skin->makeBrokenLinkObj( $title ); - } - return wfSpecialList( $pageLink, $this->makeWlhLink( $title, $skin, $result ) ); - } else { - $tsafe = htmlspecialchars( $result->title ); - return "Invalid title in result set; {$tsafe}"; - } - } - - /** - * Make a "what links here" link for a specified result if required - * - * @param $title Title to make the link for - * @param $skin Skin to use - * @param $result Result row - * @return string - */ - private function makeWlhLink( $title, $skin, $result ) { - global $wgLang; - if( $this->nlinks ) { - $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); - $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->value ) ); - return $skin->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() ); - } else { - return null; - } - } - -} - -/** - * constructor - */ -function wfSpecialWantedpages( $par = null, $specialPage ) { - $inc = $specialPage->including(); - - if ( $inc ) { - @list( $limit, $nlinks ) = explode( '/', $par, 2 ); - $limit = (int)$limit; - $nlinks = $nlinks === 'nlinks'; - $offset = 0; - } else { - list( $limit, $offset ) = wfCheckLimits(); - $nlinks = true; - } - - $wpp = new WantedPagesPage( $inc, $nlinks ); - - $wpp->doQuery( $offset, $limit, !$inc ); -} diff --git a/includes/specials/Watchlist.php b/includes/specials/Watchlist.php deleted file mode 100644 index 4af22c7dff..0000000000 --- a/includes/specials/Watchlist.php +++ /dev/null @@ -1,372 +0,0 @@ -getSkin(); - $specialTitle = SpecialPage::getTitleFor( 'Watchlist' ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - - # 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 ) ); - return; - } - - $wgOut->setPageTitle( wfMsg( 'watchlist' ) ); - - $sub = wfMsgExt( 'watchlistfor', 'parseinline', $wgUser->getName() ); - $sub .= '
      ' . WatchlistEditor::buildTools( $wgUser->getSkin() ); - $wgOut->setSubtitle( $sub ); - - if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) { - $editor = new WatchlistEditor(); - $editor->execute( $wgUser, $wgOut, $wgRequest, $mode ); - return; - } - - $uid = $wgUser->getId(); - 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' ), - /* ? */ 'namespace' => 'all', - ); - - 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['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' ); - - # Get query variables - $days = $wgRequest->getVal( 'days', $prefs['days'] ); - $hideOwn = $wgRequest->getBool( 'hideOwn', $prefs['hideown'] ); - $hideBots = $wgRequest->getBool( 'hideBots', $prefs['hidebots'] ); - $hideMinor = $wgRequest->getBool( 'hideMinor', $prefs['hideminor'] ); - - # Get namespace value, if supplied, and prepare a WHERE fragment - $nameSpace = $wgRequest->getIntOrNull( 'namespace' ); - if( !is_null( $nameSpace ) ) { - $nameSpace = intval( $nameSpace ); - $nameSpaceClause = " AND rc_namespace = $nameSpace"; - } else { - $nameSpace = ''; - $nameSpaceClause = ''; - } - - $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - list( $page, $watchlist, $recentchanges ) = $dbr->tableNamesN( 'page', 'watchlist', 'recentchanges' ); - - $watchlistCount = $dbr->selectField( 'watchlist', 'COUNT(*)', - array( 'wl_user' => $uid ), __METHOD__ ); - // Adjust for page X, talk:page X, which are both stored separately, - // but treated together - $nitems = floor($watchlistCount / 2); - - if( is_null($days) || !is_numeric($days) ) { - $big = 1000; /* The magical big */ - if($nitems > $big) { - # Set default cutoff shorter - $days = $defaults['days'] = (12.0 / 24.0); # 12 hours... - } else { - $days = $defaults['days']; # default cutoff for shortlisters - } - } else { - $days = floatval($days); - } - - // Dump everything here - $nondefaults = array(); - - wfAppendToArrayIfNotDefault('days' , $days , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault('hideOwn' , (int)$hideOwn , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault('hideBots' , (int)$hideBots, $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) { - $wgOut->addWikiMsg( 'nowatchlist' ); - return; - } - - 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; - */ - } - - # If the watchlist is relatively short, it's simplest to zip - # down its entirety and then sort the results. - - # If it's relatively long, it may be worth our while to zip - # through the time-sorted page list checking for watched items. - - # 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) - if( $wgUser->getOption( 'extendwatchlist' )) { - $andLatest=''; - $limitWatchlist = 'LIMIT ' . 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 = ''; - } - - $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( '
      ' . - "\n\n" ); - } - if ( $wgShowUpdatedMarker ) { - $wltsfield = ", ${watchlist}.wl_notificationtimestamp "; - } else { - $wltsfield = ''; - } - $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 ); - $numRows = $dbr->numRows( $res ); - - /* Start bottom header */ - $wgOut->addHTML( "
      \n" ); - - if($days >= 1) { - $wgOut->addWikiText( wfMsgExt( 'rcnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), - $wgLang->formatNum( $days ), $wgLang->timeAndDate( wfTimestampNow(), true ) ) . '
      ' , false ); - } elseif($days > 0) { - $wgOut->addWikiText( wfMsgExt( 'wlnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), - $wgLang->formatNum( round($days*24) ) ) . '
      ' , false ); - } - - $wgOut->addHTML( "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "
      \n" ); - - # Spit out some control panel links - $thisTitle = SpecialPage::getTitleFor( 'Watchlist' ); - $skin = $wgUser->getSkin(); - - # Hide/show bot edits - $label = $hideBots ? wfMsgHtml( 'watchlist-show-bots' ) : wfMsgHtml( 'watchlist-hide-bots' ); - $linkBits = wfArrayToCGI( array( 'hideBots' => 1 - (int)$hideBots ), $nondefaults ); - $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); - - # Hide/show own edits - $label = $hideOwn ? wfMsgHtml( 'watchlist-show-own' ) : wfMsgHtml( 'watchlist-hide-own' ); - $linkBits = wfArrayToCGI( array( 'hideOwn' => 1 - (int)$hideOwn ), $nondefaults ); - $links[] = $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 ) ); - - # Form for namespace filtering - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) ); - $form .= '

      '; - $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; - $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; - $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '

      '; - $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 ); - $form .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $form ); - - # If there's nothing to show, stop here - if( $numRows == 0 ) { - $wgOut->addWikiMsg( 'watchnochange' ); - return; - } - - /* End bottom header */ - - /* Do link batch query */ - $linkBatch = new LinkBatch; - while ( $row = $dbr->fetchObject( $res ) ) { - $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); - if ( $row->rc_user != 0 ) { - $linkBatch->add( NS_USER, $userNameUnderscored ); - } - $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); - } - $linkBatch->execute(); - $dbr->dataSeek( $res, 0 ); - - $list = ChangesList::newFromUser( $wgUser ); - - $s = $list->beginRecentChangesList(); - $counter = 1; - while ( $obj = $dbr->fetchObject( $res ) ) { - # Make RC entry - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - - if ( $wgShowUpdatedMarker ) { - $updated = $obj->wl_notificationtimestamp; - } else { - $updated = false; - } - - if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) { - $rc->numberofWatchingusers = $dbr->selectField( 'watchlist', - 'COUNT(*)', - array( - 'wl_namespace' => $obj->rc_namespace, - 'wl_title' => $obj->rc_title, - ), - __METHOD__ ); - } else { - $rc->numberofWatchingusers = 0; - } - - $s .= $list->recentChangesLine( $rc, $updated ); - } - $s .= $list->endRecentChangesList(); - - $dbr->freeResult( $res ); - $wgOut->addHTML( $s ); - -} - -function wlHoursLink( $h, $page, $options = array() ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( - $wgContLang->specialPage( $page ), - $wgLang->formatNum( $h ), - wfArrayToCGI( array('days' => ($h / 24.0)), $options ) ); - return $s; -} - -function wlDaysLink( $d, $page, $options = array() ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( - $wgContLang->specialPage( $page ), - ($d ? $wgLang->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) ), - wfArrayToCGI( array('days' => $d), $options ) ); - return $s; -} - -/** - * Returns html - */ -function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { - $hours = array( 1, 2, 6, 12 ); - $days = array( 1, 3, 7 ); - $i = 0; - foreach( $hours as $h ) { - $hours[$i++] = wlHoursLink( $h, $page, $options ); - } - $i = 0; - foreach( $days as $d ) { - $days[$i++] = wlDaysLink( $d, $page, $options ); - } - return wfMsgExt('wlshowlast', - array('parseinline', 'replaceafter'), - implode(' | ', $hours), - implode(' | ', $days), - wlDaysLink( 0, $page, $options ) ); -} - -/** - * Count the number of items on a user's watchlist - * - * @param $talk Include talk pages - * @return integer - */ -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' ); - $row = $dbr->fetchObject( $res ); - $count = $row->count; - $dbr->freeResult( $res ); - - # Halve to remove talk pages if needed - if( !$talk ) - $count = floor( $count / 2 ); - - return( $count ); -} diff --git a/includes/specials/Whatlinkshere.php b/includes/specials/Whatlinkshere.php deleted file mode 100644 index a57df5e03f..0000000000 --- a/includes/specials/Whatlinkshere.php +++ /dev/null @@ -1,408 +0,0 @@ -execute(); -} - -/** - * implements Special:Whatlinkshere - * @ingroup SpecialPage - */ -class WhatLinksHerePage { - // Stored data - protected $par; - - // Stored objects - protected $opts, $target, $selfTitle; - - // Stored globals - protected $skin, $request; - - protected $limits = array( 20, 50, 100, 250, 500 ); - - function WhatLinksHerePage( $request, $par = null ) { - global $wgUser; - $this->request = $request; - $this->skin = $wgUser->getSkin(); - $this->par = $par; - } - - function execute() { - global $wgOut; - - $opts = new FormOptions(); - - $opts->add( 'target', '' ); - $opts->add( 'namespace', '', FormOptions::INTNULL ); - $opts->add( 'limit', 50 ); - $opts->add( 'from', 0 ); - $opts->add( 'back', 0 ); - $opts->add( 'hideredirs', false ); - $opts->add( 'hidetrans', false ); - $opts->add( 'hidelinks', false ); - $opts->add( 'hideimages', false ); - - $opts->fetchValuesFromRequest( $this->request ); - $opts->validateIntBounds( 'limit', 0, 5000 ); - - // Give precedence to subpage syntax - if ( isset($this->par) ) { - $opts->setValue( 'target', $this->par ); - } - - // Bind to member variable - $this->opts = $opts; - - $this->target = Title::newFromURL( $opts->getValue( 'target' ) ); - if( !$this->target ) { - $wgOut->addHTML( $this->whatlinkshereForm() ); - return; - } - - $this->selfTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->target->getPrefixedDBkey() ); - - $wgOut->setPageTitle( wfMsgExt( 'whatlinkshere-title', 'escape', $this->target->getPrefixedText() ) ); - $wgOut->setSubtitle( wfMsgHtml( 'linklistsub' ) ); - - $wgOut->addHTML( wfMsgExt( 'whatlinkshere-barrow', array( 'escapenoentities') ) . ' ' .$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."
      \n"); - - $this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ), - $opts->getValue( 'from' ), $opts->getValue( 'back' ) ); - } - - /** - * @param $level int Recursion level - * @param $target Title Target title - * @param $limit int Number of entries to display - * @param $from Title Display from this article ID - * @param $back Title Display from this article ID at backwards scrolling - * @private - */ - function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) { - global $wgOut, $wgMaxRedirectLinksRetrieved; - $dbr = wfGetDB( DB_SLAVE ); - $options = array(); - - $hidelinks = $this->opts->getValue( 'hidelinks' ); - $hideredirs = $this->opts->getValue( 'hideredirs' ); - $hidetrans = $this->opts->getValue( 'hidetrans' ); - $hideimages = $target->getNamespace() != NS_IMAGE || $this->opts->getValue( 'hideimages' ); - - $fetchlinks = (!$hidelinks || !$hideredirs); - - // Make the query - $plConds = array( - 'page_id=pl_from', - 'pl_namespace' => $target->getNamespace(), - 'pl_title' => $target->getDBkey(), - ); - if( $hideredirs ) { - $plConds['page_is_redirect'] = 0; - } elseif( $hidelinks ) { - $plConds['page_is_redirect'] = 1; - } - - $tlConds = array( - 'page_id=tl_from', - 'tl_namespace' => $target->getNamespace(), - 'tl_title' => $target->getDBkey(), - ); - - $ilConds = array( - 'page_id=il_from', - 'il_to' => $target->getDBkey(), - ); - - $namespace = $this->opts->getValue( 'namespace' ); - if ( is_int($namespace) ) { - $plConds['page_namespace'] = $namespace; - $tlConds['page_namespace'] = $namespace; - $ilConds['page_namespace'] = $namespace; - } - - if ( $from ) { - $tlConds[] = "tl_from >= $from"; - $plConds[] = "pl_from >= $from"; - $ilConds[] = "il_from >= $from"; - } - - // Read an extra row as an at-end check - $queryLimit = $limit + 1; - - // Enforce join order, sometimes namespace selector may - // trigger filesorts which are far less efficient than scanning many entries - $options[] = 'STRAIGHT_JOIN'; - - $options['LIMIT'] = $queryLimit; - $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ); - - if( $fetchlinks ) { - $options['ORDER BY'] = 'pl_from'; - $plRes = $dbr->select( array( 'pagelinks', 'page' ), $fields, - $plConds, __METHOD__, $options ); - } - - if( !$hidetrans ) { - $options['ORDER BY'] = 'tl_from'; - $tlRes = $dbr->select( array( 'templatelinks', 'page' ), $fields, - $tlConds, __METHOD__, $options ); - } - - if( !$hideimages ) { - $options['ORDER BY'] = 'il_from'; - $ilRes = $dbr->select( array( 'imagelinks', 'page' ), $fields, - $ilConds, __METHOD__, $options ); - } - - 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() ); - } - return; - } - - // Read the rows into an array and remove duplicates - // templatelinks comes second so that the templatelinks row overwrites the - // pagelinks row, so we get (inclusion) rather than nothing - if( $fetchlinks ) { - while ( $row = $dbr->fetchObject( $plRes ) ) { - $row->is_template = 0; - $row->is_image = 0; - $rows[$row->page_id] = $row; - } - $dbr->freeResult( $plRes ); - - } - if( !$hidetrans ) { - while ( $row = $dbr->fetchObject( $tlRes ) ) { - $row->is_template = 1; - $row->is_image = 0; - $rows[$row->page_id] = $row; - } - $dbr->freeResult( $tlRes ); - } - if( !$hideimages ) { - while ( $row = $dbr->fetchObject( $ilRes ) ) { - $row->is_template = 0; - $row->is_image = 1; - $rows[$row->page_id] = $row; - } - $dbr->freeResult( $ilRes ); - } - - // Sort by key and then change the keys to 0-based indices - ksort( $rows ); - $rows = array_values( $rows ); - - $numRows = count( $rows ); - - // Work out the start and end IDs, for prev/next links - if ( $numRows > $limit ) { - // More rows available after these ones - // Get the ID from the last row in the result set - $nextId = $rows[$limit]->page_id; - // Remove undisplayed rows - $rows = array_slice( $rows, 0, $limit ); - } else { - // No more rows after - $nextId = false; - } - $prevId = $from; - - if ( $level == 0 ) { - $wgOut->addHTML( $this->whatlinkshereForm() ); - $wgOut->addHTML( $this->getFilterPanel() ); - $wgOut->addWikiMsg( 'linkshere', $this->target->getPrefixedText() ); - - $prevnext = $this->getPrevNext( $prevId, $nextId ); - $wgOut->addHTML( $prevnext ); - } - - $wgOut->addHTML( $this->listStart() ); - foreach ( $rows as $row ) { - $nt = Title::makeTitle( $row->page_namespace, $row->page_title ); - - if ( $row->page_is_redirect && $level < 2 ) { - $wgOut->addHTML( $this->listItem( $row, $nt, true ) ); - $this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved ); - $wgOut->addHTML( Xml::closeElement( 'li' ) ); - } else { - $wgOut->addHTML( $this->listItem( $row, $nt ) ); - } - } - - $wgOut->addHTML( $this->listEnd() ); - - if( $level == 0 ) { - $wgOut->addHTML( $prevnext ); - } - } - - protected function listStart() { - return Xml::openElement( 'ul' ); - } - - protected function listItem( $row, $nt, $notClose = false ) { - # local message cache - static $msgcache = null; - if ( $msgcache === null ) { - static $msgs = array( 'isredirect', 'istemplate', 'semicolon-separator', - 'whatlinkshere-links', 'isimage' ); - $msgcache = array(); - foreach ( $msgs as $msg ) { - $msgcache[$msg] = wfMsgHtml( $msg ); - } - } - - $suppressRedirect = $row->page_is_redirect ? 'redirect=no' : ''; - $link = $this->skin->makeKnownLinkObj( $nt, '', $suppressRedirect ); - - // Display properties (redirect or template) - $propsText = ''; - $props = array(); - if ( $row->page_is_redirect ) - $props[] = $msgcache['isredirect']; - if ( $row->is_template ) - $props[] = $msgcache['istemplate']; - if( $row->is_image ) - $props[] = $msgcache['isimage']; - - if ( count( $props ) ) { - $propsText = '(' . implode( $msgcache['semicolon-separator'], $props ) . ')'; - } - - # Space for utilities links, with a what-links-here link provided - $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'] ); - $wlh = Xml::wrapClass( "($wlhLink)", 'mw-whatlinkshere-tools' ); - - return $notClose ? - Xml::openElement( 'li' ) . "$link $propsText $wlh\n" : - Xml::tags( 'li', null, "$link $propsText $wlh" ) . "\n"; - } - - protected function listEnd() { - return Xml::closeElement( 'ul' ); - } - - protected function wlhLink( Title $target, $text ) { - static $title = null; - if ( $title === null ) - $title = SpecialPage::getTitleFor( 'Whatlinkshere' ); - - $targetText = $target->getPrefixedUrl(); - return $this->skin->makeKnownLinkObj( $title, $text, 'target=' . $targetText ); - } - - function makeSelfLink( $text, $query ) { - return $this->skin->makeKnownLinkObj( $this->selfTitle, $text, $query ); - } - - function getPrevNext( $prevId, $nextId ) { - global $wgLang; - $currentLimit = $this->opts->getValue( 'limit' ); - $fmtLimit = $wgLang->formatNum( $currentLimit ); - $prev = wfMsgExt( 'whatlinkshere-prev', array( 'parsemag', 'escape' ), $fmtLimit ); - $next = wfMsgExt( 'whatlinkshere-next', array( 'parsemag', 'escape' ), $fmtLimit ); - - $changed = $this->opts->getChangedValues(); - unset($changed['target']); // Already in the request title - - if ( 0 != $prevId ) { - $overrides = array( 'from' => $this->opts->getValue( 'back' ) ); - $prev = $this->makeSelfLink( $prev, wfArrayToCGI( $overrides, $changed ) ); - } - if ( 0 != $nextId ) { - $overrides = array( 'from' => $nextId, 'back' => $prevId ); - $next = $this->makeSelfLink( $next, wfArrayToCGI( $overrides, $changed ) ); - } - - $limitLinks = array(); - foreach ( $this->limits as $limit ) { - $prettyLimit = $wgLang->formatNum( $limit ); - $overrides = array( 'limit' => $limit ); - $limitLinks[] = $this->makeSelfLink( $prettyLimit, wfArrayToCGI( $overrides, $changed ) ); - } - - $nums = implode ( ' | ', $limitLinks ); - - return wfMsgHtml( 'viewprevnext', $prev, $next, $nums ); - } - - function whatlinkshereForm() { - global $wgScript, $wgTitle; - - // We get nicer value from the title object - $this->opts->consumeValue( 'target' ); - // Reset these for new requests - $this->opts->consumeValues( array( 'back', 'from' ) ); - - $target = $this->target ? $this->target->getPrefixedText() : ''; - $namespace = $this->opts->consumeValue( 'namespace' ); - - # Build up the form - $f = Xml::openElement( 'form', array( 'action' => $wgScript ) ); - - # Values that should not be forgotten - $f .= Xml::hidden( 'title', $wgTitle->getPrefixedText() ); - foreach ( $this->opts->getUnconsumedValues() as $name => $value ) { - $f .= Xml::hidden( $name, $value ); - } - - $f .= Xml::fieldset( wfMsg( 'whatlinkshere' ) ); - - # Target input - $f .= Xml::inputLabel( wfMsg( 'whatlinkshere-page' ), 'target', - 'mw-whatlinkshere-target', 40, $target ); - - $f .= ' '; - - # Namespace selector - $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . - Xml::namespaceSelector( $namespace, '' ); - - # Submit - $f .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); - - # Close - $f .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n"; - - return $f; - } - - function getFilterPanel() { - $show = wfMsgHtml( 'show' ); - $hide = wfMsgHtml( 'hide' ); - - $changed = $this->opts->getChangedValues(); - unset($changed['target']); // Already in the request title - - $links = array(); - $types = array( 'hidetrans', 'hidelinks', 'hideredirs' ); - if( $this->target->getNamespace() == NS_IMAGE ) - $types[] = 'hideimages'; - foreach( $types as $type ) { - $chosen = $this->opts->getValue( $type ); - $msg = wfMsgHtml( "whatlinkshere-{$type}", $chosen ? $show : $hide ); - $overrides = array( $type => !$chosen ); - $links[] = $this->makeSelfLink( $msg, wfArrayToCGI( $overrides, $changed ) ); - } - return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), implode( ' | ', $links ) ); - } -} diff --git a/includes/specials/Withoutinterwiki.php b/includes/specials/Withoutinterwiki.php deleted file mode 100644 index 2092e43b52..0000000000 --- a/includes/specials/Withoutinterwiki.php +++ /dev/null @@ -1,88 +0,0 @@ - - */ -class WithoutInterwikiPage extends PageQueryPage { - private $prefix = ''; - - function getName() { - return 'Withoutinterwiki'; - } - - function getPageHeader() { - global $wgScript, $wgMiserMode; - - # Do not show useless input form if wiki is running in misermode - if( $wgMiserMode ) { - return ''; - } - - $prefix = $this->prefix; - $t = SpecialPage::getTitleFor( $this->getName() ); - - return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'withoutinterwiki-legend' ) ) . - Xml::hidden( 'title', $t->getPrefixedText() ) . - Xml::inputLabel( wfMsg( 'allpagesprefix' ), 'prefix', 'wiprefix', 20, $prefix ) . ' ' . - Xml::submitButton( wfMsg( 'withoutinterwiki-submit' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ); - } - - function sortDescending() { - return false; - } - - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' ); - $prefix = $this->prefix ? "AND page_title LIKE '" . $dbr->escapeLike( $this->prefix ) . "%'" : ''; - return - "SELECT 'Withoutinterwiki' AS type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $langlinks - ON ll_from = page_id - WHERE ll_title IS NULL - AND page_namespace=" . NS_MAIN . " - AND page_is_redirect = 0 - {$prefix}"; - } - - function setPrefix( $prefix = '' ) { - $this->prefix = $prefix; - } - -} - -function wfSpecialWithoutinterwiki() { - global $wgRequest, $wgContLang, $wgCapitalLinks; - list( $limit, $offset ) = wfCheckLimits(); - if( $wgCapitalLinks ) { - $prefix = $wgContLang->ucfirst( $wgRequest->getVal( 'prefix' ) ); - } else { - $prefix = $wgRequest->getVal( 'prefix' ); - } - $wip = new WithoutInterwikiPage(); - $wip->setPrefix( $prefix ); - $wip->doQuery( $offset, $limit ); -}