Bazaarvoice_Connector - Version 6.3.3

Version Notes

Plugin corresponds to version 6.0.2 of the Bazaarvoice platform

Download this release

Release Info

Developer BV DTS
Extension Bazaarvoice_Connector
Version 6.3.3
Comparing to
See all releases


Code changes from version 6.3.2.1 to 6.3.3

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