Completed
Push — master ( f04c00...1765d7 )
by Peter
03:10
created
src/SphinxClient.php 1 patch
Indentation   +1665 added lines, -1665 removed lines patch added patch discarded remove patch
@@ -140,1701 +140,1701 @@
 block discarded – undo
140 140
 /// pack 64-bit signed
141 141
 function sphPackI64($v)
142 142
 {
143
-    assert(is_numeric($v));
144
-
145
-    // x64
146
-    if (PHP_INT_SIZE>=8)
147
-    {
148
-        $v = (int)$v;
149
-        return pack("NN", $v>>32, $v&0xFFFFFFFF);
150
-    }
151
-
152
-    // x32, int
153
-    if (is_int($v))
154
-        return pack("NN", $v < 0 ? -1 : 0, $v);
155
-
156
-    // x32, bcmath
157
-    if (function_exists("bcmul"))
158
-    {
159
-        if (bccomp($v, 0) == -1)
160
-            $v = bcadd("18446744073709551616", $v);
161
-        $h = bcdiv($v, "4294967296", 0);
162
-        $l = bcmod($v, "4294967296");
163
-        return pack("NN", (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
164
-    }
165
-
166
-    // x32, no-bcmath
167
-    $p = max(0, strlen($v) - 13);
168
-    $lo = abs((float)substr($v, $p));
169
-    $hi = abs((float)substr($v, 0, $p));
170
-
171
-    $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
172
-    $q = floor($m/4294967296.0);
173
-    $l = $m - ($q*4294967296.0);
174
-    $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
175
-
176
-    if ($v<0)
177
-    {
178
-        if ($l==0)
179
-            $h = 4294967296.0 - $h;
180
-        else
181
-        {
182
-            $h = 4294967295.0 - $h;
183
-            $l = 4294967296.0 - $l;
184
-        }
185
-    }
186
-    return pack("NN", $h, $l);
143
+	assert(is_numeric($v));
144
+
145
+	// x64
146
+	if (PHP_INT_SIZE>=8)
147
+	{
148
+		$v = (int)$v;
149
+		return pack("NN", $v>>32, $v&0xFFFFFFFF);
150
+	}
151
+
152
+	// x32, int
153
+	if (is_int($v))
154
+		return pack("NN", $v < 0 ? -1 : 0, $v);
155
+
156
+	// x32, bcmath
157
+	if (function_exists("bcmul"))
158
+	{
159
+		if (bccomp($v, 0) == -1)
160
+			$v = bcadd("18446744073709551616", $v);
161
+		$h = bcdiv($v, "4294967296", 0);
162
+		$l = bcmod($v, "4294967296");
163
+		return pack("NN", (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
164
+	}
165
+
166
+	// x32, no-bcmath
167
+	$p = max(0, strlen($v) - 13);
168
+	$lo = abs((float)substr($v, $p));
169
+	$hi = abs((float)substr($v, 0, $p));
170
+
171
+	$m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
172
+	$q = floor($m/4294967296.0);
173
+	$l = $m - ($q*4294967296.0);
174
+	$h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
175
+
176
+	if ($v<0)
177
+	{
178
+		if ($l==0)
179
+			$h = 4294967296.0 - $h;
180
+		else
181
+		{
182
+			$h = 4294967295.0 - $h;
183
+			$l = 4294967296.0 - $l;
184
+		}
185
+	}
186
+	return pack("NN", $h, $l);
187 187
 }
188 188
 
189 189
 /// pack 64-bit unsigned
190 190
 function sphPackU64($v)
191 191
 {
192
-    assert(is_numeric($v));
193
-
194
-    // x64
195
-    if (PHP_INT_SIZE>=8)
196
-    {
197
-        assert($v>=0);
198
-
199
-        // x64, int
200
-        if (is_int($v))
201
-            return pack("NN", $v>>32, $v&0xFFFFFFFF);
202
-
203
-        // x64, bcmath
204
-        if (function_exists("bcmul"))
205
-        {
206
-            $h = bcdiv($v, 4294967296, 0);
207
-            $l = bcmod($v, 4294967296);
208
-            return pack("NN", $h, $l);
209
-        }
210
-
211
-        // x64, no-bcmath
212
-        $p = max(0, strlen($v) - 13);
213
-        $lo = (int)substr($v, $p);
214
-        $hi = (int)substr($v, 0, $p);
215
-
216
-        $m = $lo + $hi*1316134912;
217
-        $l = $m % 4294967296;
218
-        $h = $hi*2328 + (int)($m/4294967296);
219
-
220
-        return pack("NN", $h, $l);
221
-    }
222
-
223
-    // x32, int
224
-    if (is_int($v))
225
-        return pack("NN", 0, $v);
226
-
227
-    // x32, bcmath
228
-    if (function_exists("bcmul"))
229
-    {
230
-        $h = bcdiv($v, "4294967296", 0);
231
-        $l = bcmod($v, "4294967296");
232
-        return pack("NN", (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
233
-    }
234
-
235
-    // x32, no-bcmath
236
-    $p = max(0, strlen($v) - 13);
237
-    $lo = (float)substr($v, $p);
238
-    $hi = (float)substr($v, 0, $p);
239
-
240
-    $m = $lo + $hi*1316134912.0;
241
-    $q = floor($m / 4294967296.0);
242
-    $l = $m - ($q * 4294967296.0);
243
-    $h = $hi*2328.0 + $q;
244
-
245
-    return pack("NN", $h, $l);
192
+	assert(is_numeric($v));
193
+
194
+	// x64
195
+	if (PHP_INT_SIZE>=8)
196
+	{
197
+		assert($v>=0);
198
+
199
+		// x64, int
200
+		if (is_int($v))
201
+			return pack("NN", $v>>32, $v&0xFFFFFFFF);
202
+
203
+		// x64, bcmath
204
+		if (function_exists("bcmul"))
205
+		{
206
+			$h = bcdiv($v, 4294967296, 0);
207
+			$l = bcmod($v, 4294967296);
208
+			return pack("NN", $h, $l);
209
+		}
210
+
211
+		// x64, no-bcmath
212
+		$p = max(0, strlen($v) - 13);
213
+		$lo = (int)substr($v, $p);
214
+		$hi = (int)substr($v, 0, $p);
215
+
216
+		$m = $lo + $hi*1316134912;
217
+		$l = $m % 4294967296;
218
+		$h = $hi*2328 + (int)($m/4294967296);
219
+
220
+		return pack("NN", $h, $l);
221
+	}
222
+
223
+	// x32, int
224
+	if (is_int($v))
225
+		return pack("NN", 0, $v);
226
+
227
+	// x32, bcmath
228
+	if (function_exists("bcmul"))
229
+	{
230
+		$h = bcdiv($v, "4294967296", 0);
231
+		$l = bcmod($v, "4294967296");
232
+		return pack("NN", (float)$h, (float)$l); // conversion to float is intentional; int would lose 31st bit
233
+	}
234
+
235
+	// x32, no-bcmath
236
+	$p = max(0, strlen($v) - 13);
237
+	$lo = (float)substr($v, $p);
238
+	$hi = (float)substr($v, 0, $p);
239
+
240
+	$m = $lo + $hi*1316134912.0;
241
+	$q = floor($m / 4294967296.0);
242
+	$l = $m - ($q * 4294967296.0);
243
+	$h = $hi*2328.0 + $q;
244
+
245
+	return pack("NN", $h, $l);
246 246
 }
247 247
 
248 248
 // unpack 64-bit unsigned
249 249
 function sphUnpackU64($v)
250 250
 {
251
-    list($hi, $lo) = array_values(unpack("N*N*", $v));
252
-
253
-    if (PHP_INT_SIZE>=8)
254
-    {
255
-        if ($hi<0) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
256
-        if ($lo<0) $lo += (1<<32);
257
-
258
-        // x64, int
259
-        if ($hi<=2147483647)
260
-            return ($hi<<32) + $lo;
261
-
262
-        // x64, bcmath
263
-        if (function_exists("bcmul"))
264
-            return bcadd($lo, bcmul($hi, "4294967296"));
265
-
266
-        // x64, no-bcmath
267
-        $C = 100000;
268
-        $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
269
-        $l = (($hi % $C) << 32) + ($lo % $C);
270
-        if ($l>$C)
271
-        {
272
-            $h += (int)($l / $C);
273
-            $l  = $l % $C;
274
-        }
275
-
276
-        if ($h==0)
277
-            return $l;
278
-        return sprintf("%d%05d", $h, $l);
279
-    }
280
-
281
-    // x32, int
282
-    if ($hi==0)
283
-    {
284
-        if ($lo>0)
285
-            return $lo;
286
-        return sprintf("%u", $lo);
287
-    }
288
-
289
-    $hi = sprintf("%u", $hi);
290
-    $lo = sprintf("%u", $lo);
291
-
292
-    // x32, bcmath
293
-    if (function_exists("bcmul"))
294
-        return bcadd($lo, bcmul($hi, "4294967296"));
295
-
296
-    // x32, no-bcmath
297
-    $hi = (float)$hi;
298
-    $lo = (float)$lo;
299
-
300
-    $q = floor($hi/10000000.0);
301
-    $r = $hi - $q*10000000.0;
302
-    $m = $lo + $r*4967296.0;
303
-    $mq = floor($m/10000000.0);
304
-    $l = $m - $mq*10000000.0;
305
-    $h = $q*4294967296.0 + $r*429.0 + $mq;
306
-
307
-    $h = sprintf("%.0f", $h);
308
-    $l = sprintf("%07.0f", $l);
309
-    if ($h=="0")
310
-        return sprintf( "%.0f", (float)$l);
311
-    return $h . $l;
251
+	list($hi, $lo) = array_values(unpack("N*N*", $v));
252
+
253
+	if (PHP_INT_SIZE>=8)
254
+	{
255
+		if ($hi<0) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
256
+		if ($lo<0) $lo += (1<<32);
257
+
258
+		// x64, int
259
+		if ($hi<=2147483647)
260
+			return ($hi<<32) + $lo;
261
+
262
+		// x64, bcmath
263
+		if (function_exists("bcmul"))
264
+			return bcadd($lo, bcmul($hi, "4294967296"));
265
+
266
+		// x64, no-bcmath
267
+		$C = 100000;
268
+		$h = ((int)($hi / $C) << 32) + (int)($lo / $C);
269
+		$l = (($hi % $C) << 32) + ($lo % $C);
270
+		if ($l>$C)
271
+		{
272
+			$h += (int)($l / $C);
273
+			$l  = $l % $C;
274
+		}
275
+
276
+		if ($h==0)
277
+			return $l;
278
+		return sprintf("%d%05d", $h, $l);
279
+	}
280
+
281
+	// x32, int
282
+	if ($hi==0)
283
+	{
284
+		if ($lo>0)
285
+			return $lo;
286
+		return sprintf("%u", $lo);
287
+	}
288
+
289
+	$hi = sprintf("%u", $hi);
290
+	$lo = sprintf("%u", $lo);
291
+
292
+	// x32, bcmath
293
+	if (function_exists("bcmul"))
294
+		return bcadd($lo, bcmul($hi, "4294967296"));
295
+
296
+	// x32, no-bcmath
297
+	$hi = (float)$hi;
298
+	$lo = (float)$lo;
299
+
300
+	$q = floor($hi/10000000.0);
301
+	$r = $hi - $q*10000000.0;
302
+	$m = $lo + $r*4967296.0;
303
+	$mq = floor($m/10000000.0);
304
+	$l = $m - $mq*10000000.0;
305
+	$h = $q*4294967296.0 + $r*429.0 + $mq;
306
+
307
+	$h = sprintf("%.0f", $h);
308
+	$l = sprintf("%07.0f", $l);
309
+	if ($h=="0")
310
+		return sprintf( "%.0f", (float)$l);
311
+	return $h . $l;
312 312
 }
313 313
 
314 314
 // unpack 64-bit signed
315 315
 function sphUnpackI64($v)
316 316
 {
317
-    list($hi, $lo) = array_values(unpack("N*N*", $v));
318
-
319
-    // x64
320
-    if (PHP_INT_SIZE>=8)
321
-    {
322
-        if ($hi<0) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
323
-        if ($lo<0) $lo += (1<<32);
324
-
325
-        return ($hi<<32) + $lo;
326
-    }
327
-
328
-    // x32, int
329
-    if ($hi==0)
330
-    {
331
-        if ($lo>0)
332
-            return $lo;
333
-        return sprintf("%u", $lo);
334
-    }
335
-    // x32, int
336
-    elseif ($hi==-1)
337
-    {
338
-        if ($lo<0)
339
-            return $lo;
340
-        return sprintf("%.0f", $lo - 4294967296.0);
341
-    }
342
-
343
-    $neg = "";
344
-    $c = 0;
345
-    if ($hi<0)
346
-    {
347
-        $hi = ~$hi;
348
-        $lo = ~$lo;
349
-        $c = 1;
350
-        $neg = "-";
351
-    }
352
-
353
-    $hi = sprintf("%u", $hi);
354
-    $lo = sprintf("%u", $lo);
355
-
356
-    // x32, bcmath
357
-    if (function_exists("bcmul"))
358
-        return $neg . bcadd(bcadd($lo, bcmul($hi, "4294967296")), $c);
359
-
360
-    // x32, no-bcmath
361
-    $hi = (float)$hi;
362
-    $lo = (float)$lo;
363
-
364
-    $q = floor($hi/10000000.0);
365
-    $r = $hi - $q*10000000.0;
366
-    $m = $lo + $r*4967296.0;
367
-    $mq = floor($m/10000000.0);
368
-    $l = $m - $mq*10000000.0 + $c;
369
-    $h = $q*4294967296.0 + $r*429.0 + $mq;
370
-    if ($l==10000000)
371
-    {
372
-        $l = 0;
373
-        $h += 1;
374
-    }
375
-
376
-    $h = sprintf("%.0f", $h);
377
-    $l = sprintf("%07.0f", $l);
378
-    if ($h=="0")
379
-        return $neg . sprintf( "%.0f", (float)$l);
380
-    return $neg . $h . $l;
317
+	list($hi, $lo) = array_values(unpack("N*N*", $v));
318
+
319
+	// x64
320
+	if (PHP_INT_SIZE>=8)
321
+	{
322
+		if ($hi<0) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
323
+		if ($lo<0) $lo += (1<<32);
324
+
325
+		return ($hi<<32) + $lo;
326
+	}
327
+
328
+	// x32, int
329
+	if ($hi==0)
330
+	{
331
+		if ($lo>0)
332
+			return $lo;
333
+		return sprintf("%u", $lo);
334
+	}
335
+	// x32, int
336
+	elseif ($hi==-1)
337
+	{
338
+		if ($lo<0)
339
+			return $lo;
340
+		return sprintf("%.0f", $lo - 4294967296.0);
341
+	}
342
+
343
+	$neg = "";
344
+	$c = 0;
345
+	if ($hi<0)
346
+	{
347
+		$hi = ~$hi;
348
+		$lo = ~$lo;
349
+		$c = 1;
350
+		$neg = "-";
351
+	}
352
+
353
+	$hi = sprintf("%u", $hi);
354
+	$lo = sprintf("%u", $lo);
355
+
356
+	// x32, bcmath
357
+	if (function_exists("bcmul"))
358
+		return $neg . bcadd(bcadd($lo, bcmul($hi, "4294967296")), $c);
359
+
360
+	// x32, no-bcmath
361
+	$hi = (float)$hi;
362
+	$lo = (float)$lo;
363
+
364
+	$q = floor($hi/10000000.0);
365
+	$r = $hi - $q*10000000.0;
366
+	$m = $lo + $r*4967296.0;
367
+	$mq = floor($m/10000000.0);
368
+	$l = $m - $mq*10000000.0 + $c;
369
+	$h = $q*4294967296.0 + $r*429.0 + $mq;
370
+	if ($l==10000000)
371
+	{
372
+		$l = 0;
373
+		$h += 1;
374
+	}
375
+
376
+	$h = sprintf("%.0f", $h);
377
+	$l = sprintf("%07.0f", $l);
378
+	if ($h=="0")
379
+		return $neg . sprintf( "%.0f", (float)$l);
380
+	return $neg . $h . $l;
381 381
 }
382 382
 
383 383
 
384 384
 function sphFixUint($value)
385 385
 {
386
-    if (PHP_INT_SIZE>=8)
387
-    {
388
-        // x64 route, workaround broken unpack() in 5.2.2+
389
-        if ($value<0) $value += (1<<32);
390
-        return $value;
391
-    }
392
-    else
393
-    {
394
-        // x32 route, workaround php signed/unsigned braindamage
395
-        return sprintf("%u", $value);
396
-    }
386
+	if (PHP_INT_SIZE>=8)
387
+	{
388
+		// x64 route, workaround broken unpack() in 5.2.2+
389
+		if ($value<0) $value += (1<<32);
390
+		return $value;
391
+	}
392
+	else
393
+	{
394
+		// x32 route, workaround php signed/unsigned braindamage
395
+		return sprintf("%u", $value);
396
+	}
397 397
 }
398 398
 
399 399
 function sphSetBit($flag, $bit, $on)
400 400
 {
401
-    if ($on)
402
-    {
403
-        $flag |=(1<<$bit);
404
-    } else
405
-    {
406
-        $reset = 16777215 ^(1<<$bit);
407
-        $flag = $flag & $reset;
408
-    }
409
-    return $flag;
401
+	if ($on)
402
+	{
403
+		$flag |=(1<<$bit);
404
+	} else
405
+	{
406
+		$reset = 16777215 ^(1<<$bit);
407
+		$flag = $flag & $reset;
408
+	}
409
+	return $flag;
410 410
 }
411 411
 
412 412
 
413 413
 /// sphinx searchd client class
414 414
 class SphinxClient
415 415
 {
416
-    var $_host; ///< searchd host (default is "localhost")
417
-    var $_port; ///< searchd port (default is 9312)
418
-    var $_offset; ///< how many records to seek from result-set start (default is 0)
419
-    var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
420
-    var $_mode; ///< query matching mode (default is SPH_MATCH_EXTENDED2)
421
-    var $_weights; ///< per-field weights (default is 1 for all fields)
422
-    var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
423
-    var $_sortby; ///< attribute to sort by (defualt is "")
424
-    var $_min_id; ///< min ID to match (default is 0, which means no limit)
425
-    var $_max_id; ///< max ID to match (default is 0, which means no limit)
426
-    var $_filters; ///< search filters
427
-    var $_groupby; ///< group-by attribute name
428
-    var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
429
-    var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
430
-    var $_groupdistinct; ///< group-by count-distinct attribute
431
-    var $_maxmatches; ///< max matches to retrieve
432
-    var $_cutoff; ///< cutoff to stop searching at (default is 0)
433
-    var $_retrycount; ///< distributed retries count
434
-    var $_retrydelay; ///< distributed retries delay
435
-    var $_anchor; ///< geographical anchor point
436
-    var $_indexweights; ///< per-index weights
437
-    var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
438
-    var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR)
439
-    var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
440
-    var $_fieldweights; ///< per-field-name weights
441
-    var $_overrides; ///< per-query attribute values overrides
442
-    var $_select; ///< select-list (attributes or expressions, with optional aliases)
443
-    var $_query_flags; ///< per-query various flags
444
-    var $_predictedtime; ///< per-query max_predicted_time
445
-    var $_outerorderby; ///< outer match sort by
446
-    var $_outeroffset; ///< outer offset
447
-    var $_outerlimit; ///< outer limit
448
-    var $_hasouter;
449
-
450
-    var $_error; ///< last error message
451
-    var $_warning; ///< last warning message
452
-    var $_connerror; ///< connection error vs remote error flag
453
-
454
-    var $_reqs; ///< requests array for multi-query
455
-    var $_mbenc; ///< stored mbstring encoding
456
-    var $_arrayresult; ///< whether $result["matches"] should be a hash or an array
457
-    var $_timeout; ///< connect timeout
458
-
459
-    /////////////////////////////////////////////////////////////////////////////
460
-    // common stuff
461
-    /////////////////////////////////////////////////////////////////////////////
462
-
463
-    /// create a new client object and fill defaults
464
-    function SphinxClient ()
465
-    {
466
-        // per-client-object settings
467
-        $this->_host = "localhost";
468
-        $this->_port = 9312;
469
-        $this->_path = false;
470
-        $this->_socket = false;
471
-
472
-        // per-query settings
473
-        $this->_offset = 0;
474
-        $this->_limit = 20;
475
-        $this->_mode = SPH_MATCH_EXTENDED2;
476
-        $this->_weights = array ();
477
-        $this->_sort = SPH_SORT_RELEVANCE;
478
-        $this->_sortby = "";
479
-        $this->_min_id = 0;
480
-        $this->_max_id = 0;
481
-        $this->_filters = array ();
482
-        $this->_groupby = "";
483
-        $this->_groupfunc = SPH_GROUPBY_DAY;
484
-        $this->_groupsort = "@group desc";
485
-        $this->_groupdistinct = "";
486
-        $this->_maxmatches = 1000;
487
-        $this->_cutoff = 0;
488
-        $this->_retrycount = 0;
489
-        $this->_retrydelay = 0;
490
-        $this->_anchor = array ();
491
-        $this->_indexweights = array ();
492
-        $this->_ranker = SPH_RANK_PROXIMITY_BM25;
493
-        $this->_rankexpr = "";
494
-        $this->_maxquerytime = 0;
495
-        $this->_fieldweights = array();
496
-        $this->_overrides = array();
497
-        $this->_select = "*";
498
-        $this->_query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
499
-        $this->_predictedtime = 0;
500
-        $this->_outerorderby = "";
501
-        $this->_outeroffset = 0;
502
-        $this->_outerlimit = 0;
503
-        $this->_hasouter = false;
504
-
505
-        $this->_error = ""; // per-reply fields (for single-query case)
506
-        $this->_warning = "";
507
-        $this->_connerror = false;
508
-
509
-        $this->_reqs = array ();// requests storage (for multi-query case)
510
-        $this->_mbenc = "";
511
-        $this->_arrayresult = false;
512
-        $this->_timeout = 0;
513
-    }
514
-
515
-    function __destruct()
516
-    {
517
-        if ($this->_socket !== false)
518
-            fclose($this->_socket);
519
-    }
520
-
521
-    /// get last error message (string)
522
-    function GetLastError ()
523
-    {
524
-        return $this->_error;
525
-    }
526
-
527
-    /// get last warning message (string)
528
-    function GetLastWarning ()
529
-    {
530
-        return $this->_warning;
531
-    }
532
-
533
-    /// get last error flag (to tell network connection errors from searchd errors or broken responses)
534
-    function IsConnectError()
535
-    {
536
-        return $this->_connerror;
537
-    }
538
-
539
-    /// set searchd host name (string) and port (integer)
540
-    function SetServer($host, $port = 0)
541
-    {
542
-        assert(is_string($host));
543
-        if ($host[0] == '/')
544
-        {
545
-            $this->_path = 'unix://' . $host;
546
-            return;
547
-        }
548
-        if (substr($host, 0, 7)=="unix://")
549
-        {
550
-            $this->_path = $host;
551
-            return;
552
-        }
553
-
554
-        $this->_host = $host;
555
-        $port = intval($port);
556
-        assert(0<=$port && $port<65536);
557
-        $this->_port =($port==0) ? 9312 : $port;
558
-        $this->_path = '';
559
-    }
560
-
561
-    /// set server connection timeout (0 to remove)
562
-    function SetConnectTimeout($timeout)
563
-    {
564
-        assert(is_numeric($timeout));
565
-        $this->_timeout = $timeout;
566
-    }
567
-
568
-
569
-    function _Send($handle, $data, $length)
570
-    {
571
-        if (feof($handle) || fwrite($handle, $data, $length) !== $length)
572
-        {
573
-            $this->_error = 'connection unexpectedly closed (timed out?)';
574
-            $this->_connerror = true;
575
-            return false;
576
-        }
577
-        return true;
578
-    }
579
-
580
-    /////////////////////////////////////////////////////////////////////////////
581
-
582
-    /// enter mbstring workaround mode
583
-    function _MBPush ()
584
-    {
585
-        $this->_mbenc = "";
586
-        if (ini_get("mbstring.func_overload") & 2)
587
-        {
588
-            $this->_mbenc = mb_internal_encoding();
589
-            mb_internal_encoding("latin1");
590
-        }
591
-    }
592
-
593
-    /// leave mbstring workaround mode
594
-    function _MBPop ()
595
-    {
596
-        if ($this->_mbenc)
597
-            mb_internal_encoding($this->_mbenc);
598
-    }
599
-
600
-    /// connect to searchd server
601
-    function _Connect ()
602
-    {
603
-        if ($this->_socket!==false)
604
-        {
605
-            // we are in persistent connection mode, so we have a socket
606
-            // however, need to check whether it's still alive
607
-            if (!@feof($this->_socket))
608
-                return $this->_socket;
609
-
610
-            // force reopen
611
-            $this->_socket = false;
612
-        }
613
-
614
-        $errno = 0;
615
-        $errstr = "";
616
-        $this->_connerror = false;
617
-
618
-        if ($this->_path)
619
-        {
620
-            $host = $this->_path;
621
-            $port = 0;
622
-        }
623
-        else
624
-        {
625
-            $host = $this->_host;
626
-            $port = $this->_port;
627
-        }
628
-
629
-        if ($this->_timeout<=0)
630
-            $fp = @fsockopen($host, $port, $errno, $errstr);
631
-        else
632
-            $fp = @fsockopen($host, $port, $errno, $errstr, $this->_timeout);
633
-
634
-        if (!$fp)
635
-        {
636
-            if ($this->_path)
637
-                $location = $this->_path;
638
-            else
639
-                $location = "{$this->_host}:{$this->_port}";
640
-
641
-            $errstr = trim($errstr);
642
-            $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
643
-            $this->_connerror = true;
644
-            return false;
645
-        }
646
-
647
-        // send my version
648
-        // this is a subtle part. we must do it before (!) reading back from searchd.
649
-        // because otherwise under some conditions (reported on FreeBSD for instance)
650
-        // TCP stack could throttle write-write-read pattern because of Nagle.
651
-        if (!$this->_Send($fp, pack("N", 1), 4))
652
-        {
653
-            fclose($fp);
654
-            $this->_error = "failed to send client protocol version";
655
-            return false;
656
-        }
657
-
658
-        // check version
659
-        list(,$v) = unpack("N*", fread($fp, 4));
660
-        $v = (int)$v;
661
-        if ($v<1)
662
-        {
663
-            fclose($fp);
664
-            $this->_error = "expected searchd protocol version 1+, got version '$v'";
665
-            return false;
666
-        }
667
-
668
-        return $fp;
669
-    }
670
-
671
-    /// get and check response packet from searchd server
672
-    function _GetResponse($fp, $client_ver)
673
-    {
674
-        $response = "";
675
-        $len = 0;
676
-
677
-        $header = fread($fp, 8);
678
-        if (strlen($header)==8)
679
-        {
680
-            list($status, $ver, $len) = array_values(unpack("n2a/Nb", $header));
681
-            $left = $len;
682
-            while ($left>0 && !feof($fp))
683
-            {
684
-                $chunk = fread($fp, min(8192, $left));
685
-                if ($chunk)
686
-                {
687
-                    $response .= $chunk;
688
-                    $left -= strlen($chunk);
689
-                }
690
-            }
691
-        }
692
-        if ($this->_socket === false)
693
-            fclose($fp);
694
-
695
-        // check response
696
-        $read = strlen($response);
697
-        if (!$response || $read!=$len)
698
-        {
699
-            $this->_error = $len
700
-                ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
701
-                : "received zero-sized searchd response";
702
-            return false;
703
-        }
704
-
705
-        // check status
706
-        if ($status==SEARCHD_WARNING)
707
-        {
708
-            list(,$wlen) = unpack("N*", substr($response, 0, 4));
709
-            $this->_warning = substr($response, 4, $wlen);
710
-            return substr($response, 4+$wlen);
711
-        }
712
-        if ($status==SEARCHD_ERROR)
713
-        {
714
-            $this->_error = "searchd error: " . substr($response, 4);
715
-            return false;
716
-        }
717
-        if ($status==SEARCHD_RETRY)
718
-        {
719
-            $this->_error = "temporary searchd error: " . substr($response, 4);
720
-            return false;
721
-        }
722
-        if ($status!=SEARCHD_OK)
723
-        {
724
-            $this->_error = "unknown status code '$status'";
725
-            return false;
726
-        }
727
-
728
-        // check version
729
-        if ($ver<$client_ver)
730
-        {
731
-            $this->_warning = sprintf("searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
732
-                $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff);
733
-        }
734
-
735
-        return $response;
736
-    }
737
-
738
-    /////////////////////////////////////////////////////////////////////////////
739
-    // searching
740
-    /////////////////////////////////////////////////////////////////////////////
741
-
742
-    /// set offset and count into result set,
743
-    /// and optionally set max-matches and cutoff limits
744
-    function SetLimits($offset, $limit, $max=0, $cutoff=0)
745
-    {
746
-        assert(is_int($offset));
747
-        assert(is_int($limit));
748
-        assert($offset>=0);
749
-        assert($limit>0);
750
-        assert($max>=0);
751
-        $this->_offset = $offset;
752
-        $this->_limit = $limit;
753
-        if ($max>0)
754
-            $this->_maxmatches = $max;
755
-        if ($cutoff>0)
756
-            $this->_cutoff = $cutoff;
757
-    }
758
-
759
-    /// set maximum query time, in milliseconds, per-index
760
-    /// integer, 0 means "do not limit"
761
-    function SetMaxQueryTime($max)
762
-    {
763
-        assert(is_int($max));
764
-        assert($max>=0);
765
-        $this->_maxquerytime = $max;
766
-    }
767
-
768
-    /// set matching mode
769
-    function SetMatchMode($mode)
770
-    {
771
-        trigger_error('DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API', E_USER_DEPRECATED);
772
-        assert($mode==SPH_MATCH_ALL
773
-            || $mode==SPH_MATCH_ANY
774
-            || $mode==SPH_MATCH_PHRASE
775
-            || $mode==SPH_MATCH_BOOLEAN
776
-            || $mode==SPH_MATCH_EXTENDED
777
-            || $mode==SPH_MATCH_FULLSCAN
778
-            || $mode==SPH_MATCH_EXTENDED2);
779
-        $this->_mode = $mode;
780
-    }
781
-
782
-    /// set ranking mode
783
-    function SetRankingMode($ranker, $rankexpr="")
784
-    {
785
-        assert($ranker===0 || $ranker>=1 && $ranker<SPH_RANK_TOTAL);
786
-        assert(is_string($rankexpr));
787
-        $this->_ranker = $ranker;
788
-        $this->_rankexpr = $rankexpr;
789
-    }
790
-
791
-    /// set matches sorting mode
792
-    function SetSortMode($mode, $sortby="")
793
-    {
794
-        assert (
795
-            $mode==SPH_SORT_RELEVANCE ||
796
-            $mode==SPH_SORT_ATTR_DESC ||
797
-            $mode==SPH_SORT_ATTR_ASC ||
798
-            $mode==SPH_SORT_TIME_SEGMENTS ||
799
-            $mode==SPH_SORT_EXTENDED ||
800
-            $mode==SPH_SORT_EXPR);
801
-        assert(is_string($sortby));
802
-        assert($mode==SPH_SORT_RELEVANCE || strlen($sortby)>0);
803
-
804
-        $this->_sort = $mode;
805
-        $this->_sortby = $sortby;
806
-    }
807
-
808
-    /// bind per-field weights by order
809
-    /// DEPRECATED; use SetFieldWeights() instead
810
-    function SetWeights($weights)
811
-    {
812
-        die("This method is now deprecated; please use SetFieldWeights instead");
813
-    }
814
-
815
-    /// bind per-field weights by name
816
-    function SetFieldWeights($weights)
817
-    {
818
-        assert(is_array($weights));
819
-        foreach ($weights as $name=>$weight)
820
-        {
821
-            assert(is_string($name));
822
-            assert(is_int($weight));
823
-        }
824
-        $this->_fieldweights = $weights;
825
-    }
826
-
827
-    /// bind per-index weights by name
828
-    function SetIndexWeights($weights)
829
-    {
830
-        assert(is_array($weights));
831
-        foreach ($weights as $index=>$weight)
832
-        {
833
-            assert(is_string($index));
834
-            assert(is_int($weight));
835
-        }
836
-        $this->_indexweights = $weights;
837
-    }
838
-
839
-    /// set IDs range to match
840
-    /// only match records if document ID is beetwen $min and $max (inclusive)
841
-    function SetIDRange($min, $max)
842
-    {
843
-        assert(is_numeric($min));
844
-        assert(is_numeric($max));
845
-        assert($min<=$max);
846
-        $this->_min_id = $min;
847
-        $this->_max_id = $max;
848
-    }
849
-
850
-    /// set values set filter
851
-    /// only match records where $attribute value is in given set
852
-    function SetFilter($attribute, $values, $exclude=false)
853
-    {
854
-        assert(is_string($attribute));
855
-        assert(is_array($values));
856
-        assert(count($values));
857
-
858
-        if (is_array($values) && count($values))
859
-        {
860
-            foreach ($values as $value)
861
-                assert(is_numeric($value));
862
-
863
-            $this->_filters[] = array("type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values);
864
-        }
865
-    }
866
-
867
-    /// set string filter
868
-    /// only match records where $attribute value is equal
869
-    function SetFilterString($attribute, $value, $exclude=false)
870
-    {
871
-        assert(is_string($attribute));
872
-        assert(is_string($value));
873
-        $this->_filters[] = array("type"=>SPH_FILTER_STRING, "attr"=>$attribute, "exclude"=>$exclude, "value"=>$value);
874
-    }    
875
-
876
-    /// set range filter
877
-    /// only match records if $attribute value is beetwen $min and $max (inclusive)
878
-    function SetFilterRange($attribute, $min, $max, $exclude=false)
879
-    {
880
-        assert(is_string($attribute));
881
-        assert(is_numeric($min));
882
-        assert(is_numeric($max));
883
-        assert($min<=$max);
884
-
885
-        $this->_filters[] = array("type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max);
886
-    }
887
-
888
-    /// set float range filter
889
-    /// only match records if $attribute value is beetwen $min and $max (inclusive)
890
-    function SetFilterFloatRange($attribute, $min, $max, $exclude=false)
891
-    {
892
-        assert(is_string($attribute));
893
-        assert(is_float($min));
894
-        assert(is_float($max));
895
-        assert($min<=$max);
896
-
897
-        $this->_filters[] = array("type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max);
898
-    }
899
-
900
-    /// setup anchor point for geosphere distance calculations
901
-    /// required to use @geodist in filters and sorting
902
-    /// latitude and longitude must be in radians
903
-    function SetGeoAnchor($attrlat, $attrlong, $lat, $long)
904
-    {
905
-        assert(is_string($attrlat));
906
-        assert(is_string($attrlong));
907
-        assert(is_float($lat));
908
-        assert(is_float($long));
909
-
910
-        $this->_anchor = array("attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long);
911
-    }
912
-
913
-    /// set grouping attribute and function
914
-    function SetGroupBy($attribute, $func, $groupsort="@group desc")
915
-    {
916
-        assert(is_string($attribute));
917
-        assert(is_string($groupsort));
918
-        assert($func==SPH_GROUPBY_DAY
919
-            || $func==SPH_GROUPBY_WEEK
920
-            || $func==SPH_GROUPBY_MONTH
921
-            || $func==SPH_GROUPBY_YEAR
922
-            || $func==SPH_GROUPBY_ATTR
923
-            || $func==SPH_GROUPBY_ATTRPAIR);
924
-
925
-        $this->_groupby = $attribute;
926
-        $this->_groupfunc = $func;
927
-        $this->_groupsort = $groupsort;
928
-    }
929
-
930
-    /// set count-distinct attribute for group-by queries
931
-    function SetGroupDistinct($attribute)
932
-    {
933
-        assert(is_string($attribute));
934
-        $this->_groupdistinct = $attribute;
935
-    }
936
-
937
-    /// set distributed retries count and delay
938
-    function SetRetries($count, $delay=0)
939
-    {
940
-        assert(is_int($count) && $count>=0);
941
-        assert(is_int($delay) && $delay>=0);
942
-        $this->_retrycount = $count;
943
-        $this->_retrydelay = $delay;
944
-    }
945
-
946
-    /// set result set format (hash or array; hash by default)
947
-    /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
948
-    function SetArrayResult($arrayresult)
949
-    {
950
-        assert(is_bool($arrayresult));
951
-        $this->_arrayresult = $arrayresult;
952
-    }
953
-
954
-    /// set attribute values override
955
-    /// there can be only one override per attribute
956
-    /// $values must be a hash that maps document IDs to attribute values
957
-    function SetOverride($attrname, $attrtype, $values)
958
-    {
959
-        trigger_error('DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.', E_USER_DEPRECATED);
960
-        assert(is_string($attrname));
961
-        assert(in_array($attrtype, array(SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT)));
962
-        assert(is_array($values));
963
-
964
-        $this->_overrides[$attrname] = array("attr"=>$attrname, "type"=>$attrtype, "values"=>$values);
965
-    }
966
-
967
-    /// set select-list (attributes or expressions), SQL-like syntax
968
-    function SetSelect($select)
969
-    {
970
-        assert(is_string($select));
971
-        $this->_select = $select;
972
-    }
973
-
974
-    function SetQueryFlag($flag_name, $flag_value)
975
-    {
976
-        $known_names = array("reverse_scan", "sort_method", "max_predicted_time", "boolean_simplify", "idf", "global_idf", "low_priority");
977
-        $flags = array (
978
-        "reverse_scan" => array(0, 1),
979
-        "sort_method" => array("pq", "kbuffer"),
980
-        "max_predicted_time" => array(0),
981
-        "boolean_simplify" => array(true, false),
982
-        "idf" => array ("normalized", "plain", "tfidf_normalized", "tfidf_unnormalized"),
983
-        "global_idf" => array(true, false),
984
-        "low_priority" => array(true, false)
985
-        );
986
-
987
-        assert(isset($flag_name, $known_names));
988
-        assert(in_array( $flag_value, $flags[$flag_name], true) ||($flag_name=="max_predicted_time" && is_int($flag_value) && $flag_value>=0));
989
-
990
-        if ($flag_name=="reverse_scan") $this->_query_flags = sphSetBit($this->_query_flags, 0, $flag_value==1);
991
-        if ($flag_name=="sort_method") $this->_query_flags = sphSetBit($this->_query_flags, 1, $flag_value=="kbuffer");
992
-        if ($flag_name=="max_predicted_time")
993
-        {
994
-            $this->_query_flags = sphSetBit($this->_query_flags, 2, $flag_value>0);
995
-            $this->_predictedtime = (int)$flag_value;
996
-        }
997
-        if ($flag_name=="boolean_simplify") $this->_query_flags = sphSetBit($this->_query_flags, 3, $flag_value);
998
-        if ($flag_name=="idf" &&($flag_value=="normalized" || $flag_value=="plain")) $this->_query_flags = sphSetBit($this->_query_flags, 4, $flag_value=="plain");
999
-        if ($flag_name=="global_idf") $this->_query_flags = sphSetBit($this->_query_flags, 5, $flag_value);
1000
-        if ($flag_name=="idf" &&($flag_value=="tfidf_normalized" || $flag_value=="tfidf_unnormalized")) $this->_query_flags = sphSetBit($this->_query_flags, 6, $flag_value=="tfidf_normalized");
1001
-        if ($flag_name=="low_priority") $this->_query_flags = sphSetBit($this->_query_flags, 8, $flag_value);
1002
-    }
1003
-
1004
-    /// set outer order by parameters
1005
-    function SetOuterSelect($orderby, $offset, $limit)
1006
-    {
1007
-        assert(is_string($orderby));
1008
-        assert(is_int($offset));
1009
-        assert(is_int($limit));
1010
-        assert($offset>=0);
1011
-        assert($limit>0);
1012
-
1013
-        $this->_outerorderby = $orderby;
1014
-        $this->_outeroffset = $offset;
1015
-        $this->_outerlimit = $limit;
1016
-        $this->_hasouter = true;
1017
-    }
1018
-
1019
-
1020
-    //////////////////////////////////////////////////////////////////////////////
1021
-
1022
-    /// clear all filters (for multi-queries)
1023
-    function ResetFilters ()
1024
-    {
1025
-        $this->_filters = array();
1026
-        $this->_anchor = array();
1027
-    }
1028
-
1029
-    /// clear groupby settings (for multi-queries)
1030
-    function ResetGroupBy ()
1031
-    {
1032
-        $this->_groupby = "";
1033
-        $this->_groupfunc = SPH_GROUPBY_DAY;
1034
-        $this->_groupsort = "@group desc";
1035
-        $this->_groupdistinct = "";
1036
-    }
1037
-
1038
-    /// clear all attribute value overrides (for multi-queries)
1039
-    function ResetOverrides ()
1040
-    {
1041
-        $this->_overrides = array ();
1042
-    }
1043
-
1044
-    function ResetQueryFlag ()
1045
-    {
1046
-        $this->_query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
1047
-        $this->_predictedtime = 0;
1048
-    }
1049
-
1050
-    function ResetOuterSelect ()
1051
-    {
1052
-        $this->_outerorderby = '';
1053
-        $this->_outeroffset = 0;
1054
-        $this->_outerlimit = 0;
1055
-        $this->_hasouter = false;
1056
-    }
1057
-
1058
-    //////////////////////////////////////////////////////////////////////////////
1059
-
1060
-    /// connect to searchd server, run given search query through given indexes,
1061
-    /// and return the search results
1062
-    function Query($query, $index="*", $comment="")
1063
-    {
1064
-        assert(empty($this->_reqs));
1065
-
1066
-        $this->AddQuery($query, $index, $comment);
1067
-        $results = $this->RunQueries ();
1068
-        $this->_reqs = array (); // just in case it failed too early
1069
-
1070
-        if (!is_array($results))
1071
-            return false; // probably network error; error message should be already filled
1072
-
1073
-        $this->_error = $results[0]["error"];
1074
-        $this->_warning = $results[0]["warning"];
1075
-        if ($results[0]["status"]==SEARCHD_ERROR)
1076
-            return false;
1077
-        else
1078
-            return $results[0];
1079
-    }
1080
-
1081
-    /// helper to pack floats in network byte order
1082
-    function _PackFloat($f)
1083
-    {
1084
-        $t1 = pack("f", $f); // machine order
1085
-        list(,$t2) = unpack("L*", $t1); // int in machine order
1086
-        return pack("N", $t2);
1087
-    }
1088
-
1089
-    /// add query to multi-query batch
1090
-    /// returns index into results array from RunQueries() call
1091
-    function AddQuery($query, $index="*", $comment="")
1092
-    {
1093
-        // mbstring workaround
1094
-        $this->_MBPush ();
1095
-
1096
-        // build request
1097
-        $req = pack("NNNNN", $this->_query_flags, $this->_offset, $this->_limit, $this->_mode, $this->_ranker);
1098
-        if ($this->_ranker==SPH_RANK_EXPR)
1099
-            $req .= pack("N", strlen($this->_rankexpr)) . $this->_rankexpr;
1100
-        $req .= pack("N", $this->_sort); // (deprecated) sort mode
1101
-        $req .= pack("N", strlen($this->_sortby)) . $this->_sortby;
1102
-        $req .= pack("N", strlen($query)) . $query; // query itself
1103
-        $req .= pack("N", count($this->_weights)); // weights
1104
-        foreach ($this->_weights as $weight)
1105
-            $req .= pack("N", (int)$weight);
1106
-        $req .= pack("N", strlen($index)) . $index; // indexes
1107
-        $req .= pack("N", 1); // id64 range marker
1108
-        $req .= sphPackU64($this->_min_id) . sphPackU64($this->_max_id); // id64 range
1109
-
1110
-        // filters
1111
-        $req .= pack("N", count($this->_filters));
1112
-        foreach ($this->_filters as $filter)
1113
-        {
1114
-            $req .= pack("N", strlen($filter["attr"])) . $filter["attr"];
1115
-            $req .= pack("N", $filter["type"]);
1116
-            switch ($filter["type"])
1117
-            {
1118
-                case SPH_FILTER_VALUES:
1119
-                    $req .= pack("N", count($filter["values"]));
1120
-                    foreach ($filter["values"] as $value)
1121
-                        $req .= sphPackI64($value);
1122
-                    break;
1123
-
1124
-                case SPH_FILTER_RANGE:
1125
-                    $req .= sphPackI64($filter["min"]) . sphPackI64($filter["max"]);
1126
-                    break;
1127
-
1128
-                case SPH_FILTER_FLOATRANGE:
1129
-                    $req .= $this->_PackFloat($filter["min"]) . $this->_PackFloat($filter["max"]);
1130
-                    break;
1131
-
1132
-                case SPH_FILTER_STRING:
1133
-                    $req .= pack("N", strlen($filter["value"])) . $filter["value"];
1134
-                    break;
1135
-
1136
-                default:
1137
-                    assert(0 && "internal error: unhandled filter type");
1138
-            }
1139
-            $req .= pack("N", $filter["exclude"]);
1140
-        }
1141
-
1142
-        // group-by clause, max-matches count, group-sort clause, cutoff count
1143
-        $req .= pack("NN", $this->_groupfunc, strlen($this->_groupby)) . $this->_groupby;
1144
-        $req .= pack("N", $this->_maxmatches);
1145
-        $req .= pack("N", strlen($this->_groupsort)) . $this->_groupsort;
1146
-        $req .= pack("NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay);
1147
-        $req .= pack("N", strlen($this->_groupdistinct)) . $this->_groupdistinct;
1148
-
1149
-        // anchor point
1150
-        if (empty($this->_anchor))
1151
-        {
1152
-            $req .= pack("N", 0);
1153
-        } else
1154
-        {
1155
-            $a =& $this->_anchor;
1156
-            $req .= pack("N", 1);
1157
-            $req .= pack("N", strlen($a["attrlat"])) . $a["attrlat"];
1158
-            $req .= pack("N", strlen($a["attrlong"])) . $a["attrlong"];
1159
-            $req .= $this->_PackFloat($a["lat"]) . $this->_PackFloat($a["long"]);
1160
-        }
1161
-
1162
-        // per-index weights
1163
-        $req .= pack("N", count($this->_indexweights));
1164
-        foreach ($this->_indexweights as $idx=>$weight)
1165
-            $req .= pack("N", strlen($idx)) . $idx . pack("N", $weight);
1166
-
1167
-        // max query time
1168
-        $req .= pack("N", $this->_maxquerytime);
1169
-
1170
-        // per-field weights
1171
-        $req .= pack("N", count($this->_fieldweights));
1172
-        foreach ($this->_fieldweights as $field=>$weight)
1173
-            $req .= pack("N", strlen($field)) . $field . pack("N", $weight);
1174
-
1175
-        // comment
1176
-        $req .= pack("N", strlen($comment)) . $comment;
1177
-
1178
-        // attribute overrides
1179
-        $req .= pack("N", count($this->_overrides));
1180
-        foreach ($this->_overrides as $key => $entry)
1181
-        {
1182
-            $req .= pack("N", strlen($entry["attr"])) . $entry["attr"];
1183
-            $req .= pack("NN", $entry["type"], count($entry["values"]));
1184
-            foreach ($entry["values"] as $id=>$val)
1185
-            {
1186
-                assert(is_numeric($id));
1187
-                assert(is_numeric($val));
1188
-
1189
-                $req .= sphPackU64($id);
1190
-                switch ($entry["type"])
1191
-                {
1192
-                    case SPH_ATTR_FLOAT:  $req .= $this->_PackFloat($val); break;
1193
-                    case SPH_ATTR_BIGINT: $req .= sphPackI64($val); break;
1194
-                    default:              $req .= pack("N", $val); break;
1195
-                }
1196
-            }
1197
-        }
1198
-
1199
-        // select-list
1200
-        $req .= pack("N", strlen($this->_select)) . $this->_select;
1201
-
1202
-        // max_predicted_time
1203
-        if ($this->_predictedtime>0)
1204
-            $req .= pack("N", (int)$this->_predictedtime);
1205
-
1206
-        $req .= pack("N", strlen($this->_outerorderby)) . $this->_outerorderby;
1207
-        $req .= pack("NN", $this->_outeroffset, $this->_outerlimit);
1208
-        if ($this->_hasouter)
1209
-            $req .= pack("N", 1);
1210
-        else
1211
-            $req .= pack("N", 0);
1212
-
1213
-        // mbstring workaround
1214
-        $this->_MBPop ();
1215
-
1216
-        // store request to requests array
1217
-        $this->_reqs[] = $req;
1218
-        return count($this->_reqs)-1;
1219
-    }
1220
-
1221
-    /// connect to searchd, run queries batch, and return an array of result sets
1222
-    function RunQueries ()
1223
-    {
1224
-        if (empty($this->_reqs))
1225
-        {
1226
-            $this->_error = "no queries defined, issue AddQuery() first";
1227
-            return false;
1228
-        }
1229
-
1230
-        // mbstring workaround
1231
-        $this->_MBPush ();
1232
-
1233
-        if (!( $fp = $this->_Connect()))
1234
-        {
1235
-            $this->_MBPop ();
1236
-            return false;
1237
-        }
1238
-
1239
-        // send query, get response
1240
-        $nreqs = count($this->_reqs);
1241
-        $req = join("", $this->_reqs);
1242
-        $len = 8+strlen($req);
1243
-        $req = pack("nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs) . $req; // add header
1244
-
1245
-        if (!( $this->_Send($fp, $req, $len+8)) ||
1246
-             !( $response = $this->_GetResponse($fp, VER_COMMAND_SEARCH)))
1247
-        {
1248
-            $this->_MBPop ();
1249
-            return false;
1250
-        }
1251
-
1252
-        // query sent ok; we can reset reqs now
1253
-        $this->_reqs = array ();
1254
-
1255
-        // parse and return response
1256
-        return $this->_ParseSearchResponse($response, $nreqs);
1257
-    }
1258
-
1259
-    /// parse and return search query (or queries) response
1260
-    function _ParseSearchResponse($response, $nreqs)
1261
-    {
1262
-        $p = 0; // current position
1263
-        $max = strlen($response); // max position for checks, to protect against broken responses
1264
-
1265
-        $results = array ();
1266
-        for($ires=0; $ires<$nreqs && $p<$max; $ires++)
1267
-        {
1268
-            $results[] = array();
1269
-            $result =& $results[$ires];
1270
-
1271
-            $result["error"] = "";
1272
-            $result["warning"] = "";
1273
-
1274
-            // extract status
1275
-            list(,$status) = unpack("N*", substr($response, $p, 4)); $p += 4;
1276
-            $result["status"] = $status;
1277
-            if ($status!=SEARCHD_OK)
1278
-            {
1279
-                list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1280
-                $message = substr($response, $p, $len); $p += $len;
1281
-
1282
-                if ($status==SEARCHD_WARNING)
1283
-                {
1284
-                    $result["warning"] = $message;
1285
-                } else
1286
-                {
1287
-                    $result["error"] = $message;
1288
-                    continue;
1289
-                }
1290
-            }
1291
-
1292
-            // read schema
1293
-            $fields = array ();
1294
-            $attrs = array ();
1295
-
1296
-            list(,$nfields) = unpack("N*", substr($response, $p, 4)); $p += 4;
1297
-            while ($nfields-->0 && $p<$max)
1298
-            {
1299
-                list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1300
-                $fields[] = substr($response, $p, $len); $p += $len;
1301
-            }
1302
-            $result["fields"] = $fields;
1303
-
1304
-            list(,$nattrs) = unpack("N*", substr($response, $p, 4)); $p += 4;
1305
-            while ($nattrs-->0 && $p<$max )
1306
-            {
1307
-                list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1308
-                $attr = substr($response, $p, $len); $p += $len;
1309
-                list(,$type) = unpack("N*", substr($response, $p, 4)); $p += 4;
1310
-                $attrs[$attr] = $type;
1311
-            }
1312
-            $result["attrs"] = $attrs;
1313
-
1314
-            // read match count
1315
-            list(,$count) = unpack("N*", substr($response, $p, 4)); $p += 4;
1316
-            list(,$id64) = unpack("N*", substr($response, $p, 4)); $p += 4;
1317
-
1318
-            // read matches
1319
-            $idx = -1;
1320
-            while ($count-->0 && $p<$max)
1321
-            {
1322
-                // index into result array
1323
-                $idx++;
1324
-
1325
-                // parse document id and weight
1326
-                if ($id64)
1327
-                {
1328
-                    $doc = sphUnpackU64(substr($response, $p, 8)); $p += 8;
1329
-                    list(,$weight) = unpack("N*", substr($response, $p, 4)); $p += 4;
1330
-                }
1331
-                else
1332
-                {
1333
-                    list($doc, $weight) = array_values(unpack("N*N*",
1334
-                        substr($response, $p, 8)));
1335
-                    $p += 8;
1336
-                    $doc = sphFixUint($doc);
1337
-                }
1338
-                $weight = sprintf("%u", $weight);
1339
-
1340
-                // create match entry
1341
-                if ($this->_arrayresult)
1342
-                    $result["matches"][$idx] = array("id"=>$doc, "weight"=>$weight);
1343
-                else
1344
-                    $result["matches"][$doc]["weight"] = $weight;
1345
-
1346
-                // parse and create attributes
1347
-                $attrvals = array ();
1348
-                foreach ($attrs as $attr=>$type)
1349
-                {
1350
-                    // handle 64bit ints
1351
-                    if ($type==SPH_ATTR_BIGINT)
1352
-                    {
1353
-                        $attrvals[$attr] = sphUnpackI64(substr($response, $p, 8)); $p += 8;
1354
-                        continue;
1355
-                    }
1356
-
1357
-                    // handle floats
1358
-                    if ($type==SPH_ATTR_FLOAT)
1359
-                    {
1360
-                        list(,$uval) = unpack("N*", substr($response, $p, 4)); $p += 4;
1361
-                        list(,$fval) = unpack("f*", pack("L", $uval)); 
1362
-                        $attrvals[$attr] = $fval;
1363
-                        continue;
1364
-                    }
1365
-
1366
-                    // handle everything else as unsigned ints
1367
-                    list(,$val) = unpack("N*", substr($response, $p, 4)); $p += 4;
1368
-                    if ($type==SPH_ATTR_MULTI)
1369
-                    {
1370
-                        $attrvals[$attr] = array ();
1371
-                        $nvalues = $val;
1372
-                        while ($nvalues-->0 && $p<$max)
1373
-                        {
1374
-                            list(,$val) = unpack("N*", substr($response, $p, 4)); $p += 4;
1375
-                            $attrvals[$attr][] = sphFixUint($val);
1376
-                        }
1377
-                    } else if ($type==SPH_ATTR_MULTI64)
1378
-                    {
1379
-                        $attrvals[$attr] = array ();
1380
-                        $nvalues = $val;
1381
-                        while ($nvalues>0 && $p<$max)
1382
-                        {
1383
-                            $attrvals[$attr][] = sphUnpackI64(substr($response, $p, 8)); $p += 8;
1384
-                            $nvalues -= 2;
1385
-                        }
1386
-                    } else if ($type==SPH_ATTR_STRING)
1387
-                    {
1388
-                        $attrvals[$attr] = substr($response, $p, $val);
1389
-                        $p += $val;
1390
-                    } else if ($type==SPH_ATTR_FACTORS)
1391
-                    {
1392
-                        $attrvals[$attr] = substr($response, $p, $val-4);
1393
-                        $p += $val-4;
1394
-                    } else
1395
-                    {
1396
-                        $attrvals[$attr] = sphFixUint($val);
1397
-                    }
1398
-                }
1399
-
1400
-                if ($this->_arrayresult)
1401
-                    $result["matches"][$idx]["attrs"] = $attrvals;
1402
-                else
1403
-                    $result["matches"][$doc]["attrs"] = $attrvals;
1404
-            }
1405
-
1406
-            list($total, $total_found, $msecs, $words) =
1407
-                array_values(unpack("N*N*N*N*", substr($response, $p, 16)));
1408
-            $result["total"] = sprintf("%u", $total);
1409
-            $result["total_found"] = sprintf("%u", $total_found);
1410
-            $result["time"] = sprintf("%.3f", $msecs/1000);
1411
-            $p += 16;
1412
-
1413
-            while ($words-->0 && $p<$max)
1414
-            {
1415
-                list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1416
-                $word = substr($response, $p, $len); $p += $len;
1417
-                list($docs, $hits) = array_values(unpack("N*N*", substr($response, $p, 8))); $p += 8;
1418
-                $result["words"][$word] = array (
1419
-                    "docs"=>sprintf("%u", $docs),
1420
-                    "hits"=>sprintf("%u", $hits));
1421
-            }
1422
-        }
1423
-
1424
-        $this->_MBPop ();
1425
-        return $results;
1426
-    }
1427
-
1428
-    /////////////////////////////////////////////////////////////////////////////
1429
-    // excerpts generation
1430
-    /////////////////////////////////////////////////////////////////////////////
1431
-
1432
-    /// connect to searchd server, and generate exceprts (snippets)
1433
-    /// of given documents for given query. returns false on failure,
1434
-    /// an array of snippets on success
1435
-    function BuildExcerpts($docs, $index, $words, $opts=array())
1436
-    {
1437
-        assert(is_array($docs));
1438
-        assert(is_string($index));
1439
-        assert(is_string($words));
1440
-        assert(is_array($opts));
1441
-
1442
-        $this->_MBPush ();
1443
-
1444
-        if (!( $fp = $this->_Connect()))
1445
-        {
1446
-            $this->_MBPop();
1447
-            return false;
1448
-        }
1449
-
1450
-        /////////////////
1451
-        // fixup options
1452
-        /////////////////
1453
-
1454
-        if (!isset($opts["before_match"])) $opts["before_match"] = "<b>";
1455
-        if (!isset($opts["after_match"])) $opts["after_match"] = "</b>";
1456
-        if (!isset($opts["chunk_separator"])) $opts["chunk_separator"] = " ... ";
1457
-        if (!isset($opts["limit"])) $opts["limit"] = 256;
1458
-        if (!isset($opts["limit_passages"])) $opts["limit_passages"] = 0;
1459
-        if (!isset($opts["limit_words"])) $opts["limit_words"] = 0;
1460
-        if (!isset($opts["around"])) $opts["around"] = 5;
1461
-        if (!isset($opts["exact_phrase"])) $opts["exact_phrase"] = false;
1462
-        if (!isset($opts["single_passage"])) $opts["single_passage"] = false;
1463
-        if (!isset($opts["use_boundaries"])) $opts["use_boundaries"] = false;
1464
-        if (!isset($opts["weight_order"])) $opts["weight_order"] = false;
1465
-        if (!isset($opts["query_mode"])) $opts["query_mode"] = false;
1466
-        if (!isset($opts["force_all_words"])) $opts["force_all_words"] = false;
1467
-        if (!isset($opts["start_passage_id"])) $opts["start_passage_id"] = 1;
1468
-        if (!isset($opts["load_files"])) $opts["load_files"] = false;
1469
-        if (!isset($opts["html_strip_mode"])) $opts["html_strip_mode"] = "index";
1470
-        if (!isset($opts["allow_empty"])) $opts["allow_empty"] = false;
1471
-        if (!isset($opts["passage_boundary"])) $opts["passage_boundary"] = "none";
1472
-        if (!isset($opts["emit_zones"])) $opts["emit_zones"] = false;
1473
-        if (!isset($opts["load_files_scattered"])) $opts["load_files_scattered"] = false;
1474
-
1475
-
1476
-        /////////////////
1477
-        // build request
1478
-        /////////////////
1479
-
1480
-        // v.1.2 req
1481
-        $flags = 1; // remove spaces
1482
-        if ($opts["exact_phrase"]) $flags |= 2;
1483
-        if ($opts["single_passage"]) $flags |= 4;
1484
-        if ($opts["use_boundaries"]) $flags |= 8;
1485
-        if ($opts["weight_order"]) $flags |= 16;
1486
-        if ($opts["query_mode"]) $flags |= 32;
1487
-        if ($opts["force_all_words"]) $flags |= 64;
1488
-        if ($opts["load_files"]) $flags |= 128;
1489
-        if ($opts["allow_empty"]) $flags |= 256;
1490
-        if ($opts["emit_zones"]) $flags |= 512;
1491
-        if ($opts["load_files_scattered"]) $flags |= 1024;
1492
-        $req = pack("NN", 0, $flags); // mode=0, flags=$flags
1493
-        $req .= pack("N", strlen($index)) . $index; // req index
1494
-        $req .= pack("N", strlen($words)) . $words; // req words
1495
-
1496
-        // options
1497
-        $req .= pack("N", strlen($opts["before_match"])) . $opts["before_match"];
1498
-        $req .= pack("N", strlen($opts["after_match"])) . $opts["after_match"];
1499
-        $req .= pack("N", strlen($opts["chunk_separator"])) . $opts["chunk_separator"];
1500
-        $req .= pack("NN", (int)$opts["limit"], (int)$opts["around"]);
1501
-        $req .= pack("NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"]); // v.1.2
1502
-        $req .= pack("N", strlen($opts["html_strip_mode"])) . $opts["html_strip_mode"];
1503
-        $req .= pack("N", strlen($opts["passage_boundary"])) . $opts["passage_boundary"];
1504
-
1505
-        // documents
1506
-        $req .= pack("N", count($docs));
1507
-        foreach ($docs as $doc)
1508
-        {
1509
-            assert(is_string($doc));
1510
-            $req .= pack("N", strlen($doc)) . $doc;
1511
-        }
1512
-
1513
-        ////////////////////////////
1514
-        // send query, get response
1515
-        ////////////////////////////
1516
-
1517
-        $len = strlen($req);
1518
-        $req = pack("nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len) . $req; // add header
1519
-        if (!( $this->_Send($fp, $req, $len+8)) ||
1520
-             !( $response = $this->_GetResponse($fp, VER_COMMAND_EXCERPT)))
1521
-        {
1522
-            $this->_MBPop ();
1523
-            return false;
1524
-        }
1525
-
1526
-        //////////////////
1527
-        // parse response
1528
-        //////////////////
1529
-
1530
-        $pos = 0;
1531
-        $res = array ();
1532
-        $rlen = strlen($response);
1533
-        for($i=0; $i<count($docs); $i++)
1534
-        {
1535
-            list(,$len) = unpack("N*", substr($response, $pos, 4));
1536
-            $pos += 4;
1537
-
1538
-            if ($pos+$len > $rlen)
1539
-            {
1540
-                $this->_error = "incomplete reply";
1541
-                $this->_MBPop ();
1542
-                return false;
1543
-            }
1544
-            $res[] = $len ? substr($response, $pos, $len) : "";
1545
-            $pos += $len;
1546
-        }
1547
-
1548
-        $this->_MBPop ();
1549
-        return $res;
1550
-    }
1551
-
1552
-
1553
-    /////////////////////////////////////////////////////////////////////////////
1554
-    // keyword generation
1555
-    /////////////////////////////////////////////////////////////////////////////
1556
-
1557
-    /// connect to searchd server, and generate keyword list for a given query
1558
-    /// returns false on failure,
1559
-    /// an array of words on success
1560
-    function BuildKeywords($query, $index, $hits)
1561
-    {
1562
-        assert(is_string($query));
1563
-        assert(is_string($index));
1564
-        assert(is_bool($hits));
1565
-
1566
-        $this->_MBPush ();
1567
-
1568
-        if (!( $fp = $this->_Connect()))
1569
-        {
1570
-            $this->_MBPop();
1571
-            return false;
1572
-        }
1573
-
1574
-        /////////////////
1575
-        // build request
1576
-        /////////////////
1577
-
1578
-        // v.1.0 req
1579
-        $req  = pack("N", strlen($query)) . $query; // req query
1580
-        $req .= pack("N", strlen($index)) . $index; // req index
1581
-        $req .= pack("N", (int)$hits);
1582
-
1583
-        ////////////////////////////
1584
-        // send query, get response
1585
-        ////////////////////////////
1586
-
1587
-        $len = strlen($req);
1588
-        $req = pack("nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len) . $req; // add header
1589
-        if (!( $this->_Send($fp, $req, $len+8)) ||
1590
-             !( $response = $this->_GetResponse($fp, VER_COMMAND_KEYWORDS)))
1591
-        {
1592
-            $this->_MBPop ();
1593
-            return false;
1594
-        }
1595
-
1596
-        //////////////////
1597
-        // parse response
1598
-        //////////////////
1599
-
1600
-        $pos = 0;
1601
-        $res = array ();
1602
-        $rlen = strlen($response);
1603
-        list(,$nwords) = unpack("N*", substr($response, $pos, 4));
1604
-        $pos += 4;
1605
-        for($i=0; $i<$nwords; $i++)
1606
-        {
1607
-            list(,$len) = unpack("N*", substr($response, $pos, 4)); $pos += 4;
1608
-            $tokenized = $len ? substr($response, $pos, $len) : "";
1609
-            $pos += $len;
1610
-
1611
-            list(,$len) = unpack("N*", substr($response, $pos, 4)); $pos += 4;
1612
-            $normalized = $len ? substr($response, $pos, $len) : "";
1613
-            $pos += $len;
1614
-
1615
-            $res[] = array("tokenized"=>$tokenized, "normalized"=>$normalized);
1616
-
1617
-            if ($hits)
1618
-            {
1619
-                list($ndocs,$nhits) = array_values(unpack("N*N*", substr($response, $pos, 8)));
1620
-                $pos += 8;
1621
-                $res [$i]["docs"] = $ndocs;
1622
-                $res [$i]["hits"] = $nhits;
1623
-            }
1624
-
1625
-            if ($pos > $rlen)
1626
-            {
1627
-                $this->_error = "incomplete reply";
1628
-                $this->_MBPop ();
1629
-                return false;
1630
-            }
1631
-        }
1632
-
1633
-        $this->_MBPop ();
1634
-        return $res;
1635
-    }
1636
-
1637
-    function EscapeString($string)
1638
-    {
1639
-        $from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<');
1640
-        $to   = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<');
1641
-
1642
-        return str_replace($from, $to, $string);
1643
-    }
1644
-
1645
-    /////////////////////////////////////////////////////////////////////////////
1646
-    // attribute updates
1647
-    /////////////////////////////////////////////////////////////////////////////
1648
-
1649
-    /// batch update given attributes in given rows in given indexes
1650
-    /// returns amount of updated documents (0 or more) on success, or -1 on failure
1651
-    function UpdateAttributes($index, $attrs, $values, $mva=false, $ignorenonexistent=false)
1652
-    {
1653
-        // verify everything
1654
-        assert(is_string($index));
1655
-        assert(is_bool($mva));
1656
-        assert(is_bool($ignorenonexistent));
1657
-
1658
-        assert(is_array($attrs));
1659
-        foreach ($attrs as $attr)
1660
-            assert(is_string($attr));
1661
-
1662
-        assert(is_array($values));
1663
-        foreach ($values as $id=>$entry)
1664
-        {
1665
-            assert(is_numeric($id));
1666
-            assert(is_array($entry));
1667
-            assert(count($entry)==count($attrs));
1668
-            foreach ($entry as $v)
1669
-            {
1670
-                if ($mva)
1671
-                {
1672
-                    assert(is_array($v));
1673
-                    foreach ($v as $vv)
1674
-                        assert(is_int($vv));
1675
-                } else
1676
-                    assert(is_int($v));
1677
-            }
1678
-        }
1679
-
1680
-        // build request
1681
-        $this->_MBPush ();
1682
-        $req = pack("N", strlen($index)) . $index;
1683
-
1684
-        $req .= pack("N", count($attrs));
1685
-        $req .= pack("N", $ignorenonexistent ? 1 : 0);
1686
-        foreach ($attrs as $attr)
1687
-        {
1688
-            $req .= pack("N", strlen($attr)) . $attr;
1689
-            $req .= pack("N", $mva ? 1 : 0);
1690
-        }
1691
-
1692
-        $req .= pack("N", count($values));
1693
-        foreach ($values as $id=>$entry)
1694
-        {
1695
-            $req .= sphPackU64($id);
1696
-            foreach ($entry as $v)
1697
-            {
1698
-                $req .= pack("N", $mva ? count($v) : $v);
1699
-                if ($mva)
1700
-                    foreach ($v as $vv)
1701
-                        $req .= pack("N", $vv);
1702
-            }
1703
-        }
1704
-
1705
-        // connect, send query, get response
1706
-        if (!( $fp = $this->_Connect()))
1707
-        {
1708
-            $this->_MBPop ();
1709
-            return -1;
1710
-        }
1711
-
1712
-        $len = strlen($req);
1713
-        $req = pack("nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len) . $req; // add header
1714
-        if (!$this->_Send($fp, $req, $len+8))
1715
-        {
1716
-            $this->_MBPop ();
1717
-            return -1;
1718
-        }
1719
-
1720
-        if (!( $response = $this->_GetResponse($fp, VER_COMMAND_UPDATE)))
1721
-        {
1722
-            $this->_MBPop ();
1723
-            return -1;
1724
-        }
1725
-
1726
-        // parse response
1727
-        list(,$updated) = unpack("N*", substr($response, 0, 4));
1728
-        $this->_MBPop ();
1729
-        return $updated;
1730
-    }
1731
-
1732
-    /////////////////////////////////////////////////////////////////////////////
1733
-    // persistent connections
1734
-    /////////////////////////////////////////////////////////////////////////////
1735
-
1736
-    function Open()
1737
-    {
1738
-        if ($this->_socket !== false)
1739
-        {
1740
-            $this->_error = 'already connected';
1741
-            return false;
1742
-        }
1743
-        if (!$fp = $this->_Connect())
1744
-            return false;
1745
-
1746
-        // command, command version = 0, body length = 4, body = 1
1747
-        $req = pack("nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1);
1748
-        if (!$this->_Send($fp, $req, 12))
1749
-            return false;
1750
-
1751
-        $this->_socket = $fp;
1752
-        return true;
1753
-    }
1754
-
1755
-    function Close()
1756
-    {
1757
-        if ($this->_socket === false)
1758
-        {
1759
-            $this->_error = 'not connected';
1760
-            return false;
1761
-        }
1762
-
1763
-        fclose($this->_socket);
1764
-        $this->_socket = false;
1765
-
1766
-        return true;
1767
-    }
1768
-
1769
-    //////////////////////////////////////////////////////////////////////////
1770
-    // status
1771
-    //////////////////////////////////////////////////////////////////////////
1772
-
1773
-    function Status ($session=false)
1774
-    {
1775
-        assert(is_bool($session));
1776
-
1777
-        $this->_MBPush ();
1778
-        if (!( $fp = $this->_Connect()))
1779
-        {
1780
-            $this->_MBPop();
1781
-            return false;
1782
-        }
1783
-
1784
-        $req = pack("nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, $session?0:1); // len=4, body=1
1785
-        if (!( $this->_Send($fp, $req, 12)) ||
1786
-             !( $response = $this->_GetResponse($fp, VER_COMMAND_STATUS)))
1787
-        {
1788
-            $this->_MBPop ();
1789
-            return false;
1790
-        }
1791
-
1792
-        $res = substr($response, 4); // just ignore length, error handling, etc
1793
-        $p = 0;
1794
-        list($rows, $cols) = array_values(unpack("N*N*", substr($response, $p, 8))); $p += 8;
1795
-
1796
-        $res = array();
1797
-        for($i=0; $i<$rows; $i++)
1798
-            for($j=0; $j<$cols; $j++)
1799
-        {
1800
-            list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1801
-            $res[$i][] = substr($response, $p, $len); $p += $len;
1802
-        }
1803
-
1804
-        $this->_MBPop ();
1805
-        return $res;
1806
-    }
1807
-
1808
-    //////////////////////////////////////////////////////////////////////////
1809
-    // flush
1810
-    //////////////////////////////////////////////////////////////////////////
1811
-
1812
-    function FlushAttributes ()
1813
-    {
1814
-        $this->_MBPush ();
1815
-        if (!( $fp = $this->_Connect()))
1816
-        {
1817
-            $this->_MBPop();
1818
-            return -1;
1819
-        }
1820
-
1821
-        $req = pack("nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0); // len=0
1822
-        if (!( $this->_Send($fp, $req, 8)) ||
1823
-             !( $response = $this->_GetResponse($fp, VER_COMMAND_FLUSHATTRS)))
1824
-        {
1825
-            $this->_MBPop ();
1826
-            return -1;
1827
-        }
1828
-
1829
-        $tag = -1;
1830
-        if (strlen($response)==4)
1831
-            list(,$tag) = unpack("N*", $response);
1832
-        else
1833
-            $this->_error = "unexpected response length";
1834
-
1835
-        $this->_MBPop ();
1836
-        return $tag;
1837
-    }
416
+	var $_host; ///< searchd host (default is "localhost")
417
+	var $_port; ///< searchd port (default is 9312)
418
+	var $_offset; ///< how many records to seek from result-set start (default is 0)
419
+	var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
420
+	var $_mode; ///< query matching mode (default is SPH_MATCH_EXTENDED2)
421
+	var $_weights; ///< per-field weights (default is 1 for all fields)
422
+	var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
423
+	var $_sortby; ///< attribute to sort by (defualt is "")
424
+	var $_min_id; ///< min ID to match (default is 0, which means no limit)
425
+	var $_max_id; ///< max ID to match (default is 0, which means no limit)
426
+	var $_filters; ///< search filters
427
+	var $_groupby; ///< group-by attribute name
428
+	var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
429
+	var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
430
+	var $_groupdistinct; ///< group-by count-distinct attribute
431
+	var $_maxmatches; ///< max matches to retrieve
432
+	var $_cutoff; ///< cutoff to stop searching at (default is 0)
433
+	var $_retrycount; ///< distributed retries count
434
+	var $_retrydelay; ///< distributed retries delay
435
+	var $_anchor; ///< geographical anchor point
436
+	var $_indexweights; ///< per-index weights
437
+	var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
438
+	var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR)
439
+	var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
440
+	var $_fieldweights; ///< per-field-name weights
441
+	var $_overrides; ///< per-query attribute values overrides
442
+	var $_select; ///< select-list (attributes or expressions, with optional aliases)
443
+	var $_query_flags; ///< per-query various flags
444
+	var $_predictedtime; ///< per-query max_predicted_time
445
+	var $_outerorderby; ///< outer match sort by
446
+	var $_outeroffset; ///< outer offset
447
+	var $_outerlimit; ///< outer limit
448
+	var $_hasouter;
449
+
450
+	var $_error; ///< last error message
451
+	var $_warning; ///< last warning message
452
+	var $_connerror; ///< connection error vs remote error flag
453
+
454
+	var $_reqs; ///< requests array for multi-query
455
+	var $_mbenc; ///< stored mbstring encoding
456
+	var $_arrayresult; ///< whether $result["matches"] should be a hash or an array
457
+	var $_timeout; ///< connect timeout
458
+
459
+	/////////////////////////////////////////////////////////////////////////////
460
+	// common stuff
461
+	/////////////////////////////////////////////////////////////////////////////
462
+
463
+	/// create a new client object and fill defaults
464
+	function SphinxClient ()
465
+	{
466
+		// per-client-object settings
467
+		$this->_host = "localhost";
468
+		$this->_port = 9312;
469
+		$this->_path = false;
470
+		$this->_socket = false;
471
+
472
+		// per-query settings
473
+		$this->_offset = 0;
474
+		$this->_limit = 20;
475
+		$this->_mode = SPH_MATCH_EXTENDED2;
476
+		$this->_weights = array ();
477
+		$this->_sort = SPH_SORT_RELEVANCE;
478
+		$this->_sortby = "";
479
+		$this->_min_id = 0;
480
+		$this->_max_id = 0;
481
+		$this->_filters = array ();
482
+		$this->_groupby = "";
483
+		$this->_groupfunc = SPH_GROUPBY_DAY;
484
+		$this->_groupsort = "@group desc";
485
+		$this->_groupdistinct = "";
486
+		$this->_maxmatches = 1000;
487
+		$this->_cutoff = 0;
488
+		$this->_retrycount = 0;
489
+		$this->_retrydelay = 0;
490
+		$this->_anchor = array ();
491
+		$this->_indexweights = array ();
492
+		$this->_ranker = SPH_RANK_PROXIMITY_BM25;
493
+		$this->_rankexpr = "";
494
+		$this->_maxquerytime = 0;
495
+		$this->_fieldweights = array();
496
+		$this->_overrides = array();
497
+		$this->_select = "*";
498
+		$this->_query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
499
+		$this->_predictedtime = 0;
500
+		$this->_outerorderby = "";
501
+		$this->_outeroffset = 0;
502
+		$this->_outerlimit = 0;
503
+		$this->_hasouter = false;
504
+
505
+		$this->_error = ""; // per-reply fields (for single-query case)
506
+		$this->_warning = "";
507
+		$this->_connerror = false;
508
+
509
+		$this->_reqs = array ();// requests storage (for multi-query case)
510
+		$this->_mbenc = "";
511
+		$this->_arrayresult = false;
512
+		$this->_timeout = 0;
513
+	}
514
+
515
+	function __destruct()
516
+	{
517
+		if ($this->_socket !== false)
518
+			fclose($this->_socket);
519
+	}
520
+
521
+	/// get last error message (string)
522
+	function GetLastError ()
523
+	{
524
+		return $this->_error;
525
+	}
526
+
527
+	/// get last warning message (string)
528
+	function GetLastWarning ()
529
+	{
530
+		return $this->_warning;
531
+	}
532
+
533
+	/// get last error flag (to tell network connection errors from searchd errors or broken responses)
534
+	function IsConnectError()
535
+	{
536
+		return $this->_connerror;
537
+	}
538
+
539
+	/// set searchd host name (string) and port (integer)
540
+	function SetServer($host, $port = 0)
541
+	{
542
+		assert(is_string($host));
543
+		if ($host[0] == '/')
544
+		{
545
+			$this->_path = 'unix://' . $host;
546
+			return;
547
+		}
548
+		if (substr($host, 0, 7)=="unix://")
549
+		{
550
+			$this->_path = $host;
551
+			return;
552
+		}
553
+
554
+		$this->_host = $host;
555
+		$port = intval($port);
556
+		assert(0<=$port && $port<65536);
557
+		$this->_port =($port==0) ? 9312 : $port;
558
+		$this->_path = '';
559
+	}
560
+
561
+	/// set server connection timeout (0 to remove)
562
+	function SetConnectTimeout($timeout)
563
+	{
564
+		assert(is_numeric($timeout));
565
+		$this->_timeout = $timeout;
566
+	}
567
+
568
+
569
+	function _Send($handle, $data, $length)
570
+	{
571
+		if (feof($handle) || fwrite($handle, $data, $length) !== $length)
572
+		{
573
+			$this->_error = 'connection unexpectedly closed (timed out?)';
574
+			$this->_connerror = true;
575
+			return false;
576
+		}
577
+		return true;
578
+	}
579
+
580
+	/////////////////////////////////////////////////////////////////////////////
581
+
582
+	/// enter mbstring workaround mode
583
+	function _MBPush ()
584
+	{
585
+		$this->_mbenc = "";
586
+		if (ini_get("mbstring.func_overload") & 2)
587
+		{
588
+			$this->_mbenc = mb_internal_encoding();
589
+			mb_internal_encoding("latin1");
590
+		}
591
+	}
592
+
593
+	/// leave mbstring workaround mode
594
+	function _MBPop ()
595
+	{
596
+		if ($this->_mbenc)
597
+			mb_internal_encoding($this->_mbenc);
598
+	}
599
+
600
+	/// connect to searchd server
601
+	function _Connect ()
602
+	{
603
+		if ($this->_socket!==false)
604
+		{
605
+			// we are in persistent connection mode, so we have a socket
606
+			// however, need to check whether it's still alive
607
+			if (!@feof($this->_socket))
608
+				return $this->_socket;
609
+
610
+			// force reopen
611
+			$this->_socket = false;
612
+		}
613
+
614
+		$errno = 0;
615
+		$errstr = "";
616
+		$this->_connerror = false;
617
+
618
+		if ($this->_path)
619
+		{
620
+			$host = $this->_path;
621
+			$port = 0;
622
+		}
623
+		else
624
+		{
625
+			$host = $this->_host;
626
+			$port = $this->_port;
627
+		}
628
+
629
+		if ($this->_timeout<=0)
630
+			$fp = @fsockopen($host, $port, $errno, $errstr);
631
+		else
632
+			$fp = @fsockopen($host, $port, $errno, $errstr, $this->_timeout);
633
+
634
+		if (!$fp)
635
+		{
636
+			if ($this->_path)
637
+				$location = $this->_path;
638
+			else
639
+				$location = "{$this->_host}:{$this->_port}";
640
+
641
+			$errstr = trim($errstr);
642
+			$this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
643
+			$this->_connerror = true;
644
+			return false;
645
+		}
646
+
647
+		// send my version
648
+		// this is a subtle part. we must do it before (!) reading back from searchd.
649
+		// because otherwise under some conditions (reported on FreeBSD for instance)
650
+		// TCP stack could throttle write-write-read pattern because of Nagle.
651
+		if (!$this->_Send($fp, pack("N", 1), 4))
652
+		{
653
+			fclose($fp);
654
+			$this->_error = "failed to send client protocol version";
655
+			return false;
656
+		}
657
+
658
+		// check version
659
+		list(,$v) = unpack("N*", fread($fp, 4));
660
+		$v = (int)$v;
661
+		if ($v<1)
662
+		{
663
+			fclose($fp);
664
+			$this->_error = "expected searchd protocol version 1+, got version '$v'";
665
+			return false;
666
+		}
667
+
668
+		return $fp;
669
+	}
670
+
671
+	/// get and check response packet from searchd server
672
+	function _GetResponse($fp, $client_ver)
673
+	{
674
+		$response = "";
675
+		$len = 0;
676
+
677
+		$header = fread($fp, 8);
678
+		if (strlen($header)==8)
679
+		{
680
+			list($status, $ver, $len) = array_values(unpack("n2a/Nb", $header));
681
+			$left = $len;
682
+			while ($left>0 && !feof($fp))
683
+			{
684
+				$chunk = fread($fp, min(8192, $left));
685
+				if ($chunk)
686
+				{
687
+					$response .= $chunk;
688
+					$left -= strlen($chunk);
689
+				}
690
+			}
691
+		}
692
+		if ($this->_socket === false)
693
+			fclose($fp);
694
+
695
+		// check response
696
+		$read = strlen($response);
697
+		if (!$response || $read!=$len)
698
+		{
699
+			$this->_error = $len
700
+				? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
701
+				: "received zero-sized searchd response";
702
+			return false;
703
+		}
704
+
705
+		// check status
706
+		if ($status==SEARCHD_WARNING)
707
+		{
708
+			list(,$wlen) = unpack("N*", substr($response, 0, 4));
709
+			$this->_warning = substr($response, 4, $wlen);
710
+			return substr($response, 4+$wlen);
711
+		}
712
+		if ($status==SEARCHD_ERROR)
713
+		{
714
+			$this->_error = "searchd error: " . substr($response, 4);
715
+			return false;
716
+		}
717
+		if ($status==SEARCHD_RETRY)
718
+		{
719
+			$this->_error = "temporary searchd error: " . substr($response, 4);
720
+			return false;
721
+		}
722
+		if ($status!=SEARCHD_OK)
723
+		{
724
+			$this->_error = "unknown status code '$status'";
725
+			return false;
726
+		}
727
+
728
+		// check version
729
+		if ($ver<$client_ver)
730
+		{
731
+			$this->_warning = sprintf("searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
732
+				$ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff);
733
+		}
734
+
735
+		return $response;
736
+	}
737
+
738
+	/////////////////////////////////////////////////////////////////////////////
739
+	// searching
740
+	/////////////////////////////////////////////////////////////////////////////
741
+
742
+	/// set offset and count into result set,
743
+	/// and optionally set max-matches and cutoff limits
744
+	function SetLimits($offset, $limit, $max=0, $cutoff=0)
745
+	{
746
+		assert(is_int($offset));
747
+		assert(is_int($limit));
748
+		assert($offset>=0);
749
+		assert($limit>0);
750
+		assert($max>=0);
751
+		$this->_offset = $offset;
752
+		$this->_limit = $limit;
753
+		if ($max>0)
754
+			$this->_maxmatches = $max;
755
+		if ($cutoff>0)
756
+			$this->_cutoff = $cutoff;
757
+	}
758
+
759
+	/// set maximum query time, in milliseconds, per-index
760
+	/// integer, 0 means "do not limit"
761
+	function SetMaxQueryTime($max)
762
+	{
763
+		assert(is_int($max));
764
+		assert($max>=0);
765
+		$this->_maxquerytime = $max;
766
+	}
767
+
768
+	/// set matching mode
769
+	function SetMatchMode($mode)
770
+	{
771
+		trigger_error('DEPRECATED: Do not call this method or, even better, use SphinxQL instead of an API', E_USER_DEPRECATED);
772
+		assert($mode==SPH_MATCH_ALL
773
+			|| $mode==SPH_MATCH_ANY
774
+			|| $mode==SPH_MATCH_PHRASE
775
+			|| $mode==SPH_MATCH_BOOLEAN
776
+			|| $mode==SPH_MATCH_EXTENDED
777
+			|| $mode==SPH_MATCH_FULLSCAN
778
+			|| $mode==SPH_MATCH_EXTENDED2);
779
+		$this->_mode = $mode;
780
+	}
781
+
782
+	/// set ranking mode
783
+	function SetRankingMode($ranker, $rankexpr="")
784
+	{
785
+		assert($ranker===0 || $ranker>=1 && $ranker<SPH_RANK_TOTAL);
786
+		assert(is_string($rankexpr));
787
+		$this->_ranker = $ranker;
788
+		$this->_rankexpr = $rankexpr;
789
+	}
790
+
791
+	/// set matches sorting mode
792
+	function SetSortMode($mode, $sortby="")
793
+	{
794
+		assert (
795
+			$mode==SPH_SORT_RELEVANCE ||
796
+			$mode==SPH_SORT_ATTR_DESC ||
797
+			$mode==SPH_SORT_ATTR_ASC ||
798
+			$mode==SPH_SORT_TIME_SEGMENTS ||
799
+			$mode==SPH_SORT_EXTENDED ||
800
+			$mode==SPH_SORT_EXPR);
801
+		assert(is_string($sortby));
802
+		assert($mode==SPH_SORT_RELEVANCE || strlen($sortby)>0);
803
+
804
+		$this->_sort = $mode;
805
+		$this->_sortby = $sortby;
806
+	}
807
+
808
+	/// bind per-field weights by order
809
+	/// DEPRECATED; use SetFieldWeights() instead
810
+	function SetWeights($weights)
811
+	{
812
+		die("This method is now deprecated; please use SetFieldWeights instead");
813
+	}
814
+
815
+	/// bind per-field weights by name
816
+	function SetFieldWeights($weights)
817
+	{
818
+		assert(is_array($weights));
819
+		foreach ($weights as $name=>$weight)
820
+		{
821
+			assert(is_string($name));
822
+			assert(is_int($weight));
823
+		}
824
+		$this->_fieldweights = $weights;
825
+	}
826
+
827
+	/// bind per-index weights by name
828
+	function SetIndexWeights($weights)
829
+	{
830
+		assert(is_array($weights));
831
+		foreach ($weights as $index=>$weight)
832
+		{
833
+			assert(is_string($index));
834
+			assert(is_int($weight));
835
+		}
836
+		$this->_indexweights = $weights;
837
+	}
838
+
839
+	/// set IDs range to match
840
+	/// only match records if document ID is beetwen $min and $max (inclusive)
841
+	function SetIDRange($min, $max)
842
+	{
843
+		assert(is_numeric($min));
844
+		assert(is_numeric($max));
845
+		assert($min<=$max);
846
+		$this->_min_id = $min;
847
+		$this->_max_id = $max;
848
+	}
849
+
850
+	/// set values set filter
851
+	/// only match records where $attribute value is in given set
852
+	function SetFilter($attribute, $values, $exclude=false)
853
+	{
854
+		assert(is_string($attribute));
855
+		assert(is_array($values));
856
+		assert(count($values));
857
+
858
+		if (is_array($values) && count($values))
859
+		{
860
+			foreach ($values as $value)
861
+				assert(is_numeric($value));
862
+
863
+			$this->_filters[] = array("type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values);
864
+		}
865
+	}
866
+
867
+	/// set string filter
868
+	/// only match records where $attribute value is equal
869
+	function SetFilterString($attribute, $value, $exclude=false)
870
+	{
871
+		assert(is_string($attribute));
872
+		assert(is_string($value));
873
+		$this->_filters[] = array("type"=>SPH_FILTER_STRING, "attr"=>$attribute, "exclude"=>$exclude, "value"=>$value);
874
+	}    
875
+
876
+	/// set range filter
877
+	/// only match records if $attribute value is beetwen $min and $max (inclusive)
878
+	function SetFilterRange($attribute, $min, $max, $exclude=false)
879
+	{
880
+		assert(is_string($attribute));
881
+		assert(is_numeric($min));
882
+		assert(is_numeric($max));
883
+		assert($min<=$max);
884
+
885
+		$this->_filters[] = array("type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max);
886
+	}
887
+
888
+	/// set float range filter
889
+	/// only match records if $attribute value is beetwen $min and $max (inclusive)
890
+	function SetFilterFloatRange($attribute, $min, $max, $exclude=false)
891
+	{
892
+		assert(is_string($attribute));
893
+		assert(is_float($min));
894
+		assert(is_float($max));
895
+		assert($min<=$max);
896
+
897
+		$this->_filters[] = array("type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max);
898
+	}
899
+
900
+	/// setup anchor point for geosphere distance calculations
901
+	/// required to use @geodist in filters and sorting
902
+	/// latitude and longitude must be in radians
903
+	function SetGeoAnchor($attrlat, $attrlong, $lat, $long)
904
+	{
905
+		assert(is_string($attrlat));
906
+		assert(is_string($attrlong));
907
+		assert(is_float($lat));
908
+		assert(is_float($long));
909
+
910
+		$this->_anchor = array("attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long);
911
+	}
912
+
913
+	/// set grouping attribute and function
914
+	function SetGroupBy($attribute, $func, $groupsort="@group desc")
915
+	{
916
+		assert(is_string($attribute));
917
+		assert(is_string($groupsort));
918
+		assert($func==SPH_GROUPBY_DAY
919
+			|| $func==SPH_GROUPBY_WEEK
920
+			|| $func==SPH_GROUPBY_MONTH
921
+			|| $func==SPH_GROUPBY_YEAR
922
+			|| $func==SPH_GROUPBY_ATTR
923
+			|| $func==SPH_GROUPBY_ATTRPAIR);
924
+
925
+		$this->_groupby = $attribute;
926
+		$this->_groupfunc = $func;
927
+		$this->_groupsort = $groupsort;
928
+	}
929
+
930
+	/// set count-distinct attribute for group-by queries
931
+	function SetGroupDistinct($attribute)
932
+	{
933
+		assert(is_string($attribute));
934
+		$this->_groupdistinct = $attribute;
935
+	}
936
+
937
+	/// set distributed retries count and delay
938
+	function SetRetries($count, $delay=0)
939
+	{
940
+		assert(is_int($count) && $count>=0);
941
+		assert(is_int($delay) && $delay>=0);
942
+		$this->_retrycount = $count;
943
+		$this->_retrydelay = $delay;
944
+	}
945
+
946
+	/// set result set format (hash or array; hash by default)
947
+	/// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
948
+	function SetArrayResult($arrayresult)
949
+	{
950
+		assert(is_bool($arrayresult));
951
+		$this->_arrayresult = $arrayresult;
952
+	}
953
+
954
+	/// set attribute values override
955
+	/// there can be only one override per attribute
956
+	/// $values must be a hash that maps document IDs to attribute values
957
+	function SetOverride($attrname, $attrtype, $values)
958
+	{
959
+		trigger_error('DEPRECATED: Do not call this method. Use SphinxQL REMAP() function instead.', E_USER_DEPRECATED);
960
+		assert(is_string($attrname));
961
+		assert(in_array($attrtype, array(SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT)));
962
+		assert(is_array($values));
963
+
964
+		$this->_overrides[$attrname] = array("attr"=>$attrname, "type"=>$attrtype, "values"=>$values);
965
+	}
966
+
967
+	/// set select-list (attributes or expressions), SQL-like syntax
968
+	function SetSelect($select)
969
+	{
970
+		assert(is_string($select));
971
+		$this->_select = $select;
972
+	}
973
+
974
+	function SetQueryFlag($flag_name, $flag_value)
975
+	{
976
+		$known_names = array("reverse_scan", "sort_method", "max_predicted_time", "boolean_simplify", "idf", "global_idf", "low_priority");
977
+		$flags = array (
978
+		"reverse_scan" => array(0, 1),
979
+		"sort_method" => array("pq", "kbuffer"),
980
+		"max_predicted_time" => array(0),
981
+		"boolean_simplify" => array(true, false),
982
+		"idf" => array ("normalized", "plain", "tfidf_normalized", "tfidf_unnormalized"),
983
+		"global_idf" => array(true, false),
984
+		"low_priority" => array(true, false)
985
+		);
986
+
987
+		assert(isset($flag_name, $known_names));
988
+		assert(in_array( $flag_value, $flags[$flag_name], true) ||($flag_name=="max_predicted_time" && is_int($flag_value) && $flag_value>=0));
989
+
990
+		if ($flag_name=="reverse_scan") $this->_query_flags = sphSetBit($this->_query_flags, 0, $flag_value==1);
991
+		if ($flag_name=="sort_method") $this->_query_flags = sphSetBit($this->_query_flags, 1, $flag_value=="kbuffer");
992
+		if ($flag_name=="max_predicted_time")
993
+		{
994
+			$this->_query_flags = sphSetBit($this->_query_flags, 2, $flag_value>0);
995
+			$this->_predictedtime = (int)$flag_value;
996
+		}
997
+		if ($flag_name=="boolean_simplify") $this->_query_flags = sphSetBit($this->_query_flags, 3, $flag_value);
998
+		if ($flag_name=="idf" &&($flag_value=="normalized" || $flag_value=="plain")) $this->_query_flags = sphSetBit($this->_query_flags, 4, $flag_value=="plain");
999
+		if ($flag_name=="global_idf") $this->_query_flags = sphSetBit($this->_query_flags, 5, $flag_value);
1000
+		if ($flag_name=="idf" &&($flag_value=="tfidf_normalized" || $flag_value=="tfidf_unnormalized")) $this->_query_flags = sphSetBit($this->_query_flags, 6, $flag_value=="tfidf_normalized");
1001
+		if ($flag_name=="low_priority") $this->_query_flags = sphSetBit($this->_query_flags, 8, $flag_value);
1002
+	}
1003
+
1004
+	/// set outer order by parameters
1005
+	function SetOuterSelect($orderby, $offset, $limit)
1006
+	{
1007
+		assert(is_string($orderby));
1008
+		assert(is_int($offset));
1009
+		assert(is_int($limit));
1010
+		assert($offset>=0);
1011
+		assert($limit>0);
1012
+
1013
+		$this->_outerorderby = $orderby;
1014
+		$this->_outeroffset = $offset;
1015
+		$this->_outerlimit = $limit;
1016
+		$this->_hasouter = true;
1017
+	}
1018
+
1019
+
1020
+	//////////////////////////////////////////////////////////////////////////////
1021
+
1022
+	/// clear all filters (for multi-queries)
1023
+	function ResetFilters ()
1024
+	{
1025
+		$this->_filters = array();
1026
+		$this->_anchor = array();
1027
+	}
1028
+
1029
+	/// clear groupby settings (for multi-queries)
1030
+	function ResetGroupBy ()
1031
+	{
1032
+		$this->_groupby = "";
1033
+		$this->_groupfunc = SPH_GROUPBY_DAY;
1034
+		$this->_groupsort = "@group desc";
1035
+		$this->_groupdistinct = "";
1036
+	}
1037
+
1038
+	/// clear all attribute value overrides (for multi-queries)
1039
+	function ResetOverrides ()
1040
+	{
1041
+		$this->_overrides = array ();
1042
+	}
1043
+
1044
+	function ResetQueryFlag ()
1045
+	{
1046
+		$this->_query_flags = sphSetBit(0, 6, true); // default idf=tfidf_normalized
1047
+		$this->_predictedtime = 0;
1048
+	}
1049
+
1050
+	function ResetOuterSelect ()
1051
+	{
1052
+		$this->_outerorderby = '';
1053
+		$this->_outeroffset = 0;
1054
+		$this->_outerlimit = 0;
1055
+		$this->_hasouter = false;
1056
+	}
1057
+
1058
+	//////////////////////////////////////////////////////////////////////////////
1059
+
1060
+	/// connect to searchd server, run given search query through given indexes,
1061
+	/// and return the search results
1062
+	function Query($query, $index="*", $comment="")
1063
+	{
1064
+		assert(empty($this->_reqs));
1065
+
1066
+		$this->AddQuery($query, $index, $comment);
1067
+		$results = $this->RunQueries ();
1068
+		$this->_reqs = array (); // just in case it failed too early
1069
+
1070
+		if (!is_array($results))
1071
+			return false; // probably network error; error message should be already filled
1072
+
1073
+		$this->_error = $results[0]["error"];
1074
+		$this->_warning = $results[0]["warning"];
1075
+		if ($results[0]["status"]==SEARCHD_ERROR)
1076
+			return false;
1077
+		else
1078
+			return $results[0];
1079
+	}
1080
+
1081
+	/// helper to pack floats in network byte order
1082
+	function _PackFloat($f)
1083
+	{
1084
+		$t1 = pack("f", $f); // machine order
1085
+		list(,$t2) = unpack("L*", $t1); // int in machine order
1086
+		return pack("N", $t2);
1087
+	}
1088
+
1089
+	/// add query to multi-query batch
1090
+	/// returns index into results array from RunQueries() call
1091
+	function AddQuery($query, $index="*", $comment="")
1092
+	{
1093
+		// mbstring workaround
1094
+		$this->_MBPush ();
1095
+
1096
+		// build request
1097
+		$req = pack("NNNNN", $this->_query_flags, $this->_offset, $this->_limit, $this->_mode, $this->_ranker);
1098
+		if ($this->_ranker==SPH_RANK_EXPR)
1099
+			$req .= pack("N", strlen($this->_rankexpr)) . $this->_rankexpr;
1100
+		$req .= pack("N", $this->_sort); // (deprecated) sort mode
1101
+		$req .= pack("N", strlen($this->_sortby)) . $this->_sortby;
1102
+		$req .= pack("N", strlen($query)) . $query; // query itself
1103
+		$req .= pack("N", count($this->_weights)); // weights
1104
+		foreach ($this->_weights as $weight)
1105
+			$req .= pack("N", (int)$weight);
1106
+		$req .= pack("N", strlen($index)) . $index; // indexes
1107
+		$req .= pack("N", 1); // id64 range marker
1108
+		$req .= sphPackU64($this->_min_id) . sphPackU64($this->_max_id); // id64 range
1109
+
1110
+		// filters
1111
+		$req .= pack("N", count($this->_filters));
1112
+		foreach ($this->_filters as $filter)
1113
+		{
1114
+			$req .= pack("N", strlen($filter["attr"])) . $filter["attr"];
1115
+			$req .= pack("N", $filter["type"]);
1116
+			switch ($filter["type"])
1117
+			{
1118
+				case SPH_FILTER_VALUES:
1119
+					$req .= pack("N", count($filter["values"]));
1120
+					foreach ($filter["values"] as $value)
1121
+						$req .= sphPackI64($value);
1122
+					break;
1123
+
1124
+				case SPH_FILTER_RANGE:
1125
+					$req .= sphPackI64($filter["min"]) . sphPackI64($filter["max"]);
1126
+					break;
1127
+
1128
+				case SPH_FILTER_FLOATRANGE:
1129
+					$req .= $this->_PackFloat($filter["min"]) . $this->_PackFloat($filter["max"]);
1130
+					break;
1131
+
1132
+				case SPH_FILTER_STRING:
1133
+					$req .= pack("N", strlen($filter["value"])) . $filter["value"];
1134
+					break;
1135
+
1136
+				default:
1137
+					assert(0 && "internal error: unhandled filter type");
1138
+			}
1139
+			$req .= pack("N", $filter["exclude"]);
1140
+		}
1141
+
1142
+		// group-by clause, max-matches count, group-sort clause, cutoff count
1143
+		$req .= pack("NN", $this->_groupfunc, strlen($this->_groupby)) . $this->_groupby;
1144
+		$req .= pack("N", $this->_maxmatches);
1145
+		$req .= pack("N", strlen($this->_groupsort)) . $this->_groupsort;
1146
+		$req .= pack("NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay);
1147
+		$req .= pack("N", strlen($this->_groupdistinct)) . $this->_groupdistinct;
1148
+
1149
+		// anchor point
1150
+		if (empty($this->_anchor))
1151
+		{
1152
+			$req .= pack("N", 0);
1153
+		} else
1154
+		{
1155
+			$a =& $this->_anchor;
1156
+			$req .= pack("N", 1);
1157
+			$req .= pack("N", strlen($a["attrlat"])) . $a["attrlat"];
1158
+			$req .= pack("N", strlen($a["attrlong"])) . $a["attrlong"];
1159
+			$req .= $this->_PackFloat($a["lat"]) . $this->_PackFloat($a["long"]);
1160
+		}
1161
+
1162
+		// per-index weights
1163
+		$req .= pack("N", count($this->_indexweights));
1164
+		foreach ($this->_indexweights as $idx=>$weight)
1165
+			$req .= pack("N", strlen($idx)) . $idx . pack("N", $weight);
1166
+
1167
+		// max query time
1168
+		$req .= pack("N", $this->_maxquerytime);
1169
+
1170
+		// per-field weights
1171
+		$req .= pack("N", count($this->_fieldweights));
1172
+		foreach ($this->_fieldweights as $field=>$weight)
1173
+			$req .= pack("N", strlen($field)) . $field . pack("N", $weight);
1174
+
1175
+		// comment
1176
+		$req .= pack("N", strlen($comment)) . $comment;
1177
+
1178
+		// attribute overrides
1179
+		$req .= pack("N", count($this->_overrides));
1180
+		foreach ($this->_overrides as $key => $entry)
1181
+		{
1182
+			$req .= pack("N", strlen($entry["attr"])) . $entry["attr"];
1183
+			$req .= pack("NN", $entry["type"], count($entry["values"]));
1184
+			foreach ($entry["values"] as $id=>$val)
1185
+			{
1186
+				assert(is_numeric($id));
1187
+				assert(is_numeric($val));
1188
+
1189
+				$req .= sphPackU64($id);
1190
+				switch ($entry["type"])
1191
+				{
1192
+					case SPH_ATTR_FLOAT:  $req .= $this->_PackFloat($val); break;
1193
+					case SPH_ATTR_BIGINT: $req .= sphPackI64($val); break;
1194
+					default:              $req .= pack("N", $val); break;
1195
+				}
1196
+			}
1197
+		}
1198
+
1199
+		// select-list
1200
+		$req .= pack("N", strlen($this->_select)) . $this->_select;
1201
+
1202
+		// max_predicted_time
1203
+		if ($this->_predictedtime>0)
1204
+			$req .= pack("N", (int)$this->_predictedtime);
1205
+
1206
+		$req .= pack("N", strlen($this->_outerorderby)) . $this->_outerorderby;
1207
+		$req .= pack("NN", $this->_outeroffset, $this->_outerlimit);
1208
+		if ($this->_hasouter)
1209
+			$req .= pack("N", 1);
1210
+		else
1211
+			$req .= pack("N", 0);
1212
+
1213
+		// mbstring workaround
1214
+		$this->_MBPop ();
1215
+
1216
+		// store request to requests array
1217
+		$this->_reqs[] = $req;
1218
+		return count($this->_reqs)-1;
1219
+	}
1220
+
1221
+	/// connect to searchd, run queries batch, and return an array of result sets
1222
+	function RunQueries ()
1223
+	{
1224
+		if (empty($this->_reqs))
1225
+		{
1226
+			$this->_error = "no queries defined, issue AddQuery() first";
1227
+			return false;
1228
+		}
1229
+
1230
+		// mbstring workaround
1231
+		$this->_MBPush ();
1232
+
1233
+		if (!( $fp = $this->_Connect()))
1234
+		{
1235
+			$this->_MBPop ();
1236
+			return false;
1237
+		}
1238
+
1239
+		// send query, get response
1240
+		$nreqs = count($this->_reqs);
1241
+		$req = join("", $this->_reqs);
1242
+		$len = 8+strlen($req);
1243
+		$req = pack("nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs) . $req; // add header
1244
+
1245
+		if (!( $this->_Send($fp, $req, $len+8)) ||
1246
+			 !( $response = $this->_GetResponse($fp, VER_COMMAND_SEARCH)))
1247
+		{
1248
+			$this->_MBPop ();
1249
+			return false;
1250
+		}
1251
+
1252
+		// query sent ok; we can reset reqs now
1253
+		$this->_reqs = array ();
1254
+
1255
+		// parse and return response
1256
+		return $this->_ParseSearchResponse($response, $nreqs);
1257
+	}
1258
+
1259
+	/// parse and return search query (or queries) response
1260
+	function _ParseSearchResponse($response, $nreqs)
1261
+	{
1262
+		$p = 0; // current position
1263
+		$max = strlen($response); // max position for checks, to protect against broken responses
1264
+
1265
+		$results = array ();
1266
+		for($ires=0; $ires<$nreqs && $p<$max; $ires++)
1267
+		{
1268
+			$results[] = array();
1269
+			$result =& $results[$ires];
1270
+
1271
+			$result["error"] = "";
1272
+			$result["warning"] = "";
1273
+
1274
+			// extract status
1275
+			list(,$status) = unpack("N*", substr($response, $p, 4)); $p += 4;
1276
+			$result["status"] = $status;
1277
+			if ($status!=SEARCHD_OK)
1278
+			{
1279
+				list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1280
+				$message = substr($response, $p, $len); $p += $len;
1281
+
1282
+				if ($status==SEARCHD_WARNING)
1283
+				{
1284
+					$result["warning"] = $message;
1285
+				} else
1286
+				{
1287
+					$result["error"] = $message;
1288
+					continue;
1289
+				}
1290
+			}
1291
+
1292
+			// read schema
1293
+			$fields = array ();
1294
+			$attrs = array ();
1295
+
1296
+			list(,$nfields) = unpack("N*", substr($response, $p, 4)); $p += 4;
1297
+			while ($nfields-->0 && $p<$max)
1298
+			{
1299
+				list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1300
+				$fields[] = substr($response, $p, $len); $p += $len;
1301
+			}
1302
+			$result["fields"] = $fields;
1303
+
1304
+			list(,$nattrs) = unpack("N*", substr($response, $p, 4)); $p += 4;
1305
+			while ($nattrs-->0 && $p<$max )
1306
+			{
1307
+				list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1308
+				$attr = substr($response, $p, $len); $p += $len;
1309
+				list(,$type) = unpack("N*", substr($response, $p, 4)); $p += 4;
1310
+				$attrs[$attr] = $type;
1311
+			}
1312
+			$result["attrs"] = $attrs;
1313
+
1314
+			// read match count
1315
+			list(,$count) = unpack("N*", substr($response, $p, 4)); $p += 4;
1316
+			list(,$id64) = unpack("N*", substr($response, $p, 4)); $p += 4;
1317
+
1318
+			// read matches
1319
+			$idx = -1;
1320
+			while ($count-->0 && $p<$max)
1321
+			{
1322
+				// index into result array
1323
+				$idx++;
1324
+
1325
+				// parse document id and weight
1326
+				if ($id64)
1327
+				{
1328
+					$doc = sphUnpackU64(substr($response, $p, 8)); $p += 8;
1329
+					list(,$weight) = unpack("N*", substr($response, $p, 4)); $p += 4;
1330
+				}
1331
+				else
1332
+				{
1333
+					list($doc, $weight) = array_values(unpack("N*N*",
1334
+						substr($response, $p, 8)));
1335
+					$p += 8;
1336
+					$doc = sphFixUint($doc);
1337
+				}
1338
+				$weight = sprintf("%u", $weight);
1339
+
1340
+				// create match entry
1341
+				if ($this->_arrayresult)
1342
+					$result["matches"][$idx] = array("id"=>$doc, "weight"=>$weight);
1343
+				else
1344
+					$result["matches"][$doc]["weight"] = $weight;
1345
+
1346
+				// parse and create attributes
1347
+				$attrvals = array ();
1348
+				foreach ($attrs as $attr=>$type)
1349
+				{
1350
+					// handle 64bit ints
1351
+					if ($type==SPH_ATTR_BIGINT)
1352
+					{
1353
+						$attrvals[$attr] = sphUnpackI64(substr($response, $p, 8)); $p += 8;
1354
+						continue;
1355
+					}
1356
+
1357
+					// handle floats
1358
+					if ($type==SPH_ATTR_FLOAT)
1359
+					{
1360
+						list(,$uval) = unpack("N*", substr($response, $p, 4)); $p += 4;
1361
+						list(,$fval) = unpack("f*", pack("L", $uval)); 
1362
+						$attrvals[$attr] = $fval;
1363
+						continue;
1364
+					}
1365
+
1366
+					// handle everything else as unsigned ints
1367
+					list(,$val) = unpack("N*", substr($response, $p, 4)); $p += 4;
1368
+					if ($type==SPH_ATTR_MULTI)
1369
+					{
1370
+						$attrvals[$attr] = array ();
1371
+						$nvalues = $val;
1372
+						while ($nvalues-->0 && $p<$max)
1373
+						{
1374
+							list(,$val) = unpack("N*", substr($response, $p, 4)); $p += 4;
1375
+							$attrvals[$attr][] = sphFixUint($val);
1376
+						}
1377
+					} else if ($type==SPH_ATTR_MULTI64)
1378
+					{
1379
+						$attrvals[$attr] = array ();
1380
+						$nvalues = $val;
1381
+						while ($nvalues>0 && $p<$max)
1382
+						{
1383
+							$attrvals[$attr][] = sphUnpackI64(substr($response, $p, 8)); $p += 8;
1384
+							$nvalues -= 2;
1385
+						}
1386
+					} else if ($type==SPH_ATTR_STRING)
1387
+					{
1388
+						$attrvals[$attr] = substr($response, $p, $val);
1389
+						$p += $val;
1390
+					} else if ($type==SPH_ATTR_FACTORS)
1391
+					{
1392
+						$attrvals[$attr] = substr($response, $p, $val-4);
1393
+						$p += $val-4;
1394
+					} else
1395
+					{
1396
+						$attrvals[$attr] = sphFixUint($val);
1397
+					}
1398
+				}
1399
+
1400
+				if ($this->_arrayresult)
1401
+					$result["matches"][$idx]["attrs"] = $attrvals;
1402
+				else
1403
+					$result["matches"][$doc]["attrs"] = $attrvals;
1404
+			}
1405
+
1406
+			list($total, $total_found, $msecs, $words) =
1407
+				array_values(unpack("N*N*N*N*", substr($response, $p, 16)));
1408
+			$result["total"] = sprintf("%u", $total);
1409
+			$result["total_found"] = sprintf("%u", $total_found);
1410
+			$result["time"] = sprintf("%.3f", $msecs/1000);
1411
+			$p += 16;
1412
+
1413
+			while ($words-->0 && $p<$max)
1414
+			{
1415
+				list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1416
+				$word = substr($response, $p, $len); $p += $len;
1417
+				list($docs, $hits) = array_values(unpack("N*N*", substr($response, $p, 8))); $p += 8;
1418
+				$result["words"][$word] = array (
1419
+					"docs"=>sprintf("%u", $docs),
1420
+					"hits"=>sprintf("%u", $hits));
1421
+			}
1422
+		}
1423
+
1424
+		$this->_MBPop ();
1425
+		return $results;
1426
+	}
1427
+
1428
+	/////////////////////////////////////////////////////////////////////////////
1429
+	// excerpts generation
1430
+	/////////////////////////////////////////////////////////////////////////////
1431
+
1432
+	/// connect to searchd server, and generate exceprts (snippets)
1433
+	/// of given documents for given query. returns false on failure,
1434
+	/// an array of snippets on success
1435
+	function BuildExcerpts($docs, $index, $words, $opts=array())
1436
+	{
1437
+		assert(is_array($docs));
1438
+		assert(is_string($index));
1439
+		assert(is_string($words));
1440
+		assert(is_array($opts));
1441
+
1442
+		$this->_MBPush ();
1443
+
1444
+		if (!( $fp = $this->_Connect()))
1445
+		{
1446
+			$this->_MBPop();
1447
+			return false;
1448
+		}
1449
+
1450
+		/////////////////
1451
+		// fixup options
1452
+		/////////////////
1453
+
1454
+		if (!isset($opts["before_match"])) $opts["before_match"] = "<b>";
1455
+		if (!isset($opts["after_match"])) $opts["after_match"] = "</b>";
1456
+		if (!isset($opts["chunk_separator"])) $opts["chunk_separator"] = " ... ";
1457
+		if (!isset($opts["limit"])) $opts["limit"] = 256;
1458
+		if (!isset($opts["limit_passages"])) $opts["limit_passages"] = 0;
1459
+		if (!isset($opts["limit_words"])) $opts["limit_words"] = 0;
1460
+		if (!isset($opts["around"])) $opts["around"] = 5;
1461
+		if (!isset($opts["exact_phrase"])) $opts["exact_phrase"] = false;
1462
+		if (!isset($opts["single_passage"])) $opts["single_passage"] = false;
1463
+		if (!isset($opts["use_boundaries"])) $opts["use_boundaries"] = false;
1464
+		if (!isset($opts["weight_order"])) $opts["weight_order"] = false;
1465
+		if (!isset($opts["query_mode"])) $opts["query_mode"] = false;
1466
+		if (!isset($opts["force_all_words"])) $opts["force_all_words"] = false;
1467
+		if (!isset($opts["start_passage_id"])) $opts["start_passage_id"] = 1;
1468
+		if (!isset($opts["load_files"])) $opts["load_files"] = false;
1469
+		if (!isset($opts["html_strip_mode"])) $opts["html_strip_mode"] = "index";
1470
+		if (!isset($opts["allow_empty"])) $opts["allow_empty"] = false;
1471
+		if (!isset($opts["passage_boundary"])) $opts["passage_boundary"] = "none";
1472
+		if (!isset($opts["emit_zones"])) $opts["emit_zones"] = false;
1473
+		if (!isset($opts["load_files_scattered"])) $opts["load_files_scattered"] = false;
1474
+
1475
+
1476
+		/////////////////
1477
+		// build request
1478
+		/////////////////
1479
+
1480
+		// v.1.2 req
1481
+		$flags = 1; // remove spaces
1482
+		if ($opts["exact_phrase"]) $flags |= 2;
1483
+		if ($opts["single_passage"]) $flags |= 4;
1484
+		if ($opts["use_boundaries"]) $flags |= 8;
1485
+		if ($opts["weight_order"]) $flags |= 16;
1486
+		if ($opts["query_mode"]) $flags |= 32;
1487
+		if ($opts["force_all_words"]) $flags |= 64;
1488
+		if ($opts["load_files"]) $flags |= 128;
1489
+		if ($opts["allow_empty"]) $flags |= 256;
1490
+		if ($opts["emit_zones"]) $flags |= 512;
1491
+		if ($opts["load_files_scattered"]) $flags |= 1024;
1492
+		$req = pack("NN", 0, $flags); // mode=0, flags=$flags
1493
+		$req .= pack("N", strlen($index)) . $index; // req index
1494
+		$req .= pack("N", strlen($words)) . $words; // req words
1495
+
1496
+		// options
1497
+		$req .= pack("N", strlen($opts["before_match"])) . $opts["before_match"];
1498
+		$req .= pack("N", strlen($opts["after_match"])) . $opts["after_match"];
1499
+		$req .= pack("N", strlen($opts["chunk_separator"])) . $opts["chunk_separator"];
1500
+		$req .= pack("NN", (int)$opts["limit"], (int)$opts["around"]);
1501
+		$req .= pack("NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"]); // v.1.2
1502
+		$req .= pack("N", strlen($opts["html_strip_mode"])) . $opts["html_strip_mode"];
1503
+		$req .= pack("N", strlen($opts["passage_boundary"])) . $opts["passage_boundary"];
1504
+
1505
+		// documents
1506
+		$req .= pack("N", count($docs));
1507
+		foreach ($docs as $doc)
1508
+		{
1509
+			assert(is_string($doc));
1510
+			$req .= pack("N", strlen($doc)) . $doc;
1511
+		}
1512
+
1513
+		////////////////////////////
1514
+		// send query, get response
1515
+		////////////////////////////
1516
+
1517
+		$len = strlen($req);
1518
+		$req = pack("nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len) . $req; // add header
1519
+		if (!( $this->_Send($fp, $req, $len+8)) ||
1520
+			 !( $response = $this->_GetResponse($fp, VER_COMMAND_EXCERPT)))
1521
+		{
1522
+			$this->_MBPop ();
1523
+			return false;
1524
+		}
1525
+
1526
+		//////////////////
1527
+		// parse response
1528
+		//////////////////
1529
+
1530
+		$pos = 0;
1531
+		$res = array ();
1532
+		$rlen = strlen($response);
1533
+		for($i=0; $i<count($docs); $i++)
1534
+		{
1535
+			list(,$len) = unpack("N*", substr($response, $pos, 4));
1536
+			$pos += 4;
1537
+
1538
+			if ($pos+$len > $rlen)
1539
+			{
1540
+				$this->_error = "incomplete reply";
1541
+				$this->_MBPop ();
1542
+				return false;
1543
+			}
1544
+			$res[] = $len ? substr($response, $pos, $len) : "";
1545
+			$pos += $len;
1546
+		}
1547
+
1548
+		$this->_MBPop ();
1549
+		return $res;
1550
+	}
1551
+
1552
+
1553
+	/////////////////////////////////////////////////////////////////////////////
1554
+	// keyword generation
1555
+	/////////////////////////////////////////////////////////////////////////////
1556
+
1557
+	/// connect to searchd server, and generate keyword list for a given query
1558
+	/// returns false on failure,
1559
+	/// an array of words on success
1560
+	function BuildKeywords($query, $index, $hits)
1561
+	{
1562
+		assert(is_string($query));
1563
+		assert(is_string($index));
1564
+		assert(is_bool($hits));
1565
+
1566
+		$this->_MBPush ();
1567
+
1568
+		if (!( $fp = $this->_Connect()))
1569
+		{
1570
+			$this->_MBPop();
1571
+			return false;
1572
+		}
1573
+
1574
+		/////////////////
1575
+		// build request
1576
+		/////////////////
1577
+
1578
+		// v.1.0 req
1579
+		$req  = pack("N", strlen($query)) . $query; // req query
1580
+		$req .= pack("N", strlen($index)) . $index; // req index
1581
+		$req .= pack("N", (int)$hits);
1582
+
1583
+		////////////////////////////
1584
+		// send query, get response
1585
+		////////////////////////////
1586
+
1587
+		$len = strlen($req);
1588
+		$req = pack("nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len) . $req; // add header
1589
+		if (!( $this->_Send($fp, $req, $len+8)) ||
1590
+			 !( $response = $this->_GetResponse($fp, VER_COMMAND_KEYWORDS)))
1591
+		{
1592
+			$this->_MBPop ();
1593
+			return false;
1594
+		}
1595
+
1596
+		//////////////////
1597
+		// parse response
1598
+		//////////////////
1599
+
1600
+		$pos = 0;
1601
+		$res = array ();
1602
+		$rlen = strlen($response);
1603
+		list(,$nwords) = unpack("N*", substr($response, $pos, 4));
1604
+		$pos += 4;
1605
+		for($i=0; $i<$nwords; $i++)
1606
+		{
1607
+			list(,$len) = unpack("N*", substr($response, $pos, 4)); $pos += 4;
1608
+			$tokenized = $len ? substr($response, $pos, $len) : "";
1609
+			$pos += $len;
1610
+
1611
+			list(,$len) = unpack("N*", substr($response, $pos, 4)); $pos += 4;
1612
+			$normalized = $len ? substr($response, $pos, $len) : "";
1613
+			$pos += $len;
1614
+
1615
+			$res[] = array("tokenized"=>$tokenized, "normalized"=>$normalized);
1616
+
1617
+			if ($hits)
1618
+			{
1619
+				list($ndocs,$nhits) = array_values(unpack("N*N*", substr($response, $pos, 8)));
1620
+				$pos += 8;
1621
+				$res [$i]["docs"] = $ndocs;
1622
+				$res [$i]["hits"] = $nhits;
1623
+			}
1624
+
1625
+			if ($pos > $rlen)
1626
+			{
1627
+				$this->_error = "incomplete reply";
1628
+				$this->_MBPop ();
1629
+				return false;
1630
+			}
1631
+		}
1632
+
1633
+		$this->_MBPop ();
1634
+		return $res;
1635
+	}
1636
+
1637
+	function EscapeString($string)
1638
+	{
1639
+		$from = array('\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=', '<');
1640
+		$to   = array('\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=', '\<');
1641
+
1642
+		return str_replace($from, $to, $string);
1643
+	}
1644
+
1645
+	/////////////////////////////////////////////////////////////////////////////
1646
+	// attribute updates
1647
+	/////////////////////////////////////////////////////////////////////////////
1648
+
1649
+	/// batch update given attributes in given rows in given indexes
1650
+	/// returns amount of updated documents (0 or more) on success, or -1 on failure
1651
+	function UpdateAttributes($index, $attrs, $values, $mva=false, $ignorenonexistent=false)
1652
+	{
1653
+		// verify everything
1654
+		assert(is_string($index));
1655
+		assert(is_bool($mva));
1656
+		assert(is_bool($ignorenonexistent));
1657
+
1658
+		assert(is_array($attrs));
1659
+		foreach ($attrs as $attr)
1660
+			assert(is_string($attr));
1661
+
1662
+		assert(is_array($values));
1663
+		foreach ($values as $id=>$entry)
1664
+		{
1665
+			assert(is_numeric($id));
1666
+			assert(is_array($entry));
1667
+			assert(count($entry)==count($attrs));
1668
+			foreach ($entry as $v)
1669
+			{
1670
+				if ($mva)
1671
+				{
1672
+					assert(is_array($v));
1673
+					foreach ($v as $vv)
1674
+						assert(is_int($vv));
1675
+				} else
1676
+					assert(is_int($v));
1677
+			}
1678
+		}
1679
+
1680
+		// build request
1681
+		$this->_MBPush ();
1682
+		$req = pack("N", strlen($index)) . $index;
1683
+
1684
+		$req .= pack("N", count($attrs));
1685
+		$req .= pack("N", $ignorenonexistent ? 1 : 0);
1686
+		foreach ($attrs as $attr)
1687
+		{
1688
+			$req .= pack("N", strlen($attr)) . $attr;
1689
+			$req .= pack("N", $mva ? 1 : 0);
1690
+		}
1691
+
1692
+		$req .= pack("N", count($values));
1693
+		foreach ($values as $id=>$entry)
1694
+		{
1695
+			$req .= sphPackU64($id);
1696
+			foreach ($entry as $v)
1697
+			{
1698
+				$req .= pack("N", $mva ? count($v) : $v);
1699
+				if ($mva)
1700
+					foreach ($v as $vv)
1701
+						$req .= pack("N", $vv);
1702
+			}
1703
+		}
1704
+
1705
+		// connect, send query, get response
1706
+		if (!( $fp = $this->_Connect()))
1707
+		{
1708
+			$this->_MBPop ();
1709
+			return -1;
1710
+		}
1711
+
1712
+		$len = strlen($req);
1713
+		$req = pack("nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len) . $req; // add header
1714
+		if (!$this->_Send($fp, $req, $len+8))
1715
+		{
1716
+			$this->_MBPop ();
1717
+			return -1;
1718
+		}
1719
+
1720
+		if (!( $response = $this->_GetResponse($fp, VER_COMMAND_UPDATE)))
1721
+		{
1722
+			$this->_MBPop ();
1723
+			return -1;
1724
+		}
1725
+
1726
+		// parse response
1727
+		list(,$updated) = unpack("N*", substr($response, 0, 4));
1728
+		$this->_MBPop ();
1729
+		return $updated;
1730
+	}
1731
+
1732
+	/////////////////////////////////////////////////////////////////////////////
1733
+	// persistent connections
1734
+	/////////////////////////////////////////////////////////////////////////////
1735
+
1736
+	function Open()
1737
+	{
1738
+		if ($this->_socket !== false)
1739
+		{
1740
+			$this->_error = 'already connected';
1741
+			return false;
1742
+		}
1743
+		if (!$fp = $this->_Connect())
1744
+			return false;
1745
+
1746
+		// command, command version = 0, body length = 4, body = 1
1747
+		$req = pack("nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1);
1748
+		if (!$this->_Send($fp, $req, 12))
1749
+			return false;
1750
+
1751
+		$this->_socket = $fp;
1752
+		return true;
1753
+	}
1754
+
1755
+	function Close()
1756
+	{
1757
+		if ($this->_socket === false)
1758
+		{
1759
+			$this->_error = 'not connected';
1760
+			return false;
1761
+		}
1762
+
1763
+		fclose($this->_socket);
1764
+		$this->_socket = false;
1765
+
1766
+		return true;
1767
+	}
1768
+
1769
+	//////////////////////////////////////////////////////////////////////////
1770
+	// status
1771
+	//////////////////////////////////////////////////////////////////////////
1772
+
1773
+	function Status ($session=false)
1774
+	{
1775
+		assert(is_bool($session));
1776
+
1777
+		$this->_MBPush ();
1778
+		if (!( $fp = $this->_Connect()))
1779
+		{
1780
+			$this->_MBPop();
1781
+			return false;
1782
+		}
1783
+
1784
+		$req = pack("nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, $session?0:1); // len=4, body=1
1785
+		if (!( $this->_Send($fp, $req, 12)) ||
1786
+			 !( $response = $this->_GetResponse($fp, VER_COMMAND_STATUS)))
1787
+		{
1788
+			$this->_MBPop ();
1789
+			return false;
1790
+		}
1791
+
1792
+		$res = substr($response, 4); // just ignore length, error handling, etc
1793
+		$p = 0;
1794
+		list($rows, $cols) = array_values(unpack("N*N*", substr($response, $p, 8))); $p += 8;
1795
+
1796
+		$res = array();
1797
+		for($i=0; $i<$rows; $i++)
1798
+			for($j=0; $j<$cols; $j++)
1799
+		{
1800
+			list(,$len) = unpack("N*", substr($response, $p, 4)); $p += 4;
1801
+			$res[$i][] = substr($response, $p, $len); $p += $len;
1802
+		}
1803
+
1804
+		$this->_MBPop ();
1805
+		return $res;
1806
+	}
1807
+
1808
+	//////////////////////////////////////////////////////////////////////////
1809
+	// flush
1810
+	//////////////////////////////////////////////////////////////////////////
1811
+
1812
+	function FlushAttributes ()
1813
+	{
1814
+		$this->_MBPush ();
1815
+		if (!( $fp = $this->_Connect()))
1816
+		{
1817
+			$this->_MBPop();
1818
+			return -1;
1819
+		}
1820
+
1821
+		$req = pack("nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0); // len=0
1822
+		if (!( $this->_Send($fp, $req, 8)) ||
1823
+			 !( $response = $this->_GetResponse($fp, VER_COMMAND_FLUSHATTRS)))
1824
+		{
1825
+			$this->_MBPop ();
1826
+			return -1;
1827
+		}
1828
+
1829
+		$tag = -1;
1830
+		if (strlen($response)==4)
1831
+			list(,$tag) = unpack("N*", $response);
1832
+		else
1833
+			$this->_error = "unexpected response length";
1834
+
1835
+		$this->_MBPop ();
1836
+		return $tag;
1837
+	}
1838 1838
 }
1839 1839
 
1840 1840
 //
Please login to merge, or discard this patch.