PluginProbe
Datafeedr API / 1.0.3
Datafeedr API v1.0.3
1.4.3 1.4.2 1.0.125 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.3 1.0.30 1.0.31 1.0.32 All 182 releases
datafeedr-api / classes / class-dfrapi-searchform.php

class-dfrapi-searchform.php in Datafeedr API 1.0.3, at classes/class-dfrapi-searchform.php

676 lines 24.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4
5 class Dfrapi_SearchForm
6 {
7 function fields() {
8 $opFulltext = array(
9 'contain' => __( 'contains', DFRAPI_DOMAIN ),
10 'not_contain' => __( 'doesn\'t contain', DFRAPI_DOMAIN ),
11 'start' => __( 'starts with', DFRAPI_DOMAIN ),
12 'end' => __( 'ends with', DFRAPI_DOMAIN ),
13 'match' => __( 'matches', DFRAPI_DOMAIN )
14 );
15 $opFulltextExact = array_merge($opFulltext, array(
16 'is' => __( 'is', DFRAPI_DOMAIN )
17 ));
18 $opRange = array(
19 'lt' => __( 'less than', DFRAPI_DOMAIN ),
20 'gt' => __( 'greater than', DFRAPI_DOMAIN ),
21 'between' => __( 'between', DFRAPI_DOMAIN )
22 );
23 $opIs = array(
24 'is' => __( 'is', DFRAPI_DOMAIN )
25 );
26 $opIsIsnt = array(
27 'is' => __( 'is', DFRAPI_DOMAIN ),
28 'is_not' => __( 'isn\'t', DFRAPI_DOMAIN )
29 );
30 $opYesNo = array(
31 'yes' => __( 'yes', DFRAPI_DOMAIN ),
32 'no' => __( 'no', DFRAPI_DOMAIN )
33 );
34
35 $sortOpts = array(
36 '' => __( 'Relevance', DFRAPI_DOMAIN ),
37 '+price' => __( 'Price Ascending', DFRAPI_DOMAIN ),
38 '-price' => __( 'Price Decending', DFRAPI_DOMAIN ),
39 '+saleprice' => __( 'Sale Price Ascending', DFRAPI_DOMAIN ),
40 '-saleprice' => __( 'Sale Price Decending', DFRAPI_DOMAIN ),
41 '+salediscount' => __( 'Discount Ascending', DFRAPI_DOMAIN ),
42 '-salediscount' => __( 'Discount Decending', DFRAPI_DOMAIN ),
43 '+merchant' => __( 'Merchant', DFRAPI_DOMAIN ),
44 '+time_updated' => __( 'Last updated Ascending', DFRAPI_DOMAIN ),
45 '-time_updated' => __( 'Last updated Decending', DFRAPI_DOMAIN )
46 );
47
48 return array(
49 array(
50 'title' => __( 'Any field', DFRAPI_DOMAIN ),
51 'name' => 'any',
52 'input' => 'text',
53 'operator' => $opFulltext
54 ),
55 array(
56 'title' => __( 'Product name', DFRAPI_DOMAIN ),
57 'name' => 'name',
58 'input' => 'text',
59 'operator' => $opFulltextExact,
60 ),
61 array(
62 'title' => __( 'Brand', DFRAPI_DOMAIN ),
63 'name' => 'brand',
64 'input' => 'text',
65 'operator' => $opFulltextExact,
66 ),
67 array(
68 'title' => __( 'Description', DFRAPI_DOMAIN ),
69 'name' => 'description',
70 'input' => 'text',
71 'operator' => $opFulltext,
72 ),
73 array(
74 'title' => __( 'Tags', DFRAPI_DOMAIN ),
75 'name' => 'tags',
76 'input' => 'text',
77 'operator' => array(
78 'in' => __( 'contain', DFRAPI_DOMAIN ),
79 'not_in' => __( 'don\'t contain', DFRAPI_DOMAIN )
80 )
81 ),
82 array(
83 'title' => __( 'Category', DFRAPI_DOMAIN ),
84 'name' => 'category',
85 'input' => 'text',
86 'operator' => $opFulltext
87 ),
88 array(
89 'title' => __( 'Product type', DFRAPI_DOMAIN ),
90 'name' => 'type',
91 'input' => 'select',
92 'options' => array(
93 'products' => __( 'Product', DFRAPI_DOMAIN ),
94 'coupons' => __( 'Coupon', DFRAPI_DOMAIN )
95 ),
96 'operator' => $opIs
97 ),
98 array(
99 'title' => __( 'Currency', DFRAPI_DOMAIN ),
100 'name' => 'currency',
101 'input' => 'select',
102 'options' => array(
103 'USD' => 'USD',
104 'CAD' => 'CAD',
105 'GBP' => 'GBP',
106 'EUR' => 'EUR',
107 'AUD' => 'AUD',
108 'DKK' => 'DKK',
109 'SEK' => 'SEK',
110 'NOK' => 'NOK',
111 'CHF' => 'CHF',
112 'NZD' => 'NZD',
113 'TRY' => 'TRY',
114 ),
115 'operator' => $opIsIsnt
116 ),
117 array(
118 'title' => __( 'Price', DFRAPI_DOMAIN ),
119 'name' => 'price',
120 'input' => 'range',
121 'operator' => $opRange
122 ),
123 array(
124 'title' => __( 'Sale Price', DFRAPI_DOMAIN ),
125 'name' => 'saleprice',
126 'input' => 'range',
127 'operator' => $opRange
128 ),
129 array(
130 'title' => __( 'Network', DFRAPI_DOMAIN ),
131 'name' => 'source_id',
132 'input' => 'network',
133 'operator' => $opIsIsnt
134 ),
135 array(
136 'title' => __( 'Merchant', DFRAPI_DOMAIN ),
137 'name' => 'merchant_id',
138 'input' => 'merchant',
139 'operator' => $opIsIsnt
140 ),
141 array(
142 'title' => __( 'On Sale', DFRAPI_DOMAIN ),
143 'name' => 'onsale',
144 'input' => 'none',
145 'operator' => $opYesNo
146 ),
147 array(
148 'title' => __( 'Discount', DFRAPI_DOMAIN ),
149 'name' => 'salediscount',
150 'input' => 'range',
151 'operator' => $opRange
152 ),
153 array(
154 'title' => __( 'Has Image', DFRAPI_DOMAIN ),
155 'name' => 'image',
156 'input' => 'none',
157 'operator' => $opYesNo
158 ),
159 array(
160 'title' => __( 'Last updated', DFRAPI_DOMAIN ),
161 'name' => 'time_updated',
162 'input' => 'range',
163 'operator' => array('lt' => 'before', 'gt' => 'after', 'between' => 'between'),
164 ),
165 array(
166 'title' => __( 'Limit', DFRAPI_DOMAIN ),
167 'name' => 'limit',
168 'input' => 'text',
169 'operator' => array('is' => 'is')
170 ),
171 array(
172 'title' => __( 'Sort By', DFRAPI_DOMAIN ),
173 'name' => 'sort',
174 'input' => 'none',
175 'operator' => $sortOpts
176 ),
177 array(
178 'title' => __( 'Exclude Duplicates', DFRAPI_DOMAIN ),
179 'name' => 'duplicates',
180 'input' => 'text',
181 'operator' => array(
182 'is' => __( 'matching these fields', DFRAPI_DOMAIN ),
183 )
184 )
185 );
186 }
187
188 function defaults() {
189 return array(
190 'any' => array('operator' => 'contain', 'value'=> ''),
191 'name' => array('operator' => 'contain', 'value'=> ''),
192 'type' => array('value'=> 'product'),
193 'currency' => array('value'=> 'USD'),
194 'price' => array('operator' => 'between', 'value'=> '0', 'value2' => '999999'),
195 'saleprice' => array('operator' => 'between', 'value'=> '0', 'value2' => '999999'),
196 'source_id' => array('value'=> array()),
197 'merchant_id' => array('value'=> array()),
198 'onsale' => array('value'=> '1'),
199 'image' => array('value'=> '1'),
200 'thumbnail' => array('value'=> '1'),
201 'time_updated' => array('operator' => 'lt', 'value'=> 'today'),
202 'limit' => array('value'=> 1000),
203 );
204 }
205
206 public $useSelected;
207 public $prefix;
208
209 function get($ary, $key, $default="") {
210 return is_array($ary) && isset($ary[$key]) ? $ary[$key] : $default;
211 }
212
213 function inputPrefix($index) {
214 return sprintf('%s[%s]', $this->prefix ? $this->prefix : "query", $index);
215 }
216
217 function byName($a, $b) {
218 return strcasecmp($a['name'], $b['name']);
219 }
220
221 function ary($s) {
222 if(!is_array($s))
223 $s = explode(',', trim($s));
224 return array_filter($s, 'strlen');
225 }
226
227 function ids($lst) {
228 $ids = array();
229 foreach($lst as $obj)
230 $ids []= $obj['_id'];
231 return $ids;
232 }
233
234 function selectOpts($opts, $selectedValue=NULL) {
235 $html = "";
236 foreach($opts as $value => $title) {
237 $sel = $selectedValue == $value ? "selected='selected'" : "";
238 $title = htmlspecialchars($title);
239 $value = htmlspecialchars($value);
240 $html .= "<option value=\"$value\" $sel>$title</option>";
241 }
242 return $html;
243 }
244
245 function allNetworks() {
246 $lst = dfrapi_api_get_all_networks();
247 usort($lst, array($this, 'byName'));
248 return $lst;
249 }
250
251 function selectedNetworks() {
252 $selected = $this->get(get_option( 'dfrapi_networks' ), 'ids');
253 if(empty($selected))
254 return array();
255
256 $lst = array();
257 foreach($this->allNetworks() as $net)
258 if(isset($selected[$net['_id']]))
259 $lst []= $net;
260 return $lst;
261 }
262
263 function groupClass($nid) {
264 if (empty($nid)) { return ''; }
265 $networks = $this->allNetworks();
266 foreach($networks as $network) {
267 if ($network['_id'] == $nid) {
268 $name = str_replace( array( " ", "-", "." ), "", $network['group'] );
269 $type = ( $network['type'] == 'coupons' ) ? '_coupons' : '';
270 return 'network_logo_16x16_' . strtolower( $name . $type );
271 }
272 }
273 }
274
275 function selectedMerchants() {
276 $selected = $this->get(get_option( 'dfrapi_merchants' ), 'ids');
277 if(empty($selected))
278 return array();
279
280 $lst = array();
281 foreach(dfrapi_api_get_merchants_by_id($selected) as $merchant) {
282 $lst []= $merchant;
283 }
284 usort($lst, array($this, 'byName'));
285 return $lst;
286 }
287
288 function networksMerchantsPopup($kind, $value) {
289 $title = array(
290 'network' => __( 'Select Networks', DFRAPI_DOMAIN ),
291 'merchant' => __( 'Select Merchants', DFRAPI_DOMAIN )
292 );
293 $clear = __( 'Clear search', DFRAPI_DOMAIN );
294 $ok = __( 'OK', DFRAPI_DOMAIN );
295 $cells = "";
296
297 $all = ($kind == 'network') ?
298 ($this->useSelected ? $this->selectedNetworks() : $this->allNetworks()) :
299 $this->selectedMerchants();
300
301 foreach($all as $obj) {
302 $id = $obj['_id'];
303 $nid = ($kind == 'network') ? $obj['_id'] : $obj['source_id'];
304 $group_class = $this->groupClass($nid);
305
306 $name = $obj['name'];
307 $checked = in_array($id, $value) ? "checked='checked'" : "";
308
309 $cells .= "
310 <div class='inline_frame_element nid_{$nid}'>
311 <label>
312 <input type='checkbox' value='{$id}' $checked />
313 <span class='element_name {$group_class}'>{$name}</span>
314 </label>
315 </div>
316 ";
317 }
318
319 return "
320 <h1>{$title[$kind]}</h1>
321 <div class='filter_action'>
322 Search: <input type='text' />
323 <a class='reset_search button' title='{$clear}'>&times;</a>
324 </div>
325 <div class='inline_frame'>
326 <div>
327 {$cells}
328 <div class='clearfix'></div>
329 </div>
330 </div>
331 <div><a class='button submit'>$ok</a></div>
332 ";
333 }
334
335 function networksMerchantsNames($kind, $value, $maxNames=5) {
336 $all = ($kind == 'network') ? $this->allNetworks() : dfrapi_api_get_merchants_by_id($value);
337 $names = array();
338 foreach($all as $obj) {
339 if(in_array($obj['_id'], $value)) {
340 $names []= "<span>{$obj['name']}</span>";
341 if(count($names) >= $maxNames)
342 break;
343 }
344 }
345 $html = implode(', ', $names);
346 if(count($value) - count($names)) {
347 $html .= ' ' . sprintf(__( 'and %s more', DFRAPI_DOMAIN ), count($value) - count($names));
348 }
349 return $html;
350 }
351
352 function chooseBox($kind, $field, $index, $value) {
353 $pfx = $this->inputPrefix($index);
354 $value = implode(',', $this->ary($value));
355 $choose = __( 'choose', DFRAPI_DOMAIN );
356 return "
357 <div class='dfrapi_choose_box' rel='{$kind}'>
358 <span class='names'></span>
359 <a class='button choose_{$kind}'>{$choose}</a>
360 <input name='{$pfx}[value]' type='hidden' value=\"$value\"/>
361 </div>
362 ";
363 }
364
365 function ajaxHandler() {
366 $command = $this->get($_POST, 'command');
367 $value = $this->ary($this->get($_POST, 'value'));
368 $this->useSelected = intval($this->get($_POST, 'useSelected', 1));
369
370 switch($command) {
371 case "choose_network":
372 return $this->networksMerchantsPopup('network', $value);
373 case "choose_merchant":
374 return $this->networksMerchantsPopup('merchant', $value);
375 case "names_network":
376 return $this->networksMerchantsNames('network', $value);
377 case "names_merchant":
378 return $this->networksMerchantsNames('merchant', $value);
379 }
380 return "";
381 }
382
383 function renderField($field, $index, $params) {
384 $pfx = $this->inputPrefix($index);
385 $value = $this->get($params, 'value');
386 $operator = $this->get($params, 'operator');
387 $input = "";
388
389 switch($field['input']) {
390 case 'text':
391 $value = htmlspecialchars($value);
392 $input = "<input class='long' name='{$pfx}[value]' type='text' value=\"$value\"/>";
393 break;
394 case 'select':
395 $opts = $this->selectOpts($field['options'], $value);
396 $input = "<select name='{$pfx}[value]'>{$opts}</select>";
397 break;
398 case 'range':
399 $value = htmlspecialchars($value);
400 $value2 = htmlspecialchars($this->get($params, 'value2'));
401 $and = __( 'and', DFRAPI_DOMAIN );
402 $input = "
403 <input class='short' name='{$pfx}[value]' type='text' value=\"$value\"/>
404 <span class='value2' style='display:none'>
405 $and
406 <input class='short' name='{$pfx}[value2]' type='text' value=\"$value2\" />
407 </span>
408 ";
409 break;
410 case 'network':
411 $input = $this->chooseBox('network', $field, $index, $value);
412 break;
413 case 'merchant':
414 $input = $this->chooseBox('merchant', $field, $index, $value);
415 break;
416 case 'none':
417 $input = "";
418 break;
419 }
420
421 if(count($field['operator']) == 1) {
422 $key = key($field['operator']);
423 $val = current($field['operator']);
424 $operator = "
425 <input type='hidden' name='{$pfx}[operator]' value=\"{$key}\" />
426 <span>{$val}</span>
427 ";
428 } else {
429 $opts = $this->selectOpts($field['operator'], $operator);
430 $operator = "<select name='{$pfx}[operator]'>{$opts}</select>";
431 }
432 return array($operator, $input);
433 }
434
435 function renderRow($field, $index, $params, $show) {
436 $pfx = $this->inputPrefix($index);
437 list($operator, $input) = $this->renderField($field, $index, $params);
438 $fieldOpts = '';
439 foreach($this->fields() as $f) {
440 if(!$this->useSelected && $f['name'] == 'merchant_id')
441 continue;
442 $sel = $field['name'] == $f['name'] ? "selected='selected'" : "";
443 $fieldOpts .= "<option value=\"{$f['name']}\" $sel>{$f['title']}</option>";
444 }
445 $style = $show ? "" : "style='display:none'";
446 return "
447 <div class='filter filter_{$field['name']}' {$style}>
448 <div class='valuewrapper'>
449 <div class='value'>{$input}</div>
450 </div>
451 <div class='plusminus'><a class='minus'></a> </div>
452 <div class='field'><select name='{$pfx}[field]' style='width: 95%'>{$fieldOpts}</select></div>
453 <div class='operator'>{$operator}</div>
454 </div>
455 ";
456 }
457
458 function render($prefix, $query, $useSelected=TRUE) {
459 $this->prefix = $prefix;
460 $this->useSelected = intval($useSelected);
461 if(!$query)
462 $query = array(
463 array('field'=>'any', 'value'=>'')
464 );
465 $defaults = $this->defaults();
466
467 $fieldMap = array();
468 $fieldUsed = array();
469 foreach($this->fields() as $f) {
470 if(!$this->useSelected && $f['name'] == 'merchant_id')
471 continue;
472 $fieldMap[$f['name']] = $f;
473 }
474
475 $form = "";
476 $index = 0;
477 foreach($query as $params) {
478 $field = $this->get($fieldMap, $this->get($params, 'field'));
479 if(!$field)
480 continue;
481 $fieldUsed[$field['name']] = 1;
482 $form .= $this->renderRow($field, $index++, $params, TRUE);
483 }
484 $show = ($index == 0); // if none shown, show the first
485 foreach($fieldMap as $field) {
486 $name = $field['name'];
487 if(!isset($fieldUsed[$name])) {
488 $form .= $this->renderRow($field, $index++, isset($defaults[$name]) ? $defaults[$name] : null, $show);
489 $show = FALSE;
490 }
491 }
492
493 $loading = __( 'Loading, please wait', DFRAPI_DOMAIN );
494 $add = __( 'add filter', DFRAPI_DOMAIN );
495 return "
496 <div id='dfrapi_search_form'>
497 <input type='hidden' id='dfrapi_useSelected' value='{$this->useSelected}' />
498 <div id='dfprs_loading_content' style='display:none'>
499 <div class='dfrapi_loading'></div>
500 <h3>{$loading}</h3>
501 </div>
502 {$form}
503 <div class='clearfix'></div>
504 <div id='dfrapi_search_form_filter'><a href='#'><span class='dashicons dashicons-plus'> </span> $add</a></div>
505 </div>
506 ";
507 }
508
509 function combineLists($lst, $func) {
510 if(!count($lst))
511 return array();
512 if(count($lst) == 1)
513 return current($lst);
514 $lst = call_user_func_array($func, $lst);
515 return count($lst) ? $lst : NULL;
516 }
517
518 function idFilter($inList, $exList) {
519 $inList = $this->combineLists($inList, 'array_intersect');
520 $exList = $this->combineLists($exList, 'array_merge');
521
522 if(is_null($inList) || is_null($exList)) {
523 return NULL;
524 }
525 if(count($inList)) {
526 if(count($exList)) {
527 $inList = array_diff($inList, $exList);
528 if(!count($inList))
529 return NULL;
530 }
531 return array("IN", $inList);
532 }
533 if(count($exList)) {
534 return array("!IN", $exList);
535 }
536 return array(NULL, NULL);
537 }
538
539 function fulltextFilter($operator, $value) {
540 if($operator == 'match')
541 return array("LIKE", $value);
542 $value = trim(preg_replace('~[!\\[\\]"^$]+~', " ", $value));
543 if(strlen($value)) {
544 switch($operator) {
545 case 'is': return array('LIKE', "^$value\$");
546 case 'contain': return array('LIKE', $value);
547 case 'not_contain': return array('!LIKE', $value);
548 case 'start': return array('LIKE', "^$value");
549 case 'end': return array('LIKE', "$value\$");
550 }
551 }
552 return array(NULL, NULL);
553 }
554
555 function makeFilters($query, $useSelected=TRUE) {
556
557 $filters = array();
558
559 $selected = array(
560 'in:source_id' => array(),
561 'ex:source_id' => array(),
562 'in:merchant_id' => array(),
563 'ex:merchant_id' => array(),
564 );
565
566 $allNetworks = $this->ids($this->allNetworks());
567
568 if($useSelected) {
569 $selected['in:source_id'] []= $this->ids($this->selectedNetworks());
570 $selected['in:merchant_id'] []= $this->ids($this->selectedMerchants());
571 } else {
572 $selected['in:source_id'] []= $allNetworks;
573 }
574
575 foreach($query as $params) {
576 $fname = $params['field'];
577 $value = $this->get($params, 'value');
578 $value2 = $this->get($params, 'value2');
579 $operator = strtolower($this->get($params, 'operator'));
580
581 switch($fname) {
582 case 'any':
583 case 'name':
584 case 'brand':
585 case 'description':
586 case 'category':
587 $s = $this->fulltextFilter($operator, $value);
588 if(!is_null($s[0])) {
589 $filters []= "{$fname} {$s[0]} {$s[1]}";
590 }
591 break;
592 case 'tags':
593 $operator = ($operator == 'in') ? "LIKE" : "!LIKE";
594 $filters []= "{$fname} {$operator} {$value}";
595 break;
596 case 'currency':
597 $filters []= "{$fname} = {$value}";
598 break;
599 case 'price':
600 case 'saleprice':
601 case 'salediscount':
602 $conv = $fname == 'salediscount' ? 'intval' : 'dfrapi_price_to_int';
603 $value = $conv($value);
604 switch($operator) {
605 case 'between':
606 $value2 = $conv($value2);
607 $filters []= "{$fname} > {$value}";
608 $filters []= "{$fname} < {$value2}";
609 break;
610 case 'lt':
611 $filters []= "{$fname} < {$value}";
612 break;
613 case 'gt':
614 $filters []= "{$fname} > {$value}";
615 break;
616 }
617 break;
618 case 'source_id':
619 case 'merchant_id':
620 $key = ($operator == 'is') ? 'in' : 'ex';
621 $selected["$key:$fname"] []= $this->ary($value);
622 break;
623 case 'type':
624 $ids = array();
625 foreach($this->allNetworks() as $net) {
626 if($net['type'] == $value)
627 $ids []= $net['_id'];
628 }
629 $selected["in:source_id"] []= $ids;
630 break;
631 case 'onsale':
632 $value = ($operator == 'yes') ? '1' : '0';
633 $filters []= "{$fname} = {$value}";
634 break;
635 case 'image':
636 case 'thumbnail':
637 $operator = ($operator == 'yes') ? '!EMPTY' : 'EMPTY';
638 $filters []= "image {$operator}";
639 break;
640 case 'time_updated':
641 $value = @date('Y-m-d H:i:s', strtotime($value));
642 switch($operator) {
643 case 'between':
644 $value2 = @date('Y-m-d H:i:s', strtotime($value2));
645 $filters []= "{$fname} > {$value}";
646 $filters []= "{$fname} < {$value2}";
647 break;
648 case 'lt':
649 $filters []= "{$fname} < {$value}";
650 break;
651 case 'gt':
652 $filters []= "{$fname} > {$value}";
653 break;
654 }
655 break;
656 }
657 }
658
659 $s = $this->idFilter($selected['in:source_id'], $selected['ex:source_id']);
660 if(is_null($s))
661 return array('error' => 'No networks selected');
662 if(!is_null($s[0]) && count($s[1]) < count($allNetworks))
663 $filters []= "source_id {$s[0]} ". implode(',', $s[1]);
664
665 $s = $this->idFilter($selected['in:merchant_id'], $selected['ex:merchant_id']);
666 if(is_null($s))
667 return array('error' => 'No merchants selected');
668 if(!is_null($s[0]))
669 $filters []= "merchant_id {$s[0]} ". implode(',', $s[1]);
670
671 return $filters;
672
673 }
674
675 }
676