Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

sphinxapi.php

Zuletzt modifiziert: 09.10.2024, 12:51 - Dateigröße: 46.40 KiB


 0001  <?php
 0002   
 0003  //
 0004  // $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
 0005  //
 0006   
 0007  //
 0008  // Copyright (c) 2001-2012, Andrew Aksyonoff
 0009  // Copyright (c) 2008-2012, Sphinx Technologies Inc
 0010  // All rights reserved
 0011  //
 0012  // This program is free software; you can redistribute it and/or modify
 0013  // it under the terms of the GNU General Public License. You should have
 0014  // received a copy of the GPL license along with this program; if you
 0015  // did not, you can find it at http://www.gnu.org/
 0016  //
 0017   
 0018  /////////////////////////////////////////////////////////////////////////////
 0019  // PHP version of Sphinx searchd client (PHP API)
 0020  /////////////////////////////////////////////////////////////////////////////
 0021   
 0022  /// known searchd commands
 0023  define ( "SEARCHD_COMMAND_SEARCH",        0 );
 0024  define ( "SEARCHD_COMMAND_EXCERPT",        1 );
 0025  define ( "SEARCHD_COMMAND_UPDATE",        2 );
 0026  define ( "SEARCHD_COMMAND_KEYWORDS",    3 );
 0027  define ( "SEARCHD_COMMAND_PERSIST",        4 );
 0028  define ( "SEARCHD_COMMAND_STATUS",        5 );
 0029  define ( "SEARCHD_COMMAND_FLUSHATTRS",    7 );
 0030   
 0031  /// current client-side command implementation versions
 0032  define ( "VER_COMMAND_SEARCH",        0x119 );
 0033  define ( "VER_COMMAND_EXCERPT",        0x104 );
 0034  define ( "VER_COMMAND_UPDATE",        0x102 );
 0035  define ( "VER_COMMAND_KEYWORDS",    0x100 );
 0036  define ( "VER_COMMAND_STATUS",        0x100 );
 0037  define ( "VER_COMMAND_QUERY",        0x100 );
 0038  define ( "VER_COMMAND_FLUSHATTRS",    0x100 );
 0039   
 0040  /// known searchd status codes
 0041  define ( "SEARCHD_OK",                0 );
 0042  define ( "SEARCHD_ERROR",            1 );
 0043  define ( "SEARCHD_RETRY",            2 );
 0044  define ( "SEARCHD_WARNING",            3 );
 0045   
 0046  /// known match modes
 0047  define ( "SPH_MATCH_ALL",            0 );
 0048  define ( "SPH_MATCH_ANY",            1 );
 0049  define ( "SPH_MATCH_PHRASE",        2 );
 0050  define ( "SPH_MATCH_BOOLEAN",        3 );
 0051  define ( "SPH_MATCH_EXTENDED",        4 );
 0052  define ( "SPH_MATCH_FULLSCAN",        5 );
 0053  define ( "SPH_MATCH_EXTENDED2",        6 );    // extended engine V2 (TEMPORARY, WILL BE REMOVED)
 0054   
 0055  /// known ranking modes (ext2 only)
 0056  define ( "SPH_RANK_PROXIMITY_BM25",    0 );    ///< default mode, phrase proximity major factor and BM25 minor one
 0057  define ( "SPH_RANK_BM25",            1 );    ///< statistical mode, BM25 ranking only (faster but worse quality)
 0058  define ( "SPH_RANK_NONE",            2 );    ///< no ranking, all matches get a weight of 1
 0059  define ( "SPH_RANK_WORDCOUNT",        3 );    ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
 0060  define ( "SPH_RANK_PROXIMITY",        4 );
 0061  define ( "SPH_RANK_MATCHANY",        5 );
 0062  define ( "SPH_RANK_FIELDMASK",        6 );
 0063  define ( "SPH_RANK_SPH04",            7 );
 0064  define ( "SPH_RANK_EXPR",            8 );
 0065  define ( "SPH_RANK_TOTAL",            9 );
 0066   
 0067  /// known sort modes
 0068  define ( "SPH_SORT_RELEVANCE",        0 );
 0069  define ( "SPH_SORT_ATTR_DESC",        1 );
 0070  define ( "SPH_SORT_ATTR_ASC",        2 );
 0071  define ( "SPH_SORT_TIME_SEGMENTS",     3 );
 0072  define ( "SPH_SORT_EXTENDED",         4 );
 0073  define ( "SPH_SORT_EXPR",             5 );
 0074   
 0075  /// known filter types
 0076  define ( "SPH_FILTER_VALUES",        0 );
 0077  define ( "SPH_FILTER_RANGE",        1 );
 0078  define ( "SPH_FILTER_FLOATRANGE",    2 );
 0079   
 0080  /// known attribute types
 0081  define ( "SPH_ATTR_INTEGER",        1 );
 0082  define ( "SPH_ATTR_TIMESTAMP",        2 );
 0083  define ( "SPH_ATTR_ORDINAL",        3 );
 0084  define ( "SPH_ATTR_BOOL",            4 );
 0085  define ( "SPH_ATTR_FLOAT",            5 );
 0086  define ( "SPH_ATTR_BIGINT",            6 );
 0087  define ( "SPH_ATTR_STRING",            7 );
 0088  define ( "SPH_ATTR_MULTI",            0x40000001 );
 0089  define ( "SPH_ATTR_MULTI64",            0x40000002 );
 0090   
 0091  /// known grouping functions
 0092  define ( "SPH_GROUPBY_DAY",            0 );
 0093  define ( "SPH_GROUPBY_WEEK",        1 );
 0094  define ( "SPH_GROUPBY_MONTH",        2 );
 0095  define ( "SPH_GROUPBY_YEAR",        3 );
 0096  define ( "SPH_GROUPBY_ATTR",        4 );
 0097  define ( "SPH_GROUPBY_ATTRPAIR",    5 );
 0098   
 0099  // important properties of PHP's integers:
 0100  //  - always signed (one bit short of PHP_INT_SIZE)
 0101  //  - conversion from string to int is saturated
 0102  //  - float is double
 0103  //  - div converts arguments to floats
 0104  //  - mod converts arguments to ints
 0105   
 0106  // the packing code below works as follows:
 0107  //  - when we got an int, just pack it
 0108  //    if performance is a problem, this is the branch users should aim for
 0109  //
 0110  //  - otherwise, we got a number in string form
 0111  //    this might be due to different reasons, but we assume that this is
 0112  //    because it didn't fit into PHP int
 0113  //
 0114  //  - factor the string into high and low ints for packing
 0115  //    - if we have bcmath, then it is used
 0116  //    - if we don't, we have to do it manually (this is the fun part)
 0117  //
 0118  //    - x64 branch does factoring using ints
 0119  //    - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
 0120  //
 0121  // unpacking routines are pretty much the same.
 0122  //  - return ints if we can
 0123  //  - otherwise format number into a string
 0124   
 0125  /// pack 64-bit signed
 0126  function sphPackI64 ( $v )
 0127  {
 0128      assert ( is_numeric($v) );
 0129      
 0130      // x64
 0131      if ( PHP_INT_SIZE>=8 )
 0132      {
 0133          $v = (int)$v;
 0134          return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
 0135      }
 0136   
 0137      // x32, int
 0138      if ( is_int($v) )
 0139          return pack ( "NN", $v < 0 ? -1 : 0, $v );
 0140   
 0141      // x32, bcmath    
 0142      if ( function_exists("bcmul") )
 0143      {
 0144          if ( bccomp ( $v, 0 ) == -1 )
 0145              $v = bcadd ( "18446744073709551616", $v );
 0146          $h = bcdiv ( $v, "4294967296", 0 );
 0147          $l = bcmod ( $v, "4294967296" );
 0148          return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
 0149      }
 0150   
 0151      // x32, no-bcmath
 0152      $p = max(0, strlen($v) - 13);
 0153      $lo = abs((float)substr($v, $p));
 0154      $hi = abs((float)substr($v, 0, $p));
 0155   
 0156      $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
 0157      $q = floor($m/4294967296.0);
 0158      $l = $m - ($q*4294967296.0);
 0159      $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
 0160   
 0161      if ( $v<0 )
 0162      {
 0163          if ( $l==0 )
 0164              $h = 4294967296.0 - $h;
 0165          else
 0166          {
 0167              $h = 4294967295.0 - $h;
 0168              $l = 4294967296.0 - $l;
 0169          }
 0170      }
 0171      return pack ( "NN", $h, $l );
 0172  }
 0173   
 0174  /// pack 64-bit unsigned
 0175  function sphPackU64 ( $v )
 0176  {
 0177      assert ( is_numeric($v) );
 0178      
 0179      // x64
 0180      if ( PHP_INT_SIZE>=8 )
 0181      {
 0182          assert ( $v>=0 );
 0183          
 0184          // x64, int
 0185          if ( is_int($v) )
 0186              return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
 0187                            
 0188          // x64, bcmath
 0189          if ( function_exists("bcmul") )
 0190          {
 0191              $h = bcdiv ( $v, 4294967296, 0 );
 0192              $l = bcmod ( $v, 4294967296 );
 0193              return pack ( "NN", $h, $l );
 0194          }
 0195          
 0196          // x64, no-bcmath
 0197          $p = max ( 0, strlen($v) - 13 );
 0198          $lo = (int)substr ( $v, $p );
 0199          $hi = (int)substr ( $v, 0, $p );
 0200      
 0201          $m = $lo + $hi*1316134912;
 0202          $l = $m % 4294967296;
 0203          $h = $hi*2328 + (int)($m/4294967296);
 0204   
 0205          return pack ( "NN", $h, $l );
 0206      }
 0207   
 0208      // x32, int
 0209      if ( is_int($v) )
 0210          return pack ( "NN", 0, $v );
 0211      
 0212      // x32, bcmath
 0213      if ( function_exists("bcmul") )
 0214      {
 0215          $h = bcdiv ( $v, "4294967296", 0 );
 0216          $l = bcmod ( $v, "4294967296" );
 0217          return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
 0218      }
 0219   
 0220      // x32, no-bcmath
 0221      $p = max(0, strlen($v) - 13);
 0222      $lo = (float)substr($v, $p);
 0223      $hi = (float)substr($v, 0, $p);
 0224      
 0225      $m = $lo + $hi*1316134912.0;
 0226      $q = floor($m / 4294967296.0);
 0227      $l = $m - ($q * 4294967296.0);
 0228      $h = $hi*2328.0 + $q;
 0229   
 0230      return pack ( "NN", $h, $l );
 0231  }
 0232   
 0233  // unpack 64-bit unsigned
 0234  function sphUnpackU64 ( $v )
 0235  {
 0236      list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
 0237   
 0238      if ( PHP_INT_SIZE>=8 )
 0239      {
 0240          if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
 0241          if ( $lo<0 ) $lo += (1<<32);
 0242   
 0243          // x64, int
 0244          if ( $hi<=2147483647 )
 0245              return ($hi<<32) + $lo;
 0246   
 0247          // x64, bcmath
 0248          if ( function_exists("bcmul") )
 0249              return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
 0250   
 0251          // x64, no-bcmath
 0252          $C = 100000;
 0253          $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
 0254          $l = (($hi % $C) << 32) + ($lo % $C);
 0255          if ( $l>$C )
 0256          {
 0257              $h += (int)($l / $C);
 0258              $l  = $l % $C;
 0259          }
 0260   
 0261          if ( $h==0 )
 0262              return $l;
 0263          return sprintf ( "%d%05d", $h, $l );
 0264      }
 0265   
 0266      // x32, int
 0267      if ( $hi==0 )
 0268      {
 0269          if ( $lo>0 )
 0270              return $lo;
 0271          return sprintf ( "%u", $lo );
 0272      }
 0273   
 0274      $hi = sprintf ( "%u", $hi );
 0275      $lo = sprintf ( "%u", $lo );
 0276   
 0277      // x32, bcmath
 0278      if ( function_exists("bcmul") )
 0279          return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
 0280      
 0281      // x32, no-bcmath
 0282      $hi = (float)$hi;
 0283      $lo = (float)$lo;
 0284      
 0285      $q = floor($hi/10000000.0);
 0286      $r = $hi - $q*10000000.0;
 0287      $m = $lo + $r*4967296.0;
 0288      $mq = floor($m/10000000.0);
 0289      $l = $m - $mq*10000000.0;
 0290      $h = $q*4294967296.0 + $r*429.0 + $mq;
 0291   
 0292      $h = sprintf ( "%.0f", $h );
 0293      $l = sprintf ( "%07.0f", $l );
 0294      if ( $h=="0" )
 0295          return sprintf( "%.0f", (float)$l );
 0296      return $h . $l;
 0297  }
 0298   
 0299  // unpack 64-bit signed
 0300  function sphUnpackI64 ( $v )
 0301  {
 0302      list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
 0303   
 0304      // x64
 0305      if ( PHP_INT_SIZE>=8 )
 0306      {
 0307          if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
 0308          if ( $lo<0 ) $lo += (1<<32);
 0309   
 0310          return ($hi<<32) + $lo;
 0311      }
 0312   
 0313      // x32, int
 0314      if ( $hi==0 )
 0315      {
 0316          if ( $lo>0 )
 0317              return $lo;
 0318          return sprintf ( "%u", $lo );
 0319      }
 0320      // x32, int
 0321      elseif ( $hi==-1 )
 0322      {
 0323          if ( $lo<0 )
 0324              return $lo;
 0325          return sprintf ( "%.0f", $lo - 4294967296.0 );
 0326      }
 0327      
 0328      $neg = "";
 0329      $c = 0;
 0330      if ( $hi<0 )
 0331      {
 0332          $hi = ~$hi;
 0333          $lo = ~$lo;
 0334          $c = 1;
 0335          $neg = "-";
 0336      }    
 0337   
 0338      $hi = sprintf ( "%u", $hi );
 0339      $lo = sprintf ( "%u", $lo );
 0340   
 0341      // x32, bcmath
 0342      if ( function_exists("bcmul") )
 0343          return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
 0344   
 0345      // x32, no-bcmath
 0346      $hi = (float)$hi;
 0347      $lo = (float)$lo;
 0348      
 0349      $q = floor($hi/10000000.0);
 0350      $r = $hi - $q*10000000.0;
 0351      $m = $lo + $r*4967296.0;
 0352      $mq = floor($m/10000000.0);
 0353      $l = $m - $mq*10000000.0 + $c;
 0354      $h = $q*4294967296.0 + $r*429.0 + $mq;
 0355      if ( $l==10000000 )
 0356      {
 0357          $l = 0;
 0358          $h += 1;
 0359      }
 0360   
 0361      $h = sprintf ( "%.0f", $h );
 0362      $l = sprintf ( "%07.0f", $l );
 0363      if ( $h=="0" )
 0364          return $neg . sprintf( "%.0f", (float)$l );
 0365      return $neg . $h . $l;
 0366  }
 0367   
 0368   
 0369  function sphFixUint ( $value )
 0370  {
 0371      if ( PHP_INT_SIZE>=8 )
 0372      {
 0373          // x64 route, workaround broken unpack() in 5.2.2+
 0374          if ( $value<0 ) $value += (1<<32);
 0375          return $value;
 0376      }
 0377      else
 0378      {
 0379          // x32 route, workaround php signed/unsigned braindamage
 0380          return sprintf ( "%u", $value );
 0381      }
 0382  }
 0383   
 0384   
 0385  /// sphinx searchd client class
 0386  class SphinxClient
 0387  {
 0388      var $_host;            ///< searchd host (default is "localhost")
 0389      var $_port;            ///< searchd port (default is 9312)
 0390      var $_offset;        ///< how many records to seek from result-set start (default is 0)
 0391      var $_limit;        ///< how many records to return from result-set starting at offset (default is 20)
 0392      var $_mode;            ///< query matching mode (default is SPH_MATCH_ALL)
 0393      var $_weights;        ///< per-field weights (default is 1 for all fields)
 0394      var $_sort;            ///< match sorting mode (default is SPH_SORT_RELEVANCE)
 0395      var $_sortby;        ///< attribute to sort by (defualt is "")
 0396      var $_min_id;        ///< min ID to match (default is 0, which means no limit)
 0397      var $_max_id;        ///< max ID to match (default is 0, which means no limit)
 0398      var $_filters;        ///< search filters
 0399      var $_groupby;        ///< group-by attribute name
 0400      var $_groupfunc;    ///< group-by function (to pre-process group-by attribute value with)
 0401      var $_groupsort;    ///< group-by sorting clause (to sort groups in result set with)
 0402      var $_groupdistinct;///< group-by count-distinct attribute
 0403      var $_maxmatches;    ///< max matches to retrieve
 0404      var $_cutoff;        ///< cutoff to stop searching at (default is 0)
 0405      var $_retrycount;    ///< distributed retries count
 0406      var $_retrydelay;    ///< distributed retries delay
 0407      var $_anchor;        ///< geographical anchor point
 0408      var $_indexweights;    ///< per-index weights
 0409      var $_ranker;        ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
 0410      var $_rankexpr;        ///< ranking mode expression (for SPH_RANK_EXPR)
 0411      var $_maxquerytime;    ///< max query time, milliseconds (default is 0, do not limit)
 0412      var $_fieldweights;    ///< per-field-name weights
 0413      var $_overrides;    ///< per-query attribute values overrides
 0414      var $_select;        ///< select-list (attributes or expressions, with optional aliases)
 0415   
 0416      var $_error;        ///< last error message
 0417      var $_warning;        ///< last warning message
 0418      var $_connerror;        ///< connection error vs remote error flag
 0419   
 0420      var $_reqs;            ///< requests array for multi-query
 0421      var $_mbenc;        ///< stored mbstring encoding
 0422      var $_arrayresult;    ///< whether $result["matches"] should be a hash or an array
 0423      var $_timeout;        ///< connect timeout
 0424   
 0425      /////////////////////////////////////////////////////////////////////////////
 0426      // common stuff
 0427      /////////////////////////////////////////////////////////////////////////////
 0428   
 0429      /// create a new client object and fill defaults
 0430      function SphinxClient ()
 0431      {
 0432          // per-client-object settings
 0433          $this->_host        = "localhost";
 0434          $this->_port        = 9312;
 0435          $this->_path        = false;
 0436          $this->_socket        = false;
 0437   
 0438          // per-query settings
 0439          $this->_offset        = 0;
 0440          $this->_limit        = 20;
 0441          $this->_mode        = SPH_MATCH_ALL;
 0442          $this->_weights        = array ();
 0443          $this->_sort        = SPH_SORT_RELEVANCE;
 0444          $this->_sortby        = "";
 0445          $this->_min_id        = 0;
 0446          $this->_max_id        = 0;
 0447          $this->_filters        = array ();
 0448          $this->_groupby        = "";
 0449          $this->_groupfunc    = SPH_GROUPBY_DAY;
 0450          $this->_groupsort    = "@group desc";
 0451          $this->_groupdistinct= "";
 0452          $this->_maxmatches    = 1000;
 0453          $this->_cutoff        = 0;
 0454          $this->_retrycount    = 0;
 0455          $this->_retrydelay    = 0;
 0456          $this->_anchor        = array ();
 0457          $this->_indexweights= array ();
 0458          $this->_ranker        = SPH_RANK_PROXIMITY_BM25;
 0459          $this->_rankexpr    = "";
 0460          $this->_maxquerytime= 0;
 0461          $this->_fieldweights= array();
 0462          $this->_overrides     = array();
 0463          $this->_select        = "*";
 0464   
 0465          $this->_error        = ""; // per-reply fields (for single-query case)
 0466          $this->_warning        = "";
 0467          $this->_connerror    = false;
 0468   
 0469          $this->_reqs        = array ();    // requests storage (for multi-query case)
 0470          $this->_mbenc        = "";
 0471          $this->_arrayresult    = false;
 0472          $this->_timeout        = 0;
 0473      }
 0474   
 0475      function __destruct()
 0476      {
 0477          if ( $this->_socket !== false )
 0478              fclose ( $this->_socket );
 0479      }
 0480   
 0481      /// get last error message (string)
 0482      function GetLastError ()
 0483      {
 0484          return $this->_error;
 0485      }
 0486   
 0487      /// get last warning message (string)
 0488      function GetLastWarning ()
 0489      {
 0490          return $this->_warning;
 0491      }
 0492   
 0493      /// get last error flag (to tell network connection errors from searchd errors or broken responses)
 0494      function IsConnectError()
 0495      {
 0496          return $this->_connerror;
 0497      }
 0498   
 0499      /// set searchd host name (string) and port (integer)
 0500      function SetServer ( $host, $port = 0 )
 0501      {
 0502          assert ( is_string($host) );
 0503          if ( $host[0] == '/')
 0504          {
 0505              $this->_path = 'unix://' . $host;
 0506              return;
 0507          }
 0508          if ( substr ( $host, 0, 7 )=="unix://" )
 0509          {
 0510              $this->_path = $host;
 0511              return;
 0512          }
 0513                  
 0514          assert ( is_int($port) );
 0515          $this->_host = $host;
 0516          $this->_port = $port;
 0517          $this->_path = '';
 0518   
 0519      }
 0520   
 0521      /// set server connection timeout (0 to remove)
 0522      function SetConnectTimeout ( $timeout )
 0523      {
 0524          assert ( is_numeric($timeout) );
 0525          $this->_timeout = $timeout;
 0526      }
 0527   
 0528   
 0529      function _Send ( $handle, $data, $length )
 0530      {
 0531          if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
 0532          {
 0533              $this->_error = 'connection unexpectedly closed (timed out?)';
 0534              $this->_connerror = true;
 0535              return false;
 0536          }
 0537          return true;
 0538      }
 0539   
 0540      /////////////////////////////////////////////////////////////////////////////
 0541   
 0542      /// enter mbstring workaround mode
 0543      function _MBPush ()
 0544      {
 0545          $this->_mbenc = "";
 0546          if ( ini_get ( "mbstring.func_overload" ) & 2 )
 0547          {
 0548              $this->_mbenc = mb_internal_encoding();
 0549              mb_internal_encoding ( "latin1" );
 0550          }
 0551      }
 0552   
 0553      /// leave mbstring workaround mode
 0554      function _MBPop ()
 0555      {
 0556          if ( $this->_mbenc )
 0557              mb_internal_encoding ( $this->_mbenc );
 0558      }
 0559   
 0560      /// connect to searchd server
 0561      function _Connect ()
 0562      {
 0563          if ( $this->_socket!==false )
 0564          {
 0565              // we are in persistent connection mode, so we have a socket
 0566              // however, need to check whether it's still alive
 0567              if ( !@feof ( $this->_socket ) )
 0568                  return $this->_socket;
 0569   
 0570              // force reopen
 0571              $this->_socket = false;
 0572          }
 0573   
 0574          $errno = 0;
 0575          $errstr = "";
 0576          $this->_connerror = false;
 0577   
 0578          if ( $this->_path )
 0579          {
 0580              $host = $this->_path;
 0581              $port = 0;
 0582          }
 0583          else
 0584          {
 0585              $host = $this->_host;
 0586              $port = $this->_port;
 0587          }
 0588   
 0589          if ( $this->_timeout<=0 )
 0590              $fp = @fsockopen ( $host, $port, $errno, $errstr );
 0591          else
 0592              $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
 0593          
 0594          if ( !$fp )
 0595          {
 0596              if ( $this->_path )
 0597                  $location = $this->_path;
 0598              else
 0599                  $location = "{$this->_host}:{$this->_port}";
 0600              
 0601              $errstr = trim ( $errstr );
 0602              $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
 0603              $this->_connerror = true;
 0604              return false;
 0605          }
 0606   
 0607          // send my version
 0608          // this is a subtle part. we must do it before (!) reading back from searchd.
 0609          // because otherwise under some conditions (reported on FreeBSD for instance)
 0610          // TCP stack could throttle write-write-read pattern because of Nagle.
 0611          if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
 0612          {
 0613              fclose ( $fp );
 0614              $this->_error = "failed to send client protocol version";
 0615              return false;
 0616          }
 0617   
 0618          // check version
 0619          list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
 0620          $v = (int)$v;
 0621          if ( $v<1 )
 0622          {
 0623              fclose ( $fp );
 0624              $this->_error = "expected searchd protocol version 1+, got version '$v'";
 0625              return false;
 0626          }
 0627   
 0628          return $fp;
 0629      }
 0630   
 0631      /// get and check response packet from searchd server
 0632      function _GetResponse ( $fp, $client_ver )
 0633      {
 0634          $response = "";
 0635          $len = 0;
 0636   
 0637          $header = fread ( $fp, 8 );
 0638          if ( strlen($header)==8 )
 0639          {
 0640              list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
 0641              $left = $len;
 0642              while ( $left>0 && !feof($fp) )
 0643              {
 0644                  $chunk = fread ( $fp, min ( 8192, $left ) );
 0645                  if ( $chunk )
 0646                  {
 0647                      $response .= $chunk;
 0648                      $left -= strlen($chunk);
 0649                  }
 0650              }
 0651          }
 0652          if ( $this->_socket === false )
 0653              fclose ( $fp );
 0654   
 0655          // check response
 0656          $read = strlen ( $response );
 0657          if ( !$response || $read!=$len )
 0658          {
 0659              $this->_error = $len
 0660                  ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
 0661                  : "received zero-sized searchd response";
 0662              return false;
 0663          }
 0664   
 0665          // check status
 0666          if ( $status==SEARCHD_WARNING )
 0667          {
 0668              list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
 0669              $this->_warning = substr ( $response, 4, $wlen );
 0670              return substr ( $response, 4+$wlen );
 0671          }
 0672          if ( $status==SEARCHD_ERROR )
 0673          {
 0674              $this->_error = "searchd error: " . substr ( $response, 4 );
 0675              return false;
 0676          }
 0677          if ( $status==SEARCHD_RETRY )
 0678          {
 0679              $this->_error = "temporary searchd error: " . substr ( $response, 4 );
 0680              return false;
 0681          }
 0682          if ( $status!=SEARCHD_OK )
 0683          {
 0684              $this->_error = "unknown status code '$status'";
 0685              return false;
 0686          }
 0687   
 0688          // check version
 0689          if ( $ver<$client_ver )
 0690          {
 0691              $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
 0692                  $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
 0693          }
 0694   
 0695          return $response;
 0696      }
 0697   
 0698      /////////////////////////////////////////////////////////////////////////////
 0699      // searching
 0700      /////////////////////////////////////////////////////////////////////////////
 0701   
 0702      /// set offset and count into result set,
 0703      /// and optionally set max-matches and cutoff limits
 0704      function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
 0705      {
 0706          assert ( is_int($offset) );
 0707          assert ( is_int($limit) );
 0708          assert ( $offset>=0 );
 0709          assert ( $limit>0 );
 0710          assert ( $max>=0 );
 0711          $this->_offset = $offset;
 0712          $this->_limit = $limit;
 0713          if ( $max>0 )
 0714              $this->_maxmatches = $max;
 0715          if ( $cutoff>0 )
 0716              $this->_cutoff = $cutoff;
 0717      }
 0718   
 0719      /// set maximum query time, in milliseconds, per-index
 0720      /// integer, 0 means "do not limit"
 0721      function SetMaxQueryTime ( $max )
 0722      {
 0723          assert ( is_int($max) );
 0724          assert ( $max>=0 );
 0725          $this->_maxquerytime = $max;
 0726      }
 0727   
 0728      /// set matching mode
 0729      function SetMatchMode ( $mode )
 0730      {
 0731          assert ( $mode==SPH_MATCH_ALL
 0732              || $mode==SPH_MATCH_ANY
 0733              || $mode==SPH_MATCH_PHRASE
 0734              || $mode==SPH_MATCH_BOOLEAN
 0735              || $mode==SPH_MATCH_EXTENDED
 0736              || $mode==SPH_MATCH_FULLSCAN
 0737              || $mode==SPH_MATCH_EXTENDED2 );
 0738          $this->_mode = $mode;
 0739      }
 0740   
 0741      /// set ranking mode
 0742      function SetRankingMode ( $ranker, $rankexpr="" )
 0743      {
 0744          assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL );
 0745          assert ( is_string($rankexpr) );
 0746          $this->_ranker = $ranker;
 0747          $this->_rankexpr = $rankexpr;
 0748      }
 0749   
 0750      /// set matches sorting mode
 0751      function SetSortMode ( $mode, $sortby="" )
 0752      {
 0753          assert (
 0754              $mode==SPH_SORT_RELEVANCE ||
 0755              $mode==SPH_SORT_ATTR_DESC ||
 0756              $mode==SPH_SORT_ATTR_ASC ||
 0757              $mode==SPH_SORT_TIME_SEGMENTS ||
 0758              $mode==SPH_SORT_EXTENDED ||
 0759              $mode==SPH_SORT_EXPR );
 0760          assert ( is_string($sortby) );
 0761          assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
 0762   
 0763          $this->_sort = $mode;
 0764          $this->_sortby = $sortby;
 0765      }
 0766   
 0767      /// bind per-field weights by order
 0768      /// DEPRECATED; use SetFieldWeights() instead
 0769      function SetWeights ( $weights )
 0770      {
 0771          assert ( is_array($weights) );
 0772          foreach ( $weights as $weight )
 0773              assert ( is_int($weight) );
 0774   
 0775          $this->_weights = $weights;
 0776      }
 0777   
 0778      /// bind per-field weights by name
 0779      function SetFieldWeights ( $weights )
 0780      {
 0781          assert ( is_array($weights) );
 0782          foreach ( $weights as $name=>$weight )
 0783          {
 0784              assert ( is_string($name) );
 0785              assert ( is_int($weight) );
 0786          }
 0787          $this->_fieldweights = $weights;
 0788      }
 0789   
 0790      /// bind per-index weights by name
 0791      function SetIndexWeights ( $weights )
 0792      {
 0793          assert ( is_array($weights) );
 0794          foreach ( $weights as $index=>$weight )
 0795          {
 0796              assert ( is_string($index) );
 0797              assert ( is_int($weight) );
 0798          }
 0799          $this->_indexweights = $weights;
 0800      }
 0801   
 0802      /// set IDs range to match
 0803      /// only match records if document ID is beetwen $min and $max (inclusive)
 0804      function SetIDRange ( $min, $max )
 0805      {
 0806          assert ( is_numeric($min) );
 0807          assert ( is_numeric($max) );
 0808          assert ( $min<=$max );
 0809          $this->_min_id = $min;
 0810          $this->_max_id = $max;
 0811      }
 0812   
 0813      /// set values set filter
 0814      /// only match records where $attribute value is in given set
 0815      function SetFilter ( $attribute, $values, $exclude=false )
 0816      {
 0817          assert ( is_string($attribute) );
 0818          assert ( is_array($values) );
 0819          assert ( count($values) );
 0820   
 0821          if ( is_array($values) && count($values) )
 0822          {
 0823              foreach ( $values as $value )
 0824                  assert ( is_numeric($value) );
 0825   
 0826              $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
 0827          }
 0828      }
 0829   
 0830      /// set range filter
 0831      /// only match records if $attribute value is beetwen $min and $max (inclusive)
 0832      function SetFilterRange ( $attribute, $min, $max, $exclude=false )
 0833      {
 0834          assert ( is_string($attribute) );
 0835          assert ( is_numeric($min) );
 0836          assert ( is_numeric($max) );
 0837          assert ( $min<=$max );
 0838   
 0839          $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
 0840      }
 0841   
 0842      /// set float range filter
 0843      /// only match records if $attribute value is beetwen $min and $max (inclusive)
 0844      function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
 0845      {
 0846          assert ( is_string($attribute) );
 0847          assert ( is_float($min) );
 0848          assert ( is_float($max) );
 0849          assert ( $min<=$max );
 0850   
 0851          $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
 0852      }
 0853   
 0854      /// setup anchor point for geosphere distance calculations
 0855      /// required to use @geodist in filters and sorting
 0856      /// latitude and longitude must be in radians
 0857      function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
 0858      {
 0859          assert ( is_string($attrlat) );
 0860          assert ( is_string($attrlong) );
 0861          assert ( is_float($lat) );
 0862          assert ( is_float($long) );
 0863   
 0864          $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
 0865      }
 0866   
 0867      /// set grouping attribute and function
 0868      function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
 0869      {
 0870          assert ( is_string($attribute) );
 0871          assert ( is_string($groupsort) );
 0872          assert ( $func==SPH_GROUPBY_DAY
 0873              || $func==SPH_GROUPBY_WEEK
 0874              || $func==SPH_GROUPBY_MONTH
 0875              || $func==SPH_GROUPBY_YEAR
 0876              || $func==SPH_GROUPBY_ATTR
 0877              || $func==SPH_GROUPBY_ATTRPAIR );
 0878   
 0879          $this->_groupby = $attribute;
 0880          $this->_groupfunc = $func;
 0881          $this->_groupsort = $groupsort;
 0882      }
 0883   
 0884      /// set count-distinct attribute for group-by queries
 0885      function SetGroupDistinct ( $attribute )
 0886      {
 0887          assert ( is_string($attribute) );
 0888          $this->_groupdistinct = $attribute;
 0889      }
 0890   
 0891      /// set distributed retries count and delay
 0892      function SetRetries ( $count, $delay=0 )
 0893      {
 0894          assert ( is_int($count) && $count>=0 );
 0895          assert ( is_int($delay) && $delay>=0 );
 0896          $this->_retrycount = $count;
 0897          $this->_retrydelay = $delay;
 0898      }
 0899   
 0900      /// set result set format (hash or array; hash by default)
 0901      /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
 0902      function SetArrayResult ( $arrayresult )
 0903      {
 0904          assert ( is_bool($arrayresult) );
 0905          $this->_arrayresult = $arrayresult;
 0906      }
 0907   
 0908      /// set attribute values override
 0909      /// there can be only one override per attribute
 0910      /// $values must be a hash that maps document IDs to attribute values
 0911      function SetOverride ( $attrname, $attrtype, $values )
 0912      {
 0913          assert ( is_string ( $attrname ) );
 0914          assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
 0915          assert ( is_array ( $values ) );
 0916   
 0917          $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
 0918      }
 0919   
 0920      /// set select-list (attributes or expressions), SQL-like syntax
 0921      function SetSelect ( $select )
 0922      {
 0923          assert ( is_string ( $select ) );
 0924          $this->_select = $select;
 0925      }
 0926   
 0927      //////////////////////////////////////////////////////////////////////////////
 0928   
 0929      /// clear all filters (for multi-queries)
 0930      function ResetFilters ()
 0931      {
 0932          $this->_filters = array();
 0933          $this->_anchor = array();
 0934      }
 0935   
 0936      /// clear groupby settings (for multi-queries)
 0937      function ResetGroupBy ()
 0938      {
 0939          $this->_groupby        = "";
 0940          $this->_groupfunc    = SPH_GROUPBY_DAY;
 0941          $this->_groupsort    = "@group desc";
 0942          $this->_groupdistinct= "";
 0943      }
 0944   
 0945      /// clear all attribute value overrides (for multi-queries)
 0946      function ResetOverrides ()
 0947      {
 0948          $this->_overrides = array ();
 0949      }
 0950   
 0951      //////////////////////////////////////////////////////////////////////////////
 0952   
 0953      /// connect to searchd server, run given search query through given indexes,
 0954      /// and return the search results
 0955      function Query ( $query, $index="*", $comment="" )
 0956      {
 0957          assert ( empty($this->_reqs) );
 0958   
 0959          $this->AddQuery ( $query, $index, $comment );
 0960          $results = $this->RunQueries ();
 0961          $this->_reqs = array (); // just in case it failed too early
 0962   
 0963          if ( !is_array($results) )
 0964              return false; // probably network error; error message should be already filled
 0965   
 0966          $this->_error = $results[0]["error"];
 0967          $this->_warning = $results[0]["warning"];
 0968          if ( $results[0]["status"]==SEARCHD_ERROR )
 0969              return false;
 0970          else
 0971              return $results[0];
 0972      }
 0973   
 0974      /// helper to pack floats in network byte order
 0975      function _PackFloat ( $f )
 0976      {
 0977          $t1 = pack ( "f", $f ); // machine order
 0978          list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
 0979          return pack ( "N", $t2 );
 0980      }
 0981   
 0982      /// add query to multi-query batch
 0983      /// returns index into results array from RunQueries() call
 0984      function AddQuery ( $query, $index="*", $comment="" )
 0985      {
 0986          // mbstring workaround
 0987          $this->_MBPush ();
 0988   
 0989          // build request
 0990          $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker );
 0991          if ( $this->_ranker==SPH_RANK_EXPR )
 0992              $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr;
 0993          $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode
 0994          $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
 0995          $req .= pack ( "N", strlen($query) ) . $query; // query itself
 0996          $req .= pack ( "N", count($this->_weights) ); // weights
 0997          foreach ( $this->_weights as $weight )
 0998              $req .= pack ( "N", (int)$weight );
 0999          $req .= pack ( "N", strlen($index) ) . $index; // indexes
 1000          $req .= pack ( "N", 1 ); // id64 range marker
 1001          $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
 1002   
 1003          // filters
 1004          $req .= pack ( "N", count($this->_filters) );
 1005          foreach ( $this->_filters as $filter )
 1006          {
 1007              $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
 1008              $req .= pack ( "N", $filter["type"] );
 1009              switch ( $filter["type"] )
 1010              {
 1011                  case SPH_FILTER_VALUES:
 1012                      $req .= pack ( "N", count($filter["values"]) );
 1013                      foreach ( $filter["values"] as $value )
 1014                          $req .= sphPackI64 ( $value );
 1015                      break;
 1016   
 1017                  case SPH_FILTER_RANGE:
 1018                      $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
 1019                      break;
 1020   
 1021                  case SPH_FILTER_FLOATRANGE:
 1022                      $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
 1023                      break;
 1024   
 1025                  default:
 1026                      assert ( 0 && "internal error: unhandled filter type" );
 1027              }
 1028              $req .= pack ( "N", $filter["exclude"] );
 1029          }
 1030   
 1031          // group-by clause, max-matches count, group-sort clause, cutoff count
 1032          $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
 1033          $req .= pack ( "N", $this->_maxmatches );
 1034          $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
 1035          $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
 1036          $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
 1037   
 1038          // anchor point
 1039          if ( empty($this->_anchor) )
 1040          {
 1041              $req .= pack ( "N", 0 );
 1042          } else
 1043          {
 1044              $a =& $this->_anchor;
 1045              $req .= pack ( "N", 1 );
 1046              $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
 1047              $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
 1048              $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
 1049          }
 1050   
 1051          // per-index weights
 1052          $req .= pack ( "N", count($this->_indexweights) );
 1053          foreach ( $this->_indexweights as $idx=>$weight )
 1054              $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
 1055   
 1056          // max query time
 1057          $req .= pack ( "N", $this->_maxquerytime );
 1058   
 1059          // per-field weights
 1060          $req .= pack ( "N", count($this->_fieldweights) );
 1061          foreach ( $this->_fieldweights as $field=>$weight )
 1062              $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
 1063   
 1064          // comment
 1065          $req .= pack ( "N", strlen($comment) ) . $comment;
 1066   
 1067          // attribute overrides
 1068          $req .= pack ( "N", count($this->_overrides) );
 1069          foreach ( $this->_overrides as $key => $entry )
 1070          {
 1071              $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
 1072              $req .= pack ( "NN", $entry["type"], count($entry["values"]) );
 1073              foreach ( $entry["values"] as $id=>$val )
 1074              {
 1075                  assert ( is_numeric($id) );
 1076                  assert ( is_numeric($val) );
 1077   
 1078                  $req .= sphPackU64 ( $id );
 1079                  switch ( $entry["type"] )
 1080                  {
 1081                      case SPH_ATTR_FLOAT:    $req .= $this->_PackFloat ( $val ); break;
 1082                      case SPH_ATTR_BIGINT:    $req .= sphPackI64 ( $val ); break;
 1083                      default:                $req .= pack ( "N", $val ); break;
 1084                  }
 1085              }
 1086          }
 1087   
 1088          // select-list
 1089          $req .= pack ( "N", strlen($this->_select) ) . $this->_select;
 1090   
 1091          // mbstring workaround
 1092          $this->_MBPop ();
 1093   
 1094          // store request to requests array
 1095          $this->_reqs[] = $req;
 1096          return count($this->_reqs)-1;
 1097      }
 1098   
 1099      /// connect to searchd, run queries batch, and return an array of result sets
 1100      function RunQueries ()
 1101      {
 1102          if ( empty($this->_reqs) )
 1103          {
 1104              $this->_error = "no queries defined, issue AddQuery() first";
 1105              return false;
 1106          }
 1107   
 1108          // mbstring workaround
 1109          $this->_MBPush ();
 1110   
 1111          if (!( $fp = $this->_Connect() ))
 1112          {
 1113              $this->_MBPop ();
 1114              return false;
 1115          }
 1116   
 1117          // send query, get response
 1118          $nreqs = count($this->_reqs);
 1119          $req = join ( "", $this->_reqs );
 1120          $len = 8+strlen($req);
 1121          $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header
 1122   
 1123          if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
 1124               !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
 1125          {
 1126              $this->_MBPop ();
 1127              return false;
 1128          }
 1129   
 1130          // query sent ok; we can reset reqs now
 1131          $this->_reqs = array ();
 1132   
 1133          // parse and return response
 1134          return $this->_ParseSearchResponse ( $response, $nreqs );
 1135      }
 1136   
 1137      /// parse and return search query (or queries) response
 1138      function _ParseSearchResponse ( $response, $nreqs )
 1139      {
 1140          $p = 0; // current position
 1141          $max = strlen($response); // max position for checks, to protect against broken responses
 1142   
 1143          $results = array ();
 1144          for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
 1145          {
 1146              $results[] = array();
 1147              $result =& $results[$ires];
 1148   
 1149              $result["error"] = "";
 1150              $result["warning"] = "";
 1151   
 1152              // extract status
 1153              list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1154              $result["status"] = $status;
 1155              if ( $status!=SEARCHD_OK )
 1156              {
 1157                  list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1158                  $message = substr ( $response, $p, $len ); $p += $len;
 1159   
 1160                  if ( $status==SEARCHD_WARNING )
 1161                  {
 1162                      $result["warning"] = $message;
 1163                  } else
 1164                  {
 1165                      $result["error"] = $message;
 1166                      continue;
 1167                  }
 1168              }
 1169   
 1170              // read schema
 1171              $fields = array ();
 1172              $attrs = array ();
 1173   
 1174              list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1175              while ( $nfields-->0 && $p<$max )
 1176              {
 1177                  list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1178                  $fields[] = substr ( $response, $p, $len ); $p += $len;
 1179              }
 1180              $result["fields"] = $fields;
 1181   
 1182              list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1183              while ( $nattrs-->0 && $p<$max  )
 1184              {
 1185                  list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1186                  $attr = substr ( $response, $p, $len ); $p += $len;
 1187                  list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1188                  $attrs[$attr] = $type;
 1189              }
 1190              $result["attrs"] = $attrs;
 1191   
 1192              // read match count
 1193              list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1194              list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1195   
 1196              // read matches
 1197              $idx = -1;
 1198              while ( $count-->0 && $p<$max )
 1199              {
 1200                  // index into result array
 1201                  $idx++;
 1202   
 1203                  // parse document id and weight
 1204                  if ( $id64 )
 1205                  {
 1206                      $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
 1207                      list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1208                  }
 1209                  else
 1210                  {
 1211                      list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
 1212                          substr ( $response, $p, 8 ) ) );
 1213                      $p += 8;
 1214                      $doc = sphFixUint($doc);
 1215                  }
 1216                  $weight = sprintf ( "%u", $weight );
 1217   
 1218                  // create match entry
 1219                  if ( $this->_arrayresult )
 1220                      $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
 1221                  else
 1222                      $result["matches"][$doc]["weight"] = $weight;
 1223   
 1224                  // parse and create attributes
 1225                  $attrvals = array ();
 1226                  foreach ( $attrs as $attr=>$type )
 1227                  {
 1228                      // handle 64bit ints
 1229                      if ( $type==SPH_ATTR_BIGINT )
 1230                      {
 1231                          $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
 1232                          continue;
 1233                      }
 1234   
 1235                      // handle floats
 1236                      if ( $type==SPH_ATTR_FLOAT )
 1237                      {
 1238                          list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1239                          list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); 
 1240                          $attrvals[$attr] = $fval;
 1241                          continue;
 1242                      }
 1243   
 1244                      // handle everything else as unsigned ints
 1245                      list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1246                      if ( $type==SPH_ATTR_MULTI )
 1247                      {
 1248                          $attrvals[$attr] = array ();
 1249                          $nvalues = $val;
 1250                          while ( $nvalues-->0 && $p<$max )
 1251                          {
 1252                              list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1253                              $attrvals[$attr][] = sphFixUint($val);
 1254                          }
 1255                      } else if ( $type==SPH_ATTR_MULTI64 )
 1256                      {
 1257                          $attrvals[$attr] = array ();
 1258                          $nvalues = $val;
 1259                          while ( $nvalues>0 && $p<$max )
 1260                          {
 1261                              $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
 1262                              $nvalues -= 2;
 1263                          }
 1264                      } else if ( $type==SPH_ATTR_STRING )
 1265                      {
 1266                          $attrvals[$attr] = substr ( $response, $p, $val );
 1267                          $p += $val;                        
 1268                      } else
 1269                      {
 1270                          $attrvals[$attr] = sphFixUint($val);
 1271                      }
 1272                  }
 1273   
 1274                  if ( $this->_arrayresult )
 1275                      $result["matches"][$idx]["attrs"] = $attrvals;
 1276                  else
 1277                      $result["matches"][$doc]["attrs"] = $attrvals;
 1278              }
 1279   
 1280              list ( $total, $total_found, $msecs, $words ) =
 1281                  array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
 1282              $result["total"] = sprintf ( "%u", $total );
 1283              $result["total_found"] = sprintf ( "%u", $total_found );
 1284              $result["time"] = sprintf ( "%.3f", $msecs/1000 );
 1285              $p += 16;
 1286   
 1287              while ( $words-->0 && $p<$max )
 1288              {
 1289                  list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1290                  $word = substr ( $response, $p, $len ); $p += $len;
 1291                  list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
 1292                  $result["words"][$word] = array (
 1293                      "docs"=>sprintf ( "%u", $docs ),
 1294                      "hits"=>sprintf ( "%u", $hits ) );
 1295              }
 1296          }
 1297   
 1298          $this->_MBPop ();
 1299          return $results;
 1300      }
 1301   
 1302      /////////////////////////////////////////////////////////////////////////////
 1303      // excerpts generation
 1304      /////////////////////////////////////////////////////////////////////////////
 1305   
 1306      /// connect to searchd server, and generate exceprts (snippets)
 1307      /// of given documents for given query. returns false on failure,
 1308      /// an array of snippets on success
 1309      function BuildExcerpts ( $docs, $index, $words, $opts=array() )
 1310      {
 1311          assert ( is_array($docs) );
 1312          assert ( is_string($index) );
 1313          assert ( is_string($words) );
 1314          assert ( is_array($opts) );
 1315   
 1316          $this->_MBPush ();
 1317   
 1318          if (!( $fp = $this->_Connect() ))
 1319          {
 1320              $this->_MBPop();
 1321              return false;
 1322          }
 1323   
 1324          /////////////////
 1325          // fixup options
 1326          /////////////////
 1327   
 1328          if ( !isset($opts["before_match"]) )        $opts["before_match"] = "<b>";
 1329          if ( !isset($opts["after_match"]) )            $opts["after_match"] = "</b>";
 1330          if ( !isset($opts["chunk_separator"]) )        $opts["chunk_separator"] = " ... ";
 1331          if ( !isset($opts["limit"]) )                $opts["limit"] = 256;
 1332          if ( !isset($opts["limit_passages"]) )        $opts["limit_passages"] = 0;
 1333          if ( !isset($opts["limit_words"]) )            $opts["limit_words"] = 0;
 1334          if ( !isset($opts["around"]) )                $opts["around"] = 5;
 1335          if ( !isset($opts["exact_phrase"]) )        $opts["exact_phrase"] = false;
 1336          if ( !isset($opts["single_passage"]) )        $opts["single_passage"] = false;
 1337          if ( !isset($opts["use_boundaries"]) )        $opts["use_boundaries"] = false;
 1338          if ( !isset($opts["weight_order"]) )        $opts["weight_order"] = false;
 1339          if ( !isset($opts["query_mode"]) )            $opts["query_mode"] = false;
 1340          if ( !isset($opts["force_all_words"]) )        $opts["force_all_words"] = false;
 1341          if ( !isset($opts["start_passage_id"]) )    $opts["start_passage_id"] = 1;
 1342          if ( !isset($opts["load_files"]) )            $opts["load_files"] = false;
 1343          if ( !isset($opts["html_strip_mode"]) )        $opts["html_strip_mode"] = "index";
 1344          if ( !isset($opts["allow_empty"]) )            $opts["allow_empty"] = false;
 1345          if ( !isset($opts["passage_boundary"]) )    $opts["passage_boundary"] = "none";
 1346          if ( !isset($opts["emit_zones"]) )            $opts["emit_zones"] = false;
 1347          if ( !isset($opts["load_files_scattered"]) )        $opts["load_files_scattered"] = false;
 1348          
 1349   
 1350          /////////////////
 1351          // build request
 1352          /////////////////
 1353   
 1354          // v.1.2 req
 1355          $flags = 1; // remove spaces
 1356          if ( $opts["exact_phrase"] )    $flags |= 2;
 1357          if ( $opts["single_passage"] )    $flags |= 4;
 1358          if ( $opts["use_boundaries"] )    $flags |= 8;
 1359          if ( $opts["weight_order"] )    $flags |= 16;
 1360          if ( $opts["query_mode"] )        $flags |= 32;
 1361          if ( $opts["force_all_words"] )    $flags |= 64;
 1362          if ( $opts["load_files"] )        $flags |= 128;
 1363          if ( $opts["allow_empty"] )        $flags |= 256;
 1364          if ( $opts["emit_zones"] )        $flags |= 512;
 1365          if ( $opts["load_files_scattered"] )    $flags |= 1024;
 1366          $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
 1367          $req .= pack ( "N", strlen($index) ) . $index; // req index
 1368          $req .= pack ( "N", strlen($words) ) . $words; // req words
 1369   
 1370          // options
 1371          $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
 1372          $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
 1373          $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
 1374          $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] );
 1375          $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2
 1376          $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"];
 1377          $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"];
 1378   
 1379          // documents
 1380          $req .= pack ( "N", count($docs) );
 1381          foreach ( $docs as $doc )
 1382          {
 1383              assert ( is_string($doc) );
 1384              $req .= pack ( "N", strlen($doc) ) . $doc;
 1385          }
 1386   
 1387          ////////////////////////////
 1388          // send query, get response
 1389          ////////////////////////////
 1390   
 1391          $len = strlen($req);
 1392          $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
 1393          if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
 1394               !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
 1395          {
 1396              $this->_MBPop ();
 1397              return false;
 1398          }
 1399   
 1400          //////////////////
 1401          // parse response
 1402          //////////////////
 1403   
 1404          $pos = 0;
 1405          $res = array ();
 1406          $rlen = strlen($response);
 1407          for ( $i=0; $i<count($docs); $i++ )
 1408          {
 1409              list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
 1410              $pos += 4;
 1411   
 1412              if ( $pos+$len > $rlen )
 1413              {
 1414                  $this->_error = "incomplete reply";
 1415                  $this->_MBPop ();
 1416                  return false;
 1417              }
 1418              $res[] = $len ? substr ( $response, $pos, $len ) : "";
 1419              $pos += $len;
 1420          }
 1421   
 1422          $this->_MBPop ();
 1423          return $res;
 1424      }
 1425   
 1426   
 1427      /////////////////////////////////////////////////////////////////////////////
 1428      // keyword generation
 1429      /////////////////////////////////////////////////////////////////////////////
 1430   
 1431      /// connect to searchd server, and generate keyword list for a given query
 1432      /// returns false on failure,
 1433      /// an array of words on success
 1434      function BuildKeywords ( $query, $index, $hits )
 1435      {
 1436          assert ( is_string($query) );
 1437          assert ( is_string($index) );
 1438          assert ( is_bool($hits) );
 1439   
 1440          $this->_MBPush ();
 1441   
 1442          if (!( $fp = $this->_Connect() ))
 1443          {
 1444              $this->_MBPop();
 1445              return false;
 1446          }
 1447   
 1448          /////////////////
 1449          // build request
 1450          /////////////////
 1451   
 1452          // v.1.0 req
 1453          $req  = pack ( "N", strlen($query) ) . $query; // req query
 1454          $req .= pack ( "N", strlen($index) ) . $index; // req index
 1455          $req .= pack ( "N", (int)$hits );
 1456   
 1457          ////////////////////////////
 1458          // send query, get response
 1459          ////////////////////////////
 1460   
 1461          $len = strlen($req);
 1462          $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
 1463          if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
 1464               !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
 1465          {
 1466              $this->_MBPop ();
 1467              return false;
 1468          }
 1469   
 1470          //////////////////
 1471          // parse response
 1472          //////////////////
 1473   
 1474          $pos = 0;
 1475          $res = array ();
 1476          $rlen = strlen($response);
 1477          list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
 1478          $pos += 4;
 1479          for ( $i=0; $i<$nwords; $i++ )
 1480          {
 1481              list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );    $pos += 4;
 1482              $tokenized = $len ? substr ( $response, $pos, $len ) : "";
 1483              $pos += $len;
 1484   
 1485              list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );    $pos += 4;
 1486              $normalized = $len ? substr ( $response, $pos, $len ) : "";
 1487              $pos += $len;
 1488   
 1489              $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
 1490   
 1491              if ( $hits )
 1492              {
 1493                  list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
 1494                  $pos += 8;
 1495                  $res [$i]["docs"] = $ndocs;
 1496                  $res [$i]["hits"] = $nhits;
 1497              }
 1498   
 1499              if ( $pos > $rlen )
 1500              {
 1501                  $this->_error = "incomplete reply";
 1502                  $this->_MBPop ();
 1503                  return false;
 1504              }
 1505          }
 1506   
 1507          $this->_MBPop ();
 1508          return $res;
 1509      }
 1510   
 1511      function EscapeString ( $string )
 1512      {
 1513          $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' );
 1514          $to   = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' );
 1515   
 1516          return str_replace ( $from, $to, $string );
 1517      }
 1518   
 1519      /////////////////////////////////////////////////////////////////////////////
 1520      // attribute updates
 1521      /////////////////////////////////////////////////////////////////////////////
 1522   
 1523      /// batch update given attributes in given rows in given indexes
 1524      /// returns amount of updated documents (0 or more) on success, or -1 on failure
 1525      function UpdateAttributes ( $index, $attrs, $values, $mva=false )
 1526      {
 1527          // verify everything
 1528          assert ( is_string($index) );
 1529          assert ( is_bool($mva) );
 1530   
 1531          assert ( is_array($attrs) );
 1532          foreach ( $attrs as $attr )
 1533              assert ( is_string($attr) );
 1534   
 1535          assert ( is_array($values) );
 1536          foreach ( $values as $id=>$entry )
 1537          {
 1538              assert ( is_numeric($id) );
 1539              assert ( is_array($entry) );
 1540              assert ( count($entry)==count($attrs) );
 1541              foreach ( $entry as $v )
 1542              {
 1543                  if ( $mva )
 1544                  {
 1545                      assert ( is_array($v) );
 1546                      foreach ( $v as $vv )
 1547                          assert ( is_int($vv) );
 1548                  } else
 1549                      assert ( is_int($v) );
 1550              }
 1551          }
 1552   
 1553          // build request
 1554          $this->_MBPush ();
 1555          $req = pack ( "N", strlen($index) ) . $index;
 1556   
 1557          $req .= pack ( "N", count($attrs) );
 1558          foreach ( $attrs as $attr )
 1559          {
 1560              $req .= pack ( "N", strlen($attr) ) . $attr;
 1561              $req .= pack ( "N", $mva ? 1 : 0 );
 1562          }
 1563   
 1564          $req .= pack ( "N", count($values) );
 1565          foreach ( $values as $id=>$entry )
 1566          {
 1567              $req .= sphPackU64 ( $id );
 1568              foreach ( $entry as $v )
 1569              {
 1570                  $req .= pack ( "N", $mva ? count($v) : $v );
 1571                  if ( $mva )
 1572                      foreach ( $v as $vv )
 1573                          $req .= pack ( "N", $vv );
 1574              }
 1575          }
 1576   
 1577          // connect, send query, get response
 1578          if (!( $fp = $this->_Connect() ))
 1579          {
 1580              $this->_MBPop ();
 1581              return -1;
 1582          }
 1583   
 1584          $len = strlen($req);
 1585          $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
 1586          if ( !$this->_Send ( $fp, $req, $len+8 ) )
 1587          {
 1588              $this->_MBPop ();
 1589              return -1;
 1590          }
 1591   
 1592          if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
 1593          {
 1594              $this->_MBPop ();
 1595              return -1;
 1596          }
 1597   
 1598          // parse response
 1599          list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
 1600          $this->_MBPop ();
 1601          return $updated;
 1602      }
 1603   
 1604      /////////////////////////////////////////////////////////////////////////////
 1605      // persistent connections
 1606      /////////////////////////////////////////////////////////////////////////////
 1607   
 1608      function Open()
 1609      {
 1610          if ( $this->_socket !== false )
 1611          {
 1612              $this->_error = 'already connected';
 1613              return false;
 1614          }
 1615          if ( !$fp = $this->_Connect() )
 1616              return false;
 1617   
 1618          // command, command version = 0, body length = 4, body = 1
 1619          $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
 1620          if ( !$this->_Send ( $fp, $req, 12 ) )
 1621              return false;
 1622   
 1623          $this->_socket = $fp;
 1624          return true;
 1625      }
 1626   
 1627      function Close()
 1628      {
 1629          if ( $this->_socket === false )
 1630          {
 1631              $this->_error = 'not connected';
 1632              return false;
 1633          }
 1634   
 1635          fclose ( $this->_socket );
 1636          $this->_socket = false;
 1637          
 1638          return true;
 1639      }
 1640   
 1641      //////////////////////////////////////////////////////////////////////////
 1642      // status
 1643      //////////////////////////////////////////////////////////////////////////
 1644   
 1645      function Status ()
 1646      {
 1647          $this->_MBPush ();
 1648          if (!( $fp = $this->_Connect() ))
 1649          {
 1650              $this->_MBPop();
 1651              return false;
 1652          }
 1653   
 1654          $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
 1655          if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
 1656               !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
 1657          {
 1658              $this->_MBPop ();
 1659              return false;
 1660          }
 1661   
 1662          $res = substr ( $response, 4 ); // just ignore length, error handling, etc
 1663          $p = 0;
 1664          list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
 1665   
 1666          $res = array();
 1667          for ( $i=0; $i<$rows; $i++ )
 1668              for ( $j=0; $j<$cols; $j++ )
 1669          {
 1670              list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 1671              $res[$i][] = substr ( $response, $p, $len ); $p += $len;
 1672          }
 1673   
 1674          $this->_MBPop ();
 1675          return $res;
 1676      }
 1677   
 1678      //////////////////////////////////////////////////////////////////////////
 1679      // flush
 1680      //////////////////////////////////////////////////////////////////////////
 1681   
 1682      function FlushAttributes ()
 1683      {
 1684          $this->_MBPush ();
 1685          if (!( $fp = $this->_Connect() ))
 1686          {
 1687              $this->_MBPop();
 1688              return -1;
 1689          }
 1690   
 1691          $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
 1692          if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
 1693               !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
 1694          {
 1695              $this->_MBPop ();
 1696              return -1;
 1697          }
 1698   
 1699          $tag = -1;
 1700          if ( strlen($response)==4 )
 1701              list(,$tag) = unpack ( "N*", $response );
 1702          else
 1703              $this->_error = "unexpected response length";
 1704   
 1705          $this->_MBPop ();
 1706          return $tag;
 1707      }
 1708  }
 1709   
 1710  //
 1711  // $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $
 1712  //
 1713