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