This repository has been archived by the owner on Nov 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathbvseosdk.php
1042 lines (894 loc) · 32.9 KB
/
bvseosdk.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
/**
* BV PHP SEO SDK
*
* Base code to power either SEO or SEO and display. This SDK
* is provided as is and Bazaarvoice, Inc. is not responsible
* for future maintenance or support. You are free to modify
* this SDK as needed to suit your needs.
*
* This SDK was built with the following assumptions:
* - you are running PHP 5 or greater
* - you have the curl library installed
* - every request has the user agent header
* in it (if using a CDN like Akamai additional configuration
* maybe required).
*
*/
/**
* Example usage:
*
* require(bvsdk.php);
*
* $bv = new BV(array(
* 'bv_root_folder' => '1234-en_US',
* 'subject_id' => 'XXYYY',
* 'cloud_key' => 'company-cdfa682b84bef44672efed074093ccd3',
* 'staging' => FALSE
* ));
*
*/
require_once 'BVUtility.php';
require_once 'BVFooter.php';
// Default charset will be used in case charset parameter is not properly configured by user.
define('DEFAULT_CHARSET', 'UTF-8');
// ------------------------------------------------------------------------
/**
* BV Class
*
* When you instantiate the BV class, pass it's constructor an array
* containing the following key value pairs.
*
* Required fields:
* bv_root_folder (string)
* subject_id (string)
* cloud_key (string)
*
* Optional fields
* base_url (string) (defaults to detecting the base_url automatically)
* page_url (string) (defaults to empty, to provide query parameters )
* staging (boolean) (defaults to false, need to put true for testing with staging data)
* testing (boolean) (defaults to false, need to put true for testing with testing data)
* content_type (string) (defaults to reviews, you can pass content type here if needed)
* subject_type (string) (defaults to product, you can pass subject type here if needed)
* content_sub_type (string) (defaults to stories, for stories you can pass either STORIES_LIST or STORIES_GRID content type)
* execution_timeout (int) (in milliseconds) (defaults to 500ms, to set period of time before the BVSEO injection times out for user agents that do not match the criteria set in CRAWLER_AGENT_PATTERN)
* execution_timeout_bot (int) (in milliseconds) (defaults to 2000ms, to set period of time before the BVSEO injection times out for user agents that match the criteria set in CRAWLER_AGENT_PATTERN)
* charset (string) (defaults to UTF-8, to set alternate character for SDK output)
* crawler_agent_pattern (string) (defaults to msnbot|googlebot|teoma|bingbot|yandexbot|yahoo)
*/
class BV {
/**
* BV Class Constructor
*
* The constructor takes in all the arguments via a single array.
*
* @access public
* @param array
* @return object
*/
public function __construct($params = array()) {
$this->validateParameters($params);
// config array, defaults are defined here.
$this->config = array(
'staging' => FALSE,
'testing' => FALSE,
'content_type' => isset($params['content_type']) ? $params['content_type'] : 'reviews',
'subject_type' => isset($params['subject_type']) ? $params['subject_type'] : 'product',
'page_url' => isset($params['page_url']) ? $params['page_url'] : '',
'base_url' => isset($params['base_url']) ? $params['base_url'] : '',
'include_display_integration_code' => FALSE,
'client_name' => $params['bv_root_folder'],
'local_seo_file_root' => '',
'load_seo_files_locally' => FALSE,
// used in regex to determine if request is a bot or not
'crawler_agent_pattern' => 'msnbot|google|teoma|bingbot|yandexbot|yahoo',
'ssl_enabled' => FALSE,
'proxy_host' => '',
'proxy_port' => '',
'charset' => 'UTF-8',
'seo_sdk_enabled' => TRUE,
'execution_timeout' => 500,
'execution_timeout_bot' => 2000,
'bvreveal' => isset($params['bvreveal']) ? $params['bvreveal'] : '',
'page' => 1,
'page_params' => array()
);
// Merge passed in params with defaults for config.
$this->config = array_merge($this->config, $params);
// Obtain all the name=value parameters from either the page URL passed in,
// or from the actual page URL as seen by PHP. Parameter values from the
// actual URL override those from the URL passed in, as that is usually a
// trucated URL where present at all.
//
// Note that we're taking parameters from query string, fragment, or
// _escaped_fragment_. (Though fragment is not passed to the server, so
// we won't actually see that in practice).
//
// We're after bvrrp, bvqap, bvsyp, and bvstate, but sweep up everything
// while we're here.
if (isset($params['page_url'])) {
$this->config['bv_page_data'] = BVUtility::parseUrlParameters($params['page_url']);
}
// Extract bvstate if present and parse that into a set of useful values.
if (isset($this->config['bv_page_data']['bvstate'])) {
$this->config['page_params'] = BVUtility::getBVStateParams($this->config['bv_page_data']['bvstate']);
}
// Remove any trailing URL delimeters from the base URL. E.g.:
// http://example.com?
// http://example.com?a=b&
// http://example.com?a=b&_escaped_fragment_=x/y/z?r=s%26
//
$this->config['base_url'] = mb_ereg_replace('(&|\?|%26)$', '', $this->config['base_url']);
// Get rid of all the other things we care about from the base URL, so that
// we don't double up the parameters.
$this->config['base_url'] = BVUtility::removeUrlParam($this->config['base_url'], 'bvstate');
$this->config['base_url'] = BVUtility::removeUrlParam($this->config['base_url'], 'bvrrp');
$this->config['base_url'] = BVUtility::removeUrlParam($this->config['base_url'], 'bvqap');
$this->config['base_url'] = BVUtility::removeUrlParam($this->config['base_url'], 'bvsyp');
// Create the processor objects.
$this->reviews = new Reviews($this->config);
$this->questions = new Questions($this->config);
$this->stories = new Stories($this->config);
$this->spotlights = new Spotlights($this->config);
$this->sellerratings = new SellerRatings($this->config);
// Assign one to $this->SEO based on the content type.
$ct = isset($this->config['page_params']['content_type']) ? $this->config['page_params']['content_type'] : $this->config['content_type'];
if (isset($ct)) {
switch ($ct) {
case 'reviews': {
$st = isset($this->config['page_params']['subject_type']) ? $this->config['page_params']['subject_type'] : $this->config['subject_type'];
if (isset($st) && $st == 'seller') {
$this->SEO = $this->sellerratings;
} else {
$this->SEO = $this->reviews;
}
break;
}
case 'questions': $this->SEO = $this->questions;
break;
case 'stories': $this->SEO = $this->stories;
break;
case 'spotlights': $this->SEO = $this->spotlights;
break;
default:
throw new Exception('Invalid content_type value provided: ' . $this->config['content_type']);
}
}
}
protected function validateParameters($params) {
if (!is_array($params)) {
throw new Exception(
'BV class constructor argument $params must be an array.'
);
}
// check to make sure we have the required parameters.
if (empty($params['bv_root_folder'])) {
throw new Exception(
'BV class constructor argument $params is missing required bv_root_folder key. An ' .
'array containing bv_root_folder (string) is expected.'
);
}
if (empty($params['subject_id'])) {
throw new Exception(
'BV class constructor argument $params is missing required subject_id key. An ' .
'array containing subject_id (string) is expected.'
);
}
}
}
// end of BV class
/**
* Base Class containing most shared functionality. So when we add support for
* questions and answers it should be minimal changes. Just need to create an
* answers class which inherits from Base.
*
* Configuration array is required for creation class object.
*
*/
class Base {
private $msg = '';
public function __construct($params = array()) {
$this->validateParams($params);
$this->config = $params;
// setup bv (internal) defaults
$this->bv_config['seo-domain']['staging'] = 'seo-stg.bazaarvoice.com';
$this->bv_config['seo-domain']['production'] = 'seo.bazaarvoice.com';
$this->bv_config['seo-domain']['testing_staging'] = 'seo-qa-stg.bazaarvoice.com';
$this->bv_config['seo-domain']['testing_production'] = 'seo-qa.bazaarvoice.com';
// seller rating display is a special snowflake
$this->bv_config['srd-domain'] = 'srd.bazaarvoice.com';
$this->bv_config['srd-prefix-staging'] = 'stg';
$this->bv_config['srd-prefix-production'] = 'prod';
$this->bv_config['srd-prefix-testing_staging'] = 'qa-stg';
$this->bv_config['srd-prefix-testing_production'] = 'qa';
$this->config['latency_timeout'] = $this->_isBot()
? $this->config['execution_timeout_bot']
: $this->config['execution_timeout'];
// set up combined user agent to be passed to cloud storage (if needed)
$this->config['user_agent'] = "bv_php_sdk/3.2.1;" . $_SERVER['HTTP_USER_AGENT'];
}
protected function validateParams($params) {
if (!is_array($params)) {
throw new Exception('BV Base Class missing config array.');
}
}
/**
* A check on the bvstate parameter content type value.
*/
protected function _checkBVStateContentType() {
if (empty($this->config['page_params']['content_type'])) {
return TRUE;
}
if (
!empty($this->config['page_params']['content_type']) &&
$this->config['page_params']['content_type'] == $this->config['content_type']
) {
return TRUE;
}
return FALSE;
}
/**
* Function for collecting messages.
*/
protected function _setBuildMessage($msg) {
$msg = rtrim($msg, ";");
$this->msg .= ' ' . $msg . ';';
}
/**
* Is this SDK enabled?
*
* Return true if either seo_sdk_enabled is set truthy or bvreveal flags are
* set.
*/
private function _isSdkEnabled() {
return $this->config['seo_sdk_enabled'] || $this->_getBVReveal();
}
/**
* Check if charset is correct, if not set to default
*/
private function _checkCharset($seo_content) {
if (isset($this->config['charset'])) {
$supportedCharsets = mb_list_encodings();
if (!in_array($this->config['charset'], $supportedCharsets)) {
$this->config['charset'] = DEFAULT_CHARSET;
$this->_setBuildMessage("Charset is not configured properly. "
. "BV-SEO-SDK will load default charset and continue.");
}
} else {
$this->config['charset'] = DEFAULT_CHARSET;
}
}
/**
* Return encoded content with set charset
*/
private function _charsetEncode($seo_content) {
if (isset($this->config['charset'])) {
$enc = mb_detect_encoding($seo_content);
$seo_content = mb_convert_encoding($seo_content, $this->config['charset'], $enc);
}
return $seo_content;
}
/**
* Return full SEO content.
*/
private function _getFullSeoContents() {
$seo_content = '';
// get the page number of SEO content to load
$page_number = $this->_getPageNumber();
// build the URL to access the SEO content for
// this product / page combination
$this->seo_url = $this->_buildSeoUrl($page_number);
// make call to get SEO payload from cloud unless seo_sdk_enabled is false
// make call if bvreveal param in query string is set to 'debug'
if ($this->_isSdkEnabled()) {
$seo_content = $this->_fetchSeoContent($this->seo_url);
$this->_checkCharset($seo_content);
$seo_content = $this->_charsetEncode($seo_content);
// replace tokens for pagination URLs with page_url
$seo_content = $this->_replaceTokens($seo_content);
}
// show footer even if seo_sdk_enabled flag is false
else {
$this->_setBuildMessage(
'SEO SDK is disabled. Enable by setting seo.sdk.enabled to true.'
);
}
$payload = $seo_content;
return $payload;
}
/**
* Remove predefined section from a string.
*/
private function _replaceSection($str, $search_str_begin, $search_str_end) {
$result = $str;
$start_index = mb_strrpos($str, $search_str_begin);
if ($start_index !== false) {
$end_index = mb_strrpos($str, $search_str_end);
if ($end_index !== false) {
$end_index += mb_strlen($search_str_end);
$str_begin = mb_substr($str, 0, $start_index);
$str_end = mb_substr($str, $end_index);
$result = $str_begin . $str_end;
}
}
return $result;
}
/**
* Get only aggregate rating from SEO content.
*/
protected function _renderAggregateRating() {
$payload = $this->_renderSEO('getAggregateRating');
// remove reviews section from full_contents
$payload = $this->_replaceSection($payload, '<!--begin-reviews-->', '<!--end-reviews-->');
// remove pagination section from full contents
$payload = $this->_replaceSection($payload, '<!--begin-pagination-->', '<!--end-pagination-->');
return $payload;
}
/**
* Get only reviews from SEO content.
*/
protected function _renderReviews() {
$payload = $this->_renderSEO('getReviews');
// remove aggregate rating section from full_contents
$payload = $this->_replaceSection($payload, '<!--begin-aggregate-rating-->', '<!--end-aggregate-rating-->');
// Remove schema.org product text from reviews if it exists
$schema_org_text = "itemscope itemtype=\"http://schema.org/Product\"";
$payload = mb_ereg_replace($schema_org_text, '', $payload);
return $payload;
}
/**
* Render SEO
*
* Method used to do all the work to fetch, parse, and then return
* the SEO payload. This is set as protected so classes inheriting
* from the base class can invoke it or replace it if needed.
*
* @access protected
* @param $access_method
* @return string
*/
protected function _renderSEO($access_method) {
$payload = '';
$this->start_time = microtime(1);
$isBot = $this->_isBot();
if (!$isBot && $this->config['latency_timeout'] == 0) {
$this->_setBuildMessage("EXECUTION_TIMEOUT is set to 0 ms; JavaScript-only Display.");
} else {
if ($isBot && $this->config['latency_timeout'] < 100) {
$this->config['latency_timeout'] = 100;
$this->_setBuildMessage("EXECUTION_TIMEOUT_BOT is less than the minimum value allowed. Minimum value of 100ms used.");
}
try {
$payload = $this->_getFullSeoContents($access_method);
} catch (Exception $e) {
$this->_setBuildMessage($e->getMessage());
}
}
$payload .= $this->_buildComment($access_method);
return $payload;
}
// -------------------------------------------------------------------
// Private methods. Internal workings of SDK.
//--------------------------------------------------------------------
/**
* isBot
*
* Helper method to determine if current request is a bot or not. Will
* use the configured regex string which can be overridden with params.
*
* @access private
* @return bool
*/
private function _isBot() {
$bvreveal = $this->_getBVReveal();
if ($bvreveal) {
return TRUE;
}
// search the user agent string for an indication if this is a search bot or not
return mb_eregi('(' . $this->config['crawler_agent_pattern'] . ')', $_SERVER['HTTP_USER_AGENT']);
}
/**
* getBVReveal
*
* Return true if bvreveal flags are set, either via reveal:debug in the
* bvstate query parameter or in the old bvreveal query parameter, or is
* passed in via the configuration of the main class.
*/
private function _getBVReveal() {
// Passed in as configuration override?
if (
!empty($this->config['bvreveal']) &&
$this->config['bvreveal'] == 'debug'
) {
return TRUE;
}
// Set via bvstate query parameter?
else if (
!empty($this->config['page_params']['bvreveal']) &&
$this->config['page_params']['bvreveal'] == 'debug'
) {
return TRUE;
}
// Set via bvreveal query parameter?
else if (
!empty($this->config['bv_page_data']['bvreveal']) &&
$this->config['bv_page_data']['bvreveal'] == 'debug'
) {
return TRUE;
} else {
return FALSE;
}
}
/**
* getPageNumber
*
* Helper method to pull from the URL the page of SEO we need to view.
*
* @access private
* @return int
*/
private function _getPageNumber() {
$page_number = 1;
// Override from config.
if (isset($this->config['page']) && $this->config['page'] != $page_number) {
$page_number = (int) $this->config['page'];
}
// Check the bvstate parameter if one was found and successfully parsed.
else if (isset($this->config['page_params']['base_url_bvstate'])) {
// We only apply the bvstate page number parameter if the content type
// specified matches the content type being generated here. E.g. if
// someone calls up a page with bvstate=ct:r/pg:2 and loads stories rather
// than reviews, show page 1 for stories. Only show page 2 if they are in
// fact displaying review content.
if ($this->config['content_type'] == $this->config['page_params']['content_type']) {
$page_number = $this->config['page_params']['page'];
}
}
// other implementations use the bvrrp, bvqap, or bvsyp parameter
// ?bvrrp=1234-en_us/reviews/product/2/ASF234.htm
//
// Note that unlike bvstate, we don't actually check for the content type
// to match the parameter type for the legacy page parameters bvrrp, bvqap,
// and bvsyp. This is consistent with the behavior of the other SDKs, even
// if it doesn't really make much sense.
//
// Note that there is a bug in the SEO-CPS content generation where it uses
// the bvrrp parameter in place of bvqap, so this may all be sort of
// deliberate, if not sensible.
else if (isset($this->config['bv_page_data']['bvrrp'])) {
$bvparam = $this->config['bv_page_data']['bvrrp'];
} else if (isset($this->config['bv_page_data']['bvqap'])) {
$bvparam = $this->config['bv_page_data']['bvqap'];
} else if (isset($this->config['bv_page_data']['bvsyp'])) {
$bvparam = $this->config['bv_page_data']['bvsyp'];
}
if (!empty($bvparam)) {
$match = array();
mb_ereg('\/(\d+)\/', $bvparam, $match);
$page_number = max(1, (int) $match[1]);
}
return $page_number;
}
/**
* buildSeoUrl
*
* Helper method to that builds the URL to the SEO payload
*
* @access private
* @param int (page number)
* @return string
*/
private function _buildSeoUrl($page_number) {
$primary_selector = 'seo-domain';
// calculate, which environment should we be using
if ($this->config['testing']) {
if ($this->config['staging']) {
$env_selector = 'testing_staging';
} else {
$env_selector = 'testing_production';
}
} else {
if ($this->config['staging']) {
$env_selector = 'staging';
} else {
$env_selector = 'production';
}
}
$url_scheme = $this->config['ssl_enabled'] ? 'https://' : 'http://';
if ($this->config['content_type'] == 'reviews' &&
$this->config['subject_type'] == 'seller') {
// when content type is reviews and subject type is seller,
// we're dealing with seller rating, so use different primary selector
$primary_selector = 'srd-domain';
// for seller rating we use different selector for prefix
$hostname = $this->bv_config[$primary_selector] . '/' . $this->bv_config['srd-prefix-' . $env_selector];
} else {
$hostname = $this->bv_config[$primary_selector][$env_selector];
};
// dictates order of URL
$url_parts = array(
$url_scheme . $hostname,
$this->config['cloud_key'],
$this->config['bv_root_folder'],
$this->config['content_type'],
$this->config['subject_type'],
$page_number
);
if (isset($this->config['content_sub_type']) && !empty($this->config['content_sub_type'])) {
$url_parts[] = $this->config['content_sub_type'];
}
if (!empty($this->config['page_params']['subject_id']) && $this->_checkBVStateContentType()) {
$url_parts[] = urlencode($this->config['page_params']['subject_id']) . '.htm';
} else {
$url_parts[] = urlencode($this->config['subject_id']) . '.htm';
}
// if our SEO content source is a file path
// we need to remove the first two sections
// and prepend the passed in file path
if (!empty($this->config['load_seo_files_locally']) && !empty($this->config['local_seo_file_root'])) {
unset($url_parts[0]);
unset($url_parts[1]);
return $this->config['local_seo_file_root'] . implode("/", $url_parts);
}
// implode will convert array to a string with / in between each value in array
return implode("/", $url_parts);
}
/**
* Return a SEO content from local or distant sourse.
*/
private function _fetchSeoContent($resource) {
if ($this->config['load_seo_files_locally']) {
return $this->_fetchFileContent($resource);
} else {
return $this->_fetchCloudContent($resource);
}
}
/**
* fetchFileContent
*
* Helper method that will take in a file path and return it's payload while
* handling the possible errors or exceptions that can happen.
*
* @access private
* @param string (valid file path)
* @return string (content of file)
*/
private function _fetchFileContent($path) {
$file = @file_get_contents($path);
if ($file === FALSE) {
$this->_setBuildMessage('Trying to get content from "' . $path
. '". The resource file is currently unavailable');
} else {
$this->_setBuildMessage('Local file content was uploaded');
}
return $file;
}
public function curlExecute($ch) {
return curl_exec($ch);
}
public function curlInfo($ch) {
return curl_getinfo($ch);
}
public function curlErrorNo($ch) {
return curl_errno($ch);
}
public function curlError($ch) {
return curl_error($ch);
}
/**
* fetchCloudContent
*
* Helper method that will take in a URL and return it's payload while
* handling the possible errors or exceptions that can happen.
*
* @access private
* @param string (valid url)
* @return string
*/
private function _fetchCloudContent($url) {
// is cURL installed yet?
// if ( ! function_exists('curl_init')){
// return '<!-- curl library is not installed -->';
// }
// create a new cURL resource handle
$ch = curl_init();
// Set URL to download
curl_setopt($ch, CURLOPT_URL, $url);
// Set a referer as coming from the current page url
curl_setopt($ch, CURLOPT_REFERER, $this->config['page_url']);
// Include header in result? (0 = yes, 1 = no)
curl_setopt($ch, CURLOPT_HEADER, 0);
// Should cURL return or print out the data? (true = return, false = print)
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Timeout in seconds
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->config['latency_timeout']);
// Enable decoding of the response
curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate');
// Enable following of redirects
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// set user agent if needed
if ($this->config['user_agent'] != '') {
curl_setopt($ch, CURLOPT_USERAGENT, $this->config['user_agent']);
}
if ($this->config['proxy_host'] != '') {
curl_setopt($ch, CURLOPT_PROXY, $this->config['proxy_host']);
curl_setopt($ch, CURLOPT_PROXYPORT, $this->config['proxy_port']);
}
// make the request to the given URL and then store the response,
// request info, and error number
// so we can use them later
$request = array(
'response' => $this->curlExecute($ch),
'info' => $this->curlInfo($ch),
'error_number' => $this->curlErrorNo($ch),
'error_message' => $this->curlError($ch)
);
// Close the cURL resource, and free system resources
curl_close($ch);
// see if we got any errors with the connection
if ($request['error_number'] != 0) {
$this->_setBuildMessage('Error - ' . $request['error_message']);
}
// see if we got a status code of something other than 200
if ($request['info']['http_code'] != 200) {
$this->_setBuildMessage('HTTP status code of '
. $request['info']['http_code'] . ' was returned');
return '';
}
// if we are here we got a response so let's return it
$this->response_time = round($request['info']['total_time'] * 1000);
return $request['response'];
}
/**
* replaceTokens
*
* After we have an SEO payload we need to replace the {INSERT_PAGE_URI}
* tokens with the current page url so pagination works.
*
* @access private
* @param string (valid url)
* @return string
*/
private function _replaceTokens($content) {
$page_url_query_prefix = '';
// Attach a suitable ending to the base URL if it doesn't already end with
// either ? or &. This is complicated by the _escaped_fragment_ case.
//
// We're assuming that the base URL can't have a fragment or be a hashbang
// URL - that just won't work in conjunction with the assumption that we
// always postfix the SEO query parameters to the end of the URL.
//
// If the base url ends with an empty _escaped_fragment_ property.
if (mb_ereg('_escaped_fragment_=$', $this->config['base_url'])) {
// Append nothing for this annoying edge case.
}
// Otherwise if there is something in the _escaped_fragment_ then append
// the escaped ampersand.
else if (mb_ereg('_escaped_fragment_=.+$', $this->config['base_url'])) {
$page_url_query_prefix = '%26';
}
// Otherwise we're back to thinking about query strings.
else if (!mb_ereg('[\?&]$', $this->config['base_url'])) {
if(mb_ereg('\?', $this->config['base_url'])) {
$page_url_query_prefix = '&';
} else {
$page_url_query_prefix = '?';
}
}
$content = mb_ereg_replace(
'{INSERT_PAGE_URI}',
// Make sure someone doesn't sneak in "><script>...<script> in the URL
// contents.
htmlspecialchars(
$this->config['base_url'] . $page_url_query_prefix,
ENT_QUOTES | ENT_HTML5,
$this->config['charset'],
// Don't double-encode.
false
),
$content
);
return $content;
}
/**
* Return hidden metadata for adding to SEO content.
*/
private function _buildComment($access_method) {
$bvf = new BVFooter($this, $access_method, $this->msg);
$footer = $bvf->buildSDKFooter();
$reveal = $this->_getBVReveal();
if ($reveal) {
$footer .= $bvf->buildSDKDebugFooter();
}
return $footer;
}
public function getBVMessages() {
return $this->msg;
}
public function getContent() {
$this->_setBuildMessage('Content Type "' . $this->config['content_type'] . '" is not supported by getContent().');
$pay_load = $this->_buildComment('', 'getContent');
return $pay_load;
}
public function getAggregateRating() {
$this->_setBuildMessage('Content Type "' . $this->config['content_type'] . '" is not supported by getAggregateRating().');
$pay_load = $this->_buildComment('', 'getAggregateRating');
return $pay_load;
}
public function getReviews() {
$this->_setBuildMessage('Content Type "' . $this->config['content_type'] . '" is not supported by getReviews().');
$pay_load = $this->_buildComment('', 'getReviews');
return $pay_load;
}
}
// end of Base class
/**
* Reviews Class
*
* Base class extention for work with "reviews" content type.
*/
class Reviews extends Base {
function __construct($params = array()) {
// call Base Class constructor
parent::__construct($params);
// since we are in the reviews class
// we need to set the content_type config
// to reviews so we get reviews in our
// SEO request
$this->config['content_type'] = 'reviews';
// for reviews subject type will always
// need to be product
$this->config['subject_type'] = 'product';
}
public function getAggregateRating() {
return $this->_renderAggregateRating();
}
public function getReviews() {
return $this->_renderReviews();
}
public function getContent() {
$payload = $this->_renderSEO('getContent');
if (!empty($this->config['page_params']['subject_id']) && $this->_checkBVStateContentType()) {
$subject_id = $this->config['page_params']['subject_id'];
} else {
$subject_id = $this->config['subject_id'];
}
// if they want to power display integration as well
// then we need to include the JS integration code
if ($this->config['include_display_integration_code']) {
$payload .= '
<script>
$BV.ui("rr", "show_reviews", {
productId: "' . $subject_id . '"
});
</script>
';
}
return $payload;
}
}
// end of Reviews class
/**
* Questions Class
*
* Base class extention for work with "questions" content type.
*/
class Questions extends Base {
function __construct($params = array()) {
// call Base Class constructor
parent::__construct($params);
// since we are in the questions class
// we need to set the content_type config
// to questions so we get questions in our
// SEO request
$this->config['content_type'] = 'questions';
}
public function getContent() {
$payload = $this->_renderSEO('getContent');
if (!empty($this->config['page_params']['subject_id']) && $this->_checkBVStateContentType()) {
$subject_id = $this->config['page_params']['subject_id'];
} else {
$subject_id = $this->config['subject_id'];
}
// if they want to power display integration as well
// then we need to include the JS integration code
if ($this->config['include_display_integration_code']) {
$payload .= '
<script>
$BV.ui("qa", "show_questions", {
productId: "' . $subject_id . '"
});
</script>
';
}
return $payload;
}
}
// end of Questions class
/**
* Stories Class
*
* Base class extention for work with "stories" content type.
*/
class Stories extends Base {
function __construct($params = array()) {
// call Base Class constructor
parent::__construct($params);
// since we are in the stories class
// we need to set the content_type config
// to stories so we get stories in our
// SEO request
$this->config['content_type'] = 'stories';
// for stories subject type will always
// need to be product
$this->config['subject_type'] = 'product';
// for stories we have to set content sub type
// the sub type is configured as either STORIES_LIST or STORIES_GRID
// the folder names are "stories" and "storiesgrid" respectively.
if (isset($this->config['content_sub_type'])
&& $this->config['content_sub_type'] == "stories_grid") {
$this->config['content_sub_type'] = "storiesgrid";
} else {
$this->config['content_sub_type'] = "stories";
}
}
public function getContent() {
$payload = $this->_renderSEO('getContent');
if (!empty($this->config['page_params']['subject_id']) && $this->_checkBVStateContentType()) {
$subject_id = $this->config['page_params']['subject_id'];
} else {
$subject_id = $this->config['subject_id'];
}
// if they want to power display integration as well
// then we need to include the JS integration code
if ($this->config['include_display_integration_code']) {
$payload .= '
<script>
$BV.ui("su", "show_stories", {
productId: "' . $subject_id . '"
});
</script>
';
}
return $payload;
}
}
// end of Stories class
class Spotlights extends Base {
function __construct($params = array()) {
// call Base Class constructor
parent::__construct($params);
// since we are in the spotlights class
// we need to set the content_type config
// to spotlights so we get reviews in our
// SEO request