* Merged backends for OpenSearch suggestions and AJAX search.
authorBrion Vibber <brion@users.mediawiki.org>
Wed, 30 Jan 2008 01:07:49 +0000 (01:07 +0000)
committerBrion Vibber <brion@users.mediawiki.org>
Wed, 30 Jan 2008 01:07:49 +0000 (01:07 +0000)
  Both now accept namespace prefixes, handle 'Media:' and 'Special:' pages,
  and reject interwiki prefixes. PrefixSearch class centralizes this code,
  and the backend part can be overridden by the PrefixSearchBackend hook.

RELEASE-NOTES
docs/hooks.txt
includes/AjaxFunctions.php
includes/AutoLoader.php
includes/PrefixSearch.php [new file with mode: 0644]
includes/api/ApiOpenSearch.php

index 308fe08..9798b77 100644 (file)
@@ -143,6 +143,11 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * (bug 11897) Add alias [[Special:CreateAccount]] & [[Special:Userlogin/signup]]
   for Special:Userlogin?type=signup
 * (bug 12214) Add a predefined list of delete reasons to the file deletion form
+* Merged backends for OpenSearch suggestions and AJAX search.
+  Both now accept namespace prefixes, handle 'Media:' and 'Special:' pages,
+  and reject interwiki prefixes. PrefixSearch class centralizes this code,
+  and the backend part can be overridden by the PrefixSearchBackend hook.
+
 
 === Bug fixes in 1.12 ===
 
index b059299..bca4439 100644 (file)
@@ -800,6 +800,13 @@ the built-in rate limiting checks are used, if enabled.
 $form : PreferencesForm object
 &$html : HTML to append to
 
+'PrefixSearchBackend': Override the title prefix search used for OpenSearch and
+AJAX search suggestions. Put results into &$results outparam and return false.
+$ns : int namespace key to search in
+$search : search term (not guaranteed to be conveniently normalized)
+$limit : maximum number of results to return
+&$results : out param: array of page names (strings)
+
 'PrefsEmailAudit': called when user changes his email address
 $user: User (object) changing his email address
 $oldaddr: old email address (string)
index 50182d8..ffd3168 100644 (file)
@@ -73,7 +73,7 @@ function code2utf($num){
    return '';
 }
 
-define( 'AJAX_SEARCH_VERSION', 1 );    //AJAX search cache version
+define( 'AJAX_SEARCH_VERSION', 2 );    //AJAX search cache version
 
 function wfSajaxSearch( $term ) {
        global $wgContLang, $wgOut, $wgUser, $wgCapitalLinks, $wgMemc;
@@ -81,12 +81,6 @@ function wfSajaxSearch( $term ) {
        $sk = $wgUser->getSkin();
        $output = '';
 
-       if( !wfRunHooks( 'SajaxSearch', array( $term, &$output ) ) ) {
-               $response = new AjaxResponse( $output );
-               $response->setCacheDuration( 30*60 );
-               return $response;
-       }
-
        $term = trim( $term );
        $term = $wgContLang->checkTitleEncoding( $wgContLang->recodeInput( js_unescape( $term ) ) );
        if ( $wgCapitalLinks )
@@ -103,41 +97,33 @@ function wfSajaxSearch( $term ) {
 
        $r = $more = '';
        $canSearch = true;
-       if( $term_title && $term_title->getNamespace() != NS_SPECIAL ) {
-               $db = wfGetDB( DB_SLAVE );
-               $res = $db->select( 'page', array( 'page_title', 'page_namespace' ),
-                               array(  'page_namespace' => $term_title->getNamespace(),
-                                       "page_title LIKE '". $db->strencode( $term_title->getDBkey() ) ."%'" ),
-                                       "wfSajaxSearch",
-                                       array( 'LIMIT' => $limit+1 )
-                               );
-
-               $i = 0;
-               while ( ( $row = $db->fetchObject( $res ) ) && ( ++$i <= $limit ) ) {
-                       $nt = Title::makeTitle( $row->page_namespace, $row->page_title );
-                       $r .= '<li>' . $sk->makeKnownLinkObj( $nt ) . "</li>\n";
-               }
-               if ( $i > $limit ) {
-                       $more = '<i>' .  $sk->makeKnownLink( $wgContLang->specialPage( "Allpages" ),
-                                                                                       wfMsg('moredotdotdot'),
-                                                                                       "namespace=0&from=" . wfUrlEncode ( $term ) ) .
-                               '</i>';
-               }
-       } else if( $term_title && $term_title->getNamespace() == NS_SPECIAL ) {
-               SpecialPage::initList();
-               SpecialPage::initAliasList();
-               $specialPages = array_merge(
-                       array_keys( SpecialPage::$mList ),
-                       array_keys( SpecialPage::$mAliases )
-               );
-
-               foreach( $specialPages as $page ) {
-                       if( $wgContLang->uc( $page ) != $page && strpos( $page, $term_title->getText() ) === 0 ) {
-                               $r .= '<li>' . $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, $page ) ) . '</li>';
+       
+       $results = PrefixSearch::titleSearch( $term, $limit + 1 );
+       foreach( array_slice( $results, 0, $limit ) as $titleText ) {
+               $r .= '<li>' . $sk->makeKnownLink( $titleText ) . "</li>\n";
+       }
+       
+       // Hack to check for specials
+       if( $results ) {
+               $t = Title::newFromText( $results[0] );
+               if( $t && $t->getNamespace() == NS_SPECIAL ) {
+                       $canSearch = false;
+                       if( count( $results ) > $limit ) {
+                               $more = '<i>' .
+                                       $sk->makeKnownLinkObj(
+                                               SpecialPage::getTitleFor( 'Specialpages' ),
+                                               wfMsgHtml( 'moredotdotdot' ) ) .
+                                       '</i>';
+                       }
+               } else {
+                       if( count( $results ) > $limit ) {
+                               $more = '<i>' .
+                                       $sk->makeKnownLinkObj(
+                                               SpecialPage::getTitleFor( "Allpages", $term ),
+                                               wfMsgHtml( 'moredotdotdot' ) ) .
+                                       '</i>';
                        }
                }
-
-               $canSearch = false;
        }
 
        $valid = (bool) $term_title;
index 3d34909..6faa5c6 100644 (file)
@@ -144,6 +144,7 @@ function __autoload($className) {
                'ParserOptions' => 'includes/ParserOptions.php',
                'PatrolLog' => 'includes/PatrolLog.php',
                'Preprocessor' => 'includes/Preprocessor.php',
+               'PrefixSearch' => 'includes/PrefixSearch.php',
                'PPFrame' => 'includes/Preprocessor.php',
                'PPNode' => 'includes/Preprocessor.php',
                'Preprocessor_DOM' => 'includes/Preprocessor_DOM.php',
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php
new file mode 100644 (file)
index 0000000..bddfb9f
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+
+class PrefixSearch {
+       /**
+        * Do a prefix search of titles and return a list of matching page names.
+        * @param string $search
+        * @param int $limit
+        * @return array of strings
+        */
+       public static function titleSearch( $search, $limit ) {
+               $search = trim( $search );
+               if( $search == '' ) {
+                       return array(); // Return empty result
+               }
+
+               $title = Title::newFromText( $search );
+               if( $title && $title->getInterwiki() == '' ) {
+                       $ns = $title->getNamespace();
+                       return self::searchBackend(
+                               $title->getNamespace(), $title->getText(), $limit );
+               }
+
+               // Is this a namespace prefix?
+               $title = Title::newFromText( $search . 'Dummy' );
+               if( $title && $title->getText() == 'Dummy'
+                       && $title->getNamespace() != NS_MAIN
+                       && $title->getInterwiki() == '' ) {
+                       return self::searchBackend(
+                               $title->getNamespace(), '', $limit );
+               }
+
+               return self::searchBackend( 0, $search, $limit );
+       }
+       
+       
+       /**
+        * Do a prefix search of titles and return a list of matching page names.
+        * @param string $search
+        * @param int $limit
+        * @return array of strings
+        */
+       protected static function searchBackend( $ns, $search, $limit ) {
+               if( $ns == NS_MEDIA ) {
+                       $ns = NS_IMAGE;
+               } elseif( $ns == NS_SPECIAL ) {
+                       return self::specialSearch( $search, $limit );
+               }
+               
+               $srchres = array();
+               if( wfRunHooks( 'PrefixSearchBackend', array( $ns, $search, $limit, &$srchres ) ) ) {
+                       return self::defaultSearchBackend( $ns, $search, $limit );
+               }
+               return $srchres;
+       }
+       
+       /**
+        * Prefix search special-case for Special: namespace.
+        */
+       protected static function specialSearch( $search, $limit ) {
+               global $wgContLang;
+               $searchKey = $wgContLang->caseFold( $search );
+               
+               // Unlike SpecialPage itself, we want the canonical forms of both
+               // canonical and alias title forms...
+               SpecialPage::initList();
+               SpecialPage::initAliasList();
+               $keys = array();
+               foreach( array_keys( SpecialPage::$mList ) as $page ) {
+                       $keys[$wgContLang->caseFold( $page )] = $page;
+               }
+               foreach( $wgContLang->getSpecialPageAliases() as $page => $aliases ) {
+                       foreach( $aliases as $alias ) {
+                               $keys[$wgContLang->caseFold( $alias )] = $alias;
+                       }
+               }
+               ksort( $keys );
+               
+               $srchres = array();
+               foreach( $keys as $pageKey => $page ) {
+                       if( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
+                               $srchres[] = Title::makeTitle( NS_SPECIAL, $page )->getPrefixedText();
+                       }
+                       if( count( $srchres ) >= $limit ) {
+                               break;
+                       }
+               }
+               return $srchres;
+       }
+       
+       /**
+        * Unless overridden by PrefixSearchBackend hook...
+        * This is case-sensitive except the first letter (per $wgCapitalLinks)
+        *
+        * @param int $ns Namespace to search in
+        * @param string $search term
+        * @param int $limit max number of items to return
+        * @return array of title strings
+        */
+       protected static function defaultSearchBackend( $ns, $search, $limit ) {
+               global $wgCapitalLinks, $wgContLang;
+               
+               if( $wgCapitalLinks ) {
+                       $search = $wgContLang->ucfirst( $search );
+               }
+               
+               // Prepare nested request
+               $req = new FauxRequest(array (
+                       'action' => 'query',
+                       'list' => 'allpages',
+                       'apnamespace' => $ns,
+                       'aplimit' => $limit,
+                       'apprefix' => $search
+               ));
+
+               // Execute
+               $module = new ApiMain($req);
+               $module->execute();
+
+               // Get resulting data
+               $data = $module->getResultData();
+
+               // Reformat useful data for future printing by JSON engine
+               $srchres = array ();
+               foreach ($data['query']['allpages'] as & $pageinfo) {
+                       // Note: this data will no be printable by the xml engine
+                       // because it does not support lists of unnamed items
+                       $srchres[] = $pageinfo['title'];
+               }
+               
+               return $srchres;
+       }
+
+}
+
+?>
\ No newline at end of file
index bfeb2a5..c3987ec 100644 (file)
@@ -44,37 +44,12 @@ class ApiOpenSearch extends ApiBase {
        public function execute() {
                $params = $this->extractRequestParams();
                $search = $params['search'];
+               $limit = $params['limit'];
 
                // Open search results may be stored for a very long time
                $this->getMain()->setCacheMaxAge(1200);
-
-               $title = Title :: newFromText($search);
-               if(!$title)
-                       return; // Return empty result
-                       
-               // Prepare nested request
-               $req = new FauxRequest(array (
-                       'action' => 'query',
-                       'list' => 'allpages',
-                       'apnamespace' => $title->getNamespace(),
-                       'aplimit' => $params['limit'],
-                       'apprefix' => $title->getDBkey()
-               ));
-
-               // Execute
-               $module = new ApiMain($req);
-               $module->execute();
-
-               // Get resulting data
-               $data = $module->getResultData();
-
-               // Reformat useful data for future printing by JSON engine
-               $srchres = array ();
-               foreach ($data['query']['allpages'] as & $pageinfo) {
-                       // Note: this data will no be printable by the xml engine
-                       // because it does not support lists of unnamed items
-                       $srchres[] = $pageinfo['title'];
-               }
+               
+               $srchres = PrefixSearch::titleSearch( $search, $limit );
 
                // Set top level elements
                $result = $this->getResult();