r64543 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r64542‎ | r64543 | r64544 >
Date:23:19, 2 April 2010
Author:svemir
Status:deferred (Comments)
Tags:
Comment:
Initial commit. There is more cleanup to be done, but it seemed proper
to have the first revision match what is currently hosted at SF
http://sourceforge.net/projects/sphinxsearch/
Modified paths:
  • /trunk/extensions/SphinxSearch (added) (history)
  • /trunk/extensions/SphinxSearch/LICENSE (added) (history)
  • /trunk/extensions/SphinxSearch/README (added) (history)
  • /trunk/extensions/SphinxSearch/SphinxSearch.alias.php (added) (history)
  • /trunk/extensions/SphinxSearch/SphinxSearch.i18n.php (added) (history)
  • /trunk/extensions/SphinxSearch/SphinxSearch.js (added) (history)
  • /trunk/extensions/SphinxSearch/SphinxSearch.php (added) (history)
  • /trunk/extensions/SphinxSearch/SphinxSearch_PersonalDict.php (added) (history)
  • /trunk/extensions/SphinxSearch/SphinxSearch_body.php (added) (history)
  • /trunk/extensions/SphinxSearch/SphinxSearch_spell.php (added) (history)
  • /trunk/extensions/SphinxSearch/sphinx.conf (added) (history)

Diff [purge]

Index: trunk/extensions/SphinxSearch/LICENSE
@@ -0,0 +1,339 @@
 2+ GNU GENERAL PUBLIC LICENSE
 3+ Version 2, June 1991
 4+
 5+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 6+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 7+ Everyone is permitted to copy and distribute verbatim copies
 8+ of this license document, but changing it is not allowed.
 9+
 10+ Preamble
 11+
 12+ The licenses for most software are designed to take away your
 13+freedom to share and change it. By contrast, the GNU General Public
 14+License is intended to guarantee your freedom to share and change free
 15+software--to make sure the software is free for all its users. This
 16+General Public License applies to most of the Free Software
 17+Foundation's software and to any other program whose authors commit to
 18+using it. (Some other Free Software Foundation software is covered by
 19+the GNU Lesser General Public License instead.) You can apply it to
 20+your programs, too.
 21+
 22+ When we speak of free software, we are referring to freedom, not
 23+price. Our General Public Licenses are designed to make sure that you
 24+have the freedom to distribute copies of free software (and charge for
 25+this service if you wish), that you receive source code or can get it
 26+if you want it, that you can change the software or use pieces of it
 27+in new free programs; and that you know you can do these things.
 28+
 29+ To protect your rights, we need to make restrictions that forbid
 30+anyone to deny you these rights or to ask you to surrender the rights.
 31+These restrictions translate to certain responsibilities for you if you
 32+distribute copies of the software, or if you modify it.
 33+
 34+ For example, if you distribute copies of such a program, whether
 35+gratis or for a fee, you must give the recipients all the rights that
 36+you have. You must make sure that they, too, receive or can get the
 37+source code. And you must show them these terms so they know their
 38+rights.
 39+
 40+ We protect your rights with two steps: (1) copyright the software, and
 41+(2) offer you this license which gives you legal permission to copy,
 42+distribute and/or modify the software.
 43+
 44+ Also, for each author's protection and ours, we want to make certain
 45+that everyone understands that there is no warranty for this free
 46+software. If the software is modified by someone else and passed on, we
 47+want its recipients to know that what they have is not the original, so
 48+that any problems introduced by others will not reflect on the original
 49+authors' reputations.
 50+
 51+ Finally, any free program is threatened constantly by software
 52+patents. We wish to avoid the danger that redistributors of a free
 53+program will individually obtain patent licenses, in effect making the
 54+program proprietary. To prevent this, we have made it clear that any
 55+patent must be licensed for everyone's free use or not licensed at all.
 56+
 57+ The precise terms and conditions for copying, distribution and
 58+modification follow.
 59+
 60+ GNU GENERAL PUBLIC LICENSE
 61+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 62+
 63+ 0. This License applies to any program or other work which contains
 64+a notice placed by the copyright holder saying it may be distributed
 65+under the terms of this General Public License. The "Program", below,
 66+refers to any such program or work, and a "work based on the Program"
 67+means either the Program or any derivative work under copyright law:
 68+that is to say, a work containing the Program or a portion of it,
 69+either verbatim or with modifications and/or translated into another
 70+language. (Hereinafter, translation is included without limitation in
 71+the term "modification".) Each licensee is addressed as "you".
 72+
 73+Activities other than copying, distribution and modification are not
 74+covered by this License; they are outside its scope. The act of
 75+running the Program is not restricted, and the output from the Program
 76+is covered only if its contents constitute a work based on the
 77+Program (independent of having been made by running the Program).
 78+Whether that is true depends on what the Program does.
 79+
 80+ 1. You may copy and distribute verbatim copies of the Program's
 81+source code as you receive it, in any medium, provided that you
 82+conspicuously and appropriately publish on each copy an appropriate
 83+copyright notice and disclaimer of warranty; keep intact all the
 84+notices that refer to this License and to the absence of any warranty;
 85+and give any other recipients of the Program a copy of this License
 86+along with the Program.
 87+
 88+You may charge a fee for the physical act of transferring a copy, and
 89+you may at your option offer warranty protection in exchange for a fee.
 90+
 91+ 2. You may modify your copy or copies of the Program or any portion
 92+of it, thus forming a work based on the Program, and copy and
 93+distribute such modifications or work under the terms of Section 1
 94+above, provided that you also meet all of these conditions:
 95+
 96+ a) You must cause the modified files to carry prominent notices
 97+ stating that you changed the files and the date of any change.
 98+
 99+ b) You must cause any work that you distribute or publish, that in
 100+ whole or in part contains or is derived from the Program or any
 101+ part thereof, to be licensed as a whole at no charge to all third
 102+ parties under the terms of this License.
 103+
 104+ c) If the modified program normally reads commands interactively
 105+ when run, you must cause it, when started running for such
 106+ interactive use in the most ordinary way, to print or display an
 107+ announcement including an appropriate copyright notice and a
 108+ notice that there is no warranty (or else, saying that you provide
 109+ a warranty) and that users may redistribute the program under
 110+ these conditions, and telling the user how to view a copy of this
 111+ License. (Exception: if the Program itself is interactive but
 112+ does not normally print such an announcement, your work based on
 113+ the Program is not required to print an announcement.)
 114+
 115+These requirements apply to the modified work as a whole. If
 116+identifiable sections of that work are not derived from the Program,
 117+and can be reasonably considered independent and separate works in
 118+themselves, then this License, and its terms, do not apply to those
 119+sections when you distribute them as separate works. But when you
 120+distribute the same sections as part of a whole which is a work based
 121+on the Program, the distribution of the whole must be on the terms of
 122+this License, whose permissions for other licensees extend to the
 123+entire whole, and thus to each and every part regardless of who wrote it.
 124+
 125+Thus, it is not the intent of this section to claim rights or contest
 126+your rights to work written entirely by you; rather, the intent is to
 127+exercise the right to control the distribution of derivative or
 128+collective works based on the Program.
 129+
 130+In addition, mere aggregation of another work not based on the Program
 131+with the Program (or with a work based on the Program) on a volume of
 132+a storage or distribution medium does not bring the other work under
 133+the scope of this License.
 134+
 135+ 3. You may copy and distribute the Program (or a work based on it,
 136+under Section 2) in object code or executable form under the terms of
 137+Sections 1 and 2 above provided that you also do one of the following:
 138+
 139+ a) Accompany it with the complete corresponding machine-readable
 140+ source code, which must be distributed under the terms of Sections
 141+ 1 and 2 above on a medium customarily used for software interchange; or,
 142+
 143+ b) Accompany it with a written offer, valid for at least three
 144+ years, to give any third party, for a charge no more than your
 145+ cost of physically performing source distribution, a complete
 146+ machine-readable copy of the corresponding source code, to be
 147+ distributed under the terms of Sections 1 and 2 above on a medium
 148+ customarily used for software interchange; or,
 149+
 150+ c) Accompany it with the information you received as to the offer
 151+ to distribute corresponding source code. (This alternative is
 152+ allowed only for noncommercial distribution and only if you
 153+ received the program in object code or executable form with such
 154+ an offer, in accord with Subsection b above.)
 155+
 156+The source code for a work means the preferred form of the work for
 157+making modifications to it. For an executable work, complete source
 158+code means all the source code for all modules it contains, plus any
 159+associated interface definition files, plus the scripts used to
 160+control compilation and installation of the executable. However, as a
 161+special exception, the source code distributed need not include
 162+anything that is normally distributed (in either source or binary
 163+form) with the major components (compiler, kernel, and so on) of the
 164+operating system on which the executable runs, unless that component
 165+itself accompanies the executable.
 166+
 167+If distribution of executable or object code is made by offering
 168+access to copy from a designated place, then offering equivalent
 169+access to copy the source code from the same place counts as
 170+distribution of the source code, even though third parties are not
 171+compelled to copy the source along with the object code.
 172+
 173+ 4. You may not copy, modify, sublicense, or distribute the Program
 174+except as expressly provided under this License. Any attempt
 175+otherwise to copy, modify, sublicense or distribute the Program is
 176+void, and will automatically terminate your rights under this License.
 177+However, parties who have received copies, or rights, from you under
 178+this License will not have their licenses terminated so long as such
 179+parties remain in full compliance.
 180+
 181+ 5. You are not required to accept this License, since you have not
 182+signed it. However, nothing else grants you permission to modify or
 183+distribute the Program or its derivative works. These actions are
 184+prohibited by law if you do not accept this License. Therefore, by
 185+modifying or distributing the Program (or any work based on the
 186+Program), you indicate your acceptance of this License to do so, and
 187+all its terms and conditions for copying, distributing or modifying
 188+the Program or works based on it.
 189+
 190+ 6. Each time you redistribute the Program (or any work based on the
 191+Program), the recipient automatically receives a license from the
 192+original licensor to copy, distribute or modify the Program subject to
 193+these terms and conditions. You may not impose any further
 194+restrictions on the recipients' exercise of the rights granted herein.
 195+You are not responsible for enforcing compliance by third parties to
 196+this License.
 197+
 198+ 7. If, as a consequence of a court judgment or allegation of patent
 199+infringement or for any other reason (not limited to patent issues),
 200+conditions are imposed on you (whether by court order, agreement or
 201+otherwise) that contradict the conditions of this License, they do not
 202+excuse you from the conditions of this License. If you cannot
 203+distribute so as to satisfy simultaneously your obligations under this
 204+License and any other pertinent obligations, then as a consequence you
 205+may not distribute the Program at all. For example, if a patent
 206+license would not permit royalty-free redistribution of the Program by
 207+all those who receive copies directly or indirectly through you, then
 208+the only way you could satisfy both it and this License would be to
 209+refrain entirely from distribution of the Program.
 210+
 211+If any portion of this section is held invalid or unenforceable under
 212+any particular circumstance, the balance of the section is intended to
 213+apply and the section as a whole is intended to apply in other
 214+circumstances.
 215+
 216+It is not the purpose of this section to induce you to infringe any
 217+patents or other property right claims or to contest validity of any
 218+such claims; this section has the sole purpose of protecting the
 219+integrity of the free software distribution system, which is
 220+implemented by public license practices. Many people have made
 221+generous contributions to the wide range of software distributed
 222+through that system in reliance on consistent application of that
 223+system; it is up to the author/donor to decide if he or she is willing
 224+to distribute software through any other system and a licensee cannot
 225+impose that choice.
 226+
 227+This section is intended to make thoroughly clear what is believed to
 228+be a consequence of the rest of this License.
 229+
 230+ 8. If the distribution and/or use of the Program is restricted in
 231+certain countries either by patents or by copyrighted interfaces, the
 232+original copyright holder who places the Program under this License
 233+may add an explicit geographical distribution limitation excluding
 234+those countries, so that distribution is permitted only in or among
 235+countries not thus excluded. In such case, this License incorporates
 236+the limitation as if written in the body of this License.
 237+
 238+ 9. The Free Software Foundation may publish revised and/or new versions
 239+of the General Public License from time to time. Such new versions will
 240+be similar in spirit to the present version, but may differ in detail to
 241+address new problems or concerns.
 242+
 243+Each version is given a distinguishing version number. If the Program
 244+specifies a version number of this License which applies to it and "any
 245+later version", you have the option of following the terms and conditions
 246+either of that version or of any later version published by the Free
 247+Software Foundation. If the Program does not specify a version number of
 248+this License, you may choose any version ever published by the Free Software
 249+Foundation.
 250+
 251+ 10. If you wish to incorporate parts of the Program into other free
 252+programs whose distribution conditions are different, write to the author
 253+to ask for permission. For software which is copyrighted by the Free
 254+Software Foundation, write to the Free Software Foundation; we sometimes
 255+make exceptions for this. Our decision will be guided by the two goals
 256+of preserving the free status of all derivatives of our free software and
 257+of promoting the sharing and reuse of software generally.
 258+
 259+ NO WARRANTY
 260+
 261+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 262+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
 263+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 264+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 265+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 266+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
 267+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
 268+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 269+REPAIR OR CORRECTION.
 270+
 271+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 272+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 273+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 274+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
 275+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
 276+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
 277+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 278+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 279+POSSIBILITY OF SUCH DAMAGES.
 280+
 281+ END OF TERMS AND CONDITIONS
 282+
 283+ How to Apply These Terms to Your New Programs
 284+
 285+ If you develop a new program, and you want it to be of the greatest
 286+possible use to the public, the best way to achieve this is to make it
 287+free software which everyone can redistribute and change under these terms.
 288+
 289+ To do so, attach the following notices to the program. It is safest
 290+to attach them to the start of each source file to most effectively
 291+convey the exclusion of warranty; and each file should have at least
 292+the "copyright" line and a pointer to where the full notice is found.
 293+
 294+ <one line to give the program's name and a brief idea of what it does.>
 295+ Copyright (C) <year> <name of author>
 296+
 297+ This program is free software; you can redistribute it and/or modify
 298+ it under the terms of the GNU General Public License as published by
 299+ the Free Software Foundation; either version 2 of the License, or
 300+ (at your option) any later version.
 301+
 302+ This program is distributed in the hope that it will be useful,
 303+ but WITHOUT ANY WARRANTY; without even the implied warranty of
 304+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 305+ GNU General Public License for more details.
 306+
 307+ You should have received a copy of the GNU General Public License along
 308+ with this program; if not, write to the Free Software Foundation, Inc.,
 309+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 310+
 311+Also add information on how to contact you by electronic and paper mail.
 312+
 313+If the program is interactive, make it output a short notice like this
 314+when it starts in an interactive mode:
 315+
 316+ Gnomovision version 69, Copyright (C) year name of author
 317+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 318+ This is free software, and you are welcome to redistribute it
 319+ under certain conditions; type `show c' for details.
 320+
 321+The hypothetical commands `show w' and `show c' should show the appropriate
 322+parts of the General Public License. Of course, the commands you use may
 323+be called something other than `show w' and `show c'; they could even be
 324+mouse-clicks or menu items--whatever suits your program.
 325+
 326+You should also get your employer (if you work as a programmer) or your
 327+school, if any, to sign a "copyright disclaimer" for the program, if
 328+necessary. Here is a sample; alter the names:
 329+
 330+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
 331+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
 332+
 333+ <signature of Ty Coon>, 1 April 1989
 334+ Ty Coon, President of Vice
 335+
 336+This General Public License does not permit incorporating your program into
 337+proprietary programs. If your program is a subroutine library, you may
 338+consider it more useful to permit linking proprietary applications with the
 339+library. If this is what you want to do, use the GNU Lesser General
 340+Public License instead of this License.
Index: trunk/extensions/SphinxSearch/SphinxSearch_body.php
@@ -0,0 +1,691 @@
 2+<?php
 3+
 4+/**
 5+ * SphinxSearch extension code for MediaWiki
 6+ *
 7+ * http://www.mediawiki.org/wiki/Extension:SphinxSearch
 8+ *
 9+ * Developed by Paul Grinberg and Svemir Brkic
 10+ *
 11+ * Released under GNU General Public License (see http://www.fsf.org/licenses/gpl.html)
 12+ *
 13+ */
 14+
 15+class SphinxSearch extends SpecialPage {
 16+
 17+ var $search_term = ''; // what are we looking for
 18+ var $namespaces = array(); // namespaces to search
 19+ var $categories = array(); // categories to include
 20+ var $exc_categories = array(); // categories to exclude
 21+ var $page = 1; // results page we are on
 22+
 23+ function SphinxSearch() {
 24+ global $wgDisableInternalSearch, $wgSphinxSuggestMode, $wgAutoloadClasses;
 25+
 26+ if ($wgDisableInternalSearch) {
 27+ SpecialPage::SpecialPage( 'Search' );
 28+ } else {
 29+ SpecialPage::SpecialPage( 'SphinxSearch' );
 30+ }
 31+
 32+ if ($wgSphinxSuggestMode) {
 33+ $wgAutoloadClasses['SphinxSearch_spell'] = dirname( __FILE__ ) . '/SphinxSearch_spell.php';
 34+ }
 35+
 36+ if (function_exists('wfLoadExtensionMessages')) {
 37+ wfLoadExtensionMessages( 'SphinxSearch' );
 38+ } else {
 39+ static $messagesLoaded = false;
 40+ global $wgMessageCache;
 41+ if (!$messagesLoaded) {
 42+ $messagesLoaded = true;
 43+ include dirname(__FILE__) . '/SphinxSearch.i18n.php';
 44+ foreach ( $messages as $lang => $langMessages ) {
 45+ $wgMessageCache->addMessages( $langMessages, $lang );
 46+ }
 47+ }
 48+ }
 49+ return true;
 50+ }
 51+
 52+ function searchableNamespaces() {
 53+ $namespaces = SearchEngine::searchableNamespaces();
 54+ wfRunHooks('SphinxSearchFilterSearchableNamespaces', array(&$namespaces));
 55+ return $namespaces;
 56+ }
 57+
 58+ function searchableCategories() {
 59+ global $wgSphinxTopSearchableCategory;
 60+
 61+ if ($wgSphinxTopSearchableCategory) {
 62+ $categories = self::getChildrenCategories($wgSphinxTopSearchableCategory);
 63+ } else {
 64+ $categories = array();
 65+ }
 66+ wfRunHooks('SphinxSearchGetSearchableCategories', array(&$categories));
 67+ return $categories;
 68+ }
 69+
 70+ function getChildrenCategories($parent) {
 71+ global $wgMemc, $wgDBname;
 72+
 73+ $categories = null;
 74+ if (is_object($wgMemc)) {
 75+ $cache_key = $wgDBname . ':sphinx_cats:' . md5($parent);
 76+ $categories = $wgMemc->get( $cache_key );
 77+ }
 78+ if (!is_array($categories)) {
 79+ $categories = array();
 80+ $dbr =& wfGetDB( DB_SLAVE );
 81+ $res = $dbr->select(
 82+ array( 'categorylinks', 'page' ),
 83+ array( 'cl_from', 'cl_sortkey', 'page_title' ),
 84+ array( '1',
 85+ 'cl_from = page_id',
 86+ 'cl_to' => $parent,
 87+ 'page_namespace' => NS_CATEGORY),
 88+ 'epSearchableCategories',
 89+ array( 'ORDER BY' => 'cl_sortkey' )
 90+ );
 91+ while( $x = $dbr->fetchObject ( $res ) ) {
 92+ $categories[$x->cl_from] = $x->cl_sortkey;
 93+ }
 94+ if ($cache_key) {
 95+ $wgMemc->set( $cache_key, $categories, 86400 );
 96+ }
 97+ $dbr->freeResult($res);
 98+ }
 99+ return $categories;
 100+ }
 101+
 102+ function ajaxGetCategoryChildren($parent_id) {
 103+
 104+ $title = Title::newFromID( $parent_id );
 105+
 106+ if ( ! $title ) return false;
 107+
 108+ # Retrieve page_touched for the category
 109+ $dbkey = $title->getDBkey();
 110+ $dbr =& wfGetDB( DB_SLAVE );
 111+ $touched = $dbr->selectField(
 112+ 'page', 'page_touched',
 113+ array(
 114+ 'page_namespace' => NS_CATEGORY,
 115+ 'page_title' => $dbkey,
 116+ ),
 117+ __METHOD__
 118+ );
 119+
 120+ $response = new AjaxResponse();
 121+
 122+ if ( $response->checkLastModified( $touched ) ) {
 123+ return $response;
 124+ }
 125+
 126+ $categories = self::getChildrenCategories( $dbkey );
 127+
 128+ $html = self::getCategoryCheckboxes($categories, $parent_id);
 129+
 130+ $response->addText( $html );
 131+
 132+ return $response;
 133+ }
 134+
 135+ function execute($par) {
 136+ global $wgRequest, $wgOut, $wgUser, $wgSphinxMatchAll, $wgSphinxSearch_index_list;
 137+
 138+ # extract the options from the GET query
 139+ $term = $wgRequest->getText( 'search', $par );
 140+ if ( $term === '' ) {
 141+ $term = $wgRequest->getText( 'sphinxsearch', $par );
 142+ }
 143+ # see if we want to go the title directly
 144+ # this logic is actually reversed (if we are not doing a search,
 145+ # thn try to go to title directly). This is needed because IE has a
 146+ # different behavior when the <ENTER> button is pressed in a form -
 147+ # it does not send the name of the default button!
 148+ if( ! $wgRequest->getVal( 'fulltext' )) {
 149+ $this->goResult( $term );
 150+ }
 151+
 152+ $this->setHeaders();
 153+ $wgOut->setPagetitle(wfMsg('sphinxsearch'));
 154+
 155+ $this->namespaces = array();
 156+ $all_namespaces = self::searchableNamespaces();
 157+ foreach( $all_namespaces as $ns => $name ) {
 158+ if ($wgRequest->getCheck("ns{$ns}")) {
 159+ $this->namespaces[] = $ns;
 160+ }
 161+ }
 162+ if (!count($this->namespaces)) {
 163+ foreach( $all_namespaces as $ns => $name ) {
 164+ if ($wgUser->getOption('searchNs' . $ns)) {
 165+ $this->namespaces[] = $ns;
 166+ }
 167+ }
 168+ }
 169+
 170+ $this->categories = $wgRequest->getIntArray("cat", array());
 171+ $this->exc_categories = $wgRequest->getIntArray("exc", array());
 172+
 173+ $this->page = $wgRequest->getInt('page', 1);
 174+ $wgSphinxMatchAll = $wgRequest->getInt('match_all', intval($wgSphinxMatchAll));
 175+ $match_titles_only = ($wgRequest->getInt('match_titles') == 1);
 176+
 177+ # do the actual search
 178+ $found = 0;
 179+ $cl = $this->prepareSphinxClient( $term, $match_titles_only );
 180+ if ( $cl ) {
 181+ $res = $cl->Query( addcslashes($this->search_term, '/()[]"!'), $wgSphinxSearch_index_list );
 182+ if ( $res === false ) {
 183+ $wgOut->addWikiText( wfMsg('sphinxSearchFailed') . ': ' . $cl->GetLastError() . ".\n" );
 184+ } else {
 185+ $found = $this->wfSphinxDisplayResults( $term, $res, $cl );
 186+ }
 187+ } else {
 188+ $wgOut->addWikiText( wfMsg('sphinxClientFailed') . ".\n" );
 189+ }
 190+
 191+ # prepare for the next search
 192+ if ($found) {
 193+ $this->createNextPageBar( $found, $term );
 194+ }
 195+
 196+ $this->createNewSearchForm( $term );
 197+ }
 198+
 199+ function goResult( $term ) {
 200+ global $wgOut, $wgGoToEdit;
 201+
 202+ # Try to go to page as entered.
 203+ $t = Title::newFromText( $term );
 204+
 205+ # If the string cannot be used to create a title
 206+ if( is_null( $t ) ){
 207+ return;
 208+ }
 209+
 210+ # If there's an exact or very near match, jump right there.
 211+ $t = SearchEngine::getNearMatch( $term );
 212+ wfRunHooks('SphinxSearchGetNearMatch', array(&$term, &$t));
 213+ if( !is_null( $t ) ) {
 214+ $wgOut->redirect( $t->getFullURL() );
 215+ return;
 216+ }
 217+
 218+ # No match, generate an edit URL
 219+ $t = Title::newFromText( $term );
 220+ if( ! is_null( $t ) ) {
 221+ # If the feature is enabled, go straight to the edit page
 222+ if ( $wgGoToEdit ) {
 223+ $wgOut->redirect( $t->getFullURL( 'action=edit' ) );
 224+ return;
 225+ }
 226+ }
 227+
 228+ $wgOut->addWikiText( wfMsg( 'noexactmatch', wfEscapeWikiText( $term ) ) );
 229+ }
 230+
 231+ /**
 232+ * Set the maximum number of results to return
 233+ * and how many to skip before returning the first.
 234+ *
 235+ * @param int $limit
 236+ * @param int $offset
 237+ * @access public
 238+ */
 239+ function setLimitOffset( $limit, $offset = 0 ) {
 240+ global $wgSphinxSearch_matches;
 241+
 242+ $wgSphinxSearch_matches = intval( $limit );
 243+
 244+ if ( $offset > 0 && $limit > 0 ) {
 245+ $this->page = 1 + intval( $offset / $limit );
 246+ }
 247+ }
 248+
 249+ /**
 250+ * Set which namespaces the search should include.
 251+ * Give an array of namespace index numbers.
 252+ *
 253+ * @param array $namespaces
 254+ * @access public
 255+ */
 256+ function setNamespaces( $namespaces ) {
 257+ $this->namespaces = $namespaces;
 258+ }
 259+
 260+ /**
 261+ * Perform a full text search query and return a result set.
 262+ *
 263+ * @param string $term - Raw search term
 264+ * @return SphinxSearchResultSet
 265+ * @access public
 266+ */
 267+ function searchText( $term, $titles_only = false ) {
 268+ global $wgSphinxSearch_index_list;
 269+
 270+ $cl = $this->prepareSphinxClient( $term, $titles_only );
 271+ if ( $cl ) {
 272+ $res = $cl->Query( addcslashes($this->search_term, '/()[]"!'), $wgSphinxSearch_index_list );
 273+ } else {
 274+ $res = false;
 275+ }
 276+
 277+ if ( $res === false ) {
 278+ return null;
 279+ } else {
 280+ return new SphinxSearchResultSet( $term, $res, $cl );
 281+ }
 282+ }
 283+
 284+ /**
 285+ * Perform a title-only search query and return a result set.
 286+ *
 287+ * @param string $term - Raw search term
 288+ * @return SphinxSearchResultSet
 289+ * @access public
 290+ */
 291+ function searchTitle( $term ) {
 292+ return $this->searchText( $term, true );
 293+ }
 294+
 295+ /**
 296+ * Search for "$term"
 297+ * Display the results of the search one page at a time.
 298+ * Returns the number of matches.
 299+ */
 300+ function prepareSphinxClient($term, $match_titles_only = false) {
 301+ global $wgSphinxSearch_sortmode, $wgSphinxSearch_sortby,
 302+ $wgSphinxSearch_host, $wgSphinxSearch_port, $wgSphinxSearch_index_weights,
 303+ $wgSphinxSearch_index, $wgSphinxSearch_matches, $wgSphinxSearch_mode, $wgSphinxSearch_weights,
 304+ $wgSphinxMatchAll, $wgSphinxSearch_maxmatches, $wgSphinxSearch_cutoff;
 305+
 306+ # don't do anything for blank searches
 307+ if (trim($term) === '') {
 308+ return false;
 309+ }
 310+
 311+ wfRunHooks( 'SphinxSearchBeforeResults', array( &$term, &$this->page, &$this->namespaces, &$this->categories, &$this->exc_categories ) );
 312+
 313+ if ( $wgSphinxSearch_mode == SPH_MATCH_EXTENDED && $wgSphinxMatchAll != '1' ) {
 314+ # make OR the default in extended mode
 315+ $this->search_term = preg_replace( '/[\s_\-&]+/', '|', trim($term) );
 316+ } else {
 317+ $this->search_term = $term;
 318+ }
 319+
 320+ $cl = new SphinxClient();
 321+
 322+ # setup the options for searching
 323+ if ( isset($wgSphinxSearch_host) && isset($wgSphinxSearch_port) ) {
 324+ $cl->SetServer($wgSphinxSearch_host, $wgSphinxSearch_port);
 325+ }
 326+ if (count($wgSphinxSearch_weights)) {
 327+ if (is_string(key($wgSphinxSearch_weights))) {
 328+ $cl->SetFieldWeights($wgSphinxSearch_weights);
 329+ } else {
 330+ $cl->SetWeights($wgSphinxSearch_weights);
 331+ }
 332+ }
 333+ if (is_array($wgSphinxSearch_index_weights)) {
 334+ $cl->SetIndexWeights($wgSphinxSearch_index_weights);
 335+ }
 336+ if (isset($wgSphinxSearch_mode)) {
 337+ $cl->SetMatchMode($wgSphinxSearch_mode);
 338+ }
 339+ if (count($this->namespaces)) {
 340+ $cl->SetFilter('page_namespace', $this->namespaces);
 341+ }
 342+
 343+ if (count($this->categories)) {
 344+ $cl->SetFilter('category', $this->categories);
 345+ }
 346+
 347+ if (count($this->exc_categories)) {
 348+ $cl->SetFilter('category', $this->exc_categories, true);
 349+ }
 350+
 351+ if (isset($wgSphinxSearch_groupby) && isset($wgSphinxSearch_groupsort)) {
 352+ $cl->SetGroupBy($wgSphinxSearch_groupby, SPH_GROUPBY_ATTR, $wgSphinxSearch_groupsort);
 353+ }
 354+ $cl->SetSortMode($wgSphinxSearch_sortmode, $wgSphinxSearch_sortby);
 355+ $cl->SetLimits( ($this->page - 1) * $wgSphinxSearch_matches, $wgSphinxSearch_matches, $wgSphinxSearch_maxmatches, $wgSphinxSearch_cutoff );
 356+
 357+ if ( $match_titles_only ) {
 358+ $this->search_term = '@page_title ' . $this->search_term;
 359+ }
 360+
 361+ wfRunHooks('SphinxSearchBeforeQuery', array(&$this->search_term, &$cl));
 362+
 363+ return $cl;
 364+ }
 365+
 366+ function wfSphinxDisplayResults($term, $res, $cl) {
 367+
 368+ global $wgOut, $wgSphinxSuggestMode, $wgSphinxSearch_matches, $wgSphinxSearch_index, $wgSphinxSearch_maxmatches;
 369+
 370+ if ($cl->GetLastWarning()) {
 371+ $wgOut->addWikiText(wfMsg('sphinxSearchWarning') . ': ' . $cl->GetLastWarning() . "\n\n");
 372+ }
 373+ $found = $res['total_found'];
 374+
 375+ if ($wgSphinxSuggestMode) {
 376+ $sc = new SphinxSearch_spell;
 377+ $didyoumean = $sc->spell($this->search_term);
 378+ if ($didyoumean) {
 379+ $wgOut->addhtml(wfMsg('sphinxSearchDidYouMean') .
 380+ " <b><a href='" .
 381+ $this->getActionURL($didyoumean, $this->namespaces) .
 382+ "1'>" . $didyoumean . '</a></b>?');
 383+ }
 384+ }
 385+
 386+ $preamble = sprintf(wfMsg('sphinxSearchPreamble'),
 387+ ( (($this->page - 1) * $wgSphinxSearch_matches) + 1 > $res['total'] ) ? $res['total'] : (($this->page - 1) * $wgSphinxSearch_matches) + 1,
 388+ ($this->page * $wgSphinxSearch_matches > $res['total']) ? $res['total'] : $this->page * $wgSphinxSearch_matches,
 389+ $res['total'],
 390+ $term,
 391+ $res['time']
 392+ );
 393+ $wgOut->addWikiText($preamble . ':');
 394+ if (is_array($res["words"])) {
 395+ $warn = false;
 396+ foreach ($res["words"] as $word => $info) {
 397+ $wgOut->addWikiText( '* ' . sprintf( wfMsg('sphinxSearchStats'), $word, $info['hits'], $info['docs'] ) );
 398+ if ( ( $info['docs'] < $wgSphinxSearch_maxmatches ) && ( $info['docs'] > $res['total'] ) ) {
 399+ $warn = true;
 400+ }
 401+ }
 402+ if ( $warn ) {
 403+ $wgOut->addWikiText( "''" . wfMsg('sphinxSearchStatsInfo') . ".''" );
 404+ } else {
 405+ $wgOut->addWikiText( "\n" );
 406+ }
 407+ }
 408+ $start_time = microtime(true);
 409+
 410+ if (isset($res["matches"]) && is_array($res["matches"])) {
 411+ $wgOut->addWikiText("----");
 412+ $db = wfGetDB( DB_SLAVE );
 413+ $excerpts_opt = array(
 414+ "before_match" => "<span style='color:red'>",
 415+ "after_match" => "</span>",
 416+ "chunk_separator" => " ... ",
 417+ "limit" => 400,
 418+ "around" => 15
 419+ );
 420+
 421+ foreach ($res["matches"] as $doc => $docinfo) {
 422+ $sql = "SELECT old_text FROM ".$db->tableName('text')." WHERE old_id=".$docinfo['attrs']['old_id'];
 423+ $res = $db->query( $sql, __METHOD__ );
 424+ if ($db->numRows($res)) {
 425+ $row = $db->fetchRow($res);
 426+ $title_obj = Title::newFromID( $doc );
 427+ if (is_object($title_obj)) {
 428+ $wiki_title = $title_obj->getPrefixedText();
 429+ $wiki_path = $title_obj->getPrefixedDBkey();
 430+ $wgOut->addWikiText("* <span style='font-size:110%;'>[[:$wiki_path|$wiki_title]]</span>");
 431+ # uncomment this line to see the weights etc. as HTML comments in the source of the page
 432+ #$wgOut->addHTML("<!-- page_id: ".$doc."\ninfo: ".print_r($docinfo, true)." -->");
 433+ $excerpts = $cl->BuildExcerpts(array($row[0]), $wgSphinxSearch_index, $term, $excerpts_opt);
 434+ if ( !is_array($excerpts) ) {
 435+ $excerpts = array( "ERROR: " . $cl->GetLastError() );
 436+ }
 437+ foreach ( $excerpts as $entry ) {
 438+ # add excerpt to output, removing some wiki markup, and breaking apart long strings
 439+ $entry = preg_replace('/([\[\]\{\}\*\#\|\!]+|==+)/',
 440+ ' ',
 441+ strip_tags($entry, '<span><br>')
 442+ );
 443+ # force breaks on very
 444+ /*$entry = join('<br>',
 445+ preg_split('/(\S{60})/',
 446+ $entry,
 447+ -1,
 448+ PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
 449+ )
 450+ );*/
 451+
 452+ $wgOut->addHTML("<div style='margin: 0.2em 1em 1em 1em;'>$entry</div>\n");
 453+ }
 454+ }
 455+ }
 456+ $db->freeResult($res);
 457+ }
 458+ $wgOut->addWikiText(sprintf(wfMsg('sphinxSearchEpilogue'), microtime(true) - $start_time));
 459+ }
 460+
 461+ wfRunHooks('SphinxSearchAfterResults', array($term, $this->page));
 462+
 463+ return $found;
 464+ }
 465+
 466+ function getActionURL($term) {
 467+ global $wgDisableInternalSearch, $wgSphinxMatchAll, $wgRequest;
 468+
 469+ $search_title = ($wgDisableInternalSearch ? 'Search' : 'SphinxSearch');
 470+ $titleObj = SpecialPage::getTitleFor( $search_title );
 471+ $qry = $titleObj->getLocalUrl();
 472+ $searchField = strtolower($search_title);
 473+ $term = urlencode($term);
 474+ $qry .= (strpos($qry, '?') === false ? '?' : '&amp;').$searchField."={$term}&amp;fulltext=".wfMsg('sphinxSearchButton')."&amp;";
 475+ if ($wgSphinxMatchAll == '1') {
 476+ $qry .= "match_all=1&amp;";
 477+ }
 478+ if ($wgRequest->getInt('match_titles')) {
 479+ $qry .= "match_titles=1&amp;";
 480+ }
 481+ foreach ($this->namespaces as $ns) {
 482+ $qry .= "ns{$ns}=1&amp;";
 483+ }
 484+ foreach ($this->categories as $c) {
 485+ $qry .= "cat[]={$c}&amp;";
 486+ }
 487+ foreach ($this->exc_categories as $c) {
 488+ $qry .= "exc[]={$c}&amp;";
 489+ }
 490+ $qry .= "page=";
 491+
 492+ return $qry;
 493+ }
 494+
 495+ function createNextPageBar( $found, $term ) {
 496+ global $wgOut, $wgSphinxSearch_matches;
 497+
 498+ $qry = $this->getActionURL($term);
 499+
 500+ $display_pages = 10;
 501+ $max_page = ceil($found / $wgSphinxSearch_matches);
 502+ $center_page = floor(($this->page + $display_pages) / 2);
 503+ $first_page = $center_page - $display_pages / 2;
 504+ if ($first_page < 1) {
 505+ $first_page = 1;
 506+ }
 507+ $last_page = $first_page + $display_pages - 1;
 508+ if ($last_page > $max_page) {
 509+ $last_page = $max_page;
 510+ }
 511+ if ($first_page != $last_page) {
 512+ $wgOut->addWikiText("----");
 513+ $wgOut->addHTML("<center><table border='0' cellpadding='0' width='1%' cellspacing='0'><tr align='center' valign='top'><td valign='bottom' nowrap='1'>" . wfMsg('sphinxResultPage') . ":</td>");
 514+
 515+ if ($first_page > 1) {
 516+ $prev_page = "<td>&nbsp;<a href='{$qry}";
 517+ $prev_page .= ($this->page - 1) . "'>" . wfMsg('sphinxPreviousPage') . "</a>&nbsp;</td>";
 518+ $wgOut->addHTML($prev_page);
 519+ }
 520+ for ($i = $first_page; $i < $this->page; $i++) {
 521+ $wgOut->addHTML("<td>&nbsp;<a href='{$qry}{$i}'>{$i}</a>&nbsp;</td>");
 522+ }
 523+ $wgOut->addHTML("<td>&nbsp;<b>{$this->page}</b>&nbsp;</td>");
 524+ for ($i = $this->page + 1; $i <= $last_page; $i++) {
 525+ $wgOut->addHTML("<td>&nbsp;<a href='{$qry}{$i}'>{$i}</a>&nbsp;</td>");
 526+ }
 527+ if ($last_page < $max_page) {
 528+ $next_page = "<td>&nbsp;<a href='{$qry}";
 529+ $next_page .= ($this->page + 1) . "'>" . wfMsg('sphinxNextPage') . "</a>&nbsp;</td>";
 530+ $wgOut->addHTML($next_page);
 531+ }
 532+
 533+ $wgOut->addHTML("</tr></table></center>");
 534+ }
 535+ }
 536+
 537+ function createNewSearchForm($term) {
 538+ global $wgOut, $wgDisableInternalSearch, $wgSphinxSearch_mode, $wgSphinxMatchAll, $wgUseExcludes;
 539+ global $wgUseAjax, $wgJsMimeType, $wgScriptPath, $wgSphinxSearchExtPath, $wgSphinxSearchJSPath, $wgRequest;
 540+
 541+ $search_title = ($wgDisableInternalSearch ? 'Search' : 'SphinxSearch');
 542+ $titleObj = SpecialPage::getTitleFor( $search_title );
 543+ $kiAction = $titleObj->getLocalUrl();
 544+ $searchField = strtolower($search_title);
 545+ $wgOut->addHTML("<form action='$kiAction' method='GET'>
 546+ <input type='hidden' name='title' value='".$titleObj->getPrefixedText()."'>
 547+ <input type='text' name='$searchField' maxlength='100' value='$term'>
 548+ <input type='submit' name='fulltext' value='" . wfMsg('sphinxSearchButton') ."'>");
 549+
 550+ $wgOut->addHTML("<div style='margin:0.5em 0 0.5em 0;'>");
 551+ if ($wgSphinxSearch_mode == SPH_MATCH_EXTENDED) {
 552+ $wgOut->addHTML("<input type='radio' name='match_all' value='0' ".($wgSphinxMatchAll ? "" : "checked='checked'")." />".wfMsg('sphinxMatchAny')." <input type='radio' name='match_all' value='1' ".($wgSphinxMatchAll ? "checked='checked'" : "")." />".wfMsg('sphinxMatchAll'));
 553+ }
 554+ $wgOut->addHTML(" &nbsp; <input type='checkbox' name='match_titles' value='1' " . ($wgRequest->getInt('match_titles') ? "checked='checked'" : ""). ">" . wfMsg('sphinxMatchTitles') . "</div>");
 555+ # get user settings for which namespaces to search
 556+ $wgOut->addHTML("<div style='width:30%; border:1px #eee solid; padding:4px; margin-right:1px; float:left;'>");
 557+ $wgOut->addHTML(wfMsg('sphinxSearchInNamespaces') . ':<br/>');
 558+ $all_namespaces = self::searchableNamespaces();
 559+ foreach( $all_namespaces as $ns => $name ) {
 560+ $checked = in_array($ns, $this->namespaces) ? ' checked="checked"' : '';
 561+ $name = str_replace( '_', ' ', $name );
 562+ if('' == $name) {
 563+ $name = wfMsg('blanknamespace');
 564+ }
 565+ $wgOut->addHTML("<label><input type='checkbox' value='1' name='ns$ns'$checked />$name</label><br/>");
 566+ }
 567+
 568+ $all_categories = self::searchableCategories();
 569+ if (is_array($all_categories) && count($all_categories)) {
 570+ $cat_parents = $wgRequest->getIntArray("catp", array());
 571+ $wgOut->addScript(Skin::makeVariablesScript(array(
 572+ 'sphinxLoadingMsg' => wfMsg('sphinxLoading'),
 573+ 'wgSphinxSearchExtPath' => ($wgSphinxSearchJSPath ? $wgSphinxSearchJSPath : $wgSphinxSearchExtPath)
 574+ )));
 575+ $wgOut->addScript(
 576+ "<script type='{$wgJsMimeType}' src='".($wgSphinxSearchJSPath ? $wgSphinxSearchJSPath : $wgSphinxSearchExtPath)."/SphinxSearch.js?2'></script>\n"
 577+ );
 578+ $wgOut->addHTML("</div><div style='width:30%; border:1px #eee solid; padding:4px; margin-right:1px; float:left;'>");
 579+ $wgOut->addHTML(wfMsg('sphinxSearchInCategories') . ':');
 580+ if ($wgUseExcludes) {
 581+ $wgOut->addHTML("<div style='float:right; font-size:80%;'>exclude</div>");
 582+ }
 583+ $wgOut->addHTML('<br/>');
 584+ $wgOut->addHTML($this->getCategoryCheckboxes($all_categories, '', $cat_parents));
 585+ }
 586+ $wgOut->addHTML("</div></form><br clear='both'>");
 587+
 588+ # Put a Sphinx label for this search
 589+ $wgOut->addHTML("<div style='text-align:center'>" . sprintf(wfMsg('sphinxPowered'), "<a href='http://www.sphinxsearch.com/'>Sphinx</a>") . "</div>");
 590+ }
 591+
 592+ function getCategoryCheckboxes($all_categories, $parent_id, $cat_parents = array()) {
 593+ global $wgUseAjax, $wgRequest, $wgUseExcludes;
 594+
 595+ $html = '';
 596+
 597+ foreach( $all_categories as $cat => $name ) {
 598+ $input_attrs = '';
 599+ if ($this && in_array($cat, $this->categories)) {
 600+ $input_attrs .= ' checked="checked"';
 601+ }
 602+ $name = str_replace( '_', ' ', $name );
 603+ if('' == $name) {
 604+ $name = wfMsg('blanknamespace');
 605+ }
 606+ $children = '';
 607+ if (isset($cat_parents['_'.$cat]) && ($input_attrs || $cat_parents['_'.$cat] > 0)) {
 608+ $title = Title::newFromID( $cat );
 609+ $children_cats = self::getChildrenCategories($title->getDBkey());
 610+ if (count($children_cats)) {
 611+ if ($this) {
 612+ $children = $this->getCategoryCheckboxes($children_cats, $cat, $cat_parents);
 613+ } else {
 614+ $children = self::getCategoryCheckboxes($children_cats, $cat, $cat_parents);
 615+ }
 616+ }
 617+ }
 618+ if ($wgUseAjax) {
 619+ $input_attrs .= " onmouseup='sphinxShowCats(this)'";
 620+ }
 621+ $html .= "<label><input type='checkbox' id='{$parent_id}_$cat' value='$cat' name='cat[]'$input_attrs />$name</label>";
 622+ if ($wgUseExcludes) {
 623+ $input_attrs = '';
 624+ if ($this && in_array($cat, $this->exc_categories)) {
 625+ $input_attrs .= ' checked="checked"';
 626+ }
 627+ if ($wgUseAjax) {
 628+ $input_attrs .= " onmouseup='sphinxShowCats(this)'";
 629+ }
 630+ $html .= "<input type='checkbox' id='exc_{$parent_id}_$cat' value='$cat' name='exc[]'$input_attrs style='float:right' />";
 631+ }
 632+ $html .= "<div id='cat{$cat}_children'>$children</div>\n";
 633+ }
 634+ if ($parent_id && $html) {
 635+ $html = "<input type='hidden' name='catp[_$parent_id]' value='".intval($cat_parents['_'.$parent_id])."' /><div style='margin-left:10px; margin-bottom:4px; padding-left:8px; border-left:1px dashed #ccc; border-bottom:1px solid #ccc;'>".$html."</div>";
 636+ }
 637+ return $html;
 638+ }
 639+
 640+}
 641+
 642+/**
 643+ * @ingroup Search
 644+ */
 645+class SphinxSearchResultSet extends SearchResultSet {
 646+ var $mNdx = 0;
 647+
 648+ function SphinxSearchResultSet( $term, $rs, $cl ) {
 649+ global $wgSphinxSearch_index;
 650+
 651+ $this->mResultSet = array();
 652+ if (is_array($rs) && is_array($rs['matches'])) {
 653+ $dbr = wfGetDB( DB_SLAVE );
 654+ foreach ($rs['matches'] as $id => $docinfo) {
 655+ $res = $dbr->select(
 656+ 'page',
 657+ array( 'page_id', 'page_title', 'page_namespace' ),
 658+ array( 'page_id' => $id ),
 659+ __METHOD__,
 660+ array()
 661+ );
 662+ if( $dbr->numRows( $res ) > 0 ) {
 663+ $this->mResultSet[] = $dbr->fetchObject($res);
 664+ }
 665+ }
 666+ }
 667+ $this->mNdx = 0;
 668+ $this->mTerms = $term;
 669+ }
 670+
 671+ function termMatches() {
 672+ return $this->mTerms;
 673+ }
 674+
 675+ function numRows() {
 676+ return count( $this->mResultSet );
 677+ }
 678+
 679+ function next() {
 680+ if( isset($this->mResultSet[$this->mNdx]) ) {
 681+ $row = $this->mResultSet[$this->mNdx];
 682+ ++$this->mNdx;
 683+ return new SearchResult( $row );
 684+ } else {
 685+ return false;
 686+ }
 687+ }
 688+
 689+ function free() {
 690+ unset( $this->mResultSet );
 691+ }
 692+}
Property changes on: trunk/extensions/SphinxSearch/SphinxSearch_body.php
___________________________________________________________________
Added: svn:eol-style
1693 + native
Index: trunk/extensions/SphinxSearch/SphinxSearch.i18n.php
@@ -0,0 +1,30 @@
 2+<?php
 3+
 4+$messages = array();
 5+
 6+/* *** English *** */
 7+$messages['en'] = array(
 8+ 'sphinxsearch' => 'Search Wiki Using Sphinx',
 9+ 'sphinxsearch-desc' => 'Replace MediaWiki search engine with [http://www.sphinxsearch.com/ Sphinx].',
 10+ 'sphinxSearchInNamespaces' => 'Search in namespaces',
 11+ 'sphinxSearchInCategories' => 'Search in categories',
 12+ 'sphinxExcludeCategories' => 'Categories to exclude',
 13+ 'sphinxResultPage' => 'Result Page',
 14+ 'sphinxPreviousPage' => 'Previous',
 15+ 'sphinxNextPage' => 'Next',
 16+ 'sphinxSearchPreamble' => "Displaying %d-%d of %d matches for query '''<nowiki>%s</nowiki>''' retrieved in %0.3f sec with these stats",
 17+ 'sphinxSearchStats' => "'''%s''' found %d times in %d documents",
 18+ 'sphinxSearchStatsInfo' => 'Above numbers may include documents not listed due to search options',
 19+ 'sphinxSearchButton' => 'Search',
 20+ 'sphinxSearchEpilogue' => 'Additional database time was %0.3f sec.',
 21+ 'sphinxSearchDidYouMean' => 'Did you mean',
 22+ 'sphinxMatchAny' => 'match any word',
 23+ 'sphinxMatchAll' => 'match all words',
 24+ 'sphinxMatchTitles' => 'match titles only',
 25+ 'sphinxLoading' => 'Loading...',
 26+ 'sphinxPowered' => 'Powered by %s',
 27+ 'sphinxClientFailed' => 'Could not instantiate SphinxClient',
 28+ 'sphinxSearchFailed' => 'Query failed',
 29+ 'sphinxSearchWarning' => 'WARNING'
 30+);
 31+
Property changes on: trunk/extensions/SphinxSearch/SphinxSearch.i18n.php
___________________________________________________________________
Added: svn:eol-style
132 + native
Index: trunk/extensions/SphinxSearch/SphinxSearch_spell.php
@@ -0,0 +1,147 @@
 2+<?php
 3+
 4+class SphinxSearch_spell {
 5+
 6+ var $string; // what to check
 7+ var $words; // words from $string
 8+ var $suggestion_needed; // is the suggestion needed
 9+ var $suggestion; // the actual suggestion
 10+
 11+ function spell ($string) {
 12+ $this->string = str_replace('"', '', $string);
 13+ $this->words = preg_split('/(\s+|\|)/', $this->string, -1, PREG_SPLIT_NO_EMPTY);
 14+ if (function_exists('pspell_check')) {
 15+ $this->suggestion = $this->builtin_spell();
 16+ } else {
 17+ $this->suggestion = $this->nonnative_spell();
 18+ }
 19+ if ($this->suggestion_needed)
 20+ return $this->suggestion;
 21+ else
 22+ return '';
 23+ }
 24+
 25+ function builtin_spell () {
 26+ global $wgUser, $wgSphinxSearchPersonalDictionary, $wgSphinxSearchPspellDictionaryDir;
 27+
 28+ $ret = '';
 29+ $this->suggestion_needed = false;
 30+ foreach ($this->words as $word) {
 31+ $pspell_config = pspell_config_create(
 32+ $wgUser->getDefaultOption('language'),
 33+ $wgUser->getDefaultOption('variant'));
 34+ if ($wgSphinxSearchPspellDictionaryDir) {
 35+ pspell_config_data_dir($pspell_config, $wgSphinxSearchPspellDictionaryDir);
 36+ pspell_config_dict_dir($pspell_config, $wgSphinxSearchPspellDictionaryDir);
 37+ }
 38+ pspell_config_mode($pspell_config, PSPELL_FAST|PSPELL_RUN_TOGETHER);
 39+ if ($wgSphinxSearchPersonalDictionary)
 40+ pspell_config_personal($pspell_config, $wgSphinxSearchPersonalDictionary);
 41+ $pspell_link = pspell_new_config($pspell_config);
 42+
 43+ if (!$pspell_link)
 44+ return "Error starting pspell personal dictionary\n";
 45+
 46+ if (!pspell_check($pspell_link, $word)) {
 47+ $suggestions = pspell_suggest($pspell_link, $word);
 48+ $guess = $this->bestguess($word, $suggestions);
 49+ if (strtolower($word) == strtolower($guess)) {
 50+ $ret .= "$word ";
 51+ } else {
 52+ $ret .= "$guess ";
 53+ $this->suggestion_needed = true;
 54+ }
 55+ unset($suggestion);
 56+ unset($guess);
 57+ } else {
 58+ $ret .= "$word ";
 59+ }
 60+ }
 61+
 62+ unset($pspell_config);
 63+ unset($pspell_link);
 64+ return trim($ret);
 65+
 66+ }
 67+
 68+ function nonnative_spell () {
 69+ global $wgUser, $wgSphinxSearchPersonalDictionary, $wgSphinxSearchAspellPath;
 70+
 71+ // aspell will only return mis-spelled words, so remember all here
 72+ $word_suggestions = array();
 73+ foreach ($this->words as $word) {
 74+ $word_suggestions[$word] = $word;
 75+ }
 76+
 77+ // prepare the system call with optional dictionary
 78+ $aspellcommand = 'echo ' . escapeshellarg($this->string) .
 79+ ' | ' . escapeshellarg($wgSphinxSearchAspellPath) .
 80+ ' -a --ignore-accents --ignore-case';
 81+ if ($wgUser) {
 82+ $aspellcommand .= ' --lang='.$wgUser->getDefaultOption('language');
 83+ }
 84+ if ($wgSphinxSearchPersonalDictionary) {
 85+ $aspellcommand .= ' --home-dir='.dirname($wgSphinxSearchPersonalDictionary);
 86+ $aspellcommand .= ' -p '.basename($wgSphinxSearchPersonalDictionary);
 87+ }
 88+
 89+ // run aspell
 90+ $shell_return = shell_exec($aspellcommand);
 91+
 92+ // parse return line by line
 93+ $returnarray = explode("\n", $shell_return);
 94+ $this->suggestion_needed = false;
 95+ foreach($returnarray as $key=>$value) {
 96+ // lines with suggestions start with &
 97+ if (substr($value, 0, 1) == "&") {
 98+ $correction = explode(" ",$value);
 99+ $word = $correction[1];
 100+ $suggstart = strpos($value, ":") + 2;
 101+ $suggestions = substr($value, $suggstart);
 102+ $suggestionarray = explode(", ", $suggestions);
 103+ $guess = $this->bestguess($word, $suggestionarray);
 104+
 105+ if (strtolower($word) != strtolower($guess)) {
 106+ $word_suggestions[$word] = $guess;
 107+ $this->suggestion_needed = true;
 108+ }
 109+ }
 110+ }
 111+
 112+ return join(' ', $word_suggestions);
 113+ }
 114+
 115+ /* This function takes a word, and an array of suggested words
 116+ * and figure out which suggestion is closest sounding to
 117+ * the word. Thif is made possible with the use of the
 118+ * levenshtein() function.
 119+ */
 120+ function bestguess($word, $suggestions) {
 121+ $shortest = -1;
 122+
 123+ if (preg_match('/^[^a-zA-Z]*$/', $word))
 124+ return $word;
 125+
 126+ foreach ($suggestions as $suggested) {
 127+ $lev = levenshtein(strtolower($word), strtolower($suggested));
 128+ if ($lev == 0) {
 129+ // closest word is this one (exact match)
 130+ $closest = $word;
 131+ $shortest = 0;
 132+
 133+ // break out of the loop; we've found an exact match
 134+ break;
 135+ }
 136+
 137+ // if this distance is less than the next found shortest
 138+ // distance, OR if a next shortest word has not yet been found
 139+ if ($lev <= $shortest || $shortest < 0) {
 140+ // set the closest match, and shortest distance
 141+ $closest = $suggested;
 142+ $shortest = $lev;
 143+ }
 144+ }
 145+
 146+ return $closest;
 147+ }
 148+}
Property changes on: trunk/extensions/SphinxSearch/SphinxSearch_spell.php
___________________________________________________________________
Added: svn:eol-style
1149 + native
Index: trunk/extensions/SphinxSearch/SphinxSearch.php
@@ -0,0 +1,132 @@
 2+<?php
 3+
 4+# Alert the user that this is not a valid entry point to MediaWiki if they try to access the special pages file directly.
 5+if (!defined('MEDIAWIKI')) {
 6+ echo <<<EOT
 7+To install SphinxSearch extension, put the following line in LocalSettings.php:
 8+require_once( "\$IP/extensions/SphinxSearch/SphinxSearch.php" );
 9+
 10+EOT;
 11+ exit( 1 );
 12+}
 13+
 14+$wgExtensionCredits['specialpage'][] = array(
 15+ 'version' => '0.7.0',
 16+ 'name' => 'SphinxSearch',
 17+ 'author' => 'Svemir Brkic, Paul Grinberg',
 18+ 'email' => 'svemir at thirdblessing dot net, gri6507 at yahoo dot com',
 19+ 'url' => 'http://www.mediawiki.org/wiki/Extension:SphinxSearch',
 20+ 'descriptionmsg' => 'sphinxsearch-desc'
 21+);
 22+
 23+$dir = dirname(__FILE__) . '/';
 24+
 25+$wgAutoloadClasses['SphinxSearch'] = $dir . 'SphinxSearch_body.php';
 26+$wgExtensionMessagesFiles['SphinxSearch'] = $dir . 'SphinxSearch.i18n.php';
 27+$wgExtensionAliasesFiles['SphinxSearch'] = $dir . 'SphinxSearch.alias.php';
 28+
 29+##########################################################
 30+# To completely disable the default search and replace it with SphinxSearch,
 31+# set this BEFORE including SphinxSearch.php in LocalSettings.php
 32+# $wgSearchType = 'SphinxSearch';
 33+##########################################################
 34+
 35+if ($wgSearchType == 'SphinxSearch') {
 36+ $wgDisableInternalSearch = true;
 37+ $wgDisableSearchUpdate = true;
 38+ $wgSpecialPages['Search'] = 'SphinxSearch';
 39+} else {
 40+ $wgSpecialPages['SphinxSearch'] = 'SphinxSearch';
 41+}
 42+
 43+# this assumes you have copied sphinxapi.php from your Sphinx
 44+# installation folder to your SphinxSearch extension folder
 45+if (!class_exists('SphinxClient')) {
 46+ require_once ( $dir . "sphinxapi.php" );
 47+}
 48+
 49+# Host and port on which searchd deamon is tunning
 50+$wgSphinxSearch_host = 'localhost';
 51+$wgSphinxSearch_port = 9312;
 52+
 53+# Main sphinx.conf index to search
 54+$wgSphinxSearch_index = "wiki_main";
 55+
 56+# By default, we search all available indexes
 57+# You can also specify them explicitly, e.g
 58+#$wgSphinxSearch_index_list = "wiki_main,wiki_incremental";
 59+$wgSphinxSearch_index_list = "*";
 60+
 61+# If you have multiple index files, you can specify their weights like this
 62+# See http://www.sphinxsearch.com/docs/current.html#api-func-setindexweights
 63+#$wgSphinxSearch_index_weights = array(
 64+# "wiki_main" => 100,
 65+# "wiki_incremental" => 10
 66+#);
 67+
 68+# Default Sphinx search mode
 69+$wgSphinxSearch_mode = SPH_MATCH_EXTENDED;
 70+
 71+# Default sort mode
 72+$wgSphinxSearch_sortmode = SPH_SORT_RELEVANCE;
 73+$wgSphinxSearch_sortby = '';
 74+
 75+# By default, search will return articles that match any of the words in the search
 76+# To change that to require all words to match by default, set the following to true
 77+$wgSphinxMatchAll = false;
 78+
 79+# Number of matches to display at once
 80+$wgSphinxSearch_matches = 10;
 81+# How many matches searchd will keep in RAM while searching
 82+$wgSphinxSearch_maxmatches = 1000;
 83+# When to stop searching all together (if different from zero)
 84+$wgSphinxSearch_cutoff = 0;
 85+
 86+# Weights of individual indexed columns. This gives page titles extra weight
 87+$wgSphinxSearch_weights = array('old_text'=>1, 'page_title'=>100);
 88+
 89+# If you want to enable hierarchical category search, specify the top category of your hierarchy like this
 90+#$wgSphinxTopSearchableCategory = 'Subject_areas';
 91+
 92+# If you want sub-categories to be fetched as parent categories are checked,
 93+# also set $wgUseAjax to true in your LocalSettings file, so that the following can be used:
 94+#$wgAjaxExportList[] = 'SphinxSearch::ajaxGetCategoryChildren';
 95+
 96+# EXPERIMENTAL: allow excluding selected categories when filtering
 97+#$wgUseExcludes = true;
 98+
 99+# Web-accessible path to the extension's folder
 100+$wgSphinxSearchExtPath = '/extensions/SphinxSearch';
 101+# Web-accessible path to the folder with SphinxSearch.js file (if different from $wgSphinxSearchExtPath)
 102+#$wgSphinxSearchJSPath = '';
 103+
 104+##########################################################
 105+# Use Aspell to suggest possible misspellings. This could be provided via either
 106+# PHP pspell module (http://www.php.net/manual/en/ref.pspell.php) or command line
 107+# insterface to ASpell
 108+
 109+# Should the suggestion mode be enabled?
 110+# Should be set BEFORE SphinxSearch.php is included in LocalSettings
 111+if (!isset($wgSphinxSuggestMode)) {
 112+ $wgSphinxSuggestMode = false;
 113+}
 114+
 115+# Path to personal dictionary (for example personal.en.pws.) Needed only if using a personal dictionary
 116+# Should be set BEFORE SphinxSearch.php is included in LocalSettings
 117+if (!isset($wgSphinxSearchPersonalDictionary)) {
 118+ $wgSphinxSearchPersonalDictionary = "";
 119+}
 120+
 121+# Here is why some vars need to be set before SphinxSearch is included.
 122+# We setup a special page to edit the personal dictionary.
 123+if ($wgSphinxSuggestMode && $wgSphinxSearchPersonalDictionary) {
 124+ $wgAutoloadClasses['SphinxSearchPersonalDict'] = $dir . 'SphinxSearch_PersonalDict.php';
 125+ $wgSpecialPages['SphinxSearchPersonalDict'] = 'SphinxSearchPersonalDict';
 126+}
 127+
 128+# Path to Aspell. Used only if your PHP does not have the pspell extension.
 129+$wgSphinxSearchAspellPath = "/usr/bin/aspell";
 130+
 131+# Path to aspell location and language data files. Do not set if not sure.
 132+#$wgSphinxSearchPspellDictionaryDir = "/usr/lib/aspell";
 133+
Property changes on: trunk/extensions/SphinxSearch/SphinxSearch.php
___________________________________________________________________
Added: svn:eol-style
1134 + native
Index: trunk/extensions/SphinxSearch/SphinxSearch.js
@@ -0,0 +1,54 @@
 2+function sphinxShowCats(input) {
 3+
 4+ var parent_id = input.id.substring(0, input.id.indexOf('_'));
 5+ var delta = (input.checked ? -1 : 1);
 6+ var parent_fld = input.form['catp[_' + parent_id + ']'];
 7+ var seen_parents = [];
 8+ seen_parents[parent_id] = true;
 9+ var cnt = input.form.elements.length;
 10+
 11+ while (parent_fld) {
 12+ var parent_cnt = parseInt(parent_fld.value);
 13+ if (isNaN(parent_cnt)) {
 14+ parent_cnt = 0;
 15+ }
 16+ parent_fld.value = parent_cnt + delta;
 17+ parent_fld = null;
 18+ for (var i = 0; i < cnt; i++) {
 19+ var el = input.form.elements[i];
 20+ if (el.name.indexOf('catp') == 0) {
 21+ var grandparent_id = el.name.replace(/[^\d]+/g, '');
 22+ if (seen_parents[grandparent_id]) {
 23+ continue;
 24+ }
 25+ if (document.getElementById(grandparent_id + '_' + parent_id)) {
 26+ parent_fld = input.form['catp[_' + grandparent_id + ']'];
 27+ seen_parents[grandparent_id] = true;
 28+ }
 29+ }
 30+ }
 31+ }
 32+
 33+ if (input.checked) {
 34+ return;
 35+ }
 36+
 37+ var div = document.getElementById('cat' + input.value + '_children');
 38+ if (div.innerHTML.length > 10) {
 39+ return;
 40+ }
 41+
 42+ injectSpinner( input, 'sphinxsearch' );
 43+
 44+ function f( request ) {
 45+ var result = request.responseText;
 46+
 47+ if (request.status != 200) {
 48+ result = "<div class='error'> " + request.status + " " + request.statusText + ": " + result + "</div>";
 49+ }
 50+ removeSpinner( 'sphinxsearch' );
 51+ div.innerHTML = result;
 52+ }
 53+
 54+ sajax_do_call( "SphinxSearch::ajaxGetCategoryChildren", [input.value] , f );
 55+}
\ No newline at end of file
Property changes on: trunk/extensions/SphinxSearch/SphinxSearch.js
___________________________________________________________________
Added: svn:eol-style
156 + native
Index: trunk/extensions/SphinxSearch/SphinxSearch_PersonalDict.php
@@ -0,0 +1,238 @@
 2+<?php
 3+
 4+/**
 5+ * SphinxSearch extension code for MediaWiki
 6+ *
 7+ * http://www.mediawiki.org/wiki/Extension:SphinxSearch
 8+ *
 9+ * Developed by Paul Grinberg and Svemir Brkic
 10+ *
 11+ * Released under GNU General Public License (see http://www.fsf.org/licenses/gpl.html)
 12+ *
 13+ */
 14+
 15+class SphinxSearchPersonalDict extends SpecialPage {
 16+
 17+ function SphinxSearchPersonalDict() {
 18+ SpecialPage::SpecialPage("SphinxSearchPersonalDict", 'delete');
 19+ self::loadMessages();
 20+ return true;
 21+ }
 22+
 23+ function loadMessages() {
 24+ static $messagesLoaded = false;
 25+ global $wgMessageCache;
 26+ if ($messagesLoaded) {
 27+ return;
 28+ }
 29+ $messagesLoaded = true;
 30+
 31+ $allMessages = array(
 32+ 'en' => array(
 33+ 'sphinxsearchpersonaldict' => 'Wiki-specific Sphinx search spellcheck dictionary',
 34+ 'sphinxsearchindictionary' => 'Already in personal dictionary',
 35+ 'sphinxsearchtobeadded' => 'To be added to personal dictionary',
 36+ 'sphinxsearchnotadded' => "Word '''%s''' was not added to dictionary because it contained non alphabetic characters",
 37+ 'sphinxsearchcantpersonaldict' => 'You are not allowed to modify the {{SITENAME}} specific dictionary',
 38+ )
 39+ );
 40+
 41+ foreach ( $allMessages as $lang => $langMessages ) {
 42+ $wgMessageCache->addMessages( $langMessages, $lang );
 43+ }
 44+ return true;
 45+ }
 46+
 47+ function execute($par) {
 48+ global $wgRequest, $wgOut, $wgUser;
 49+
 50+ $this->setHeaders();
 51+ $wgOut->setPagetitle(wfMsg('sphinxsearchpersonaldict'));
 52+
 53+ if (!$wgUser->isAllowed("delete")) {
 54+ $wgOut->addWikiText(wfMsg('sphinxsearchcantpersonaldict'));
 55+ $wgOut->addWikiText('----');
 56+ }
 57+
 58+ $toberemoved = $wgRequest->getArray('indictionary', array());
 59+ $tobeadded = $wgRequest->getVal('tobeadded','');
 60+ $tobeadded = preg_split('/\s/', trim($tobeadded), -1, PREG_SPLIT_NO_EMPTY);
 61+
 62+ $this->deleteFromPersonalDictionary($toberemoved);
 63+ $this->addToPersonalDictionary($tobeadded);
 64+
 65+ $this->CreateForm($wgUser->isAllowed("delete"));
 66+ }
 67+
 68+ function CreateForm($allowed_to_add) {
 69+ global $wgOut;
 70+ global $wgSphinxSearchPersonalDictionary;
 71+
 72+ $wgOut->addHTML("<form method=post>");
 73+ $wgOut->addHTML("<div style=\"border: thin solid #000000; width:90%;\"><table cellpadding=\"15\" width=\"100%\" cellspacing=\"0\" border=\"0\">");
 74+ $wgOut->addHTML("<tr><td valign=top>");
 75+ $wgOut->addWikiText("<center>'''" . wfMsg('sphinxsearchindictionary') . "'''</center><p>");
 76+ $wgOut->addHTML('<select name="indictionary[]" size="15" multiple="multiple">');
 77+
 78+ if (file_exists($wgSphinxSearchPersonalDictionary)) {
 79+ $this->readPersonalDictionary($langauge, $numwords, $words);
 80+ sort($words);
 81+
 82+ if (sizeof($words)>0) {
 83+ foreach ($words as $w)
 84+ $wgOut->addHTML("<option value='$w'>$w</option>");
 85+ } else {
 86+ $wgOut->addHTML("<option disabled value=''>Dictionary empty</option>");
 87+ }
 88+ } else {
 89+ $wgOut->addHTML("<option disabled value=''>Dictionary not found</option>");
 90+ }
 91+
 92+ $wgOut->addHTML('</select></td><td valign=top>');
 93+ if ($allowed_to_add) {
 94+ $wgOut->addWikiText("<center>'''" . wfMsg('sphinxsearchtobeadded') . "'''</center><p>");
 95+ $wgOut->addHTML("<textarea name=\"tobeadded\" cols=\"30\" rows=\"15\"></textarea>");
 96+ $wgOut->addHTML('</td></tr><tr><td colspan=2>');
 97+ $wgOut->addHTML("<center><input type=\"submit\" value=\"Execute\" /></center>");
 98+ }
 99+ $wgOut->addHTML("</td></tr></table></div></form>");
 100+ }
 101+
 102+ function addToPersonalDictionary($list) {
 103+ if (function_exists('pspell_config_create')) {
 104+ $this->builtin_addword($list);
 105+ } else {
 106+ $this->nonnative_addword($list);
 107+ }
 108+ }
 109+
 110+ function getSearchLanguage() {
 111+ global $wgUser, $wgLanguageCode;
 112+
 113+ // Try to read the default language from $wgUser:
 114+ $language = trim($wgUser->getDefaultOption('language'));
 115+
 116+ // Use global variable: $wgLanguageCode (from LocalSettings.php) as fallback:
 117+ if (empty($language)) { $language = trim($wgLanguageCode); }
 118+
 119+ // If we still don't have a valid language yet, assume English:
 120+ if (empty($language)) { $language = 'en'; }
 121+
 122+ return $language;
 123+ }
 124+
 125+ function builtin_addword($list) {
 126+ global $wgUser, $wgOut;
 127+ global $wgSphinxSearchPersonalDictionary;
 128+ global $wgSphinxSearchPspellDictionaryDir;
 129+
 130+ $language = $this->getSearchLanguage();
 131+
 132+ $pspell_config = pspell_config_create(
 133+ $language,
 134+ $wgUser->getDefaultOption('variant'));
 135+ if ($wgSphinxSearchPspellDictionaryDir) {
 136+ pspell_config_data_dir($pspell_config, $wgSphinxSearchPspellDictionaryDir);
 137+ pspell_config_dict_dir($pspell_config, $wgSphinxSearchPspellDictionaryDir);
 138+ }
 139+ pspell_config_mode($pspell_config, PSPELL_FAST|PSPELL_RUN_TOGETHER);
 140+ if ($wgSphinxSearchPersonalDictionary)
 141+ pspell_config_personal($pspell_config, $wgSphinxSearchPersonalDictionary);
 142+ $pspell_link = pspell_new_config($pspell_config);
 143+
 144+ $write_needed = false;
 145+ foreach ($list as $word) {
 146+ if ($word == '')
 147+ continue;
 148+ if (preg_match('/[^a-zA-Z]/', $word)) {
 149+ $wgOut->addWikiText(sprintf(wfMsg('sphinxsearchnotadded'), $word));
 150+ continue;
 151+ }
 152+ pspell_add_to_personal($pspell_link, $word);
 153+ $write_needed = true;
 154+ }
 155+
 156+ if ($write_needed) {
 157+ pspell_save_wordlist($pspell_link);
 158+ }
 159+ }
 160+
 161+ function nonnative_addword($list) {
 162+ global $wgUser;
 163+ global $wgSphinxSearchPersonalDictionary;
 164+
 165+ if (!file_exists($wgSphinxSearchPersonalDictionary)) {
 166+ // create the personal dictionary file if it does not already exist
 167+ $language = $this->getSearchLanguage();
 168+ $numwords = 0;
 169+ $words = array();
 170+ } else {
 171+ $this->readPersonalDictionary($language, $numwords, $words);
 172+ }
 173+
 174+ $write_needed = false;
 175+ foreach ($list as $word) {
 176+ if (!in_array($word, $words)) {
 177+ $numwords++;
 178+ array_push($words, $word);
 179+ $write_needed = true;
 180+ }
 181+ }
 182+
 183+ if ($write_needed)
 184+ $this->writePersonalDictionary($language, $numwords, $words);
 185+ }
 186+
 187+ function writePersonalDictionary($language, $numwords, $words) {
 188+ global $wgSphinxSearchPersonalDictionary;
 189+
 190+ $handle = fopen($wgSphinxSearchPersonalDictionary, "wt");
 191+ if ($handle) {
 192+ fwrite($handle, "personal_ws-1.1 $language $numwords\n");
 193+ foreach ($words as $w) {
 194+ fwrite($handle, "$w\n");
 195+ }
 196+ fclose($handle);
 197+ }
 198+ }
 199+
 200+ function readPersonalDictionary(&$language, &$numwords, &$words) {
 201+ global $wgSphinxSearchPersonalDictionary;
 202+
 203+ $words = array();
 204+ $lines = explode("\n", file_get_contents($wgSphinxSearchPersonalDictionary));
 205+ foreach ($lines as $line) {
 206+ trim($line);
 207+ if (preg_match('/\s(\w+)\s(\d+)/', $line, $matches)) {
 208+ $language = $matches[1];
 209+ $numwords = $matches[2];
 210+ } else
 211+ if ($line)
 212+ array_push($words, $line);
 213+ }
 214+
 215+ // Make sure that we have a valid value for language if it wasn't in the .pws file:
 216+ if (empty($language)) { $language = $this->getSearchLanguage(); }
 217+ }
 218+
 219+ function deleteFromPersonalDictionary($list) {
 220+ // there is no built in way to delete from the personal dictionary.
 221+
 222+ $this->readPersonalDictionary($language, $numwords, $words);
 223+
 224+ $write_needed = false;
 225+ foreach ($list as $w) {
 226+ if ($w == '')
 227+ continue;
 228+ if (in_array($w, $words)) {
 229+ $index = array_keys($words, $w);
 230+ unset($words[$index[0]]);
 231+ $numwords--;
 232+ $write_needed = true;
 233+ }
 234+ }
 235+
 236+ if ($write_needed)
 237+ $this->writePersonalDictionary($language, $numwords, $words);
 238+ }
 239+}
Property changes on: trunk/extensions/SphinxSearch/SphinxSearch_PersonalDict.php
___________________________________________________________________
Added: svn:eol-style
1240 + native
Index: trunk/extensions/SphinxSearch/README
@@ -0,0 +1,30 @@
 2+STATUS:
 3+======
 4+
 5+This software is beta in the sense that there are MediaWiki configurations
 6+it will not work with - for example if you compress all page revisions.
 7+
 8+INSTALLATION:
 9+============
 10+
 11+See: http://www.mediawiki.org/wiki/Extension:SphinxSearch
 12+
 13+UPGRADES:
 14+========
 15+
 16+When upgrading your sphinx binaries, make sure to copy the latest version
 17+of sphinxapi.php to the extenstion folder.
 18+
 19+CONTACT:
 20+=======
 21+
 22+* Svemir Brkic
 23+* svemir@deveblog.com
 24+* svemir in #mediawiki on irc.freenode.net
 25+* http://www.mediawiki.org/wiki/Extension_talk:SphinxSearch
 26+
 27+CREDITS:
 28+=======
 29+
 30+Initial development by Paul Grinberg, based on the idea from Hank at
 31+http://www.ralree.info/2007/9/15/fulltext-indexing-wikipedia-with-sphinx/
Index: trunk/extensions/SphinxSearch/SphinxSearch.alias.php
@@ -0,0 +1,15 @@
 2+<?php
 3+
 4+/**
 5+ * Aliases for special pages
 6+ *
 7+ * @file
 8+ * @ingroup Extensions
 9+ */
 10+
 11+$aliases = array();
 12+
 13+/** English */
 14+$aliases['en'] = array(
 15+ 'SphinxSearch' => array( 'SphinxSearch' ),
 16+);
Property changes on: trunk/extensions/SphinxSearch/SphinxSearch.alias.php
___________________________________________________________________
Added: svn:eol-style
117 + native
Index: trunk/extensions/SphinxSearch/sphinx.conf
@@ -0,0 +1,143 @@
 2+#
 3+# Sphinx configuration for MediaWiki
 4+#
 5+# Based on examples by Paul Grinberg at http://www.mediawiki.org/wiki/Extension:SphinxSearch
 6+# and Hank at http://www.ralree.info/2007/9/15/fulltext-indexing-wikipedia-with-sphinx
 7+#
 8+# Modified by Svemir Brkic for http://www.newworldencyclopedia.org/
 9+#
 10+# Released under GNU General Public License (see http://www.fsf.org/licenses/gpl.html)
 11+#
 12+# Latest version available at http://www.mediawiki.org/wiki/Extension:SphinxSearch
 13+
 14+# data source definition for the main index
 15+source src_wiki_main
 16+{
 17+ # data source
 18+ type = mysql
 19+ sql_host = localhost
 20+ sql_user = #replace with your db username
 21+ sql_pass = #replace with your db password
 22+ sql_db = #replace with your db name
 23+ # these two are optional
 24+ #sql_port = 3306
 25+ #sql_sock = /var/lib/mysql/mysql.sock
 26+
 27+ # pre-query, executed before the main fetch query
 28+ sql_query_pre = SET NAMES utf8
 29+
 30+ # main document fetch query - change the table names if you are using a prefix
 31+ sql_query = SELECT page_id, page_title, page_namespace, old_id, old_text FROM page, revision, text WHERE rev_id=page_latest AND old_id=rev_text_id
 32+
 33+ # attribute columns
 34+ sql_attr_uint = page_namespace
 35+ sql_attr_uint = old_id
 36+
 37+ # uncomment next line to collect all category ids for a category filter
 38+ #sql_attr_multi = uint category from query; SELECT cl_from, page_id AS category FROM categorylinks, page WHERE page_title=cl_to AND page_namespace=14
 39+
 40+ # optional - used by command-line search utility to display document information
 41+ sql_query_info = SELECT page_title, page_namespace FROM page WHERE page_id=$id
 42+}
 43+
 44+# data source definition for the incremental index
 45+source src_wiki_incremental : src_wiki_main
 46+{
 47+ # adjust this query based on the time you run the full index
 48+ # in this case, full index runs at 3 AM (server time) which translates to 7 AM UTC
 49+ sql_query = SELECT page_id, page_title, page_namespace, old_id, old_text FROM page, revision, text WHERE rev_id=page_latest AND old_id=rev_text_id AND page_touched>=DATE_FORMAT(CURDATE(), '%Y%m%d070000')
 50+
 51+ # all other parameters are copied from the parent source,
 52+}
 53+
 54+# main index definition
 55+index wiki_main
 56+{
 57+ # which document source to index
 58+ source = src_wiki_main
 59+
 60+ # this is path and index file name without extension
 61+ # you may need to change this path or create this folder
 62+ path = /var/data/sphinx/wiki_main
 63+
 64+ # docinfo (ie. per-document attribute values) storage strategy
 65+ docinfo = extern
 66+
 67+ # morphology
 68+ morphology = stem_en
 69+
 70+ # stopwords file
 71+ #stopwords = /var/data/sphinx/stopwords.txt
 72+
 73+ # minimum word length
 74+ min_word_len = 1
 75+
 76+ # uncomment next 2 lines to allow wildcard (*) searches
 77+ #min_infix_len = 1
 78+ #enable_star = 1
 79+
 80+ # charset encoding type
 81+ charset_type = utf-8
 82+
 83+ # charset definition and case folding rules "table"
 84+ charset_table = 0..9, A..Z->a..z, a..z, \
 85+ U+C0->a, U+C1->a, U+C2->a, U+C3->a, U+C4->a, U+C5->a, U+C6->a, \
 86+ U+C7->c,U+E7->c, U+C8->e, U+C9->e, U+CA->e, U+CB->e, U+CC->i, \
 87+ U+CD->i, U+CE->i, U+CF->i, U+D0->d, U+D1->n, U+D2->o, U+D3->o, \
 88+ U+D4->o, U+D5->o, U+D6->o, U+D8->o, U+D9->u, U+DA->u, U+DB->u, \
 89+ U+DC->u, U+DD->y, U+DE->t, U+DF->s, \
 90+ U+E0->a, U+E1->a, U+E2->a, U+E3->a, U+E4->a, U+E5->a, U+E6->a, \
 91+ U+E7->c,U+E7->c, U+E8->e, U+E9->e, U+EA->e, U+EB->e, U+EC->i, \
 92+ U+ED->i, U+EE->i, U+EF->i, U+F0->d, U+F1->n, U+F2->o, U+F3->o, \
 93+ U+F4->o, U+F5->o, U+F6->o, U+F8->o, U+F9->u, U+FA->u, U+FB->u, \
 94+ U+FC->u, U+FD->y, U+FE->t, U+FF->s,
 95+
 96+}
 97+
 98+# incremental index definition
 99+index wiki_incremental : wiki_main
 100+{
 101+ path = /var/data/sphinx/wiki_incremental
 102+ source = src_wiki_incremental
 103+}
 104+
 105+
 106+# indexer settings
 107+indexer
 108+{
 109+ # memory limit (default is 32M)
 110+ mem_limit = 64M
 111+}
 112+
 113+# searchd settings
 114+searchd
 115+{
 116+ # IP address on which search daemon will bind and accept
 117+ # optional, default is to listen on all addresses,
 118+ # ie. listen = 0.0.0.0
 119+ listen = 127.0.0.1
 120+
 121+ # port on which search daemon will listen
 122+ port = 9312
 123+
 124+ # searchd run info is logged here - create or change the folder
 125+ log = /var/log/sphinx/searchd.log
 126+
 127+ # all the search queries are logged here
 128+ query_log = /var/log/sphinx/query.log
 129+
 130+ # client read timeout, seconds
 131+ read_timeout = 5
 132+
 133+ # maximum amount of children to fork
 134+ max_children = 30
 135+
 136+ # a file which will contain searchd process ID
 137+ pid_file = /var/log/sphinx/searchd.pid
 138+
 139+ # maximum amount of matches this daemon would ever retrieve
 140+ # from each index and serve to client
 141+ max_matches = 1000
 142+}
 143+
 144+# --eof--

Comments

#Comment by Siebrand (talk | contribs)   12:34, 8 April 2010

Parameters in message key 'sphinxSearchPreamble' should be numbered (%1$d, %2$d, %3%d) to allow proper L10n in case a language would change the parameter order. Ideally you wouldn't use sprintf, but wgMsg( key, para1, para2, ...).

#Comment by Svemir Brkic (talk | contribs)   12:53, 8 April 2010

Thanks. Will do.

#Comment by Siebrand (talk | contribs)   22:54, 13 January 2012

Ping?

#Comment by Siebrand (talk | contribs)   22:55, 13 January 2012

Argh. Please ignore. I looked at an old version...

#Comment by Siebrand (talk | contribs)   12:28, 9 April 2010

Message 'sphinxSearchDidYouMean' should have an additional parameter $1 in which the URL will be put. Same reason as above.

#Comment by Siebrand (talk | contribs)   12:35, 9 April 2010

Where you are using 'sphinxSearchWarning' you are glueing msg+": "+somewarning. Please make that message "Warning: $1". Best not use all caps. We try not to shout at users :)

#Comment by Svemir Brkic (talk | contribs)   21:31, 10 April 2010

All of the above has been implemented in #64909

#Comment by Siebrand (talk | contribs)   21:57, 10 April 2010

Thanks.

Status & tagging log