From 7f1b7554b90fd31acf2cacb2494fa2d7610175b5 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Sun, 14 Sep 2008 19:51:25 +0000 Subject: [PATCH] * Add functionality of extension LinkSearch to core * Update release notes for adding of Special:Log/newusers --- RELEASE-NOTES | 3 + includes/AutoLoader.php | 10 +- includes/DefaultSettings.php | 1 + includes/QueryPage.php | 1 + includes/SpecialPage.php | 1 + includes/specials/SpecialLinkSearch.php | 182 ++++++++++++++++++++++++ languages/messages/MessagesEn.php | 10 ++ maintenance/language/messages.inc | 10 ++ 8 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 includes/specials/SpecialLinkSearch.php diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 14fb877f80..b119e13639 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -126,6 +126,9 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * Added Wantedfiles special pages, allowing users to find image links with no image. * (bug 12650) It is now possible to set different expiration times for different restriction types on the protection form. +* Special:Log/newusers recording new users.was added (was extension Newuserlog) +* Special:LinkSearch to search for external links was added (was extension + LinkSearch) === Bug fixes in 1.14 === diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 19d349ef23..38bed465a8 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -8,7 +8,7 @@ ini_set('unserialize_callback_func', '__autoload' ); # Extension classes are specified with $wgAutoloadClasses # This array is a global instead of a static member of AutoLoader to work around a bug in APC global $wgAutoloadLocalClasses; -$wgAutoloadLocalClasses = array( +$wgAutoloadLocalClasses = array( # Includes 'AjaxDispatcher' => 'includes/AjaxDispatcher.php', 'AjaxResponse' => 'includes/AjaxResponse.php', @@ -356,7 +356,7 @@ $wgAutoloadLocalClasses = array( 'WhiteSpaceNode' => 'includes/diff/Nodes.php', 'WikiDiff3' => 'includes/diff/Diff.php', 'WordLevelDiff' => 'includes/diff/DifferenceEngine.php', - + # includes/filerepo 'ArchivedFile' => 'includes/filerepo/ArchivedFile.php', 'File' => 'includes/filerepo/File.php', @@ -446,6 +446,7 @@ $wgAutoloadLocalClasses = array( 'ImportReporter' => 'includes/specials/SpecialImport.php', 'ImportStreamSource' => 'includes/specials/SpecialImport.php', 'ImportStringSource' => 'includes/specials/SpecialImport.php', + 'LinkSearchPage' => 'includes/specials/SpecialLinkSearch.php', 'ListredirectsPage' => 'includes/specials/SpecialListredirects.php', 'LoginForm' => 'includes/specials/SpecialUserlogin.php', 'LonelyPagesPage' => 'includes/specials/SpecialLonelypages.php', @@ -517,11 +518,11 @@ $wgAutoloadLocalClasses = array( class AutoLoader { /** * autoload - take a class name and attempt to load it - * + * * @param string $className Name of class we're looking for. * @return bool Returning false is important on failure as * it allows Zend to try and look in other registered autoloaders - * as well. + * as well. */ static function autoload( $className ) { global $wgAutoloadClasses, $wgAutoloadLocalClasses; @@ -580,4 +581,3 @@ if ( function_exists( 'spl_autoload_register' ) ) { AutoLoader::autoload( $class ); } } - diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index c017c35d39..2e0a48e3d4 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2828,6 +2828,7 @@ $wgSpecialPageGroups = array( 'Mytalk' => 'redirects', 'Mycontributions' => 'redirects', 'Search' => 'redirects', + 'LinkSearch' => 'redirects', 'Movepage' => 'pagetools', 'MergeHistory' => 'pagetools', diff --git a/includes/QueryPage.php b/includes/QueryPage.php index c5cd03d43c..f880abbabb 100644 --- a/includes/QueryPage.php +++ b/includes/QueryPage.php @@ -21,6 +21,7 @@ $wgQueryPages = array( array( 'DeadendPagesPage', 'Deadendpages' ), array( 'DisambiguationsPage', 'Disambiguations' ), array( 'DoubleRedirectsPage', 'DoubleRedirects' ), + array( 'LinkSearchPage', 'LinkSearch' ), array( 'ListredirectsPage', 'Listredirects' ), array( 'LonelyPagesPage', 'Lonelypages' ), array( 'LongPagesPage', 'Longpages' ), diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php index 9a84a854af..416c0d2b30 100644 --- a/includes/SpecialPage.php +++ b/includes/SpecialPage.php @@ -129,6 +129,7 @@ class SpecialPage 'Contributions' => array( 'SpecialPage', 'Contributions' ), 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ), 'Whatlinkshere' => array( 'SpecialPage', 'Whatlinkshere' ), + 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ), 'Recentchangeslinked' => 'SpecialRecentchangeslinked', 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ), 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ), diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php new file mode 100644 index 0000000000..b8ff4794ff --- /dev/null +++ b/includes/specials/SpecialLinkSearch.php @@ -0,0 +1,182 @@ +getVal( 'target', $par ); + $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null ); + + $protocols_list[] = ''; + foreach( $wgUrlProtocols as $prot ) { + $protocols_list[] = $prot; + } + + $target2 = $target; + $protocol = ''; + $pr_sl = strpos($target2, '//' ); + $pr_cl = strpos($target2, ':' ); + if ( $pr_sl ) { + // For protocols with '//' + $protocol = substr( $target2, 0 , $pr_sl+2 ); + $target2 = substr( $target2, $pr_sl+2 ); + } elseif ( !$pr_sl && $pr_cl ) { + // For protocols without '//' like 'mailto:' + $protocol = substr( $target2, 0 , $pr_cl+1 ); + $target2 = substr( $target2, $pr_cl+1 ); + } elseif ( $protocol == '' && $target2 != '' ) { + // default + $protocol = 'http://'; + } + if ( !in_array( $protocol, $protocols_list ) ) { + // unsupported protocol, show original search request + $target2 = $target; + $protocol = ''; + } + + $self = Title::makeTitle( NS_SPECIAL, 'Linksearch' ); + + $wgOut->addWikiText( wfMsg( 'linksearch-text', '' . implode( ', ', $wgUrlProtocols) . '' ) ); + $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) . + Xml::hidden( 'title', $self->getPrefixedDbKey() ) . + '
' . + Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) . + Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' '; + if ( !$wgMiserMode ) { + $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' . + XML::namespaceSelector( $namespace, '' ); + } + $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) . + '
' . + Xml::closeElement( 'form' ); + $wgOut->addHtml( $s ); + + if( $target != '' ) { + $searcher = new LinkSearchPage( $target2, $namespace, $protocol ); + $searcher->doQuery( $offset, $limit ); + } +} + +class LinkSearchPage extends QueryPage { + + function __construct( $query, $ns, $prot ) { + $this->mQuery = $query; + $this->mNs = $ns; + $this->mProt = $prot; + } + + function getName() { + return 'LinkSearch'; + } + + /** + * Disable RSS/Atom feeds + */ + function isSyndicated() { + return false; + } + + /** + * Return an appropriately formatted LIKE query and the clause + */ + static function mungeQuery( $query , $prot ) { + $field = 'el_index'; + $rv = LinkFilter::makeLike( $query , $prot ); + if ($rv === false) { + //makeLike doesn't handle wildcard in IP, so we'll have to munge here. + if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) { + $rv = $prot . rtrim($query, " \t*") . '%'; + $field = 'el_to'; + } + } + return array( $rv, $field ); + } + + function linkParameters() { + global $wgMiserMode; + $params = array(); + $params['target'] = $this->mProt . $this->mQuery; + if( isset( $this->mNs ) && !$wgMiserMode ) { + $params['namespace'] = $this->mNs; + } + return $params; + } + + function getSQL() { + global $wgMiserMode; + $dbr = wfGetDB( DB_SLAVE ); + $page = $dbr->tableName( 'page' ); + $externallinks = $dbr->tableName( 'externallinks' ); + + /* strip everything past first wildcard, so that index-based-only lookup would be done */ + list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt ); + $stripped = substr($munged,0,strpos($munged,'%')+1); + $encSearch = $dbr->addQuotes( $stripped ); + + $encSQL = ''; + if ( isset ($this->mNs) && !$wgMiserMode ) + $encSQL = 'AND page_namespace=' . $dbr->addQuotes( $this->mNs ); + + $use_index = $dbr->useIndexClause( $clause ); + return + "SELECT + page_namespace AS namespace, + page_title AS title, + el_index AS value, + el_to AS url + FROM + $page, + $externallinks $use_index + WHERE + page_id=el_from + AND $clause LIKE $encSearch + $encSQL"; + } + + function formatResult( $skin, $result ) { + $title = Title::makeTitle( $result->namespace, $result->title ); + $url = $result->url; + $pageLink = $skin->makeKnownLinkObj( $title ); + $urlLink = $skin->makeExternalLink( $url, $url ); + + return wfMsgHtml( 'linksearch-line', $urlLink, $pageLink ); + } + + /** + * Override to check query validity. + */ + function doQuery( $offset, $limit, $shownavigation=true ) { + global $wgOut; + list( $this->mMungedQuery, $clause ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt ); + if( $this->mMungedQuery === false ) { + $wgOut->addWikiText( wfMsg( 'linksearch-error' ) ); + } else { + // For debugging + // Generates invalid xhtml with patterns that contain -- + //$wgOut->addHtml( "\n\n" ); + parent::doQuery( $offset, $limit, $shownavigation ); + } + } + + /** + * Override to squash the ORDER BY. + * We do a truncated index search, so the optimizer won't trust + * it as good enough for optimizing sort. The implicit ordering + * from the scan will usually do well enough for our needs. + */ + function getOrder() { + return ''; + } +} diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 9120d43c28..ae2432efb5 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -2104,6 +2104,16 @@ Also see [[Special:WantedCategories|wanted categories]].', 'special-categories-sort-count' => 'sort by count', 'special-categories-sort-abc' => 'sort alphabetically', +# Special:LinkSearch +'linksearch' => 'Search web links', +'linksearch-pat' => 'Search pattern:', +'linksearch-ns' => 'Namespace:', +'linksearch-ok' => 'Search', +'linksearch-text' => 'Wildcards such as "*.wikipedia.org" may be used.
+Supported protocols: $1', +'linksearch-line' => '$1 linked from $2', +'linksearch-error' => 'Wildcards may appear only at the start of the hostname.', + # Special:ListUsers 'listusersfrom' => 'Display users starting at:', 'listusers-submit' => 'Show', diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index c8b7918b22..37b0075f35 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -1377,6 +1377,15 @@ $wgMessageStructure = array( 'special-categories-sort-count', 'special-categories-sort-abc', ), + 'linksearch' => array( + 'linksearch', + 'linksearch-pat', + 'linksearch-ns', + 'linksearch-ok', + 'linksearch-text', + 'linksearch-line', + 'linksearch-error', + ), 'listusers' => array( 'listusersfrom', 'listusers-submit', @@ -2805,6 +2814,7 @@ XHTML id names.", 'logpages' => 'Special:Log', 'allpages' => 'Special:AllPages', 'categories' => 'Special:Categories', + 'linksearch' => 'Special:LinkSearch', 'listusers' => 'Special:ListUsers', 'newuserlog' => 'Special:Log/newusers', 'listgrouprights' => 'Special:ListGroupRights', -- 2.20.1