Bazaarvoice_Connector - Version 6.0.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.0.3
Comparing to
See all releases


Version 6.0.3

Files changed (82) hide show
  1. app/code/local/Bazaarvoice/Connector/Block/Bazaarvoice.php +24 -0
  2. app/code/local/Bazaarvoice/Connector/Block/Ratings.php +37 -0
  3. app/code/local/Bazaarvoice/Connector/Block/Reviews.php +63 -0
  4. app/code/local/Bazaarvoice/Connector/Block/Roi/Beacon.php +77 -0
  5. app/code/local/Bazaarvoice/Connector/Block/Submissioncontainer.php +18 -0
  6. app/code/local/Bazaarvoice/Connector/Helper/Data.php +484 -0
  7. app/code/local/Bazaarvoice/Connector/Model/ExportProductFeed.php +386 -0
  8. app/code/local/Bazaarvoice/Connector/Model/ExportPurchaseFeed.php +639 -0
  9. app/code/local/Bazaarvoice/Connector/Model/Mysql4/Setup.php +11 -0
  10. app/code/local/Bazaarvoice/Connector/Model/ProductFeed/Brand.php +191 -0
  11. app/code/local/Bazaarvoice/Connector/Model/ProductFeed/Category.php +301 -0
  12. app/code/local/Bazaarvoice/Connector/Model/ProductFeed/Product.php +333 -0
  13. app/code/local/Bazaarvoice/Connector/Model/RetrieveInlineRatingsFeed.php +219 -0
  14. app/code/local/Bazaarvoice/Connector/Model/RetrieveSmartSEOPackage.php +143 -0
  15. app/code/local/Bazaarvoice/Connector/Model/Source/AuthenticationMethod.php +25 -0
  16. app/code/local/Bazaarvoice/Connector/Model/Source/Environment.php +18 -0
  17. app/code/local/Bazaarvoice/Connector/Model/Source/FeedGenerationScope.php +30 -0
  18. app/code/local/Bazaarvoice/Connector/Model/Source/TriggeringEvent.php +25 -0
  19. app/code/local/Bazaarvoice/Connector/controllers/FeedController.php +50 -0
  20. app/code/local/Bazaarvoice/Connector/controllers/IndexController.php +25 -0
  21. app/code/local/Bazaarvoice/Connector/etc/adminhtml.xml +25 -0
  22. app/code/local/Bazaarvoice/Connector/etc/config.xml +159 -0
  23. app/code/local/Bazaarvoice/Connector/etc/system.xml +300 -0
  24. app/code/local/Bazaarvoice/Connector/sql/bazaarvoice_setup/mysql4-install-0.1.0.php +16 -0
  25. app/design/frontend/enterprise/default/layout/bazaarvoice.xml +131 -0
  26. app/design/frontend/enterprise/default/template/bazaarvoice/display/headerincludes.phtml +4 -0
  27. app/design/frontend/enterprise/default/template/bazaarvoice/display/headerproduct.phtml +30 -0
  28. app/design/frontend/enterprise/default/template/bazaarvoice/display/qa/questions.phtml +15 -0
  29. app/design/frontend/enterprise/default/template/bazaarvoice/display/qa/questionsummary.phtml +5 -0
  30. app/design/frontend/enterprise/default/template/bazaarvoice/display/rr/ratings.phtml +15 -0
  31. app/design/frontend/enterprise/default/template/bazaarvoice/display/rr/reviews.phtml +16 -0
  32. app/design/frontend/enterprise/default/template/bazaarvoice/display/rr/reviewsummary.phtml +5 -0
  33. app/design/frontend/enterprise/default/template/bazaarvoice/submit/roi_beacon.phtml +5 -0
  34. app/design/frontend/enterprise/default/template/bazaarvoice/submit/submissioncontainer.phtml +5 -0
  35. app/etc/modules/Bazaarvoice_Connector.xml +9 -0
  36. app/locale/en_US/template/email/bazaarvoice_notification.html +5 -0
  37. lib/Bazaarvoice/bvseosdk.php +511 -0
  38. package.xml +18 -0
  39. shell/bv_export_order_feed.php +38 -0
  40. shell/bv_export_product_feed.php +38 -0
  41. skin/frontend/base/default/images/bazaarvoice/rating-0_0.gif +0 -0
  42. skin/frontend/base/default/images/bazaarvoice/rating-1_0.gif +0 -0
  43. skin/frontend/base/default/images/bazaarvoice/rating-1_1.gif +0 -0
  44. skin/frontend/base/default/images/bazaarvoice/rating-1_2.gif +0 -0
  45. skin/frontend/base/default/images/bazaarvoice/rating-1_3.gif +0 -0
  46. skin/frontend/base/default/images/bazaarvoice/rating-1_4.gif +0 -0
  47. skin/frontend/base/default/images/bazaarvoice/rating-1_5.gif +0 -0
  48. skin/frontend/base/default/images/bazaarvoice/rating-1_6.gif +0 -0
  49. skin/frontend/base/default/images/bazaarvoice/rating-1_7.gif +0 -0
  50. skin/frontend/base/default/images/bazaarvoice/rating-1_8.gif +0 -0
  51. skin/frontend/base/default/images/bazaarvoice/rating-1_9.gif +0 -0
  52. skin/frontend/base/default/images/bazaarvoice/rating-2_0.gif +0 -0
  53. skin/frontend/base/default/images/bazaarvoice/rating-2_1.gif +0 -0
  54. skin/frontend/base/default/images/bazaarvoice/rating-2_2.gif +0 -0
  55. skin/frontend/base/default/images/bazaarvoice/rating-2_3.gif +0 -0
  56. skin/frontend/base/default/images/bazaarvoice/rating-2_4.gif +0 -0
  57. skin/frontend/base/default/images/bazaarvoice/rating-2_5.gif +0 -0
  58. skin/frontend/base/default/images/bazaarvoice/rating-2_6.gif +0 -0
  59. skin/frontend/base/default/images/bazaarvoice/rating-2_7.gif +0 -0
  60. skin/frontend/base/default/images/bazaarvoice/rating-2_8.gif +0 -0
  61. skin/frontend/base/default/images/bazaarvoice/rating-2_9.gif +0 -0
  62. skin/frontend/base/default/images/bazaarvoice/rating-3_0.gif +0 -0
  63. skin/frontend/base/default/images/bazaarvoice/rating-3_1.gif +0 -0
  64. skin/frontend/base/default/images/bazaarvoice/rating-3_2.gif +0 -0
  65. skin/frontend/base/default/images/bazaarvoice/rating-3_3.gif +0 -0
  66. skin/frontend/base/default/images/bazaarvoice/rating-3_4.gif +0 -0
  67. skin/frontend/base/default/images/bazaarvoice/rating-3_5.gif +0 -0
  68. skin/frontend/base/default/images/bazaarvoice/rating-3_6.gif +0 -0
  69. skin/frontend/base/default/images/bazaarvoice/rating-3_7.gif +0 -0
  70. skin/frontend/base/default/images/bazaarvoice/rating-3_8.gif +0 -0
  71. skin/frontend/base/default/images/bazaarvoice/rating-3_9.gif +0 -0
  72. skin/frontend/base/default/images/bazaarvoice/rating-4_0.gif +0 -0
  73. skin/frontend/base/default/images/bazaarvoice/rating-4_1.gif +0 -0
  74. skin/frontend/base/default/images/bazaarvoice/rating-4_2.gif +0 -0
  75. skin/frontend/base/default/images/bazaarvoice/rating-4_3.gif +0 -0
  76. skin/frontend/base/default/images/bazaarvoice/rating-4_4.gif +0 -0
  77. skin/frontend/base/default/images/bazaarvoice/rating-4_5.gif +0 -0
  78. skin/frontend/base/default/images/bazaarvoice/rating-4_6.gif +0 -0
  79. skin/frontend/base/default/images/bazaarvoice/rating-4_7.gif +0 -0
  80. skin/frontend/base/default/images/bazaarvoice/rating-4_8.gif +0 -0
  81. skin/frontend/base/default/images/bazaarvoice/rating-4_9.gif +0 -0
  82. skin/frontend/base/default/images/bazaarvoice/rating-5_0.gif +0 -0
app/code/local/Bazaarvoice/Connector/Block/Bazaarvoice.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bazaarvoice_Connector_Block_Bazaarvoice extends Mage_Core_Block_Template
3
+ {
4
+
5
+ /**
6
+ * NOTE: This class isn't used by the current Bazaarvoice integration, but is left
7
+ * here as a placeholder for future integration.
8
+ */
9
+
10
+
11
+ public function _prepareLayout()
12
+ {
13
+ return parent::_prepareLayout();
14
+ }
15
+
16
+ public function getRatingsandreviews()
17
+ {
18
+ if (!$this->hasData('ratingsandreviews')) {
19
+ $this->setData('ratingsandreviews', Mage::registry('ratingsandreviews'));
20
+ }
21
+ return $this->getData('ratingsandreviews');
22
+ }
23
+
24
+ }
app/code/local/Bazaarvoice/Connector/Block/Ratings.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bazaarvoice_Connector_Block_Ratings extends Mage_Core_Block_Template
3
+ {
4
+
5
+ private $_isEnabled;
6
+
7
+ public function _construct()
8
+ {
9
+ // enabled/disabled in admin
10
+ $this->_isEnabled = Mage::getStoreConfig('bazaarvoice/rr/enable_inline_ratings') === '1'
11
+ && Mage::getStoreConfig('bazaarvoice/rr/enable_rr') === '1'
12
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv') === '1';
13
+ }
14
+
15
+ /**
16
+ * returns true if feature is enabled in admin, otherwise returns false
17
+ * @return bool
18
+ */
19
+ public function getIsEnabled()
20
+ {
21
+ return $this->_isEnabled;
22
+ }
23
+
24
+ public function getLoadedProductCollection()
25
+ {
26
+ // Get reference to parent block - product list
27
+ $productListBlock = $this->getParentBlock();
28
+ // Verify the parent is really a product list
29
+ if(!($productListBlock instanceof Mage_Catalog_Block_Product_List)) {
30
+ // Return empty array to keep template code happy
31
+ return array();
32
+ }
33
+ // Get and return ref to loaded prod collection
34
+ return $productListBlock->getLoadedProductCollection();
35
+ }
36
+
37
+ }
app/code/local/Bazaarvoice/Connector/Block/Reviews.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include 'Bazaarvoice/bvseosdk.php';
4
+
5
+ class Bazaarvoice_Connector_Block_Reviews extends Mage_Core_Block_Template
6
+ {
7
+
8
+ private $_isEnabled;
9
+
10
+ public function _construct()
11
+ {
12
+ // enabled/disabled in admin
13
+ $this->_isEnabled = Mage::getStoreConfig('bazaarvoice/rr/enable_rr') === '1'
14
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv') === '1';
15
+ }
16
+
17
+ /**
18
+ * returns true if feature is enabled in admin, otherwise returns false
19
+ * @return bool
20
+ */
21
+ public function getIsEnabled()
22
+ {
23
+ return $this->_isEnabled;
24
+ }
25
+
26
+ public function getSEOContent()
27
+ {
28
+ $seoContent = '';
29
+ if(Mage::getStoreConfig('bazaarvoice/general/enable_cloud_seo') === '1' && $this->getIsEnabled()) {
30
+ // Check if admin has configured a legacy display code
31
+ if(strlen(Mage::getStoreConfig('bazaarvoice/bv_config/display_code'))) {
32
+ $deploymentZoneId =
33
+ Mage::getStoreConfig('bazaarvoice/bv_config/display_code') .
34
+ '-' . Mage::getStoreConfig('bazaarvoice/general/locale');
35
+ }
36
+ else {
37
+ $deploymentZoneId =
38
+ str_replace(' ', '_', Mage::getStoreConfig('bazaarvoice/general/deployment_zone')) .
39
+ '-' . Mage::getStoreConfig('bazaarvoice/general/locale');
40
+ }
41
+ $bv = new BV(array(
42
+ 'deployment_zone_id' => $deploymentZoneId, // replace with your display code (BV provided)
43
+ 'product_id' => Mage::helper('bazaarvoice')->getProductId(Mage::registry('current_product')), // replace with product id
44
+ 'cloud_key' => Mage::getStoreConfig('bazaarvoice/general/cloud_seo_key'), // BV provided value
45
+ 'staging' => TRUE
46
+ ));
47
+ $seoContent = $bv->reviews->renderSeo();
48
+ }
49
+
50
+ return $seoContent;
51
+ }
52
+
53
+ /**
54
+ * Retrieve block cache tags based on category
55
+ *
56
+ * @return array
57
+ */
58
+ public function getCacheTags()
59
+ {
60
+ return array_merge(parent::getCacheTags(), Mage::registry('current_product')->getCacheIdTags());
61
+ }
62
+
63
+ }
app/code/local/Bazaarvoice/Connector/Block/Roi/Beacon.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bazaarvoice_Connector_Block_Roi_Beacon extends Mage_Core_Block_Template
3
+ {
4
+ private $_isEnabled;
5
+
6
+ public function _construct()
7
+ {
8
+ // enabled/disabled in admin
9
+ $this->_isEnabled = Mage::getStoreConfig('bazaarvoice/general/enable_roibeacon') === '1'
10
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv') === '1';
11
+ }
12
+
13
+ /**
14
+ * returns true if feature is enabled in admin, otherwise returns false
15
+ * @return bool
16
+ */
17
+ public function getIsEnabled()
18
+ {
19
+ return $this->_isEnabled;
20
+ }
21
+
22
+ /**
23
+ * returns serialized order details data for transmission to Bazaarvoice
24
+ * @return string
25
+ */
26
+ public function getOrderDetails()
27
+ {
28
+ $orderDetails = array();
29
+ $orderId = Mage::getSingleton('checkout/session')->getLastOrderId();
30
+ if ($orderId)
31
+ {
32
+ $order = Mage::getModel('sales/order')->load($orderId);
33
+ if ($order->getId())
34
+ {
35
+ $address = $order->getBillingAddress();
36
+
37
+ $orderDetails['orderId'] = $order->getId();
38
+ $orderDetails['tax'] = number_format($order->getTaxAmount(), 2, '.', '');
39
+ $orderDetails['shipping'] = number_format($order->getShippingAmount(), 2, '.', '');
40
+ $orderDetails['total'] = number_format($order->getGrandTotal(), 2, '.', '');
41
+ $orderDetails['city'] = $address->getCity();
42
+ $orderDetails['state'] = Mage::getModel('directory/region')->load($address->getRegionId())->getCode();
43
+ $orderDetails['country'] = $address->getCountryId();
44
+ $orderDetails['currency'] = $order->getOrderCurrencyCode();
45
+
46
+ $orderDetails['items'] = array();
47
+ $items = $order->getAllVisibleItems();
48
+ foreach ($items as $itemId => $item)
49
+ {
50
+ $product = Mage::helper('bazaarvoice')->getReviewableProductFromOrderItem($item);
51
+
52
+ $itemDetails = array();
53
+ $itemDetails['sku'] = Mage::helper('bazaarvoice')->getProductId($product);
54
+ $itemDetails['name'] = $item->getName();
55
+ // 'category' is not included. Mage products can be in 0 - many categories. Should we try to include it?
56
+ $itemDetails['price'] = number_format($item->getPrice(), 2, '.', '');
57
+ $itemDetails['quantity'] = number_format($item->getQtyOrdered(), 0);
58
+ $itemDetails['imageUrl'] = $product->getImageUrl();
59
+
60
+ array_push($orderDetails['items'], $itemDetails);
61
+ }
62
+
63
+ $orderDetails['userId'] = $order->getCustomerId();
64
+ $orderDetails['email'] = $order->getCustomerEmail();
65
+ $orderDetails['nickname'] = $order->getCustomerEmail();
66
+ // There is no 'deliveryDate' yet
67
+ $orderDetails['locale'] = Mage::getStoreConfig('bazaarvoice/general/locale', $order->getStoreId());
68
+
69
+ // Add partnerSource field
70
+ $orderDetails['partnerSource'] = 'Magento Extension r' . Mage::helper('bazaarvoice')->getExtensionVersion();
71
+ }
72
+ }
73
+
74
+ $orderDetailsJson = Mage::helper('core')->jsonEncode($orderDetails);
75
+ return urldecode(stripslashes($orderDetailsJson));
76
+ }
77
+ }
app/code/local/Bazaarvoice/Connector/Block/Submissioncontainer.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bazaarvoice_Connector_Block_Submissioncontainer extends Mage_Core_Block_Template
3
+ {
4
+
5
+ public function _prepareLayout()
6
+ {
7
+ parent::_prepareLayout();
8
+
9
+ if ($headBlock = $this->getLayout()->getBlock('head')) {
10
+ $headBlock->addLinkRel('canonical', $this->getUrl('bazaarvoice'));
11
+ }
12
+
13
+ return $this;
14
+ }
15
+
16
+
17
+
18
+ }
app/code/local/Bazaarvoice/Connector/Helper/Data.php ADDED
@@ -0,0 +1,484 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Bazaarvoice_Connector_Helper_Data extends Mage_Core_Helper_Abstract
4
+ {
5
+
6
+ const BV_SUBJECT_TYPE = 'bvSubjectType';
7
+ const BV_EXTERNAL_SUBJECT_NAME = 'bvExternalSubjectName';
8
+ const BV_EXTERNAL_SUBJECT_ID = 'bvExternalSubjectID';
9
+
10
+ const CONST_SMARTSEO_BVRRP = 'bvrrp';
11
+ const CONST_SMARTSEO_BVQAP = 'bvqap';
12
+ const CONST_SMARTSEO_BVSYP = 'bvsyp';
13
+
14
+ /**
15
+ * Get the uniquely identifying product ID for a catalog product.
16
+ *
17
+ * This is the unique, product family-level id (duplicates are unacceptable).
18
+ * If a product has its own page, this is its product ID. It is not necessarily
19
+ * the SKU ID, as we do not collect separate Ratings & Reviews for different
20
+ * styles of product - i.e. the 'Blue' vs. 'Red Widget'.
21
+ *
22
+ * @static
23
+ * @param $product a reference to a catalog product object
24
+ * @return The unique product ID to be used with Bazaarvoice
25
+ */
26
+ public function getProductId($product)
27
+ {
28
+ $rawProductId = $product->getSku();
29
+
30
+ // >> Customizations go here
31
+ //
32
+ // << No further customizations after this
33
+
34
+ return $this->replaceIllegalCharacters($rawProductId);
35
+
36
+ }
37
+
38
+ /**
39
+ * Returns a product object that has the provided external ID. This is a complementary
40
+ * function to getProductId above.
41
+ *
42
+ * @static
43
+ * @param $productExternalId
44
+ * @return product object for the provided external ID, or null if no match is found.
45
+ */
46
+ public function getProductFromProductExternalId($productExternalId)
47
+ {
48
+ $rawId = $this->reconstructRawId($productExternalId);
49
+
50
+ $model = Mage::getModel('catalog/product');
51
+
52
+ $productCollection = $model->getCollection()->addAttributeToSelect('*')
53
+ ->addAttributeToFilter('sku', $rawId)
54
+ ->load();
55
+
56
+
57
+ foreach ($productCollection as $product) {
58
+ // return the first one
59
+ return $product;
60
+ }
61
+
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Get the uniquely identifying category ID for a catalog category.
67
+ *
68
+ * This is the unique, category or subcategory ID (duplicates are unacceptable).
69
+ * This ID should be stable: it should not change for the same logical category even
70
+ * if the category's name changes.
71
+ *
72
+ * @static
73
+ * @param $category a reference to a catalog category object
74
+ * @return The unique category ID to be used with Bazaarvoice
75
+ */
76
+ public function getCategoryId($category, $storeId = null)
77
+ {
78
+ // Check config setting to see if we should use Magento category id
79
+ $useUrlPath = Mage::getStoreConfig('bazaarvoice/bv_config/category_id_use_url_path', $storeId);
80
+ $useUrlPath = (strtoupper($useUrlPath) == 'TRUE' || $useUrlPath == true || $useUrlPath == '1');
81
+ if(!$useUrlPath) {
82
+ return $category->getId();
83
+ }
84
+ else {
85
+ // Generate a unique id based on category path
86
+ // Start with url path
87
+ $rawCategoryId = $category->getUrlPath();
88
+ // Replace slashes with dashes in url path
89
+ $rawCategoryId = str_replace('/', '-', $rawCategoryId);
90
+ // Replace any illegal characters
91
+ return $this->replaceIllegalCharacters($rawCategoryId);
92
+ }
93
+ }
94
+
95
+ public function getBrandId($product)
96
+ {
97
+ // Get brand
98
+ $brand = $product->getData('brand');
99
+ // Replace any illegal characters
100
+ $brandId = $this->replaceIllegalCharacters($brand);
101
+
102
+ return $brandId;
103
+ }
104
+
105
+ /**
106
+ * This unique ID can only contain alphanumeric characters (letters and numbers
107
+ * only) and also the asterisk, hyphen, period, and underscore characters. If your
108
+ * product IDs contain invalid characters, simply replace them with an alternate
109
+ * character like an underscore. This will only be used in the feed and not for
110
+ * any customer facing purpose.
111
+ *
112
+ * @static
113
+ * @param $rawId
114
+ * @return mixed
115
+ */
116
+ public function replaceIllegalCharacters($rawId)
117
+ {
118
+ // We need to use a reversible replacement so that we can reconstruct the original ID later.
119
+ // Example rawId = qwerty$%@#asdf
120
+ // Example encoded = qwerty_bv36__bv37__bv64__bv35_asdf
121
+
122
+ return preg_replace_callback('/[^\w\d\*-\._]/s', create_function('$match','return "_bv".ord($match[0])."_";'), $rawId);
123
+ }
124
+
125
+ public function reconstructRawId($externalId) {
126
+ return preg_replace_callback('/_bv(\d*)_/s', create_function('$match','return chr($match[1]);'), $externalId);
127
+ }
128
+
129
+ /**
130
+ * Connects to Bazaarvoice SFTP server and retrieves remote file to a local directory.
131
+ * Local directory will be created if it doesn't exist. Returns false if there
132
+ * are any problems downloading the file. Otherwise returns true.
133
+ *
134
+ * @static
135
+ * @param $localFilePath
136
+ * @param $localFileName
137
+ * @param $remoteFile
138
+ * @return boolean
139
+ */
140
+ public function downloadFile($localFilePath, $localFileName, $remoteFile, $store = null)
141
+ {
142
+ Mage::log(' BV - starting download from Bazaarvoice server');
143
+
144
+ // Create the directory if it doesn't already exist.
145
+ $ioObject = new Varien_Io_File();
146
+ try {
147
+ if (!$ioObject->fileExists($localFilePath, false)) {
148
+ $ioObject->mkdir($localFilePath, 0777, true);
149
+ }
150
+ } catch (Exception $e) {
151
+ // Most likely not enough permissions.
152
+ Mage::log(" BV - failed attempting to create local directory '".$localFilePath."' to download feed. Error trace follows: " . $e->getTraceAsString());
153
+ return false;
154
+ }
155
+
156
+ // Make sure directory is writable
157
+ if (!$ioObject->isWriteable($localFilePath)) {
158
+ Mage::log(" BV - local directory '".$localFilePath."' is not writable.");
159
+ return false;
160
+ }
161
+
162
+ // Establish a connection to the FTP host
163
+ Mage::log(' BV - beginning file download');
164
+ $connection = ftp_connect($this->getSFTPHost());
165
+ $ftpUser = strtolower(Mage::getStoreConfig('bazaarvoice/general/client_name', $store));
166
+ $ftpPw = Mage::getStoreConfig('bazaarvoice/general/ftp_password', $store);
167
+ Mage::log('Connecting with ftp user: ' . $ftpUser);
168
+ Mage::log('Connecting with ftp pw: ' . $ftpPw);
169
+ $login = ftp_login($connection, $ftpUser, $ftpPw);
170
+ ftp_pasv($connection, true);
171
+ if (!$connection || !$login) {
172
+ Mage::log(' BV - FTP connection attempt failed!');
173
+ return false;
174
+ }
175
+
176
+ // Remove the local file if it already exists
177
+ if (file_exists($localFilePath . DS . $localFileName)) {
178
+ unlink($localFilePath . DS . $localFileName);
179
+ }
180
+
181
+ try {
182
+ // Download the file
183
+ ftp_get($connection, $localFilePath . DS . $localFileName, $remoteFile, FTP_BINARY);
184
+ } catch (Exception $ex) {
185
+ Mage::log(' BV - Exception downloading file: ' . $ex->getTraceAsString());
186
+ }
187
+
188
+ // Validate file was downloaded
189
+ if (!$ioObject->fileExists($localFilePath . DS . $localFileName, true)) {
190
+ Mage::log(" BV - unable to download file '" . $localFilePath . DS . $localFileName . "'");
191
+ return false;
192
+ }
193
+
194
+ return true;
195
+ }
196
+
197
+
198
+ public function uploadFile($localFileName, $remoteFile, $store)
199
+ {
200
+ Mage::log(' BV - starting upload to Bazaarvoice server');
201
+
202
+ $ftpUser = strtolower(Mage::getStoreConfig('bazaarvoice/general/client_name', $store->getId()));
203
+ $ftpPw = Mage::getStoreConfig('bazaarvoice/general/ftp_password', $store->getId());
204
+ Mage::log('Connecting with ftp user: ' . $ftpUser);
205
+ //Mage::log('Connecting with ftp pw: ' . $ftpPw);
206
+
207
+ $connection = ftp_connect($this->getSFTPHost($store));
208
+ if (!$connection) {
209
+ Mage::log(' BV - FTP connection attempt failed!');
210
+ return false;
211
+ }
212
+ $login = ftp_login($connection, $ftpUser, $ftpPw);
213
+ ftp_pasv($connection, true);
214
+ if (!$connection || !$login) {
215
+ Mage::log(' BV - FTP connection attempt failed!');
216
+ return false;
217
+ }
218
+
219
+ $upload = ftp_put($connection, $remoteFile, $localFileName, FTP_BINARY);
220
+
221
+ ftp_close($connection);
222
+
223
+ return $upload;
224
+ }
225
+
226
+ public function getSmartSEOContent($bvProduct, $bvSubjectArr, $pageFormat)
227
+ {
228
+ $ret = '';
229
+
230
+ if (Mage::getStoreConfig('bazaarvoice/SmartSEOFeed/EnableSmartSEO') === '1') {
231
+ $deploymentZone = $this->getDeploymentZoneForBVProduct($bvProduct);
232
+ if ($pageFormat != '') {
233
+ $pageFormat += '/';
234
+ }
235
+
236
+ $baseFolder = Mage::getBaseDir('var') . DS . 'import' . DS . 'bvfeeds' . DS . 'bvsmartseo' . DS;
237
+ $smartSEOFile = $baseFolder . $deploymentZone . DS . $bvProduct . DS . $bvSubjectArr[$this->BV_SUBJECT_TYPE] . DS . '1' . DS . $pageFormat . $bvSubjectArr[$this->BV_EXTERNAL_SUBJECT_ID] . '.htm';
238
+
239
+ if (isset($_REQUEST[$this->CONST_SMARTSEO_BVRRP])) {
240
+ $smartSEOFile = $baseFolder . $_REQUEST[$this->CONST_SMARTSEO_BVRRP];
241
+ } else if (isset($_REQUEST[$this->CONST_SMARTSEO_BVQAP])) {
242
+ $smartSEOFile = $baseFolder . $_REQUEST[$this->CONST_SMARTSEO_BVQAP];
243
+ } else if (isset($_REQUEST[$this->CONST_SMARTSEO_BVSYP])) {
244
+ $smartSEOFile = $baseFolder . $_REQUEST[$this->CONST_SMARTSEO_BVSYP];
245
+ }
246
+
247
+ if (file_exists($smartSEOFile)) {
248
+ $ret = file_get_contents($smartSEOFile);
249
+ }
250
+
251
+ if (!empty($ret)) {
252
+ $helper = Mage::helper('core/url');
253
+ $url = parse_url($helper->getCurrentUrl());
254
+
255
+ $query = array();
256
+ if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '') {
257
+ foreach ($_GET as $key => $value) {
258
+ if ($key !== $this->CONST_SMARTSEO_BVRRP && $key !== $this->CONST_SMARTSEO_BVQAP && $key !== $this->CONST_SMARTSEO_BVSYP) {
259
+ $query[$helper->stripTags($key, null, true)] = $helper->stripTags($value, null, true);
260
+ }
261
+ }
262
+ $url['query'] = http_build_query($query);
263
+ }
264
+
265
+ $currentPage = $url['scheme'] . '://' . $url['host'] . $url['path'] . '?' . $url['query'];
266
+ $ret = preg_replace("/\\{INSERT_PAGE_URI\\}/", $currentPage, $ret);
267
+ }
268
+ }
269
+
270
+ return $ret;
271
+ }
272
+
273
+ /**
274
+ * @static
275
+ * @param $userID
276
+ * @param $sharedkey
277
+ * @return string
278
+ */
279
+ public function encryptReviewerId($userID)
280
+ {
281
+ $sharedKey = Mage::getStoreConfig('bazaarvoice/general/EncodingKey');
282
+ $userStr = 'date=' . date('Ymd') . '&userid=' . $userID;
283
+ return md5($sharedKey . $userStr) . bin2hex($userStr);
284
+ }
285
+
286
+ /**
287
+ * @static
288
+ * @param $isStatic boolean indicating whether or not to return a URL to fetch static BV resources
289
+ * @param $bvProduct String indicating the BV product to get the URL for ('reviews', 'questions')
290
+ * @return string
291
+ */
292
+ public function getBvUrl($isStatic, $bvProduct)
293
+ {
294
+ $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != '') ? 'https' : 'http';
295
+ $hostSubdomain = $this->getSubDomainForBVProduct($bvProduct);
296
+ $hostDomain = 'ugc.bazaarvoice.com';
297
+ $bvStaging = $this->getBvStaging();
298
+ $deploymentZone = $this->getDeploymentZoneForBVProduct($bvProduct);
299
+ $stat = ($isStatic === 1) ? 'static/' : '';
300
+
301
+ return $protocol . '://' . $hostSubdomain . '.' . $hostDomain . $bvStaging . $stat . $deploymentZone;
302
+ }
303
+
304
+ /**
305
+ * Get url to bvapi.js javascript API file
306
+ *
307
+ * C2013 staging call:
308
+ * ----------------------
309
+ * <code>
310
+ * src="//display-stg.ugc.bazaarvoice.com/static/{{ClientName}}/{{DeploymentZoneName}}/{{Locale}}/bvapi.js"
311
+ * </code>
312
+ *
313
+ * @static
314
+ * @param $isStatic
315
+ * @return string
316
+ */
317
+ public function getBvApiHostUrl($isStatic, $store = null)
318
+ {
319
+ // Build protocol based on current page
320
+ $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != '') ? 'https' : 'http';
321
+ // Build hostname based on environment setting
322
+ $environment = Mage::getStoreConfig('bazaarvoice/general/environment', $store);
323
+ if ($environment == 'staging') {
324
+ $apiHostname = 'display-stg.ugc.bazaarvoice.com';
325
+ }
326
+ else {
327
+ $apiHostname = 'display.ugc.bazaarvoice.com';
328
+ }
329
+ // Build static dir name based on param
330
+ if($isStatic) {
331
+ $static = 'static/';
332
+ }
333
+ else {
334
+ $static = '';
335
+ }
336
+ // Lookup other config settings
337
+ $clientName = Mage::getStoreConfig('bazaarvoice/general/client_name', $store);
338
+ $deploymnetZoneName = Mage::getStoreConfig('bazaarvoice/general/deployment_zone', $store);
339
+ // Get locale code from BV config,
340
+ // Note that this doesn't use Magento's locale, this will allow clients to override this and map it as they see fit
341
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store);
342
+ // Build url string
343
+ $url = $protocol . '://' . $apiHostname . '/' . $static . $clientName . '/' . urlencode($deploymnetZoneName) . '/' . $localeCode;
344
+ // Return final url
345
+ return $url;
346
+ }
347
+
348
+ /**
349
+ * @static
350
+ * @return string Either returns '/' or '/bvstaging/'
351
+ */
352
+ public function getBvStaging()
353
+ {
354
+ $environment = Mage::getStoreConfig('bazaarvoice/general/environment');
355
+ if ($environment == 'staging') {
356
+ $bvStaging = '/bvstaging/';
357
+ }
358
+ else {
359
+ $bvStaging = '/';
360
+ }
361
+ return $bvStaging;
362
+ }
363
+
364
+ public function getSFTPHost($store = null)
365
+ {
366
+ $environment = Mage::getStoreConfig('bazaarvoice/general/environment', $store);
367
+ $ftpHostOverride = trim(Mage::getStoreConfig('bazaarvoice/bv_config/ftp_host_name', $store));
368
+ if(strlen($ftpHostOverride)) {
369
+ $sftpHost = $ftpHostOverride;
370
+ }
371
+ else if ($environment == 'staging') {
372
+ $sftpHost = 'ftp-stg.bazaarvoice.com';
373
+ }
374
+ else {
375
+ $sftpHost = 'ftp.bazaarvoice.com';
376
+ }
377
+ return $sftpHost;
378
+ }
379
+
380
+ /**
381
+ * @static
382
+ * @return string representing the default display code to be used across all available BV products
383
+ */
384
+ public function getDefaultDeploymentZone()
385
+ {
386
+ return Mage::getStoreConfig('bazaarvoice/general/deployment_zone');
387
+ }
388
+
389
+ /**
390
+ * @static
391
+ * @param $bvProduct String indicating the BV product to get the displaycode for ('reviews', 'questions')
392
+ * @return string
393
+ */
394
+ public function getDeploymentZoneForBVProduct($bvProduct)
395
+ {
396
+ return getDefaultDeploymentZone();
397
+ }
398
+
399
+ /**
400
+ * @static
401
+ * @param $bvProduct String indicating the BV product to get the sub-domain for ('reviews', 'questions')
402
+ * @return string
403
+ */
404
+ public function getSubDomainForBVProduct($bvProduct)
405
+ {
406
+ return $this->getConfigPropertyForBVProduct($bvProduct, 'SubDomain');
407
+ }
408
+
409
+ // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
410
+ // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
411
+
412
+ public function getConfigPropertyForBVProduct($bvProduct, $propertyName)
413
+ {
414
+ $code = 'rr';
415
+ if ($bvProduct === 'questions') {
416
+ $code = 'qa';
417
+ }
418
+
419
+ return Mage::getStoreConfig('bazaarvoice/'.$code.'/'.$propertyName);
420
+ }
421
+
422
+ public function sendNotificationEmail($subject, $text)
423
+ {
424
+ $toEmail = Mage::getStoreConfig('bazaarvoice/feeds/admin_email');
425
+ $fromEmail = Mage::getStoreConfig('trans_email/ident_general/email'); // The 'General' contact identity is a default setting in Magento
426
+ if (empty($fromEmail)) {
427
+ $fromEmail = $toEmail;
428
+ }
429
+
430
+ if (!empty($toEmail)) {
431
+ /*
432
+ * Loads the template file from
433
+ * app/locale/en_US/template/email/bazaarvoice_notification.html
434
+ */
435
+ $emailTemplate = Mage::getModel('core/email_template')->loadDefault('bazaarvoice_notification_template');
436
+
437
+ // Create an array of variables to assign to template
438
+ $emailTemplateVariables = array();
439
+ $emailTemplateVariables['text'] = $text;
440
+
441
+ $emailTemplate->setSenderName('Bazaarvoice Magento Notifier');
442
+ $emailTemplate->setSenderEmail($fromEmail);
443
+ $emailTemplate->setTemplateSubject($subject);
444
+
445
+ $emailTemplate->send($toEmail,'Bazaarvoice Admin', $emailTemplateVariables);
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Returns the product unless the product visibility is
451
+ * set to not visible. In this case, it will try and pull
452
+ * the parent/associated product from the order item.
453
+ *
454
+ * @param Mage_Sales_Model_Order_Item $item
455
+ * @return Mage_Catalog_Model_Product
456
+ */
457
+ public function getReviewableProductFromOrderItem($item)
458
+ {
459
+ $product = Mage::getModel('catalog/product');
460
+ $product->setStoreId($item->getStoreId());
461
+ $product->load($item->getProductId());
462
+ if ($product->getVisibility() == Mage_Catalog_Model_Product_Visibility::VISIBILITY_NOT_VISIBLE)
463
+ {
464
+ $options = $item->getProductOptions();
465
+ try
466
+ {
467
+ $parentId = $options['super_product_config']['product_id'];
468
+ $product = Mage::getModel('catalog/product')->load($parentId);
469
+ }
470
+ catch (Exception $ex) {}
471
+ }
472
+
473
+ return $product;
474
+ }
475
+
476
+ /**
477
+ *
478
+ */
479
+ public function getExtensionVersion()
480
+ {
481
+ return (string) Mage::getConfig()->getNode()->modules->Bazaarvoice_Connector->version;
482
+ }
483
+
484
+ }
app/code/local/Bazaarvoice/Connector/Model/ExportProductFeed.php ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // @codingStandardsIgnoreStart
3
+ /**
4
+ * Event observer and indexer running application
5
+ *
6
+ * @author Bazaarvoice, Inc.
7
+ */
8
+ // @codingStandardsIgnoreEnd
9
+
10
+ /**
11
+ *
12
+ * Bazaarvoice product feed should be in the following format:
13
+ *
14
+ * <?xml version="1.0" encoding="UTF-8"?>
15
+ * <Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.2"
16
+ * name="SiteName"
17
+ * incremental="false"
18
+ * extractDate="2007-01-01T12:00:00.000000">
19
+ * <Categories>
20
+ * <Category>
21
+ * <ExternalId>1010</ExternalId>
22
+ * <Name>First Category</Name>
23
+ * <CategoryPageUrl>http://www.site.com/category.htm?cat=1010</CategoryPageUrl>
24
+ * </Category>
25
+ * ..... 0-n categories
26
+ * </Categories>
27
+ * <Products>
28
+ * <Product>
29
+ * <ExternalId>2000001</ExternalId>
30
+ * <Name>First Product</Name>
31
+ * <Description>First Product Description Text</Description>
32
+ * <Brand>ProductBrand</Brand>
33
+ * <CategoryExternalId>1010</CategoryExternalId>
34
+ * <ProductPageUrl>http://www.site.com/product.htm?prod=2000001</ProductPageUrl>
35
+ * <ImageUrl>http://images.site.com/prodimages/2000001.gif</ImageUrl>
36
+ * <ManufacturerPartNumber>26-12345-8Z</ManufacturerPartNumber>
37
+ * <EAN>0213354752286</EAN>
38
+ * </Product>
39
+ * ....... 0-n products
40
+ * </Products>
41
+ *</Feed>
42
+ */
43
+
44
+ /**
45
+ * Product Feed Export Class
46
+ */
47
+ class Bazaarvoice_Connector_Model_ExportProductFeed extends Mage_Core_Model_Abstract
48
+ {
49
+
50
+ protected function _construct()
51
+ {
52
+ }
53
+
54
+ /**
55
+ *
56
+ * process daily feed for the Bazaarvoice. The feed will be FTPed to the BV FTP server
57
+ *
58
+ * Product & Catalog Feed to BV
59
+ *
60
+ */
61
+ public function exportDailyProductFeed()
62
+ {
63
+ // Log
64
+ Mage::log('Start Bazaarvoice product feed generation');
65
+ // Check global setting to see what at which scope / level we should generate feeds
66
+ $feedGenScope = Mage::getStoreConfig('bazaarvoice/feeds/generation_scope');
67
+ switch ($feedGenScope) {
68
+ case Bazaarvoice_Connector_Model_Source_FeedGenerationScope::SCOPE_WEBSITE:
69
+ $this->exportDailyProductFeedByWebsite();
70
+ break;
71
+ case Bazaarvoice_Connector_Model_Source_FeedGenerationScope::SCOPE_STORE_GROUP:
72
+ $this->exportDailyProductFeedByGroup();
73
+ break;
74
+ case Bazaarvoice_Connector_Model_Source_FeedGenerationScope::SCOPE_STORE_VIEW:
75
+ $this->exportDailyProductFeedByStore();
76
+ break;
77
+ }
78
+ // Log
79
+ Mage::log('End Bazaarvoice product feed generation');
80
+ }
81
+
82
+ /**
83
+ *
84
+ */
85
+ private function exportDailyProductFeedByWebsite()
86
+ {
87
+ // Log
88
+ Mage::log('Exporting product feed file for each website...');
89
+ // Iterate through all websites in this instance
90
+ // (Not the 'admin' store view, which represents admin panel)
91
+ $websites = Mage::app()->getWebsites(false);
92
+ /** @var $website Mage_Core_Model_Website */
93
+ foreach ($websites as $website) {
94
+ try {
95
+ if (Mage::getStoreConfig('bazaarvoice/feeds/enable_product_feed', $website->getDefaultGroup()->getDefaultStoreId()) ===
96
+ '1'
97
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $website->getDefaultGroup()->getDefaultStoreId()) === '1'
98
+ ) {
99
+ if (count($website->getStores()) > 0) {
100
+ Mage::log(' BV - Exporting product feed for website: ' . $website->getName(),
101
+ Zend_Log::INFO);
102
+ $this->exportDailyProductFeedForWebsite($website);
103
+ }
104
+ else {
105
+ Mage::throwException('No stores for website: ' . $website->getName());
106
+ }
107
+ }
108
+ else {
109
+ Mage::log(' BV - Product feed disabled for website: ' . $website->getName(), Zend_Log::INFO);
110
+ }
111
+ }
112
+ catch (Exception $e) {
113
+ Mage::log(' BV - Failed to export daily product feed for website: ' . $website->getName(),
114
+ Zend_Log::ERR);
115
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
116
+ Mage::logException($e);
117
+ // Continue processing other websites
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ *
124
+ */
125
+ public function exportDailyProductFeedByGroup()
126
+ {
127
+ // Log
128
+ Mage::log('Exporting product feed file for each store group...');
129
+ // Iterate through all stores / groups in this instance
130
+ // (Not the 'admin' store view, which represents admin panel)
131
+ $groups = Mage::app()->getGroups(false);
132
+ /** @var $group Mage_Core_Model_Store_Group */
133
+ foreach ($groups as $group) {
134
+ try {
135
+ if (Mage::getStoreConfig('bazaarvoice/feeds/enable_product_feed', $group->getDefaultStoreId()) === '1'
136
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $group->getDefaultStoreId()) === '1'
137
+ ) {
138
+ if (count($group->getStores()) > 0) {
139
+ Mage::log(' BV - Exporting product feed for store group: ' . $group->getName(), Zend_Log::INFO);
140
+ $this->exportDailyProductFeedForStoreGroup($group);
141
+ }
142
+ else {
143
+ Mage::throwException('No stores for store group: ' . $group->getName());
144
+ }
145
+ }
146
+ else {
147
+ Mage::log(' BV - Product feed disabled for store group: ' . $group->getName(), Zend_Log::INFO);
148
+ }
149
+ }
150
+ catch (Exception $e) {
151
+ Mage::log(' BV - Failed to export daily product feed for store group: ' . $group->getName(),
152
+ Zend_Log::ERR);
153
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
154
+ Mage::logException($e);
155
+ // Continue processing other store groups
156
+ }
157
+ }
158
+ }
159
+
160
+ /**
161
+ *
162
+ */
163
+ private function exportDailyProductFeedByStore()
164
+ {
165
+ // Log
166
+ Mage::log('Exporting product feed file for each store / store view...');
167
+ // Iterate through all stores / groups in this instance
168
+ // (Not the 'admin' store view, which represents admin panel)
169
+ $stores = Mage::app()->getStores(false);
170
+ /** @var $store Mage_Core_Model_Store */
171
+ foreach ($stores as $store) {
172
+ try {
173
+ if (Mage::getStoreConfig('bazaarvoice/feeds/enable_product_feed', $store->getId()) === '1'
174
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $store->getId()) === '1'
175
+ ) {
176
+ Mage::log(' BV - Exporting product feed for store: ' . $store->getCode(), Zend_Log::INFO);
177
+ $this->exportDailyProductFeedForStore($store);
178
+ }
179
+ else {
180
+ Mage::log(' BV - Product feed disabled for store: ' . $store->getCode(), Zend_Log::INFO);
181
+ }
182
+ }
183
+ catch (Exception $e) {
184
+ Mage::log(' BV - Failed to export daily product feed for store: ' . $store->getCode(), Zend_Log::ERR);
185
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
186
+ Mage::logException($e);
187
+ // Continue processing other store groups
188
+ }
189
+ }
190
+ }
191
+
192
+ /**
193
+ * process daily feed for the Bazaarvoice. The feed will be FTPed to the BV FTP server
194
+ *
195
+ * Product & Catalog Feed to BV
196
+ *
197
+ * @param Mage_Core_Model_Website $website Website
198
+ *
199
+ */
200
+ public function exportDailyProductFeedForWebsite(Mage_Core_Model_Website $website)
201
+ {
202
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Category $categoryModel */
203
+ $categoryModel = Mage::getModel('bazaarvoice/productFeed_category');
204
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Product $productModel */
205
+ $productModel = Mage::getModel('bazaarvoice/productFeed_product');
206
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Brand $brandModel */
207
+ $brandModel = Mage::getModel('bazaarvoice/productFeed_brand');
208
+
209
+ // Build local file name / path
210
+ $productFeedFilePath = Mage::getBaseDir('var') . DS . 'export' . DS . 'bvfeeds';
211
+ $productFeedFileName =
212
+ $productFeedFilePath . DS . 'productFeed-website-' . $website->getId() . '-' . date('U') . '.xml';
213
+ // Get client name for the scope
214
+ $clientName = Mage::getStoreConfig('bazaarvoice/general/client_name', $website->getDefaultGroup()->getDefaultStoreId());
215
+
216
+ // Create varien io object and write local feed file
217
+ /* @var $ioObject Varien_Io_File */
218
+ $ioObject = $this->createAndStartWritingFile($productFeedFileName, $clientName);
219
+ Mage::log(' BV - processing all categories');
220
+ $categoryModel->processCategoriesForWebsite($ioObject, $website);
221
+ Mage::log(' BV - completed categories, beginning products');
222
+ $productModel->setCategoryIdList($categoryModel->getCategoryIdList());
223
+ $productModel->processProductsForWebsite($ioObject, $website);
224
+ Mage::log(' BV - completed products, beginning brands');
225
+ $brandModel->processBrandsForWebsite($ioObject, $website);
226
+ Mage::log(' BV - completed processing all brands');
227
+ $this->closeAndFinishWritingFile($ioObject);
228
+
229
+ // Upload feed
230
+ $this->uploadFeed($productFeedFileName, $website->getDefaultStore());
231
+
232
+ }
233
+
234
+ /**
235
+ * process daily feed for the Bazaarvoice. The feed will be FTPed to the BV FTP server
236
+ *
237
+ * Product & Catalog Feed to BV
238
+ *
239
+ * @param Mage_Core_Model_Store_Group $group Store Group
240
+ *
241
+ */
242
+ public function exportDailyProductFeedForStoreGroup(Mage_Core_Model_Store_Group $group)
243
+ {
244
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Category $categoryModel */
245
+ $categoryModel = Mage::getModel('bazaarvoice/productFeed_category');
246
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Product $productModel */
247
+ $productModel = Mage::getModel('bazaarvoice/productFeed_product');
248
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Brand $brandModel */
249
+ $brandModel = Mage::getModel('bazaarvoice/productFeed_brand');
250
+
251
+ // Build local file name / path
252
+ $productFeedFilePath = Mage::getBaseDir('var') . DS . 'export' . DS . 'bvfeeds';
253
+ $productFeedFileName =
254
+ $productFeedFilePath . DS . 'productFeed-group-' . $group->getId() . '-' . date('U') . '.xml';
255
+ // Get client name for the scope
256
+ $clientName = Mage::getStoreConfig('bazaarvoice/general/client_name', $group->getDefaultStoreId());
257
+
258
+ // Create varien io object and write local feed file
259
+ /* @var $ioObject Varien_Io_File */
260
+ $ioObject = $this->createAndStartWritingFile($productFeedFileName, $clientName);
261
+ Mage::log(' BV - processing all categories');
262
+ $categoryModel->processCategoriesForGroup($ioObject, $group);
263
+ Mage::log(' BV - completed categories, beginning products');
264
+ $productModel->setCategoryIdList($categoryModel->getCategoryIdList());
265
+ $productModel->processProductsForGroup($ioObject, $group);
266
+ Mage::log(' BV - completed products, beginning brands');
267
+ $brandModel->processBrandsForGroup($ioObject, $group);
268
+ Mage::log(' BV - completed processing all brands');
269
+ $this->closeAndFinishWritingFile($ioObject);
270
+
271
+ // Upload feed
272
+ $this->uploadFeed($productFeedFileName, $group->getDefaultStore());
273
+
274
+ }
275
+
276
+ /**
277
+ * process daily feed for the Bazaarvoice. The feed will be FTPed to the BV FTP server
278
+ *
279
+ * Product & Catalog Feed to BV
280
+ * @param Mage_Core_Model_Store $store
281
+ *
282
+ */
283
+ public function exportDailyProductFeedForStore(Mage_Core_Model_Store $store)
284
+ {
285
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Category $categoryModel */
286
+ $categoryModel = Mage::getModel('bazaarvoice/productFeed_category');
287
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Product $productModel */
288
+ $productModel = Mage::getModel('bazaarvoice/productFeed_product');
289
+ /** @var Bazaarvoice_Connector_Model_ProductFeed_Brand $brandModel */
290
+ $brandModel = Mage::getModel('bazaarvoice/productFeed_brand');
291
+
292
+ // Build local file name / path
293
+ $productFeedFilePath = Mage::getBaseDir('var') . DS . 'export' . DS . 'bvfeeds';
294
+ $productFeedFileName =
295
+ $productFeedFilePath . DS . 'productFeed-store-' . $store->getId() . '-' . date('U') . '.xml';
296
+ // Get client name for the scope
297
+ $clientName = Mage::getStoreConfig('bazaarvoice/general/client_name', $store->getId());
298
+
299
+ // Create varien io object and write local feed file
300
+ /* @var $ioObject Varien_Io_File */
301
+ $ioObject = $this->createAndStartWritingFile($productFeedFileName, $clientName);
302
+ Mage::log(' BV - processing all categories');
303
+ $categoryModel->processCategoriesForStore($ioObject, $store);
304
+ Mage::log(' BV - completed categories, beginning products');
305
+ $productModel->setCategoryIdList($categoryModel->getCategoryIdList());
306
+ $productModel->processProductsForStore($ioObject, $store);
307
+ Mage::log(' BV - completed products, beginning brands');
308
+ $brandModel->processBrandsForStore($ioObject, $store);
309
+ Mage::log(' BV - completed processing all brands');
310
+ $this->closeAndFinishWritingFile($ioObject);
311
+
312
+ // Upload feed
313
+ $this->uploadFeed($productFeedFileName, $store);
314
+
315
+ }
316
+
317
+ /**
318
+ * @param $productFeedFileName
319
+ * @param Mage_Core_Model_Store $store
320
+ */
321
+ private function uploadFeed($productFeedFileName, Mage_Core_Model_Store $store)
322
+ {
323
+ // Get ref to BV helper
324
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
325
+ $bvHelper = Mage::helper('bazaarvoice');
326
+
327
+ // Get path and filename from custom config settings
328
+ $destinationFile = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_export_export_path', $store->getId()) . '/' .
329
+ Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_export_filename', $store->getId());
330
+ $sourceFile = $productFeedFileName;
331
+ $upload = $bvHelper->uploadFile($sourceFile, $destinationFile, $store);
332
+
333
+ if (!$upload) {
334
+ Mage::log(' Bazaarvoice FTP upload failed! [filename = ' . $productFeedFileName . ']');
335
+ }
336
+ else {
337
+ Mage::log(' Bazaarvoice FTP upload success! [filename = ' . $productFeedFileName . ']');
338
+ $ioObject = new Varien_Io_File();
339
+ $ioObject->rm($productFeedFileName);
340
+ }
341
+ }
342
+
343
+ /**
344
+ * @param string $productFeedFileName Name of local product feed file to create and write
345
+ * @param string $clientName BV Client name text
346
+ * @return Varien_Io_File File object, opening <Feed> tag is already written
347
+ */
348
+ private function createAndStartWritingFile($productFeedFileName, $clientName)
349
+ {
350
+ // Get ref to BV helper
351
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
352
+ $bvHelper = Mage::helper('bazaarvoice');
353
+
354
+ $ioObject = new Varien_Io_File();
355
+ try {
356
+ $ioObject->open(array('path' => dirname($productFeedFileName)));
357
+ }
358
+ catch (Exception $e) {
359
+ $ioObject->mkdir(dirname($productFeedFileName), 0777, true);
360
+ $ioObject->open(array('path' => dirname($productFeedFileName)));
361
+ }
362
+
363
+ if (!$ioObject->streamOpen(basename($productFeedFileName))) {
364
+ Mage::throwException('Failed to open local feed file for writing: ' . $productFeedFileName);
365
+ }
366
+
367
+ $ioObject->streamWrite("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" .
368
+ "<Feed xmlns=\"http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.2\"" .
369
+ " generator=\"Magento Extension r" . $bvHelper->getExtensionVersion() . "\"" .
370
+ " name=\"" . $clientName . "\"" .
371
+ " incremental=\"false\"" .
372
+ " extractDate=\"" . date('Y-m-d') . "T" . date('H:i:s') . ".000000\">\n");
373
+
374
+ return $ioObject;
375
+ }
376
+
377
+ /**
378
+ * @param Varien_Io_File $ioObject File object for feed file
379
+ */
380
+ private function closeAndFinishWritingFile(Varien_Io_File $ioObject)
381
+ {
382
+ $ioObject->streamWrite("</Feed>\n");
383
+ $ioObject->streamClose();
384
+ }
385
+
386
+ }
app/code/local/Bazaarvoice/Connector/Model/ExportPurchaseFeed.php ADDED
@@ -0,0 +1,639 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bazaarvoice_Connector_Model_ExportPurchaseFeed extends Mage_Core_Model_Abstract
3
+ {
4
+
5
+ const ALREADY_SENT_IN_FEED_FLAG = 'sent_in_bv_postpurchase_feed';
6
+ const TRIGGER_EVENT_PURCHASE = 'purchase';
7
+ const TRIGGER_EVENT_SHIP = 'ship';
8
+
9
+ const NUM_DAYS_LOOKBACK = 30;
10
+
11
+ const DEBUG_OUTPUT = false;
12
+
13
+ protected function _construct()
14
+ {
15
+ }
16
+
17
+ public function exportPurchaseFeed()
18
+ {
19
+ // Log
20
+ Mage::log('Start Bazaarvoice purchase feed generation');
21
+ // Check global setting to see what at which scope / level we should generate feeds
22
+ $feedGenScope = Mage::getStoreConfig('bazaarvoice/feeds/generation_scope');
23
+ switch ($feedGenScope) {
24
+ case Bazaarvoice_Connector_Model_Source_FeedGenerationScope::SCOPE_WEBSITE:
25
+ $this->exportPurchaseFeedByWebsite();
26
+ break;
27
+ case Bazaarvoice_Connector_Model_Source_FeedGenerationScope::SCOPE_STORE_GROUP:
28
+ $this->exportPurchaseFeedByGroup();
29
+ break;
30
+ case Bazaarvoice_Connector_Model_Source_FeedGenerationScope::SCOPE_STORE_VIEW:
31
+ $this->exportPurchaseFeedByStore();
32
+ break;
33
+ }
34
+ // Log
35
+ Mage::log('End Bazaarvoice purchase feed generation');
36
+ }
37
+
38
+ /**
39
+ *
40
+ */
41
+ public function exportPurchaseFeedByWebsite()
42
+ {
43
+ // Log
44
+ Mage::log('Exporting purchase feed file for each website...');
45
+ // Iterate through all websites in this instance
46
+ // (Not the 'admin' website / store / view, which represents admin panel)
47
+ $websites = Mage::app()->getWebsites(false);
48
+ /** @var $website Mage_Core_Model_Website */
49
+ foreach ($websites as $website) {
50
+ try {
51
+ if (Mage::getStoreConfig('bazaarvoice/feeds/enable_purchase_feed', $website->getDefaultGroup()->getDefaultStoreId()) === '1'
52
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $website->getDefaultGroup()->getDefaultStoreId()) === '1'
53
+ ) {
54
+ if (count($website->getStores()) > 0) {
55
+ Mage::log(' BV - Exporting purchase feed for website: ' . $website->getName(), Zend_Log::INFO);
56
+ $this->exportPurchaseFeedForWebsite($website);
57
+ }
58
+ else {
59
+ Mage::throwException('No stores for website: ' . $website->getName());
60
+ }
61
+ }
62
+ else {
63
+ Mage::log(' BV - Purchase feed disabled for website: ' . $website->getName(), Zend_Log::INFO);
64
+ }
65
+ }
66
+ catch (Exception $e) {
67
+ Mage::log(' BV - Failed to export daily purchase feed for website: ' . $website->getName(), Zend_Log::ERR);
68
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
69
+ Mage::logException($e);
70
+ // Continue processing other websites
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ *
77
+ */
78
+ public function exportPurchaseFeedByGroup()
79
+ {
80
+ // Log
81
+ Mage::log('Exporting purchase feed file for each store group...');
82
+ // Iterate through all stores / groups in this instance
83
+ // (Not the 'admin' store view, which represents admin panel)
84
+ $groups = Mage::app()->getGroups(false);
85
+ /** @var $group Mage_Core_Model_Store_Group */
86
+ foreach ($groups as $group) {
87
+ try {
88
+ if (Mage::getStoreConfig('bazaarvoice/feeds/enable_purchase_feed', $group->getDefaultStoreId()) === '1'
89
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $group->getDefaultStoreId()) === '1'
90
+ ) {
91
+ if (count($group->getStores()) > 0) {
92
+ Mage::log(' BV - Exporting purchase feed for store group: ' . $group->getName(), Zend_Log::INFO);
93
+ $this->exportPurchaseFeedForStoreGroup($group);
94
+ }
95
+ else {
96
+ Mage::throwException('No stores for store group: ' . $group->getName());
97
+ }
98
+ }
99
+ else {
100
+ Mage::log(' BV - Purchase feed disabled for store group: ' . $group->getName(), Zend_Log::INFO);
101
+ }
102
+ }
103
+ catch (Exception $e) {
104
+ Mage::log(' BV - Failed to export daily purchase feed for store group: ' . $group->getName(), Zend_Log::ERR);
105
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
106
+ Mage::logException($e);
107
+ // Continue processing other store groups
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ *
114
+ */
115
+ public function exportPurchaseFeedByStore()
116
+ {
117
+ // Log
118
+ Mage::log('Exporting purchase feed file for each store...');
119
+ // Iterate through all stores in this instance
120
+ // (Not the 'admin' store view, which represents admin panel)
121
+ $stores = Mage::app()->getStores(false);
122
+ /** @var $store Mage_Core_Model_Store */
123
+ foreach ($stores as $store) {
124
+ try {
125
+ if (Mage::getStoreConfig('bazaarvoice/feeds/enable_purchase_feed', $store->getId()) === '1'
126
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $store->getId()) === '1'
127
+ ) {
128
+ Mage::log(' BV - Exporting purchase feed for: ' . $store->getCode(), Zend_Log::INFO);
129
+ $this->exportPurchaseFeedForStore($store);
130
+ }
131
+ else {
132
+ Mage::log(' BV - Purchase feed disabled for store: ' . $store->getCode(), Zend_Log::INFO);
133
+ }
134
+ }
135
+ catch (Exception $e) {
136
+ Mage::log(' BV - Failed to export daily purchase feed for store: ' . $store->getCode(), Zend_Log::ERR);
137
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
138
+ Mage::logException($e);
139
+ // Continue processing other stores
140
+ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * @param Mage_Core_Model_Website $website
146
+ */
147
+ public function exportPurchaseFeedForWebsite(Mage_Core_Model_Website $website)
148
+ {
149
+ // Get ref to BV helper
150
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
151
+ $bvHelper = Mage::helper('bazaarvoice');
152
+
153
+ // Build purchase export file path and name
154
+ $purchaseFeedFilePath = Mage::getBaseDir("var") . DS . 'export' . DS . 'bvfeeds';
155
+ $purchaseFeedFileName = 'purchaseFeed-website-' . $website->getId() . '-' . date('U') . '.xml';
156
+
157
+ // Make sure that the directory we want to write to exists.
158
+ $ioObject = new Varien_Io_File();
159
+ try {
160
+ $ioObject->open(array('path' => $purchaseFeedFilePath));
161
+ }
162
+ catch (Exception $e) {
163
+ $ioObject->mkdir($purchaseFeedFilePath, 0777, true);
164
+ $ioObject->open(array('path' => $purchaseFeedFilePath));
165
+ }
166
+
167
+ if ($ioObject->streamOpen($purchaseFeedFileName)) {
168
+
169
+ $ioObject->streamWrite("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Feed xmlns=\"http://www.bazaarvoice.com/xs/PRR/PostPurchaseFeed/4.9\">\n");
170
+
171
+ Mage::log(' BV - processing all orders');
172
+ $numOrdersExported = $this->processOrdersForWebsite($ioObject, $website);
173
+ Mage::log(' BV - completed processing all orders');
174
+
175
+ $ioObject->streamWrite("</Feed>\n");
176
+ $ioObject->streamClose();
177
+
178
+ // Don't bother uploading if there are no orders in the feed
179
+ $upload = false;
180
+ if ($numOrdersExported > 0) {
181
+ /*
182
+ * Hard code path and file name
183
+ * Former config setting defaults:
184
+ * <ExportPath>/ppe/inbox</ExportPath>
185
+ * <ExportFileName>bazaarvoice-order-data.xml</ExportFileName>
186
+ */
187
+ $destinationFile = '/ppe/inbox/bazaarvoice-order-data-' . date('U') . '.xml';
188
+ $sourceFile = $purchaseFeedFilePath . DS . $purchaseFeedFileName;
189
+
190
+ $upload = $bvHelper->uploadFile($sourceFile, $destinationFile, $website->getDefaultStore());
191
+ }
192
+
193
+ if (!$upload) {
194
+ Mage::log(' Bazaarvoice FTP upload failed! [filename = ' . $purchaseFeedFileName . ']');
195
+ }
196
+ else {
197
+ Mage::log(' Bazaarvoice FTP upload success! [filename = ' . $purchaseFeedFileName . ']');
198
+ $ioObject->rm($purchaseFeedFileName);
199
+ }
200
+
201
+ }
202
+ }
203
+
204
+ /**
205
+ *
206
+ * @param Mage_Core_Model_Store_Group $group Store Group
207
+ */
208
+ public function exportPurchaseFeedForStoreGroup(Mage_Core_Model_Store_Group $group)
209
+ {
210
+ // Get ref to BV helper
211
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
212
+ $bvHelper = Mage::helper('bazaarvoice');
213
+
214
+ // Build purchase export file path and name
215
+ $purchaseFeedFilePath = Mage::getBaseDir("var") . DS . 'export' . DS . 'bvfeeds';
216
+ $purchaseFeedFileName = 'purchaseFeed-group-' . $group->getId() . '-' . date('U') . '.xml';
217
+
218
+ // Make sure that the directory we want to write to exists.
219
+ $ioObject = new Varien_Io_File();
220
+ try {
221
+ $ioObject->open(array('path' => $purchaseFeedFilePath));
222
+ }
223
+ catch (Exception $e) {
224
+ $ioObject->mkdir($purchaseFeedFilePath, 0777, true);
225
+ $ioObject->open(array('path' => $purchaseFeedFilePath));
226
+ }
227
+
228
+ if ($ioObject->streamOpen($purchaseFeedFileName)) {
229
+
230
+ $ioObject->streamWrite("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Feed xmlns=\"http://www.bazaarvoice.com/xs/PRR/PostPurchaseFeed/4.9\">\n");
231
+
232
+ Mage::log(' BV - processing all orders');
233
+ $numOrdersExported = $this->processOrdersForGroup($ioObject, $group);
234
+ Mage::log(' BV - completed processing all orders');
235
+
236
+ $ioObject->streamWrite("</Feed>\n");
237
+ $ioObject->streamClose();
238
+
239
+ // Don't bother uploading if there are no orders in the feed
240
+ $upload = false;
241
+ if ($numOrdersExported > 0) {
242
+ /*
243
+ * Hard code path and file name
244
+ * Former config setting defaults:
245
+ * <ExportPath>/ppe/inbox</ExportPath>
246
+ * <ExportFileName>bazaarvoice-order-data.xml</ExportFileName>
247
+ */
248
+ $destinationFile = '/ppe/inbox/bazaarvoice-order-data-' . date('U') . '.xml';
249
+ $sourceFile = $purchaseFeedFilePath . DS . $purchaseFeedFileName;
250
+
251
+ $upload = $bvHelper->uploadFile($sourceFile, $destinationFile, $group->getDefaultStore());
252
+ }
253
+
254
+ if (!$upload) {
255
+ Mage::log(' Bazaarvoice FTP upload failed! [filename = ' . $purchaseFeedFileName . ']');
256
+ }
257
+ else {
258
+ Mage::log(' Bazaarvoice FTP upload success! [filename = ' . $purchaseFeedFileName . ']');
259
+ $ioObject->rm($purchaseFeedFileName);
260
+ }
261
+
262
+ }
263
+ }
264
+
265
+ /**
266
+ * @param Mage_Core_Model_Store $store
267
+ */
268
+ public function exportPurchaseFeedForStore(Mage_Core_Model_Store $store)
269
+ {
270
+ // Get ref to BV helper
271
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
272
+ $bvHelper = Mage::helper('bazaarvoice');
273
+
274
+ // Build purchase export file path and name
275
+ $purchaseFeedFilePath = Mage::getBaseDir('var') . DS . 'export' . DS . 'bvfeeds';
276
+ $purchaseFeedFileName = 'purchaseFeed-store-' . $store->getId() . '-' . date('U') . '.xml';
277
+
278
+ // Make sure that the directory we want to write to exists.
279
+ $ioObject = new Varien_Io_File();
280
+ try {
281
+ $ioObject->open(array('path' => $purchaseFeedFilePath));
282
+ }
283
+ catch (Exception $e) {
284
+ $ioObject->mkdir($purchaseFeedFilePath, 0777, true);
285
+ $ioObject->open(array('path' => $purchaseFeedFilePath));
286
+ }
287
+
288
+ if ($ioObject->streamOpen($purchaseFeedFileName)) {
289
+
290
+ $ioObject->streamWrite("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Feed xmlns=\"http://www.bazaarvoice.com/xs/PRR/PostPurchaseFeed/4.9\">\n");
291
+
292
+ Mage::log(" BV - processing all orders");
293
+ $numOrdersExported = $this->processOrdersForStore($ioObject, $store);
294
+ Mage::log(" BV - completed processing all orders");
295
+
296
+ $ioObject->streamWrite("</Feed>\n");
297
+ $ioObject->streamClose();
298
+
299
+ // Don't bother uploading if there are no orders in the feed
300
+ $upload = false;
301
+ if ($numOrdersExported > 0) {
302
+ /*
303
+ * Hard code path and file name
304
+ * Former config setting defaults:
305
+ * <ExportPath>/ppe/inbox</ExportPath>
306
+ * <ExportFileName>bazaarvoice-order-data.xml</ExportFileName>
307
+ */
308
+ $destinationFile = '/ppe/inbox/bazaarvoice-order-data-' . date('U') . '.xml';
309
+ $sourceFile = $purchaseFeedFilePath . DS . $purchaseFeedFileName;
310
+
311
+ $upload = $bvHelper->uploadFile($sourceFile, $destinationFile, $store);
312
+ }
313
+
314
+ if (!$upload) {
315
+ Mage::log(' Bazaarvoice FTP upload failed! [filename = ' . $purchaseFeedFileName . ']');
316
+ }
317
+ else {
318
+ Mage::log(' Bazaarvoice FTP upload success! [filename = ' . $purchaseFeedFileName . ']');
319
+ $ioObject->rm($purchaseFeedFileName);
320
+ }
321
+
322
+ }
323
+ }
324
+
325
+ /**
326
+ * @param Varien_Io_File $ioObject
327
+ * @param Mage_Core_Model_Website $website
328
+ * @return int
329
+ */
330
+ private function processOrdersForWebsite(Varien_Io_File $ioObject, Mage_Core_Model_Website $website)
331
+ {
332
+ // Get a collection of all the orders
333
+ $orders = Mage::getModel('sales/order')->getCollection();
334
+
335
+ // Filter the returned orders to minimize processing as much as possible. More available operations in method _getConditionSql in Varien_Data_Collection_Db.
336
+ // Add filter to limit orders to this store group
337
+ // Join to core_store table and grab website_id field
338
+ $orders->getSelect()
339
+ ->joinLeft('core_store', 'main_table.store_id = core_store.store_id', 'core_store.website_id')
340
+ ->where('core_store.website_id = ' . $website->getId());
341
+ // Status is 'complete' or 'closed'
342
+ $orders->addFieldToFilter('status', array(
343
+ 'in' => array(
344
+ 'complete',
345
+ 'closed'
346
+ )
347
+ ));
348
+ // Only orders created within our look-back window
349
+ $orders->addFieldToFilter('created_at', array('gteq' => $this->getNumDaysLookbackStartDate()));
350
+ // Exclude orders that have been previously sent in a feed
351
+ $orders->addFieldToFilter(self::ALREADY_SENT_IN_FEED_FLAG, array('null' => 'null')); // adds an 'IS NULL' filter to the BV flag column
352
+
353
+ // Write orders to file
354
+ $numOrdersExported = $this->writeOrdersToFile($ioObject, $orders);
355
+
356
+ return $numOrdersExported;
357
+ }
358
+
359
+ /**
360
+ * @param Varien_Io_File $ioObject
361
+ * @param Mage_Core_Model_Store_Group $group
362
+ * @return int
363
+ */
364
+ private function processOrdersForGroup(Varien_Io_File $ioObject, Mage_Core_Model_Store_Group $group)
365
+ {
366
+ // Get a collection of all the orders
367
+ $orders = Mage::getModel('sales/order')->getCollection();
368
+
369
+ // Filter the returned orders to minimize processing as much as possible. More available operations in method _getConditionSql in Varien_Data_Collection_Db.
370
+ // Add filter to limit orders to this store group
371
+ // Join to core_store table and grab group_id field
372
+ $orders->getSelect()
373
+ ->joinLeft('core_store', 'main_table.store_id = core_store.store_id', 'core_store.group_id')
374
+ ->where('core_store.group_id = ' . $group->getId());
375
+ // Status is 'complete' or 'closed'
376
+ $orders->addFieldToFilter('status', array(
377
+ 'in' => array(
378
+ 'complete',
379
+ 'closed'
380
+ )
381
+ ));
382
+ // Only orders created within our look-back window
383
+ $orders->addFieldToFilter('created_at', array('gteq' => $this->getNumDaysLookbackStartDate()));
384
+ // Exclude orders that have been previously sent in a feed
385
+ $orders->addFieldToFilter(self::ALREADY_SENT_IN_FEED_FLAG, array('null' => 'null')); // adds an 'IS NULL' filter to the BV flag column
386
+
387
+ // Write orders to file
388
+ $numOrdersExported = $this->writeOrdersToFile($ioObject, $orders);
389
+
390
+ return $numOrdersExported;
391
+ }
392
+
393
+ /**
394
+ * @param Varien_Io_File $ioObject
395
+ * @param Mage_Core_Model_Store $store
396
+ * @return int
397
+ */
398
+ private function processOrdersForStore(Varien_Io_File $ioObject, Mage_Core_Model_Store $store)
399
+ {
400
+ // Get a collection of all the orders
401
+ $orders = Mage::getModel('sales/order')->getCollection();
402
+
403
+ // Filter the returned orders to minimize processing as much as possible. More available operations in method _getConditionSql in Varien_Data_Collection_Db.
404
+ // Add filter to limit orders to this store
405
+ $orders->addFieldToFilter('store_id', $store->getId());
406
+ // Status is 'complete' or 'closed'
407
+ $orders->addFieldToFilter('status', array(
408
+ 'in' => array(
409
+ 'complete',
410
+ 'closed'
411
+ )
412
+ ));
413
+ // Only orders created within our look-back window
414
+ $orders->addFieldToFilter('created_at', array('gteq' => $this->getNumDaysLookbackStartDate()));
415
+ // Exclude orders that have been previously sent in a feed
416
+ $orders->addFieldToFilter(self::ALREADY_SENT_IN_FEED_FLAG, array('null' => 'null')); // adds an 'IS NULL' filter to the BV flag column
417
+
418
+ // Write orders to file
419
+ $numOrdersExported = $this->writeOrdersToFile($ioObject, $orders);
420
+
421
+ return $numOrdersExported;
422
+ }
423
+
424
+ /**
425
+ * @param Varien_Io_File $ioObject
426
+ * @param $orders
427
+ * @return int
428
+ */
429
+ private function writeOrdersToFile(Varien_Io_File $ioObject, $orders)
430
+ {
431
+ // Get ref to BV helper
432
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
433
+ $bvHelper = Mage::helper('bazaarvoice');
434
+
435
+ // Initialize references to the object model accessors
436
+ $orderModel = Mage::getModel('sales/order');
437
+
438
+ // Gather settings for how this feed should be generated
439
+ $triggeringEvent = Mage::getStoreConfig('bazaarvoice/feeds/triggering_event') ===
440
+ Bazaarvoice_Connector_Model_Source_TriggeringEvent::SHIPPING ? self::TRIGGER_EVENT_SHIP : self::TRIGGER_EVENT_PURCHASE;
441
+ // Hard code former settings
442
+ $delayDaysSinceEvent = 1;
443
+ Mage::log(" BV - Config {triggering_event: " . $triggeringEvent
444
+ . ", NumDaysLookback: " . self::NUM_DAYS_LOOKBACK
445
+ . ", NumDaysLookbackStartDate: " . $this->getNumDaysLookbackStartDate()
446
+ . ", DelayDaysSinceEvent: " . $delayDaysSinceEvent
447
+ . ', DelayDaysThreshold: ' . date('c', $this->getDelayDaysThresholdTimestamp($delayDaysSinceEvent)) . '}');
448
+
449
+ $numOrdersExported = 0; // Keep track of how many orders we include in the feed
450
+
451
+ foreach ($orders->getAllIds() as $orderId) {
452
+
453
+ /* @var $order Mage_Sales_Model_Order */
454
+ $order = $orderModel->load($orderId);
455
+ $store = $order->getStore();
456
+
457
+ if (!$this->shouldIncludeOrder($order, $triggeringEvent, $delayDaysSinceEvent)) {
458
+ continue;
459
+ }
460
+
461
+ $numOrdersExported++;
462
+
463
+ $ioObject->streamWrite("<Interaction>\n");
464
+ $ioObject->streamWrite(' <EmailAddress>' . $order->getCustomerEmail() . "</EmailAddress>\n");
465
+ $ioObject->streamWrite(' <Locale>' . $store->getConfig('bazaarvoice/general/locale') . "</Locale>\n");
466
+ $ioObject->streamWrite(' <UserName>' . $order->getCustomerName() . "</UserName>\n");
467
+ $ioObject->streamWrite(' <UserID>' . $order->getCustomerId() . "</UserID>\n");
468
+ $ioObject->streamWrite(' <TransactionDate>' . $this->getTriggeringEventDate($order, $triggeringEvent) .
469
+ "</TransactionDate>\n");
470
+ $ioObject->streamWrite(" <Products>\n");
471
+ /* @var $item Mage_Sales_Model_Order_Item */
472
+ foreach ($order->getAllVisibleItems() as $item) {
473
+ $product = $bvHelper->getReviewableProductFromOrderItem($item);
474
+ if (!is_null($product)) {
475
+ $ioObject->streamWrite(" <Product>\n");
476
+ $ioObject->streamWrite(' <ExternalId>' . $bvHelper->getProductId($product) .
477
+ "</ExternalId>\n");
478
+ $ioObject->streamWrite(' <Name>' . htmlspecialchars($product->getName(), ENT_QUOTES, 'UTF-8') .
479
+ "</Name>\n");
480
+ $ioObject->streamWrite(' <ImageUrl>' . $product->getImageUrl() . "</ImageUrl>\n");
481
+ $ioObject->streamWrite(' <Price>' . number_format((float)$item->getOriginalPrice(), 2) . "</Price>\n");
482
+ $ioObject->streamWrite(" </Product>\n");
483
+ }
484
+ }
485
+ $ioObject->streamWrite(" </Products>\n");
486
+ $ioObject->streamWrite("</Interaction>\n");
487
+
488
+ $order->setData(self::ALREADY_SENT_IN_FEED_FLAG, 1);
489
+ $order->save();
490
+ $order->reset(); // Forces a reload of various collections that the object caches internally so that the next time we load from the orderModel, we'll get a completely new object.
491
+
492
+ }
493
+
494
+ return $numOrdersExported;
495
+ }
496
+
497
+ private function orderToString(Mage_Sales_Model_Order $order)
498
+ {
499
+ return "\nOrder {Id: " . $order->getId()
500
+ . "\n\tCustomerId: " . $order->getCustomerId()
501
+ . "\n\tStatus: " . $order->getStatus()
502
+ . "\n\tState: " . $order->getState()
503
+ . "\n\tDate: " . date('c', strtotime($order->getCreatedAtDate()))
504
+ . "\n\tHasShipped: " . $this->hasOrderCompletelyShipped($order)
505
+ . "\n\tLatestShipmentDate: " . date('c', $this->getLatestShipmentDate($order))
506
+ . "\n\tNumItems: " . count($order->getAllItems())
507
+ . "\n\tSentInBVPPEFeed: " . $order->getData(self::ALREADY_SENT_IN_FEED_FLAG)
508
+ // . "\n\tCustomerEmail: " . $order->getCustomerEmail() // Don't put CustomerEmail in the logs - could be considered PII
509
+ . "\n}";
510
+ }
511
+
512
+ private function getTriggeringEventDate(Mage_Sales_Model_Order $order, $triggeringEvent)
513
+ {
514
+ $timestamp = strtotime($order->getCreatedAtDate());
515
+
516
+ if ($triggeringEvent === self::TRIGGER_EVENT_SHIP) {
517
+ $timestamp = $this->getLatestShipmentDate($order);
518
+ }
519
+
520
+ return date('c', $timestamp);
521
+ }
522
+
523
+ private function getNumDaysLookbackStartDate()
524
+ {
525
+ return date('Y-m-d', strtotime(date('Y-m-d', time()) . ' -' . self::NUM_DAYS_LOOKBACK . ' days'));
526
+ }
527
+
528
+ private function getDelayDaysThresholdTimestamp($delayDaysSinceEvent)
529
+ {
530
+ return time() - (24 * 60 * 60 * $delayDaysSinceEvent);
531
+ }
532
+
533
+ private function shouldIncludeOrder(Mage_Sales_Model_Order $order, $triggeringEvent, $delayDaysSinceEvent)
534
+ {
535
+ // Have we already included this order in a previous feed?
536
+ if ($order->getData(self::ALREADY_SENT_IN_FEED_FLAG) === '1') {
537
+ Mage::log(' BV - Skipping Order. Already included in previous feed. ' . $this->orderToString($order));
538
+ return false;
539
+ }
540
+
541
+ // Is the order canceled?
542
+ if ($order->isCanceled()) {
543
+ Mage::log(' BV - Skipping Order. Canceled state. ' . $this->orderToString($order));
544
+ return false;
545
+ }
546
+
547
+ // Ensure that we can get the store for the order
548
+ $store = $order->getStore();
549
+ if (is_null($store)) {
550
+ Mage::log(' BV - Skipping Order. Could not find store for order. ' . $this->orderToString($order));
551
+ return false;
552
+ }
553
+
554
+ $thresholdTimestamp = $this->getDelayDaysThresholdTimestamp($delayDaysSinceEvent);
555
+
556
+ if ($triggeringEvent === self::TRIGGER_EVENT_SHIP) {
557
+ // We need to see if this order is completely shipped, and if so, is the latest item ship date outside of the delay period.
558
+
559
+ // Is the order completely shipped?
560
+ if (!$this->hasOrderCompletelyShipped($order)) {
561
+ Mage::log(' BV - Skipping Order. Not completely shipped. ' . $this->orderToString($order));
562
+ return false;
563
+ }
564
+
565
+ // Are we outside of the delay period
566
+ $latestItemShipDateTimestamp = $this->getLatestShipmentDate($order);
567
+ if ($latestItemShipDateTimestamp > $thresholdTimestamp) {
568
+ // Latest ship date for the fully shipped order is still within the delay period
569
+ Mage::log(' BV - Skipping Order. Ship date not outside the threshold of ' . date('c', $thresholdTimestamp) . '. ' .
570
+ $this->orderToString($order));
571
+ return false;
572
+ }
573
+ }
574
+ else {
575
+ if ($triggeringEvent === self::TRIGGER_EVENT_PURCHASE) {
576
+ // We need to see if the order placement timestamp of this order is outside of the delay period
577
+ $orderPlacementTimestamp = strtotime($order->getCreatedAtDate());
578
+ if ($orderPlacementTimestamp > $thresholdTimestamp) {
579
+ // Order placement date is still within the delay period
580
+ Mage::log(' BV - Skipping Order. Order date not outside the threshold of ' . date('c', $thresholdTimestamp) .
581
+ '. ' .
582
+ $this->orderToString($order));
583
+ return false;
584
+ }
585
+ }
586
+ }
587
+
588
+
589
+ // Finally, ensure we have everything on this order that would be needed.
590
+
591
+ // Do we have what basically looks like a legit email address?
592
+ if (!preg_match('/@/', $order->getCustomerEmail())) {
593
+ Mage::log(' BV - Skipping Order. No valid email address. ' . $this->orderToString($order));
594
+ return false;
595
+ }
596
+
597
+ // Does the order have any items?
598
+ if (count($order->getAllItems()) < 1) {
599
+ Mage::log(' BV - Skipping Order. No items in this order. ' . $this->orderToString($order));
600
+ return false;
601
+ }
602
+
603
+
604
+ if (self::DEBUG_OUTPUT) {
605
+ Mage::log(' BV - Including Order. ' . $this->orderToString($order));
606
+ }
607
+ return true;
608
+ }
609
+
610
+ private function hasOrderCompletelyShipped(Mage_Sales_Model_Order $order)
611
+ {
612
+ $hasOrderCompletelyShipped = true;
613
+ $items = $order->getAllItems();
614
+ /* @var $item Mage_Sales_Model_Order_Item */
615
+ foreach ($items as $item) {
616
+ // See /var/www/magento/app/code/core/Mage/Sales/Model/Order/Item.php
617
+ if ($item->getQtyToShip() > 0 && !$item->getIsVirtual() && !$item->getLockedDoShip()) {
618
+ // This item still has qty that needs to ship
619
+ $hasOrderCompletelyShipped = false;
620
+ }
621
+ }
622
+ return $hasOrderCompletelyShipped;
623
+ }
624
+
625
+ private function getLatestShipmentDate(Mage_Sales_Model_Order $order)
626
+ {
627
+ $latestShipmentTimestamp = 0;
628
+
629
+ $shipments = $order->getShipmentsCollection();
630
+ /* @var $shipment Mage_Sales_Model_Order_Shipment */
631
+ foreach ($shipments as $shipment) {
632
+ $latestShipmentTimestamp = max(strtotime($shipment->getCreatedAtDate()), $latestShipmentTimestamp);
633
+ }
634
+
635
+ return $latestShipmentTimestamp; // This should be an int timestamp of num seconds since epoch
636
+ }
637
+
638
+ }
639
+
app/code/local/Bazaarvoice/Connector/Model/Mysql4/Setup.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This class can be used to customize any behavior during the MySQL installation or upgrade scripts between versions
4
+ * of the Bazaarvoice Magento extension.
5
+ */
6
+ class Bazaarvoice_Connector_Model_Mysql4_Setup extends Mage_Sales_Model_Mysql4_Setup
7
+ {
8
+
9
+
10
+ }
11
+
app/code/local/Bazaarvoice/Connector/Model/ProductFeed/Brand.php ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @author Bazaarvoice, Inc.
4
+ */
5
+ class Bazaarvoice_Connector_Model_ProductFeed_Brand extends Mage_Core_Model_Abstract
6
+ {
7
+
8
+ /**
9
+ * @param Varien_Io_File $ioObject File object for feed file
10
+ * @param Mage_Core_Model_Website $website
11
+ */
12
+ public function processBrandsForWebsite(Varien_Io_File $ioObject, Mage_Core_Model_Website $website)
13
+ {
14
+ // Get default store for website
15
+ $defaultStore = $website->getDefaultGroup()->getDefaultStore();
16
+ // Lookup default locale code
17
+ $defaultLocaleCode = Mage::getStoreConfig('bazaarvoice/general/locale', $defaultStore->getId());
18
+ // Check localeCode
19
+ if (!strlen($defaultLocaleCode)) {
20
+ Mage::throwException('Invalid locale code configured for store: ' . $defaultStore->getCode());
21
+ }
22
+
23
+ // Lookup the configured attribute code for "Brand"
24
+ $attributeCode = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_brand_attribute_code', $defaultStore->getId());
25
+ // If there is no attribute code for default store, then bail
26
+ if(!strlen(trim($attributeCode))) {
27
+ return;
28
+ }
29
+
30
+ // Look up options for each store
31
+ $optionsByLocale = array();
32
+ /** @var Mage_Core_Model_Store $store */
33
+ foreach ($website->getStores() as $store) {
34
+ // Get store locale
35
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
36
+ // Check localeCode
37
+ if (!strlen($localeCode)) {
38
+ Mage::throwException('Invalid locale code configured for store: ' . $store->getCode());
39
+ }
40
+ // Save options mapped to localeCode
41
+ $optionsByLocale[$localeCode] = $this->getOptionsForStore($store);
42
+ }
43
+
44
+ // Now process brands in multi-store fashion
45
+ $this->processBrandsMultiStore($ioObject, $defaultLocaleCode, $optionsByLocale);
46
+ }
47
+
48
+ /**
49
+ * @param Varien_Io_File $ioObject File object for feed file
50
+ * @param Mage_Core_Model_Store_Group $group
51
+ */
52
+ public function processBrandsForGroup(Varien_Io_File $ioObject, Mage_Core_Model_Store_Group $group)
53
+ {
54
+ // Get default store for group
55
+ $defaultStore = $group->getDefaultStore();
56
+ // Lookup default locale code
57
+ $defaultLocaleCode = Mage::getStoreConfig('bazaarvoice/general/locale', $defaultStore->getId());
58
+ // Check localeCode
59
+ if (!strlen($defaultLocaleCode)) {
60
+ Mage::throwException('Invalid locale code configured for store: ' . $defaultStore->getCode());
61
+ }
62
+
63
+ // Lookup the configured attribute code for "Brand"
64
+ $attributeCode = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_brand_attribute_code', $defaultStore->getId());
65
+ // If there is no attribute code for default store, then bail
66
+ if(!strlen(trim($attributeCode))) {
67
+ return;
68
+ }
69
+
70
+ // Look up options for each store
71
+ $optionsByLocale = array();
72
+ /** @var Mage_Core_Model_Store $store */
73
+ foreach ($group->getStores() as $store) {
74
+ // Get store locale
75
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
76
+ // Check localeCode
77
+ if (!strlen($localeCode)) {
78
+ Mage::throwException('Invalid locale code configured for store: ' . $store->getCode());
79
+ }
80
+ // Save options mapped to localeCode
81
+ $optionsByLocale[$localeCode] = $this->getOptionsForStore($store);
82
+ }
83
+
84
+ // Now process brands in multi-store fashion
85
+ $this->processBrandsMultiStore($ioObject, $defaultLocaleCode, $optionsByLocale);
86
+ }
87
+
88
+ /**
89
+ * @param Varien_Io_File $ioObject File object for feed file
90
+ * @param Mage_Core_Model_Store $store
91
+ */
92
+ public function processBrandsForStore(Varien_Io_File $ioObject, Mage_Core_Model_Store $store)
93
+ {
94
+ // Lookup the configured attribute code for "Brand"
95
+ $attributeCode = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_brand_attribute_code', $store->getId());
96
+ // If there is no attribute code for store, then bail
97
+ if(!strlen(trim($attributeCode))) {
98
+ return;
99
+ }
100
+
101
+ // Lookup up brand names
102
+ $attributeOptions = $this->getOptionsForStore($store);
103
+ // Output tag only if more than 1 brand
104
+ if (count($attributeOptions) > 0) {
105
+ $ioObject->streamWrite("<Brands>\n");
106
+ }
107
+ // Iterate brands and write each to file
108
+ foreach($attributeOptions as $optionId => $optionValue) {
109
+ $this->writeBrand($ioObject, $optionId, $optionValue);
110
+ }
111
+ if (count($attributeOptions) > 0) {
112
+ $ioObject->streamWrite("</Brands>\n");
113
+ }
114
+ }
115
+
116
+ protected function processBrandsMultiStore($ioObject, $defaultLocaleCode, $optionsByLocale)
117
+ {
118
+ // Recombine multi-dimensional array so its grouped by option id
119
+ $allOptions = array();
120
+ foreach($optionsByLocale as $localeCode => $options) {
121
+ // Check if this store is default
122
+ if($localeCode == $defaultLocaleCode) {
123
+ $defaultOptions = $options;
124
+ }
125
+ // Add this store's options to main array
126
+ foreach($options as $optionId => $optionValue) {
127
+ if(!isset($allOptions[$optionId])) {
128
+ $allOptions[$optionId] = array();
129
+ }
130
+ $allOptions[$optionId][$localeCode] = $optionValue;
131
+ }
132
+ }
133
+ // Output tag only if more than 1 brand
134
+ if (count($allOptions) > 0) {
135
+ $ioObject->streamWrite("<Brands>\n");
136
+ }
137
+ // Now iterate through all brands and write out
138
+ foreach($allOptions as $optionId => $localeValues) {
139
+ $this->writeBrandMultiLocale($ioObject, $optionId, $defaultOptions[$optionId], $localeValues);
140
+ }
141
+ if (count($allOptions) > 0) {
142
+ $ioObject->streamWrite("</Brands>\n");
143
+ }
144
+ }
145
+
146
+ protected function getOptionsForStore($store)
147
+ {
148
+ // Lookup the configured attribute code for "Brand"
149
+ $attributeCode = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_brand_attribute_code', $store->getId());
150
+ // Lookup the attribute options for this store
151
+ /** @var Mage_Catalog_Model_Resource_Eav_Attribute $attribute */
152
+ $attribute = Mage::getModel('catalog/resource_eav_attribute')->load($attributeCode, 'attribute_code');
153
+ $attribute->setStoreId($store->getId());
154
+ $attributeOptions = $attribute->getSource()->getAllOptions(false);
155
+ // Reformat array
156
+ $processedOptions = array();
157
+ foreach ($attributeOptions as $attributeOption) {
158
+ $processedOptions[$attributeOption['value']] = $attributeOption['label'];
159
+ }
160
+
161
+ return $processedOptions;
162
+ }
163
+
164
+ protected function writeBrand(Varien_Io_File $ioObject, $brandExternalId, $brandName)
165
+ {
166
+ $ioObject->streamWrite(
167
+ "<Brand>\n" .
168
+ " <ExternalId>" . $brandExternalId . "</ExternalId>\n" .
169
+ " <Name><![CDATA[" . $brandName . "]]></Name>\n" .
170
+ "</Brand>\n"
171
+ );
172
+ }
173
+
174
+ protected function writeBrandMultiLocale(Varien_Io_File $ioObject, $brandExternalId, $defaultBrandName, $brandNames)
175
+ {
176
+ $ioObject->streamWrite(
177
+ "<Brand>\n" .
178
+ " <ExternalId>" . $brandExternalId . "</ExternalId>\n" .
179
+ " <Name><![CDATA[" . $defaultBrandName . "]]></Name>\n" );
180
+ // Write out localized <Names>
181
+ $ioObject->streamWrite(" <Names>\n");
182
+ foreach ($brandNames as $curLocale => $curBrandName) {
183
+ $ioObject->streamWrite(' <Name locale="' . $curLocale . '"><![CDATA[' . $curBrandName . "]]></Name>\n");
184
+ }
185
+ $ioObject->streamWrite(
186
+ " </Names>\n" .
187
+ "</Brand>\n"
188
+ );
189
+ }
190
+
191
+ }
app/code/local/Bazaarvoice/Connector/Model/ProductFeed/Category.php ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @author Bazaarvoice, Inc.
4
+ */
5
+ class Bazaarvoice_Connector_Model_ProductFeed_Category extends Mage_Core_Model_Abstract
6
+ {
7
+ private $_categoryIdList = array();
8
+
9
+ public function setCategoryIdList(array $list)
10
+ {
11
+ $this->_categoryIdList = $list;
12
+ }
13
+
14
+ public function getCategoryIdList()
15
+ {
16
+ return $this->_categoryIdList;
17
+ }
18
+
19
+
20
+ /**
21
+ * @param Varien_Io_File $ioObject File object for feed file
22
+ * @param Mage_Core_Model_Website $website
23
+ */
24
+ public function processCategoriesForWebsite(Varien_Io_File $ioObject, Mage_Core_Model_Website $website)
25
+ {
26
+ // Lookup category path for root category for default group in this website
27
+ // NOTE: This means we are only sending the category tree from the default group if there are multiple groups
28
+ // with different category trees... In that case, admin must configure feed to be generated at group level
29
+ $rootCategoryId = $website->getDefaultGroup()->getRootCategoryId();
30
+ /* @var $rootCategory Mage_Catalog_Model_Category */
31
+ $rootCategory = Mage::getModel('catalog/category')->load($rootCategoryId);
32
+ $rootCategoryPath = $rootCategory->getData('path');
33
+ // Get category collection
34
+ $categoryIds = Mage::getModel('catalog/category')->getCollection();
35
+ // Filter category collection based on Magento store
36
+ // Do this by filtering on 'path' attribute, based on root category path found above
37
+ // Include the root category itself in the feed
38
+ $categoryIds
39
+ ->addAttributeToFilter('level', array('gt' => 1))
40
+ ->addAttributeToFilter('is_active', 1)
41
+ ->addAttributeToFilter('path', array('like' => $rootCategoryPath . '%'));
42
+ // Check count of categories
43
+ if (count($categoryIds) > 0) {
44
+ $ioObject->streamWrite("<Categories>\n");
45
+ }
46
+ /* @var $categoryId Mage_Catalog_Model_Category */
47
+ foreach ($categoryIds as $categoryId) {
48
+ // Load version of cat for all store views
49
+ $categoriesByLocale = array();
50
+ $categoryDefault = null;
51
+ /* @var $store Mage_Core_Model_Store */
52
+ foreach ($website->getStores() as $store) {
53
+ /* @var $category Mage_Catalog_Model_Category */
54
+ // Get new category model
55
+ $category = Mage::getModel('catalog/category');
56
+ // Set store id before load, to get attribs for this particular store / view
57
+ $category->setStoreId($store->getId());
58
+ // Load category object
59
+ $category->load($categoryId->getId());
60
+ // Capture localized URL in extra var
61
+ $category->setData('localized_url', $this->getCategoryUrl($category));
62
+ // Set default category
63
+ if ($website->getDefaultGroup()->getDefaultStoreId() == $store->getId()) {
64
+ $categoryDefault = $category;
65
+ }
66
+ // Get store locale
67
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
68
+ // Check localeCode
69
+ if (!strlen($localeCode)) {
70
+ Mage::throwException('Invalid locale code (' . $localeCode . ') configured for store: ' .
71
+ $store->getCode());
72
+ }
73
+ // Add product to array
74
+ $categoriesByLocale[$localeCode] = $category;
75
+ }
76
+
77
+ $this->writeCategory($ioObject, $categoryDefault, $categoriesByLocale);
78
+
79
+ }
80
+
81
+ if (count($categoryIds) > 0) {
82
+ $ioObject->streamWrite("</Categories>\n");
83
+ }
84
+ }
85
+
86
+ /**
87
+ * @param Varien_Io_File $ioObject File object for feed file
88
+ * @param Mage_Core_Model_Store_Group $group
89
+ */
90
+ public function processCategoriesForGroup(Varien_Io_File $ioObject, Mage_Core_Model_Store_Group $group)
91
+ {
92
+ // Lookup category path for root category
93
+ $rootCategoryId = $group->getRootCategoryId();
94
+ /* @var $rootCategory Mage_Catalog_Model_Category */
95
+ $rootCategory = Mage::getModel('catalog/category')->load($rootCategoryId);
96
+ $rootCategoryPath = $rootCategory->getData('path');
97
+ // Get category collection
98
+ $categoryIds = Mage::getModel('catalog/category')->getCollection();
99
+ // Filter category collection based on Magento store
100
+ // Do this by filtering on 'path' attribute, based on root category path found above
101
+ // Include the root category itself in the feed
102
+ $categoryIds
103
+ ->addAttributeToFilter('level', array('gt' => 1))
104
+ ->addAttributeToFilter('is_active', 1)
105
+ ->addAttributeToFilter('path', array('like' => $rootCategoryPath . '%'));
106
+ // Check count of categories
107
+ if (count($categoryIds) > 0) {
108
+ $ioObject->streamWrite("<Categories>\n");
109
+ }
110
+ /* @var $categoryId Mage_Catalog_Model_Category */
111
+ foreach ($categoryIds as $categoryId) {
112
+ // Load version of cat for all store views
113
+ $categoriesByLocale = array();
114
+ $categoryDefault = null;
115
+ /* @var $store Mage_Core_Model_Store */
116
+ foreach ($group->getStores() as $store) {
117
+ /* @var $category Mage_Catalog_Model_Category */
118
+ // Get new category model
119
+ $category = Mage::getModel('catalog/category');
120
+ // Set store id before load, to get attribs for this particular store / view
121
+ $category->setStoreId($store->getId());
122
+ // Load category object
123
+ $category->load($categoryId->getId());
124
+ // Capture localized URL in extra var
125
+ $category->setData('localized_url', $this->getCategoryUrl($category));
126
+ // Set default category
127
+ if ($group->getDefaultStoreId() == $store->getId()) {
128
+ $categoryDefault = $category;
129
+ }
130
+ // Get store locale
131
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
132
+ // Check localeCode
133
+ if (!strlen($localeCode)) {
134
+ Mage::throwException('Invalid locale code (' . $localeCode . ') configured for store: ' .
135
+ $store->getCode());
136
+ }
137
+ // Add product to array
138
+ $categoriesByLocale[$localeCode] = $category;
139
+ }
140
+
141
+ $this->writeCategory($ioObject, $categoryDefault, $categoriesByLocale);
142
+
143
+ }
144
+
145
+ if (count($categoryIds) > 0) {
146
+ $ioObject->streamWrite("</Categories>\n");
147
+ }
148
+ }
149
+
150
+ /**
151
+ * @param Varien_Io_File $ioObject File object for feed file
152
+ * @param Mage_Core_Model_Store $store
153
+ */
154
+ public function processCategoriesForStore(Varien_Io_File $ioObject, Mage_Core_Model_Store $store)
155
+ {
156
+ // Lookup category path for root category
157
+ $rootCategoryId = $store->getRootCategoryId();
158
+ /* @var $rootCategory Mage_Catalog_Model_Category */
159
+ $rootCategory = Mage::getModel('catalog/category')->load($rootCategoryId);
160
+ $rootCategoryPath = $rootCategory->getData('path');
161
+ // Get category collection
162
+ $categoryIds = Mage::getModel('catalog/category')->getCollection();
163
+ // Filter category collection based on Magento store
164
+ // Do this by filtering on 'path' attribute, based on root category path found above
165
+ // Include the root category itself in the feed
166
+ $categoryIds
167
+ ->addAttributeToFilter('level', array('gt' => 1))
168
+ ->addAttributeToFilter('is_active', 1)
169
+ ->addAttributeToFilter('path', array('like' => $rootCategoryPath . '%'));
170
+ // Check count of categories
171
+ if (count($categoryIds) > 0) {
172
+ $ioObject->streamWrite("<Categories>\n");
173
+ }
174
+ /* @var $categoryId Mage_Catalog_Model_Category */
175
+ foreach ($categoryIds as $categoryId) {
176
+ // Load version of cat for all store views
177
+ $categoriesByLocale = array();
178
+ // Setup parameters for writeCategory, using just this $store
179
+ /* @var $categoryDefault Mage_Catalog_Model_Category */
180
+ // Get new category model
181
+ $categoryDefault = Mage::getModel('catalog/category');
182
+ // Set store id before load, to get attributes for this particular store / view
183
+ $categoryDefault->setStoreId($store->getId());
184
+ // Load category object
185
+ $categoryDefault->load($categoryId->getId());
186
+ // Capture localized URL in extra var
187
+ $categoryDefault->setData('localized_url', $this->getCategoryUrl($categoryDefault));
188
+ // Get store locale
189
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
190
+ // Build array of category by locale
191
+ $categoriesByLocale[$localeCode] = $categoryDefault;
192
+ // Write category to file
193
+ $this->writeCategory($ioObject, $categoryDefault, $categoriesByLocale);
194
+ }
195
+
196
+ if (count($categoryIds) > 0) {
197
+ $ioObject->streamWrite("</Categories>\n");
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @param Varien_Io_File $ioObject File object for feed file
203
+ * @param Mage_Catalog_Model_Category $categoryDefault
204
+ * @param array $categoriesByLocale
205
+ */
206
+ protected function writeCategory(Varien_Io_File $ioObject, Mage_Catalog_Model_Category $categoryDefault, array $categoriesByLocale)
207
+ {
208
+ // Get ref to BV helper
209
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
210
+ $bvHelper = Mage::helper('bazaarvoice');
211
+
212
+ // Get external id
213
+ $categoryExternalId = $bvHelper->getCategoryId($categoryDefault, $categoryDefault->getStoreId());
214
+
215
+ $categoryName = htmlspecialchars($categoryDefault->getName(), ENT_QUOTES, 'UTF-8');
216
+ $categoryPageUrl = htmlspecialchars($categoryDefault->getData('localized_url'), ENT_QUOTES, 'UTF-8');
217
+
218
+ $parentExtId = '';
219
+ /* @var $parentCategory Mage_Catalog_Model_Category */
220
+ $parentCategory = Mage::getModel('catalog/category')->load($categoryDefault->getParentId());
221
+ // If parent category is the root category, then ignore it
222
+ if (!is_null($parentCategory) && $parentCategory->getLevel() != 1) {
223
+ $parentExtId = ' <ParentExternalId>' .
224
+ $bvHelper->getCategoryId($parentCategory, $categoryDefault->getStoreId()) . "</ParentExternalId>\n";
225
+ }
226
+
227
+ array_push($this->_categoryIdList, $categoryExternalId);
228
+
229
+ $ioObject->streamWrite("<Category>\n" .
230
+ " <ExternalId>" . $categoryExternalId . "</ExternalId>\n" .
231
+ $parentExtId .
232
+ " <Name><![CDATA[" . $categoryName . "]]></Name>\n" .
233
+ " <CategoryPageUrl><![CDATA[" . $categoryPageUrl . "]]></CategoryPageUrl>\n");
234
+
235
+ // Write out localized <Names>
236
+ $ioObject->streamWrite(" <Names>\n");
237
+ /* @var $curCategory Mage_Catalog_Model_Category */
238
+ foreach ($categoriesByLocale as $curLocale => $curCategory) {
239
+ $ioObject->streamWrite(' <Name locale="' . $curLocale . '"><![CDATA[' .
240
+ htmlspecialchars($curCategory->getName(), ENT_QUOTES, 'UTF-8') . "]]></Name>\n");
241
+ }
242
+ $ioObject->streamWrite(" </Names>\n");
243
+ // Write out localized <CategoryPageUrls>
244
+ $ioObject->streamWrite(" <CategoryPageUrls>\n");
245
+ /* @var $curCategory Mage_Catalog_Model_Category */
246
+ foreach ($categoriesByLocale as $curLocale => $curCategory) {
247
+ $ioObject->streamWrite(' <CategoryPageUrl locale="' . $curLocale . '">' . "<![CDATA[" .
248
+ htmlspecialchars($curCategory->getData('localized_url'), ENT_QUOTES, 'UTF-8') . "]]>" . "</CategoryPageUrl>\n");
249
+ }
250
+ $ioObject->streamWrite(" </CategoryPageUrls>\n");
251
+
252
+ $ioObject->streamWrite("</Category>\n");
253
+
254
+ }
255
+
256
+ /**
257
+ * Method to retrieve Magento category URL, modeled after Mage_Catalog_Model_Category::getUrl in EE 1.12
258
+ *
259
+ * @param Mage_Catalog_Model_Category $category
260
+ * @return string
261
+ */
262
+ protected function getCategoryUrl(Mage_Catalog_Model_Category $category)
263
+ {
264
+ /** @var Mage_Core_Model_Url $urlInstance */
265
+ $urlInstance = Mage::getModel('core/url');
266
+ $urlInstance->setStore($category->getStoreId());
267
+ $url = $category->_getData('url');
268
+ if (is_null($url)) {
269
+ Varien_Profiler::start('REWRITE: '.__METHOD__);
270
+
271
+ if ($category->hasData('request_path') && $category->getRequestPath() != '') {
272
+ $category->setData('url', $urlInstance->getDirectUrl($category->getRequestPath()));
273
+ Varien_Profiler::stop('REWRITE: '.__METHOD__);
274
+ return $category->getData('url');
275
+ }
276
+
277
+ Varien_Profiler::stop('REWRITE: '.__METHOD__);
278
+
279
+ $rewrite = $category->getUrlRewrite();
280
+ if ($category->getStoreId()) {
281
+ $rewrite->setStoreId($category->getStoreId());
282
+ }
283
+ $idPath = 'category/' . $category->getId();
284
+ $rewrite->loadByIdPath($idPath);
285
+
286
+ if ($rewrite->getId()) {
287
+ Mage::log('request path: ' . $rewrite->getRequestPath());
288
+ $category->setData('url', $urlInstance->getDirectUrl($rewrite->getRequestPath()));
289
+ Varien_Profiler::stop('REWRITE: '.__METHOD__);
290
+ return $category->getData('url');
291
+ }
292
+
293
+ Varien_Profiler::stop('REWRITE: '.__METHOD__);
294
+
295
+ $category->setData('url', $category->getCategoryIdUrl());
296
+ return $category->getData('url');
297
+ }
298
+ return $url;
299
+ }
300
+
301
+ }
app/code/local/Bazaarvoice/Connector/Model/ProductFeed/Product.php ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @author Bazaarvoice, Inc.
4
+ */
5
+ class Bazaarvoice_Connector_Model_ProductFeed_Product extends Mage_Core_Model_Abstract
6
+ {
7
+ private $_categoryIdList = array();
8
+
9
+ public function setCategoryIdList(array $list)
10
+ {
11
+ $this->_categoryIdList = $list;
12
+ }
13
+
14
+ public function getCategoryIdList()
15
+ {
16
+ return $this->_categoryIdList;
17
+ }
18
+
19
+ /**
20
+ *
21
+ * @param Varien_Io_File $ioObject File object for feed file
22
+ * @param Mage_Core_Model_Website $website
23
+ */
24
+ public function processProductsForWebsite(Varien_Io_File $ioObject, Mage_Core_Model_Website $website)
25
+ {
26
+ // *FROM MEMORY* this should get all the products
27
+ $productIds = Mage::getModel('catalog/product')->getCollection();
28
+ // Filter collection for the specific website
29
+ $productIds->addWebsiteFilter($website->getId());
30
+ // Filter collection for product status
31
+ $productIds->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
32
+ // Filter collection for product visibility
33
+ $productIds->addAttributeToFilter('visibility', array('neq' => Mage_Catalog_Model_Product_Visibility::VISIBILITY_NOT_VISIBLE));
34
+
35
+ // Output tag only if more than 1 product
36
+ if (count($productIds) > 0) {
37
+ $ioObject->streamWrite("<Products>\n");
38
+ }
39
+ /* @var $productId Mage_Catalog_Model_Product */
40
+ foreach ($productIds as $productId) {
41
+ // Load version of product for all store views
42
+ $productsByLocale = array();
43
+ /* @var $productDefault Mage_Catalog_Model_Product */
44
+ $productDefault = null;
45
+ /* @var $store Mage_Core_Model_Store */
46
+ foreach ($website->getStores() as $store) {
47
+ /* @var $product Mage_Catalog_Model_Product */
48
+ // Get new product model
49
+ $product = Mage::getModel('catalog/product');
50
+ // Set store id before load, to get attribs for this particular store / view
51
+ $product->setStoreId($store->getId());
52
+ // Load product object
53
+ $product->load($productId->getId());
54
+ // Set localized product and image url
55
+ $product->setData('localized_image_url', $this->getProductImageUrl($product));
56
+ // Set bazaarvoice specific attributes
57
+ // Brand
58
+ $brandAttributeCode = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_brand_attribute_code', $store->getId());
59
+ if (strlen(trim($brandAttributeCode))) {
60
+ $brand = $product->getData($brandAttributeCode);
61
+ $product->setData('brand', $brand);
62
+ }
63
+ // Set default product
64
+ if ($website->getDefaultGroup()->getDefaultStoreId() == $store->getId()) {
65
+ $productDefault = $product;
66
+ }
67
+ // Get store locale
68
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
69
+ // Check localeCode
70
+ if (!strlen($localeCode)) {
71
+ Mage::throwException('Invalid locale code (' . $localeCode . ') configured for store: ' .
72
+ $store->getCode());
73
+ }
74
+ // Add product to array
75
+ $productsByLocale[$localeCode] = $product;
76
+ }
77
+
78
+ // Write out individual product
79
+ $this->writeProduct($ioObject, $productDefault, $productsByLocale);
80
+
81
+ }
82
+ if (count($productIds) > 0) {
83
+ $ioObject->streamWrite("</Products>\n");
84
+ }
85
+ }
86
+
87
+ /**
88
+ *
89
+ * @param Varien_Io_File $ioObject File object for feed file
90
+ * @param Mage_Core_Model_Store_Group $group Store Group
91
+ */
92
+ public function processProductsForGroup(Varien_Io_File $ioObject, Mage_Core_Model_Store_Group $group)
93
+ {
94
+ // *FROM MEMORY* this should get all the products
95
+ $productIds = Mage::getModel('catalog/product')->getCollection();
96
+ // Filter collection for the specific website
97
+ $productIds->addWebsiteFilter($group->getWebsiteId());
98
+ // Filter collection for product status
99
+ $productIds->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
100
+ // Filter collection for product visibility
101
+ $productIds->addAttributeToFilter('visibility',
102
+ array('neq' => Mage_Catalog_Model_Product_Visibility::VISIBILITY_NOT_VISIBLE));
103
+
104
+ // Output tag only if more than 1 product
105
+ if (count($productIds) > 0) {
106
+ $ioObject->streamWrite("<Products>\n");
107
+ }
108
+ /* @var $productId Mage_Catalog_Model_Product */
109
+ foreach ($productIds as $productId) {
110
+ // Load version of product for all store views
111
+ $productsByLocale = array();
112
+ /* @var $productDefault Mage_Catalog_Model_Product */
113
+ $productDefault = null;
114
+ /* @var $store Mage_Core_Model_Store */
115
+ foreach ($group->getStores() as $store) {
116
+ /* @var $product Mage_Catalog_Model_Product */
117
+ // Get new product model
118
+ $product = Mage::getModel('catalog/product');
119
+ // Set store id before load, to get attributes for this particular store / view
120
+ $product->setStoreId($store->getId());
121
+ // Load product object
122
+ $product->load($productId->getId());
123
+ // Set localized product and image url
124
+ $product->setData('localized_image_url', $this->getProductImageUrl($product));
125
+ // Set bazaarvoice specific attributes
126
+ $brandAttributeCode = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_brand_attribute_code', $store->getId());
127
+ if (strlen(trim($brandAttributeCode))) {
128
+ $brand = $product->getData($brandAttributeCode);
129
+ $product->setData('brand', $brand);
130
+ }
131
+ // Set default product
132
+ if ($group->getDefaultStoreId() == $store->getId()) {
133
+ $productDefault = $product;
134
+ }
135
+ // Get store locale
136
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
137
+ // Check localeCode
138
+ if (!strlen($localeCode)) {
139
+ Mage::throwException('Invalid locale code (' . $localeCode . ') configured for store: ' .
140
+ $store->getCode());
141
+ }
142
+ // Add product to array
143
+ $productsByLocale[$localeCode] = $product;
144
+ }
145
+
146
+ // Write out individual product
147
+ $this->writeProduct($ioObject, $productDefault, $productsByLocale);
148
+
149
+ }
150
+ if (count($productIds) > 0) {
151
+ $ioObject->streamWrite("</Products>\n");
152
+ }
153
+ }
154
+
155
+ /**
156
+ * @param Varien_Io_File $ioObject File object for feed file
157
+ * @param Mage_Core_Model_Store $store
158
+ */
159
+ public function processProductsForStore(Varien_Io_File $ioObject, Mage_Core_Model_Store $store)
160
+ {
161
+ // *FROM MEMORY* this should get all the products
162
+ $productIds = Mage::getModel('catalog/product')->getCollection();
163
+ // Filter collection for the specific website
164
+ $productIds->addWebsiteFilter($store->getWebsiteId());
165
+ // Filter collection for product status
166
+ $productIds->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
167
+ // Filter collection for product visibility
168
+ $productIds->addAttributeToFilter('visibility',
169
+ array('neq' => Mage_Catalog_Model_Product_Visibility::VISIBILITY_NOT_VISIBLE));
170
+
171
+ // Output tag only if more than 1 product
172
+ if (count($productIds) > 0) {
173
+ $ioObject->streamWrite("<Products>\n");
174
+ }
175
+ /* @var $productId Mage_Catalog_Model_Product */
176
+ foreach ($productIds as $productId) {
177
+ // Load version of product for all store views
178
+ $productsByLocale = array();
179
+ /* @var $productDefault Mage_Catalog_Model_Product */
180
+ $productDefault = Mage::getModel('catalog/product');
181
+ // Set store id before load, to get attributes for this particular store / view
182
+ $productDefault->setStoreId($store->getId());
183
+ // Load product object
184
+ $productDefault->load($productId->getId());
185
+ // Set localized product and image url
186
+ $productDefault->setData('localized_image_url', $this->getProductImageUrl($productDefault));
187
+ // Set bazaarvoice specific attributes
188
+ $brandAttributeCode = Mage::getStoreConfig('bazaarvoice/bv_config/product_feed_brand_attribute_code', $store->getId());
189
+ if (strlen(trim($brandAttributeCode))) {
190
+ $brand = $productDefault->getData($brandAttributeCode);
191
+ $productDefault->setData('brand', $brand);
192
+ }
193
+ // Get store locale
194
+ $localeCode = Mage::getStoreConfig('bazaarvoice/general/locale', $store->getId());
195
+ // Check localeCode
196
+ if (!strlen($localeCode)) {
197
+ Mage::throwException('Invalid locale code (' . $localeCode . ') configured for store: ' .
198
+ $store->getCode());
199
+ }
200
+ // Add product to array
201
+ $productsByLocale[$localeCode] = $productDefault;
202
+ // Write out individual product
203
+ $this->writeProduct($ioObject, $productDefault, $productsByLocale);
204
+ }
205
+ if (count($productIds) > 0) {
206
+ $ioObject->streamWrite("</Products>\n");
207
+ }
208
+ }
209
+
210
+ /**
211
+ * @param Varien_Io_File $ioObject File object for feed file
212
+ * @param Mage_Catalog_Model_Product $productDefault
213
+ * @param array $productsByLocale
214
+ */
215
+ protected function writeProduct(Varien_Io_File $ioObject, Mage_Catalog_Model_Product $productDefault,
216
+ array $productsByLocale)
217
+ {
218
+ // Get ref to BV helper
219
+ /* @var $bvHelper Bazaarvoice_Connector_Helper_Data */
220
+ $bvHelper = Mage::helper('bazaarvoice');
221
+
222
+ // Generate product external ID from SKU, this is the same for all groups / stores / views
223
+ $productExternalId = $bvHelper->getProductId($productDefault);
224
+
225
+ $ioObject->streamWrite("<Product>\n" .
226
+ ' <ExternalId>' . $productExternalId . "</ExternalId>\n" .
227
+ ' <Name><![CDATA[' . htmlspecialchars($productDefault->getName(), ENT_QUOTES, 'UTF-8') . "]]></Name>\n" .
228
+ ' <Description><![CDATA[' . htmlspecialchars($productDefault->getData('short_description'), ENT_QUOTES, 'UTF-8') .
229
+ "]]></Description>\n");
230
+
231
+ $brandId = $productDefault->getData('brand');
232
+ if ($productDefault->hasData('brand') && !is_null($brandId) && !empty($brandId)) {
233
+ $ioObject->streamWrite(' <Brand><ExternalId>' . $brandId . "</ExternalId></Brand>\n");
234
+ }
235
+
236
+ /* Make sure that CategoryExternalId is one written to Category section */
237
+ $parentCategories = $productDefault->getCategoryIds();
238
+ if (!is_null($parentCategories) && count($parentCategories) > 0) {
239
+ foreach ($parentCategories as $parentCategoryId) {
240
+ $parentCategory = Mage::getModel('catalog/category')->load($parentCategoryId);
241
+ if ($parentCategory != null) {
242
+ $categoryExternalId = $bvHelper->getCategoryId($parentCategory, $productDefault->getStoreId());
243
+ if (in_array($categoryExternalId, $this->_categoryIdList)) {
244
+ $ioObject->streamWrite(' <CategoryExternalId>' . $categoryExternalId .
245
+ "</CategoryExternalId>\n");
246
+ break;
247
+ }
248
+ }
249
+ }
250
+ }
251
+
252
+ $ioObject->streamWrite(' <ProductPageUrl>' . "<![CDATA[" . $this->getProductUrl($productDefault) . "]]>" . "</ProductPageUrl>\n");
253
+ $imageUrl = $productDefault->getData('localized_image_url');
254
+ if (strlen($imageUrl)) {
255
+ $ioObject->streamWrite(' <ImageUrl>' . "<![CDATA[" . $imageUrl . "]]>" . "</ImageUrl>\n");
256
+ }
257
+
258
+ // Write out localized <Names>
259
+ $ioObject->streamWrite(" <Names>\n");
260
+ foreach ($productsByLocale as $curLocale => $curProduct) {
261
+ $ioObject->streamWrite(' <Name locale="' . $curLocale . '"><![CDATA[' .
262
+ htmlspecialchars($productDefault->getData('name'), ENT_QUOTES, 'UTF-8') . "]]></Name>\n");
263
+ }
264
+ $ioObject->streamWrite(" </Names>\n");
265
+ // Write out localized <Descriptions>
266
+ $ioObject->streamWrite(" <Descriptions>\n");
267
+ foreach ($productsByLocale as $curLocale => $curProduct) {
268
+ $ioObject->streamWrite(' <Description locale="' . $curLocale . '"><![CDATA[' .
269
+ htmlspecialchars($productDefault->getData('short_description'), ENT_QUOTES, 'UTF-8') . "]]></Description>\n");
270
+ }
271
+ $ioObject->streamWrite(" </Descriptions>\n");
272
+ // Write out localized <ProductPageUrls>
273
+ $ioObject->streamWrite(" <ProductPageUrls>\n");
274
+ foreach ($productsByLocale as $curLocale => $curProduct) {
275
+ $ioObject->streamWrite(' <ProductPageUrl locale="' . $curLocale . '">' . "<![CDATA[" .
276
+ $this->getProductUrl($curProduct) . "]]>" . "</ProductPageUrl>\n");
277
+ }
278
+ $ioObject->streamWrite(" </ProductPageUrls>\n");
279
+ // Write out localized <ImageUrls>
280
+ $ioObject->streamWrite(" <ImageUrls>\n");
281
+ foreach ($productsByLocale as $curLocale => $curProduct) {
282
+ $imageUrl = $curProduct->getData('localized_image_url');
283
+ if (strlen($imageUrl)) {
284
+ $ioObject->streamWrite(' <ImageUrl locale="' . $curLocale . '">' . "<![CDATA[" . $imageUrl .
285
+ "]]>" . "</ImageUrl>\n");
286
+ }
287
+ }
288
+ $ioObject->streamWrite(" </ImageUrls>\n");
289
+
290
+ // Close this product
291
+ $ioObject->streamWrite("</Product>\n");
292
+ }
293
+
294
+ protected function getProductImageUrl(Mage_Catalog_Model_Product $product)
295
+ {
296
+ try {
297
+ // Init return var
298
+ $imageUrl = null;
299
+ // Get store id from product
300
+ $storeId = $product->getStoreId();
301
+ // Get image url from helper (this is for the default store
302
+ $defaultStoreImageUrl = Mage::helper('catalog/image')->init($product, 'image');
303
+ // Get media base url for correct store
304
+ $mediaBaseUrl = Mage::app()->getStore($storeId)->getBaseUrl('media');
305
+ // Get default media base url
306
+ $defaultMediaBaseUrl = Mage::getBaseUrl('media');
307
+ // Replace media base url component
308
+ $imageUrl = str_replace($defaultMediaBaseUrl, $mediaBaseUrl, $defaultStoreImageUrl);
309
+
310
+ // Return resulting url
311
+ return $imageUrl;
312
+ }
313
+ catch (Exception $e) {
314
+ Mage::log('Failed to get image URL for product sku: ' . $product->getSku());
315
+ Mage::log('Continuing generating feed.');
316
+
317
+ return '';
318
+ }
319
+ }
320
+
321
+ protected function getProductUrl(Mage_Catalog_Model_Product $product)
322
+ {
323
+ $productUrl = $product->getProductUrl(false);
324
+ // Trim any url params
325
+ $questionMarkPos = strpos($productUrl, '?');
326
+ if($questionMarkPos !== FALSE) {
327
+ $productUrl = substr($productUrl, 0, $questionMarkPos);
328
+ }
329
+
330
+ return $productUrl;
331
+ }
332
+
333
+ }
app/code/local/Bazaarvoice/Connector/Model/RetrieveInlineRatingsFeed.php ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @author Bazaarvoice, Inc.
4
+ */
5
+ class Bazaarvoice_Connector_Model_RetrieveInlineRatingsFeed extends Mage_Core_Model_Abstract
6
+ {
7
+
8
+ // Set this value to true to enable the the inline ratings feed on a scheduled basis
9
+ const INLINE_RATINGS_FEED_ENABLED = false;
10
+ // Hard code feed file name in const for easy customization
11
+ const FEED_FILENAME = 'bv_ratings.xml.gz';
12
+
13
+ protected function _construct()
14
+ {
15
+ }
16
+
17
+ public function retrieveInlineRatingsFeed()
18
+ {
19
+ // Log
20
+ Mage::log('Start Bazaarvoice Inline Ratings feed import');
21
+ // Iterate through all stores / groups in this instance
22
+ // (Not the 'admin' store view, which represents admin panel)
23
+ $groups = Mage::app()->getGroups(false);
24
+ /** @var $group Mage_Core_Model_Store_Group */
25
+ foreach ($groups as $group) {
26
+ try {
27
+ if (self::INLINE_RATINGS_FEED_ENABLED && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $group->getDefaultStoreId()) === '1') {
28
+ if(count($group->getStores()) > 0) {
29
+ Mage::log(' BV - Importing Inline Ratings feed for store group: ' . $group->getName(), Zend_Log::INFO);
30
+ $this->retrieveInlineRatingsFeedForStoreGroup($group);
31
+ }
32
+ else {
33
+ Mage::throwException('No stores for store group: ' . $group->getName());
34
+ }
35
+ }
36
+ else {
37
+ Mage::log(' BV - Inline Ratings feed disabled for store group: ' . $group->getName(), Zend_Log::INFO);
38
+ }
39
+ } catch (Exception $e) {
40
+ Mage::log(' BV - Failed to import Inline Ratings feed for store group: ' . $group->getName(), Zend_Log::ERR);
41
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
42
+ Mage::logException($e);
43
+ // Continue processing other store groups
44
+ }
45
+ }
46
+ // Log
47
+ Mage::log('End Bazaarvoice Inline Ratings feed import');
48
+ }
49
+
50
+ /**
51
+ *
52
+ * @param Mage_Core_Model_Store_Group $group Store Group
53
+ *
54
+ */
55
+ public function retrieveInlineRatingsFeedForStoreGroup($group)
56
+ {
57
+ if (self::INLINE_RATINGS_FEED_ENABLED && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $group->getDefaultStoreId()) === '1') {
58
+ $localFilePath = Mage::getBaseDir('var') . DS . 'import' . DS . 'bvfeeds';
59
+ $localFileName = 'inline-ratings-' . $group->getGroupId() . '-' . date('U') . '.xml';
60
+ $gzLocalFilename = $localFileName . '.gz';
61
+ // Hard code feed file path
62
+ $remoteFile = '/feeds/' . self::FEED_FILENAME;
63
+
64
+ if (!Mage::helper('bazaarvoice')->downloadFile($localFilePath, $gzLocalFilename, $remoteFile)) {
65
+ // Unable to download the file. Check magento log for messages.
66
+ Mage::throwException(' BV - unable to process feed. Check the Magento log for further information.');
67
+ }
68
+
69
+ // Unpack the file
70
+ if (file_exists($localFilePath . DS . $localFileName)) {
71
+ unlink($localFilePath . DS . $localFileName);
72
+ }
73
+ $gzInterface = new Mage_Archive_Gz();
74
+ $gzInterface->unpack($localFilePath . DS . $gzLocalFilename, $localFilePath . DS . $localFileName);
75
+
76
+ // Create custom product attributes
77
+ $this->createProductAttributesIfNecessary();
78
+
79
+ // Parse the XML
80
+ $this->parseFeed($localFilePath . DS . $localFileName);
81
+
82
+ // Cleanup
83
+ if (file_exists($localFilePath . DS . $localFileName)) {
84
+ unlink($localFilePath . DS . $localFileName);
85
+ }
86
+ if (file_exists($localFilePath . DS . $gzLocalFilename)) {
87
+ unlink($localFilePath . DS . $gzLocalFilename);
88
+ }
89
+ }
90
+ }
91
+
92
+
93
+ private function parseFeed($fileName)
94
+ {
95
+ // Use XMLReader to parse the feed. Should be available in all PHP5 environments, which is a pre-req of Magento
96
+ // http://devzone.zend.com/article/2387
97
+
98
+ $reader = new XMLReader();
99
+ $reader->open($fileName);
100
+ while ($reader->read()) {
101
+ if (($reader->nodeType == XMLReader::ELEMENT)
102
+ && ($reader->localName == 'Product')) {
103
+
104
+ $this->processProduct($reader);
105
+
106
+ }
107
+ }
108
+
109
+ }
110
+
111
+ private function processProduct($xmlReader)
112
+ {
113
+ $endOfProduct = false;
114
+
115
+ $bvProductExternalId = $xmlReader->getAttribute('id');
116
+ $productAverageRating = 0;
117
+ $productReviewCount = 0;
118
+ $productRatingRange = 5;
119
+
120
+
121
+ while (!$endOfProduct && $xmlReader->read()) {
122
+ if ($xmlReader->nodeType == XMLReader::ELEMENT) {
123
+
124
+ if ($xmlReader->localName == 'AverageOverallRating') {
125
+
126
+ $productAverageRating = $xmlReader->readString();
127
+
128
+ } elseif ($xmlReader->localName == 'OverallRatingRange') {
129
+
130
+ $productRatingRange = $xmlReader->readString();
131
+
132
+ } elseif ($xmlReader->localName == 'TotalReviewCount') {
133
+
134
+ $productReviewCount = $xmlReader->readString();
135
+
136
+ }
137
+
138
+ } elseif (($xmlReader->nodeType == XMLReader::END_ELEMENT)
139
+ && ($xmlReader->localName == 'Product')) {
140
+ $endOfProduct = true;
141
+ }
142
+ }
143
+
144
+ // Persist data for this product
145
+ $product = Mage::helper('bazaarvoice')->getProductFromProductExternalId($bvProductExternalId);
146
+ if (!is_null($product)) {
147
+ $product->setBvAverageRating($productAverageRating);
148
+ $product->setBvReviewCount($productReviewCount);
149
+ $product->setBvRatingRange($productRatingRange);
150
+ $product->getResource()->saveAttribute($product, 'bv_average_rating');
151
+ $product->getResource()->saveAttribute($product, 'bv_review_count');
152
+ $product->getResource()->saveAttribute($product, 'bv_rating_range');
153
+
154
+ Mage::log(' BV - InlineRating for product ' . $bvProductExternalId . ' = {' . $productAverageRating . ', ' . $productReviewCount . ', ' . $productRatingRange . '}');
155
+ } else {
156
+ Mage::log(" BV - Could not find product for ExternalID '" . $bvProductExternalId . "'");
157
+ }
158
+ }
159
+
160
+ private function createProductAttributesIfNecessary()
161
+ {
162
+ $attributeModel = Mage::getModel('catalog/resource_eav_attribute');
163
+ $entityTypeId = Mage::getModel('eav/entity')->setType('catalog_product')->getTypeId();
164
+
165
+ $customAttributes = array('bv_average_rating', 'bv_review_count', 'bv_rating_range');
166
+
167
+ foreach ($customAttributes as $customAttribute) {
168
+ $attribute = $attributeModel->loadByCode($entityTypeId, $customAttribute);
169
+ if (!$attribute->getId()) {
170
+ $this->createProductAttribute($customAttribute);
171
+ }
172
+ }
173
+ }
174
+
175
+ private function createProductAttribute($attribCode)
176
+ {
177
+
178
+ $model = Mage::getModel('catalog/resource_eav_attribute');
179
+
180
+ $data = array(
181
+ 'attribute_code' => $attribCode,
182
+ 'is_global' => '1',
183
+ 'frontend_input' => 'text', // equivalent of a text field
184
+ 'default_value_text' => '',
185
+ 'default_value_yesno' => '0',
186
+ 'default_value_date' => '',
187
+ 'default_value_textarea' => '',
188
+ 'is_unique' => '0',
189
+ 'is_required' => '0',
190
+ // 'apply_to' => array(), // Apply to everything
191
+ 'is_configurable' => '0',
192
+ 'is_filterable' => '0',
193
+ 'is_filterable_in_search' => '0',
194
+ 'is_searchable' => '1',
195
+ 'is_visible_in_advanced_search' => '1',
196
+ 'is_comparable' => '0',
197
+ 'is_used_for_price_rules' => '0',
198
+ 'is_wysiwyg_enabled' => '0',
199
+ 'is_html_allowed_on_front' => '1',
200
+ 'is_visible_on_front' => '1',
201
+ 'used_in_product_listing' => '1',
202
+ 'used_for_sort_by' => '0',
203
+ 'frontend_label' => array($attribCode)
204
+ );
205
+
206
+ $data['backend_type'] = 'decimal'; // $model->getBackendTypeByInput($data['frontend_input']);
207
+
208
+ $model->addData($data);
209
+ $model->setEntityTypeId(Mage::getModel('eav/entity')->setType('catalog_product')->getTypeId());
210
+ $model->setIsUserDefined(1);
211
+ try {
212
+ $model->save();
213
+ } catch (Exception $e) {
214
+
215
+ }
216
+ }
217
+
218
+ }
219
+
app/code/local/Bazaarvoice/Connector/Model/RetrieveSmartSEOPackage.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @author Bazaarvoice, Inc.
4
+ */
5
+ class Bazaarvoice_Connector_Model_RetrieveSmartSEOPackage extends Mage_Core_Model_Abstract
6
+ {
7
+
8
+ protected function _construct()
9
+ {
10
+ }
11
+
12
+ public function retrieveSmartSEOPackage()
13
+ {
14
+ // Disable smart SEO feed for now
15
+ Mage::log('Smart SEO feed import is not enabled!');
16
+ return;
17
+ // Log
18
+ Mage::log('Start Bazaarvoice Smart SEO feed import');
19
+ // Iterate through all stores / groups in this instance
20
+ // (Not the 'admin' store view, which represents admin panel)
21
+ $groups = Mage::app()->getGroups(false);
22
+ /** @var $group Mage_Core_Model_Store_Group */
23
+ foreach ($groups as $group) {
24
+ try {
25
+ if (Mage::getStoreConfig('bazaarvoice/SmartSEOFeed/EnableSmartSEO', $group->getDefaultStoreId()) === '1'
26
+ && Mage::getStoreConfig('bazaarvoice/general/enable_bv', $group->getDefaultStoreId()) === '1') {
27
+ if(count($group->getStores()) > 0) {
28
+ Mage::log(' BV - Importing Smart SEO feed for store group: ' . $group->getName(), Zend_Log::INFO);
29
+ $this->retrieveSmartSEOPackageForStoreGroup($group);
30
+ }
31
+ else {
32
+ Mage::throwException('No stores for store group: ' . $group->getName());
33
+ }
34
+ }
35
+ else {
36
+ Mage::log(' BV - Smart SEO feed disabled for store group: ' . $group->getName(), Zend_Log::INFO);
37
+ }
38
+ } catch (Exception $e) {
39
+ Mage::log(' BV - Failed to import Smart SEO feed for store group: ' . $group->getName(), Zend_Log::ERR);
40
+ Mage::log(' BV - Error message: ' . $e->getMessage(), Zend_Log::ERR);
41
+ Mage::logException($e);
42
+ // Continue processing other store groups
43
+ }
44
+ }
45
+ // Log
46
+ Mage::log('End Bazaarvoice Smart SEO feed import');
47
+ }
48
+
49
+ /**
50
+ *
51
+ * @param Mage_Core_Model_Store_Group $group Store Group
52
+ *
53
+ */
54
+ public function retrieveSmartSEOPackageForStoreGroup($group)
55
+ {
56
+ if (Mage::getStoreConfig('bazaarvoice/SmartSEOFeed/EnableSmartSEO', $group->getDefaultStoreId()) === '1') {
57
+
58
+ $localFilePath = Mage::getBaseDir('var') . DS . 'import' . DS . 'bvfeeds';
59
+ $localExtractsPath = $localFilePath . DS . 'bvsmartseo-' . $group->getGroupId();
60
+
61
+ $gzLocalFilename = Mage::getStoreConfig('bazaarvoice/SmartSEOFeed/FeedFileName', $group->getDefaultStoreId());
62
+ // Hard code path to feed file
63
+ $remoteFile = '/feeds/' . Mage::getStoreConfig('bazaarvoice/SmartSEOFeed/FeedFileName', $group->getDefaultStoreId());
64
+
65
+ // Make sure the $remoteFile is tar.gz and not .zip (BV can create either - but Magento has no ability to deal with .zip)
66
+ $desiredExt = '.tar.gz';
67
+ if (substr_compare($remoteFile, $desiredExt, -strlen($desiredExt), strlen($desiredExt)) !== 0) {
68
+ $msg = 'BV - Unable to retrieve and process a .zip SmartSEO feed. Only .tar.gz SmartSEO feeds can be processed by this extension';
69
+ Mage::log($msg);
70
+ Mage::throwException($msg);
71
+ }
72
+
73
+ $gzInterface = new Mage_Archive_Gz();
74
+ $tarInterface = new Mage_Archive_Tar();
75
+
76
+ // Clear away any previous download that may have existed.
77
+ if (file_exists($localFilePath . DS . $gzLocalFilename)) {
78
+ unlink($localFilePath . DS . $gzLocalFilename);
79
+ }
80
+
81
+ // Download the file
82
+ if (!Mage::helper('bazaarvoice')->downloadFile($localFilePath, $gzLocalFilename, $remoteFile)) {
83
+ // Unable to download the file.
84
+
85
+ if (!file_exists($localExtractsPath)) {
86
+ // Couldn't download the file and no old SmartSEO files already exist on the filesystem
87
+ $subject = 'Bazaarvoice SmartSEO Content Unavailable';
88
+ $msg = 'The Bazaarvoice extension in your Magento store was unable to download new SmartSEO files from the Bazaarvoice server and there were no pre-existing SmartSEO files already in your Magento store.';
89
+ Mage::helper('bazaarvoice')->sendNotificationEmail($subject, $msg);
90
+ Mage::log($msg);
91
+ Mage::throwException($msg);
92
+ }
93
+
94
+
95
+ $lastModificationTime = filemtime($localExtractsPath); // num seconds since EPOCH
96
+ // Hard code maximum stale days
97
+ $maxStaleDays = 5;
98
+ if ((time() - $lastModificationTime) > ($maxStaleDays * 24 * 60 * 60)) {
99
+ // Couldn't download the file, and the old files that we DO have are too old.
100
+ $ioObject = new Varien_Io_File();
101
+ $ioObject->rmdir($localExtractsPath, true); // The 'true' indicates recursive delete
102
+
103
+ $subject = 'Bazaarvoice SmartSEO Content Unavailable';
104
+ $msg = 'The Bazaarvoice extension in your Magento store was unable to download new SmartSEO files from the Bazaarvoice server and the existing SmartSEO files that are already in the Magento store have expired.';
105
+ Mage::helper('bazaarvoice')->sendNotificationEmail($subject, $msg);
106
+ Mage::log($msg);
107
+ Mage::throwException($msg);
108
+ } else {
109
+ // Couldn't download the file, but the old files that we already have are still usable
110
+ $subject = "Bazaarvoice SmartSEO Content Couldn't be Updated";
111
+ $msg = 'The Bazaarvoice extension in your Magento store was unable to download new SmartSEO files from the Bazaarvoice server. The existing files will continue to be used.';
112
+ Mage::helper('bazaarvoice')->sendNotificationEmail($subject, $msg);
113
+ Mage::log($msg);
114
+ Mage::throwException($msg);
115
+ }
116
+
117
+ } else {
118
+ // Successfully downloaded the file.
119
+
120
+ // Clear out the existing extracts and recreate the dir.
121
+ $ioObject = new Varien_Io_File();
122
+ $ioObject->rmdir($localExtractsPath, true); // The 'true' indicates recursive delete
123
+ $ioObject->mkdir($localExtractsPath);
124
+
125
+ // Move the archive into the extracts folder. Use the native PHP function instead of Varien_Io_File
126
+ // since Varien_Io_File throws unnecessary warnings attempting to change working directories
127
+ rename($localFilePath . DS . $gzLocalFilename, $localExtractsPath . DS . $gzLocalFilename);
128
+
129
+ // Decompress the file
130
+ $tmpTarFilename = 'smartseo-tmp.tar';
131
+ $gzInterface->unpack($localExtractsPath . DS . $gzLocalFilename, $localExtractsPath . DS . $tmpTarFilename);
132
+ unlink($localExtractsPath . DS . $gzLocalFilename);
133
+
134
+ // Expand the .tar archive
135
+ $tarInterface->unpack($localExtractsPath . DS . $tmpTarFilename, $localExtractsPath . DS);
136
+ unlink($localExtractsPath . DS . $tmpTarFilename);
137
+ }
138
+
139
+ }
140
+
141
+ }
142
+
143
+ }
app/code/local/Bazaarvoice/Connector/Model/Source/AuthenticationMethod.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Source model for authentication method
4
+ */
5
+ class Bazaarvoice_Connector_Model_Source_AuthenticationMethod
6
+ {
7
+
8
+ const BV_HOSTED_AUTH = 'bv';
9
+ const MAGENTO_SITE_AUTH = 'magento';
10
+
11
+ public function toOptionArray()
12
+ {
13
+ return array(
14
+ array(
15
+ 'value' => self::BV_HOSTED_AUTH,
16
+ 'label' => Mage::helper('bazaarvoice')->__('BV Hosted Authentication')
17
+ ),
18
+ array(
19
+ 'value' => self::MAGENTO_SITE_AUTH,
20
+ 'label' => Mage::helper('bazaarvoice')->__('Magento Site Authentication')
21
+ )
22
+ );
23
+ }
24
+
25
+ }
app/code/local/Bazaarvoice/Connector/Model/Source/Environment.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Bazaarvoice_Connector_Model_Source_Environment
4
+ {
5
+ public function toOptionArray()
6
+ {
7
+ return array(
8
+ array(
9
+ 'value' => 'staging',
10
+ 'label' => Mage::helper('bazaarvoice')->__('Staging')
11
+ ),
12
+ array(
13
+ 'value' => 'production',
14
+ 'label' => Mage::helper('bazaarvoice')->__('Production')
15
+ ),
16
+ );
17
+ }
18
+ }
app/code/local/Bazaarvoice/Connector/Model/Source/FeedGenerationScope.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Source model for feed gen scope
4
+ */
5
+ class Bazaarvoice_Connector_Model_Source_FeedGenerationScope
6
+ {
7
+
8
+ const SCOPE_WEBSITE = 'website';
9
+ const SCOPE_STORE_GROUP = 'group';
10
+ const SCOPE_STORE_VIEW = 'view';
11
+
12
+ public function toOptionArray()
13
+ {
14
+ return array(
15
+ array(
16
+ 'value' => self::SCOPE_WEBSITE,
17
+ 'label' => Mage::helper('bazaarvoice')->__('Magento Website')
18
+ ),
19
+ array(
20
+ 'value' => self::SCOPE_STORE_GROUP,
21
+ 'label' => Mage::helper('bazaarvoice')->__('Magento Store / Store Group')
22
+ ),
23
+ array(
24
+ 'value' => self::SCOPE_STORE_VIEW,
25
+ 'label' => Mage::helper('bazaarvoice')->__('Magento Store View')
26
+ ),
27
+ );
28
+ }
29
+
30
+ }
app/code/local/Bazaarvoice/Connector/Model/Source/TriggeringEvent.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Source model for purchase feed triggering event
4
+ */
5
+ class Bazaarvoice_Connector_Model_Source_TriggeringEvent
6
+ {
7
+
8
+ const PURCHASE = 'purchase';
9
+ const SHIPPING = 'shipping';
10
+
11
+ public function toOptionArray()
12
+ {
13
+ return array(
14
+ array(
15
+ 'value' => self::SHIPPING,
16
+ 'label' => Mage::helper('bazaarvoice')->__('Shipping')
17
+ ),
18
+ array(
19
+ 'value' => self::PURCHASE,
20
+ 'label' => Mage::helper('bazaarvoice')->__('Purchase')
21
+ )
22
+ );
23
+ }
24
+
25
+ }
app/code/local/Bazaarvoice/Connector/controllers/FeedController.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bazaarvoice_Connector_FeedController extends Mage_Core_Controller_Front_Action
3
+ {
4
+ public function preDispatch()
5
+ {
6
+ parent::preDispatch();
7
+ if ($this->getRequest()->getParam('bvauthenticateuser') == 'true') {
8
+ if (!Mage::getSingleton('customer/session')->authenticate($this)) {
9
+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
10
+ }
11
+ }
12
+ }
13
+
14
+ public function inlineratingsAction()
15
+ {
16
+ $rerf = Mage::getModel('bazaarvoice/retrieveInlineRatingsFeed');
17
+ $rerf->retrieveInlineRatingsFeed();
18
+
19
+ $this->loadLayout();
20
+ $this->renderLayout();
21
+ }
22
+
23
+ public function productAction()
24
+ {
25
+ $epf = Mage::getModel('bazaarvoice/exportProductFeed');
26
+ $epf->exportDailyProductFeed();
27
+
28
+ $this->loadLayout();
29
+ $this->renderLayout();
30
+ }
31
+
32
+ public function smartseoAction()
33
+ {
34
+ $seo = Mage::getModel('bazaarvoice/retrieveSmartSEOPackage');
35
+ $seo->retrieveSmartSEOPackage();
36
+
37
+ $this->loadLayout();
38
+ $this->renderLayout();
39
+ }
40
+
41
+ public function ppeAction()
42
+ {
43
+ $ppe = Mage::getModel('bazaarvoice/exportPurchaseFeed');
44
+ $ppe->exportPurchaseFeed();
45
+
46
+ $this->loadLayout();
47
+ $this->renderLayout();
48
+ }
49
+
50
+ }
app/code/local/Bazaarvoice/Connector/controllers/IndexController.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bazaarvoice_Connector_IndexController extends Mage_Core_Controller_Front_Action
3
+ {
4
+ public function preDispatch()
5
+ {
6
+ parent::preDispatch();
7
+ if ($this->getRequest()->getParam('bvauthenticateuser') == 'true') {
8
+ if (!Mage::getSingleton('customer/session')->authenticate($this)) {
9
+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
10
+ }
11
+ }
12
+ }
13
+
14
+ public function indexAction()
15
+ {
16
+ if(Mage::getStoreConfig('bazaarvoice/general/enable_bv') === '1') {
17
+ $this->loadLayout();
18
+ $this->renderLayout();
19
+ }
20
+ else {
21
+ // Send customer to 404 page
22
+ $this->_forward('defaultNoRoute');
23
+ }
24
+ }
25
+ }
app/code/local/Bazaarvoice/Connector/etc/adminhtml.xml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <acl>
4
+ <resources>
5
+ <all>
6
+ <title>Allow Everything</title>
7
+ </all>
8
+ <admin>
9
+ <children>
10
+ <system>
11
+ <children>
12
+ <config>
13
+ <children>
14
+ <bazaarvoice>
15
+ <title>Bazaarvoice</title>
16
+ </bazaarvoice>
17
+ </children>
18
+ </config>
19
+ </children>
20
+ </system>
21
+ </children>
22
+ </admin>
23
+ </resources>
24
+ </acl>
25
+ </config>
app/code/local/Bazaarvoice/Connector/etc/config.xml ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <!--
3
+ /**
4
+ * @category BV
5
+ * @package Bazaarvoice
6
+ */
7
+ -->
8
+ <config>
9
+ <modules>
10
+ <Bazaarvoice_Connector>
11
+ <version>6.0.3</version>
12
+ <depends>
13
+ <!-- no dependencies -->
14
+ </depends>
15
+ </Bazaarvoice_Connector>
16
+ </modules>
17
+
18
+ <global>
19
+ <models>
20
+ <bazaarvoice>
21
+ <class>Bazaarvoice_Connector_Model</class>
22
+ </bazaarvoice>
23
+ </models>
24
+ <blocks>
25
+ <bazaarvoice>
26
+ <class>Bazaarvoice_Connector_Block</class>
27
+ </bazaarvoice>
28
+ </blocks>
29
+ <helpers>
30
+ <bazaarvoice>
31
+ <class>Bazaarvoice_Connector_Helper</class>
32
+ </bazaarvoice>
33
+ </helpers>
34
+ <template>
35
+ <email>
36
+ <bazaarvoice_notification_template module="bazaarvoice">
37
+ <label>Bazaarvoice Notifications</label>
38
+ <file>bazaarvoice_notification.html</file>
39
+ <type>text</type>
40
+ </bazaarvoice_notification_template>
41
+ </email>
42
+ </template>
43
+ <resources>
44
+ <bazaarvoice_setup>
45
+ <setup>
46
+ <module>Bazaarvoice_Connector</module>
47
+ <class>Bazaarvoice_Connector_Model_Mysql4_Setup</class>
48
+ </setup>
49
+ <connection>
50
+ <use>core_setup</use>
51
+ </connection>
52
+ </bazaarvoice_setup>
53
+ </resources>
54
+ </global>
55
+
56
+ <frontend>
57
+ <product>
58
+ <collection>
59
+ <attributes>
60
+ <bv_average_rating />
61
+ <bv_review_count />
62
+ <bv_rating_range />
63
+ </attributes>
64
+ </collection>
65
+ </product>
66
+ <routers>
67
+ <bazaarvoice>
68
+ <use>standard</use>
69
+ <args>
70
+ <module>Bazaarvoice_Connector</module>
71
+ <frontName>bazaarvoice</frontName>
72
+ </args>
73
+ </bazaarvoice>
74
+ </routers>
75
+ <layout>
76
+ <updates>
77
+ <bazaarvoice>
78
+ <file>bazaarvoice.xml</file>
79
+ </bazaarvoice>
80
+ </updates>
81
+ </layout>
82
+ </frontend>
83
+
84
+ <crontab>
85
+ <jobs>
86
+ <bazaarvoice_product_feed>
87
+ <schedule>
88
+ <cron_expr>0 1 * * *</cron_expr><!-- Every day at 1:00 AM -->
89
+ </schedule>
90
+ <run>
91
+ <model>bazaarvoice/exportProductFeed::exportDailyProductFeed</model>
92
+ </run>
93
+ </bazaarvoice_product_feed>
94
+ <bazaarvoice_purchase_feed>
95
+ <schedule>
96
+ <cron_expr>30 2 * * 6</cron_expr><!-- Every Saturday at 2:30 AM -->
97
+ </schedule>
98
+ <run>
99
+ <model>bazaarvoice/exportPurchaseFeed::exportPurchaseFeed</model>
100
+ </run>
101
+ </bazaarvoice_purchase_feed>
102
+ </jobs>
103
+ </crontab>
104
+
105
+ <default>
106
+ <bazaarvoice>
107
+ <general>
108
+ <enable_bv>0</enable_bv>
109
+ <environment>staging</environment>
110
+ <authentication_method>bv</authentication_method>
111
+ <enable_roibeacon>1</enable_roibeacon>
112
+ </general>
113
+ <rr>
114
+ <enable_rr>1</enable_rr>
115
+ <do_show_content_js><![CDATA[
116
+ // If the container is hidden (such as behind a tab), put code here to make it visible (open the tab).
117
+ ]]></do_show_content_js>
118
+ <enable_inline_ratings>1</enable_inline_ratings>
119
+ </rr>
120
+ <qa>
121
+ <enable_qa>1</enable_qa>
122
+ <do_show_content_js><![CDATA[
123
+ // If the container is hidden (such as behind a tab), put code here to make it visible (open the tab).
124
+ ]]></do_show_content_js>
125
+ </qa>
126
+ <feeds>
127
+ <enable_product_feed>1</enable_product_feed>
128
+ <enable_purchase_feed>1</enable_purchase_feed>
129
+ <triggering_event>shipping</triggering_event>
130
+ <generation_scope>website</generation_scope>
131
+ </feeds>
132
+
133
+ <!-- These config settings are intended to be overriden with installation-specific values in
134
+ app/etc/local.xml file -->
135
+ <bv_config>
136
+ <!-- Hard code export path and filename in class constants -->
137
+ <product_feed_export_filename>productfeed.xml</product_feed_export_filename>
138
+ <!-- Hard code export path and filename in class constants -->
139
+ <product_feed_export_export_path>/import-inbox</product_feed_export_export_path>
140
+ <!-- Magento Brand Attribute Code
141
+ The following attribute code will be used to locate the brand of products
142
+ which is sent in the product feed: -->
143
+ <product_feed_brand_attribute_code></product_feed_brand_attribute_code>
144
+
145
+ <!-- If true, use Magento category URL path as category id, otherwise use Mage category entity primary key -->
146
+ <category_id_use_url_path>true</category_id_use_url_path>
147
+
148
+ <!-- FTP Connection Host Name. If this is not specified, the standard ftp-stg.bazaarvoice.com and
149
+ ftp.bazaarvoice.com will be used -->
150
+ <ftp_host_name></ftp_host_name>
151
+
152
+ <!-- Display code - We still need this for some clients -->
153
+ <display_code></display_code>
154
+ </bv_config>
155
+ </bazaarvoice>
156
+ </default>
157
+
158
+
159
+ </config>
app/code/local/Bazaarvoice/Connector/etc/system.xml ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <sections>
4
+ <bazaarvoice translate="label">
5
+ <label>Bazaarvoice</label>
6
+ <tab>general</tab>
7
+ <frontend_type>text</frontend_type>
8
+ <sort_order>1000</sort_order>
9
+ <show_in_default>1</show_in_default>
10
+ <show_in_website>1</show_in_website>
11
+ <show_in_store>1</show_in_store>
12
+ <groups>
13
+ <general translate="label">
14
+ <label>General</label>
15
+ <frontend_type>text</frontend_type>
16
+ <sort_order>100</sort_order>
17
+ <show_in_default>1</show_in_default>
18
+ <show_in_website>1</show_in_website>
19
+ <show_in_store>1</show_in_store>
20
+ <fields>
21
+ <enable_bv>
22
+ <label>Enable Bazaarvoice Extension</label>
23
+ <frontend_type>select</frontend_type>
24
+ <source_model>adminhtml/system_config_source_yesno</source_model>
25
+ <comment><![CDATA[Enable all Bazaarvoice Magento extension features at this Magento Scope]]></comment>
26
+ <sort_order>1</sort_order>
27
+ <show_in_default>1</show_in_default>
28
+ <show_in_website>1</show_in_website>
29
+ <show_in_store>1</show_in_store>
30
+ </enable_bv>
31
+ <environment>
32
+ <label>Environment</label>
33
+ <frontend_type>select</frontend_type>
34
+ <source_model>bazaarvoice/source_environment</source_model>
35
+ <comment><![CDATA[BV environment that this Magento scope is integrating with]]></comment>
36
+ <sort_order>10</sort_order>
37
+ <show_in_default>1</show_in_default>
38
+ <show_in_website>1</show_in_website>
39
+ <show_in_store>1</show_in_store>
40
+ </environment>
41
+ <client_name translate="label comment">
42
+ <label>Client Name</label>
43
+ <frontend_type>text</frontend_type>
44
+ <comment><![CDATA[Client Name as defined in your BV platform account]]></comment>
45
+ <sort_order>14</sort_order>
46
+ <show_in_default>1</show_in_default>
47
+ <show_in_website>1</show_in_website>
48
+ <show_in_store>1</show_in_store>
49
+ </client_name>
50
+ <ftp_password>
51
+ <label>FTP Password</label>
52
+ <frontend_type>text</frontend_type>
53
+ <comment><![CDATA[Password used to access the BV FTP server for feed upload/download processes.]]></comment>
54
+ <sort_order>15</sort_order>
55
+ <show_in_default>1</show_in_default>
56
+ <show_in_website>1</show_in_website>
57
+ <show_in_store>1</show_in_store>
58
+ </ftp_password>
59
+ <!-- Site Auth Disabled for now -->
60
+ <!--
61
+ <authentication_method>
62
+ <label>Authentication Method</label>
63
+ <frontend_type>select</frontend_type>
64
+ <comment><![CDATA[With BV Hosted authentication method, users click the "write a review" link on a product page and authenticate by using just their email address provided in content submission page. With Magento Site authentication, users are redirected to login to the Magento site before the user can access the BV submission page.]]></comment>
65
+ <source_model>bazaarvoice/source_authenticationMethod</source_model>
66
+ <sort_order>17</sort_order>
67
+ <show_in_default>1</show_in_default>
68
+ <show_in_website>1</show_in_website>
69
+ <show_in_store>1</show_in_store>
70
+ </authentication_method>
71
+ <shared_encoding_key>
72
+ <label>Shared Encoding Key</label>
73
+ <frontend_type>text</frontend_type>
74
+ <comment><![CDATA[Key used to encode BV user authentication string. Only required when Magento Site Authentication is selected. Defined in your BV platform account.]]></comment>
75
+ <sort_order>18</sort_order>
76
+ <show_in_default>1</show_in_default>
77
+ <show_in_website>1</show_in_website>
78
+ <show_in_store>1</show_in_store>
79
+ </shared_encoding_key>
80
+ -->
81
+ <deployment_zone>
82
+ <label>Deployment Zone</label>
83
+ <frontend_type>text</frontend_type>
84
+ <comment><![CDATA[Deployment Zone Name as defined in your BV platform account]]></comment>
85
+ <sort_order>20</sort_order>
86
+ <show_in_default>1</show_in_default>
87
+ <show_in_website>1</show_in_website>
88
+ <show_in_store>1</show_in_store>
89
+ </deployment_zone>
90
+ <locale>
91
+ <label>Locale</label>
92
+ <frontend_type>text</frontend_type>
93
+ <comment>
94
+ <![CDATA[The BV locale for this Magento Configuration Scope. For example: "en_US". The locale must be enabled within your BV platform account.]]></comment>
95
+ <sort_order>21</sort_order>
96
+ <show_in_default>1</show_in_default>
97
+ <show_in_website>1</show_in_website>
98
+ <show_in_store>1</show_in_store>
99
+ </locale>
100
+ <!-- Site Auth Disabled for now -->
101
+ <!--
102
+ <submission_url>
103
+ <label>Submission Container URL</label>
104
+ <frontend_type>text</frontend_type>
105
+ <comment><![CDATA[The URL of the Bazaarvoice submission page. This extension provides a submission container page, located at the "bazaarvoice" context root. For example: http://yourdomain.com/magento/bazaarvoice]]></comment>
106
+ <sort_order>60</sort_order>
107
+ <show_in_default>1</show_in_default>
108
+ <show_in_website>1</show_in_website>
109
+ <show_in_store>1</show_in_store>
110
+ </submission_url>
111
+ -->
112
+ <enable_cloud_seo>
113
+ <label>Enable Cloud SEO</label>
114
+ <frontend_type>select</frontend_type>
115
+ <source_model>adminhtml/system_config_source_yesno</source_model>
116
+ <comment>
117
+ <![CDATA[Turn on the BV SEO solution. This will include SEO-friendly content (located in the BV SEO cloud) in your product pages. See documentation for additional steps that must be completed for this feature to take effect.]]></comment>
118
+ <sort_order>80</sort_order>
119
+ <show_in_default>1</show_in_default>
120
+ <show_in_website>1</show_in_website>
121
+ <show_in_store>1</show_in_store>
122
+ </enable_cloud_seo>
123
+ <cloud_seo_key>
124
+ <label>Cloud SEO Key</label>
125
+ <frontend_type>text</frontend_type>
126
+ <comment>
127
+ <![CDATA[SEO Cloud key value as defined in your BV platform account. This key is only required if Cloud SEO is enabled.]]></comment>
128
+ <sort_order>90</sort_order>
129
+ <show_in_default>1</show_in_default>
130
+ <show_in_website>1</show_in_website>
131
+ <show_in_store>1</show_in_store>
132
+ </cloud_seo_key>
133
+ <enable_roibeacon>
134
+ <label>Enable ROI Beacon</label>
135
+ <frontend_type>select</frontend_type>
136
+ <source_model>adminhtml/system_config_source_yesno</source_model>
137
+ <comment>
138
+ <![CDATA[Turn on the BV ROI beacon tag on the purchase transaction (checkout) page. See documentation for additional steps that must be completed for this feature to take effect.]]></comment>
139
+ <sort_order>100</sort_order>
140
+ <show_in_default>1</show_in_default>
141
+ <show_in_website>1</show_in_website>
142
+ <show_in_store>1</show_in_store>
143
+ </enable_roibeacon>
144
+ </fields>
145
+ </general>
146
+ <rr translate="label">
147
+ <label>Ratings and Reviews</label>
148
+ <frontend_type>text</frontend_type>
149
+ <sort_order>110</sort_order>
150
+ <show_in_default>1</show_in_default>
151
+ <show_in_website>1</show_in_website>
152
+ <show_in_store>1</show_in_store>
153
+ <fields>
154
+ <enable_rr>
155
+ <label>Enable Ratings &amp; Reviews</label>
156
+ <frontend_type>select</frontend_type>
157
+ <source_model>adminhtml/system_config_source_yesno</source_model>
158
+ <comment>
159
+ <![CDATA[Turn on BV Ratings &amp; Reviews. This will enable display of R&amp;R content on your product pages. See documentation for additional steps that must be completed for this feature to take effect.]]></comment>
160
+ <sort_order>10</sort_order>
161
+ <show_in_default>1</show_in_default>
162
+ <show_in_website>1</show_in_website>
163
+ <show_in_store>1</show_in_store>
164
+ </enable_rr>
165
+ <do_show_content_js>
166
+ <label>JS Callback function to display R&amp;R content (doShowContent)</label>
167
+ <frontend_type>textarea</frontend_type>
168
+ <comment><![CDATA[The Bazaarvoice doShowContent javascript callback is triggered when the "read all reviews" link in the review summary is clicked.
169
+ You may provide code here that you wish to execute on said click event. For example, you may want to activate a specific tab on the page. This
170
+ code will be wrapped in a javascript function(){...} block. See your Bazaarvoice Implementation manual for further information.]]></comment>
171
+ <sort_order>20</sort_order>
172
+ <show_in_default>1</show_in_default>
173
+ <show_in_website>1</show_in_website>
174
+ <show_in_store>1</show_in_store>
175
+ </do_show_content_js>
176
+ <enable_inline_ratings>
177
+ <label>Enable Hosted Inline Ratings</label>
178
+ <frontend_type>select</frontend_type>
179
+ <source_model>adminhtml/system_config_source_yesno</source_model>
180
+ <comment>
181
+ <![CDATA[Turn on BV Hosted inline ratings. This will enable display of product rating statistics on your category and search results pages. See documentation for additional steps that must be completed for this feature to take effect.]]></comment>
182
+ <sort_order>30</sort_order>
183
+ <show_in_default>1</show_in_default>
184
+ <show_in_website>1</show_in_website>
185
+ <show_in_store>1</show_in_store>
186
+ </enable_inline_ratings>
187
+ </fields>
188
+ </rr>
189
+ <qa translate="label">
190
+ <label>Questions and Answers</label>
191
+ <frontend_type>text</frontend_type>
192
+ <sort_order>120</sort_order>
193
+ <show_in_default>1</show_in_default>
194
+ <show_in_website>1</show_in_website>
195
+ <show_in_store>1</show_in_store>
196
+ <fields>
197
+ <enable_qa>
198
+ <label>Enable Questions &amp; Answers</label>
199
+ <frontend_type>select</frontend_type>
200
+ <source_model>adminhtml/system_config_source_yesno</source_model>
201
+ <comment>
202
+ <![CDATA[Turn on BV Questions &amp; Answers. This will enable display of Q&amp;A content on your product pages. See documentation for additional steps that must be completed for this feature to take effect.]]></comment>
203
+ <sort_order>10</sort_order>
204
+ <show_in_default>1</show_in_default>
205
+ <show_in_website>1</show_in_website>
206
+ <show_in_store>1</show_in_store>
207
+ </enable_qa>
208
+ <do_show_content_js>
209
+ <label>JS Callback function to display Q&amp;A content (doShowContent)(Product Page)</label>
210
+ <frontend_type>textarea</frontend_type>
211
+ <comment><![CDATA[The Bazaarvoice doShowContent javascript callback is triggered when the "read all Q&amp;A" link in the question/answer summary is clicked.
212
+ You may provide code here that you wish to execute on said click event. For example, you may want to activate a specific tab on the page. This
213
+ code will be wrapped in a javascript function(){...} block. See your Bazaarvoice Implementation manual for further information.]]></comment>
214
+ <sort_order>70</sort_order>
215
+ <show_in_default>1</show_in_default>
216
+ <show_in_website>1</show_in_website>
217
+ <show_in_store>1</show_in_store>
218
+ </do_show_content_js>
219
+ </fields>
220
+ </qa>
221
+ <!-- Temp disable as part of MGP-103
222
+ <InlineRatingFeed translate="label">
223
+ <label>Inline Ratings Feed Import</label>
224
+ <frontend_type>text</frontend_type>
225
+ <sort_order>160</sort_order>
226
+ <show_in_default>1</show_in_default>
227
+ <show_in_website>1</show_in_website>
228
+ <show_in_store>1</show_in_store>
229
+ <fields>
230
+ </fields>
231
+ </InlineRatingFeed>
232
+ -->
233
+ <feeds translate="label">
234
+ <label>Feeds</label>
235
+ <frontend_type>text</frontend_type>
236
+ <sort_order>200</sort_order>
237
+ <show_in_default>1</show_in_default>
238
+ <show_in_website>1</show_in_website>
239
+ <show_in_store>1</show_in_store>
240
+ <fields>
241
+ <enable_product_feed>
242
+ <label>Enable Product Feed</label>
243
+ <frontend_type>select</frontend_type>
244
+ <source_model>adminhtml/system_config_source_yesno</source_model>
245
+ <comment>
246
+ <![CDATA[Turn on Product Feed generation. This will enable generation of a product feed to be sent to the BV FTP server. See documentation for additional steps to setup a scheduled job in Magento for this feature to take effect.]]></comment>
247
+ <sort_order>10</sort_order>
248
+ <show_in_default>1</show_in_default>
249
+ <show_in_website>1</show_in_website>
250
+ <show_in_store>1</show_in_store>
251
+ </enable_product_feed>
252
+ <enable_purchase_feed>
253
+ <label>Enable Purchase Feed</label>
254
+ <frontend_type>select</frontend_type>
255
+ <source_model>adminhtml/system_config_source_yesno</source_model>
256
+ <comment>
257
+ <![CDATA[Turn on Purchase Feed generation. This will enable generation of a purchase feed to be sent to the BV FTP server to be used to send post-interaction emails to your customers in order to solicit review submission. See documentation for additional steps to setup a scheduled job for this feature to take effect.]]></comment>
258
+ <sort_order>20</sort_order>
259
+ <show_in_default>1</show_in_default>
260
+ <show_in_website>1</show_in_website>
261
+ <show_in_store>1</show_in_store>
262
+ </enable_purchase_feed>
263
+ <triggering_event>
264
+ <label>Purchase Feed Triggering Event</label>
265
+ <frontend_type>select</frontend_type>
266
+ <comment>
267
+ <![CDATA[This determines whether to use the purchase date or ship date when calculating which purchases to include in the purchase feed.]]></comment>
268
+ <source_model>bazaarvoice/source_triggeringEvent</source_model>
269
+ <sort_order>30</sort_order>
270
+ <show_in_default>1</show_in_default>
271
+ <show_in_website>1</show_in_website>
272
+ <show_in_store>1</show_in_store>
273
+ </triggering_event>
274
+ <admin_email>
275
+ <label>Administrator Email</label>
276
+ <frontend_type>text</frontend_type>
277
+ <comment>
278
+ <![CDATA[All notifications for BV-related Magento job schedules will be sent to this email address.]]></comment>
279
+ <sort_order>70</sort_order>
280
+ <show_in_default>1</show_in_default>
281
+ <show_in_website>1</show_in_website>
282
+ <show_in_store>1</show_in_store>
283
+ </admin_email>
284
+ <generation_scope>
285
+ <label>Feed Generation Scope</label>
286
+ <frontend_type>select</frontend_type>
287
+ <comment>
288
+ <![CDATA[This determines whether separate feed files will be generated and sent to BV for each website, store or store view.]]></comment>
289
+ <source_model>bazaarvoice/source_feedGenerationScope</source_model>
290
+ <sort_order>90</sort_order>
291
+ <show_in_default>1</show_in_default>
292
+ <show_in_website>0</show_in_website>
293
+ <show_in_store>0</show_in_store>
294
+ </generation_scope>
295
+ </fields>
296
+ </feeds>
297
+ </groups>
298
+ </bazaarvoice>
299
+ </sections>
300
+ </config>
app/code/local/Bazaarvoice/Connector/sql/bazaarvoice_setup/mysql4-install-0.1.0.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* @var $installer Mage_Sales_Model_Mysql4_Setup */
3
+ $installer = $this;
4
+
5
+ $installer->startSetup();
6
+
7
+ /**
8
+ * Adds an attribute of the code sent_in_bv_postpurchase_feed to the Order object.
9
+ * As this is a flat table, it adds the column to the table (SALES_FLAT_ORDER).
10
+ **/
11
+
12
+ Mage::log("BV: Installing v0.1.0");
13
+ $installer->addAttribute('order', Bazaarvoice_Connector_Model_ExportPurchaseFeed::ALREADY_SENT_IN_FEED_FLAG, array('type'=>'int'));
14
+
15
+ $installer->endSetup();
16
+
app/design/frontend/enterprise/default/layout/bazaarvoice.xml ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <layout version="0.1.0">
3
+ <default>
4
+ </default>
5
+
6
+ <!--
7
+ This is the submission container page for R&R and Q&A
8
+ It can be accessed at the root of your magento URL context. For example:
9
+ www.yourdomain.com/bazaarvoice (assuming magento runs your entire site)
10
+ or
11
+ www.yourdomain.com/magento/bazaarvoice
12
+ -->
13
+ <bazaarvoice_index_index>
14
+ <reference name="root">
15
+ <action method="setTemplate"><template>page/1column.phtml</template></action>
16
+ </reference>
17
+ <reference name="head">
18
+ <block type="bazaarvoice/bazaarvoice" name="headerincludes" template="bazaarvoice/display/headerincludes.phtml" after="-" />
19
+ <action method="setRobots"><value>noindex, nofollow</value></action>
20
+ </reference>
21
+ <reference name="content">
22
+ <block type="bazaarvoice/submissioncontainer" name="submissioncontainer" template="bazaarvoice/submit/submissioncontainer.phtml" />
23
+ </reference>
24
+ </bazaarvoice_index_index>
25
+
26
+ <!--
27
+ Set up BV content to be injectable on all product and category pages. Note that the head inclusion of bvapi.js will be
28
+ made on all pages, regardless of whether or not any other blocks are inserted into the template (like bv_reviews).
29
+ -->
30
+ <catalog_product_view>
31
+ <reference name="head">
32
+ <block type="bazaarvoice/bazaarvoice" name="headerincludes" template="bazaarvoice/display/headerincludes.phtml" after="-" />
33
+ <block type="bazaarvoice/bazaarvoice" name="headerproduct" template="bazaarvoice/display/headerproduct.phtml" after="headerincludes" />
34
+ </reference>
35
+ <reference name="content">
36
+ <reference name="product.info">
37
+ <block type="bazaarvoice/bazaarvoice" name="reviewsummary" as="bv_reviewsummary" template="bazaarvoice/display/rr/reviewsummary.phtml" />
38
+ <block type="bazaarvoice/reviews" name="reviews" as="bv_reviews" template="bazaarvoice/display/rr/reviews.phtml" />
39
+
40
+ <block type="bazaarvoice/bazaarvoice" name="questionsummary" as="bv_questionsummary" template="bazaarvoice/display/qa/questionsummary.phtml" />
41
+ <block type="bazaarvoice/bazaarvoice" name="questions" as="bv_questions" template="bazaarvoice/display/qa/questions.phtml" />
42
+ </reference>
43
+ </reference>
44
+ </catalog_product_view>
45
+
46
+ <catalog_category_default>
47
+ <reference name="head">
48
+ <block type="bazaarvoice/bazaarvoice" name="headerincludes" template="bazaarvoice/display/headerincludes.phtml" after="-" />
49
+ </reference>
50
+ <reference name="content">
51
+ <reference name="category.products">
52
+ <reference name="product_list" >
53
+ <block type="bazaarvoice/ratings" name="inline_ratings" as="inline_ratings" template="bazaarvoice/display/rr/ratings.phtml" />
54
+ </reference>
55
+ </reference>
56
+ </reference>
57
+ </catalog_category_default>
58
+
59
+ <catalog_category_layered>
60
+ <reference name="head">
61
+ <block type="bazaarvoice/bazaarvoice" name="headerincludes" template="bazaarvoice/display/headerincludes.phtml" after="-" />
62
+ </reference>
63
+ <reference name="content">
64
+ <reference name="category.products">
65
+ <reference name="product_list" >
66
+ <block type="bazaarvoice/ratings" name="inline_ratings" as="inline_ratings" template="bazaarvoice/display/rr/ratings.phtml" />
67
+ </reference>
68
+ </reference>
69
+ </reference>
70
+ </catalog_category_layered>
71
+
72
+ <catalogsearch_result_index>
73
+ <reference name="head">
74
+ <block type="bazaarvoice/bazaarvoice" name="headerincludes" template="bazaarvoice/display/headerincludes.phtml" after="-" />
75
+ </reference>
76
+ <reference name="content">
77
+ <reference name="search.result">
78
+ <reference name="search_result_list" >
79
+ <block type="bazaarvoice/ratings" name="inline_ratings" as="inline_ratings" template="bazaarvoice/display/rr/ratings.phtml" />
80
+ </reference>
81
+ </reference>
82
+ </reference>
83
+ </catalogsearch_result_index>
84
+
85
+ <catalogsearch_advanced_result>
86
+ <reference name="head">
87
+ <block type="bazaarvoice/bazaarvoice" name="headerincludes" template="bazaarvoice/display/headerincludes.phtml" after="-" />
88
+ </reference>
89
+ <reference name="content">
90
+ <reference name="catalogsearch_advanced_result">
91
+ <reference name="search_result_list" >
92
+ <block type="bazaarvoice/ratings" name="inline_ratings" as="inline_ratings" template="bazaarvoice/display/rr/ratings.phtml" />
93
+ </reference>
94
+ </reference>
95
+ </reference>
96
+ </catalogsearch_advanced_result>
97
+
98
+ <!--
99
+ The feed controller is used to more easily test/debug the feed generation or import. This should not be enabled in a production environment for security reasons.
100
+ -->
101
+ <!--
102
+ <bazaarvoice_feed_ppe>
103
+ <reference name="root">
104
+ <action method="setTemplate"><template>page/1column.phtml</template></action>
105
+ </reference>
106
+ </bazaarvoice_feed_ppe>
107
+ <bazaarvoice_feed_inlineratings>
108
+ <reference name="root">
109
+ <action method="setTemplate"><template>page/1column.phtml</template></action>
110
+ </reference>
111
+ </bazaarvoice_feed_inlineratings>
112
+ <bazaarvoice_feed_product>
113
+ <reference name="root">
114
+ <action method="setTemplate"><template>page/1column.phtml</template></action>
115
+ </reference>
116
+ </bazaarvoice_feed_product>
117
+ <bazaarvoice_feed_smartseo>
118
+ <reference name="root">
119
+ <action method="setTemplate"><template>page/1column.phtml</template></action>
120
+ </reference>
121
+ </bazaarvoice_feed_smartseo>
122
+ -->
123
+
124
+ <checkout_onepage_success>
125
+ <reference name="head">
126
+ <block type="bazaarvoice/bazaarvoice" template="bazaarvoice/display/headerincludes.phtml" after="-" />
127
+ <block type="bazaarvoice/roi_beacon" template="bazaarvoice/submit/roi_beacon.phtml" after="-" />
128
+ </reference>
129
+ </checkout_onepage_success>
130
+
131
+ </layout>
app/design/frontend/enterprise/default/template/bazaarvoice/display/headerincludes.phtml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php if(Mage::getStoreConfig("bazaarvoice/general/enable_bv") === "1"): ?>
2
+ <?php $bvApiUrl = Mage::helper('bazaarvoice')->getBvApiHostUrl(true); ?>
3
+ <script type="text/javascript" src="<?php echo $bvApiUrl ?>/bvapi.js"></script>
4
+ <?php endif; ?>
app/design/frontend/enterprise/default/template/bazaarvoice/display/headerproduct.phtml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if(Mage::getStoreConfig("bazaarvoice/general/enable_bv") === "1"): ?>
2
+
3
+ <?php
4
+ $product = Mage::registry('product');
5
+ $productId = '';
6
+ if(strlen($product->getId())) {
7
+ $productId = Mage::helper('bazaarvoice')->getProductId($product);
8
+ }
9
+ ?>
10
+
11
+ <?php if(strlen($product->getId())): ?>
12
+ <script type="text/javascript">
13
+ $BV.configure('global', { productId : "<?php echo $productId ?>" });
14
+ </script>
15
+
16
+ <?php else: ?>
17
+
18
+ <script type="text/javascript" language="javascript">
19
+ <?php // Comment out extra features until we know we need them ?>
20
+ <?php //var profileDisplayContainerPageName = "bazaarvoice/profile/display"; ?>
21
+ var configData = {};
22
+ <?php if (false) { //!empty($submissionContainerUrl)) {
23
+ echo " configData.submissionContainerUrl = \"".$submissionContainerUrl."\";\n";
24
+ } ?>
25
+ $BV.configure("global", configData);
26
+ </script>
27
+
28
+ <?php endif; ?>
29
+
30
+ <?php endif; ?>
app/design/frontend/enterprise/default/template/bazaarvoice/display/qa/questions.phtml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if(Mage::getStoreConfig("bazaarvoice/general/enable_bv") === "1"): ?>
2
+ <?php
3
+ $doShowContentJs = Mage::getStoreConfig('bazaarvoice/qa/do_show_content_js');
4
+ ?>
5
+ <?php if(Mage::getStoreConfig("bazaarvoice/qa/enable_qa") === "1"): ?>
6
+ <div id="BVQAContainer"></div>
7
+ <script type="text/javascript">
8
+ $BV.ui( 'qa', 'show_questions', {
9
+ doShowContent : function () {
10
+ <?php echo $doShowContentJs ?>
11
+ }
12
+ });
13
+ </script>
14
+ <?php endif; ?>
15
+ <?php endif; ?>
app/design/frontend/enterprise/default/template/bazaarvoice/display/qa/questionsummary.phtml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php if(Mage::getStoreConfig("bazaarvoice/general/enable_bv") === "1"): ?>
2
+ <?php if(Mage::getStoreConfig("bazaarvoice/qa/enable_qa") === "1"): ?>
3
+ <div id="BVQASummaryContainer"></div>
4
+ <?php endif; ?>
5
+ <?php endif; ?>
app/design/frontend/enterprise/default/template/bazaarvoice/display/rr/ratings.phtml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ($this->getIsEnabled()):?>
2
+ <script type="text/javascript">
3
+ $BV.ui( 'rr', 'inline_ratings', {
4
+ productIds : {
5
+ <?php foreach($this->getLoadedProductCollection() as $_product): ?>
6
+ <?php $_productId = Mage::helper('bazaarvoice')->getProductId($_product); ?>
7
+ '<?php echo $_productId ?>' : {
8
+ url : '<?php echo $_product->getProductUrl() ?>'
9
+ },
10
+ <?php endforeach; ?>
11
+ },
12
+ containerPrefix : 'BVRRInlineRating'
13
+ });
14
+ </script>
15
+ <?php endif; ?>
app/design/frontend/enterprise/default/template/bazaarvoice/display/rr/reviews.phtml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ($this->getIsEnabled()):?>
2
+ <?php
3
+ $doShowContentJs = Mage::getStoreConfig('bazaarvoice/rr/do_show_content_js');
4
+ $seoContent = $this->getSEOContent();
5
+ ?>
6
+ <div id="BVRRContainer">
7
+ <?php echo $seoContent ?>
8
+ </div>
9
+ <script type="text/javascript">
10
+ $BV.ui( 'rr', 'show_reviews', {
11
+ doShowContent : function () {
12
+ <?php echo $doShowContentJs ?>
13
+ }
14
+ });
15
+ </script>
16
+ <?php endif; ?>
app/design/frontend/enterprise/default/template/bazaarvoice/display/rr/reviewsummary.phtml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php if(Mage::getStoreConfig("bazaarvoice/general/enable_bv") === "1"): ?>
2
+ <?php if(Mage::getStoreConfig("bazaarvoice/rr/enable_rr") === "1"): ?>
3
+ <div id="BVRRSummaryContainer"></div>
4
+ <?php endif; ?>
5
+ <?php endif; ?>
app/design/frontend/enterprise/default/template/bazaarvoice/submit/roi_beacon.phtml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php if ($this->getIsEnabled()):?>
2
+ <script type="text/javascript">
3
+ $BV.SI.trackTransactionPageView(<?php echo $this->getOrderDetails(); ?>);
4
+ </script>
5
+ <?php endif;?>
app/design/frontend/enterprise/default/template/bazaarvoice/submit/submissioncontainer.phtml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php if(Mage::getStoreConfig("bazaarvoice/general/enable_bv") === "1"): ?>
2
+ <script>
3
+ $BV.container('global', {});
4
+ </script>
5
+ <?php endif;?>
app/etc/modules/Bazaarvoice_Connector.xml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <Bazaarvoice_Connector>
5
+ <active>true</active>
6
+ <codePool>local</codePool>
7
+ </Bazaarvoice_Connector>
8
+ </modules>
9
+ </config>
app/locale/en_US/template/email/bazaarvoice_notification.html ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ This is an automated email from the Bazaarvoice extension in your Magento installation.
2
+
3
+ ---------------------------------------------------------------------------------------
4
+ {{var text}}
5
+ ---------------------------------------------------------------------------------------
lib/Bazaarvoice/bvseosdk.php ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 responsbile
8
+ * for future mainentence 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
+ /**
21
+ * Example usage:
22
+ *
23
+ * require(bvsdk.php);
24
+ *
25
+ * $bv = new BV(array(
26
+ * 'deployment_zone_id' => '12325',
27
+ * 'product_id' => 'product1',
28
+ * 'cloud_key' => 'agileville-78B2EF7DE83644CAB5F8C72F2D8C8491',
29
+ * 'staging' => TRUE
30
+ * ));
31
+ *
32
+ */
33
+
34
+ // ------------------------------------------------------------------------
35
+
36
+ /**
37
+ * BV Class
38
+ *
39
+ * When you instantiate the BV class, pass it's constructor an array
40
+ * containing the following key value pairs.
41
+ *
42
+ * Required fields:
43
+ * deployment_zone_id (string)
44
+ * product_id (string)
45
+ * cloud_key (string)
46
+ *
47
+ * Optional fields
48
+ * current_page_url (string) (defaults to detecting the current_page automtically)
49
+ * staging (boolean) (defaults to true, need to put false when go to production)
50
+ * subject_type (string) (defaults to product, for questions you can pass in categories here if needed)
51
+ * latency_timeout (int) (in millseconds) (defaults to 1000ms)
52
+ * bv_product (string) (defaults to reviews which is the only supported product right now)
53
+ * bot_list (string) (defualts to msnbot|googlebot|teoma|bingbot|yandexbot|yahoo)
54
+ */
55
+
56
+ class BV {
57
+
58
+ /**
59
+ * BV Class Constructor
60
+ *
61
+ * The constructor takes in all the arguments via a single array.
62
+ *
63
+ * @access public
64
+ * @param array
65
+ * @return object
66
+ */
67
+ public function __construct($params = array())
68
+ {
69
+ // check to make sure we have the required paramaters
70
+ if( empty($params) OR ! $params['deployment_zone_id'] OR ! $params['product_id'])
71
+ {
72
+ throw new Exception('BV Class missing required paramters.
73
+ BV expects an array with the following indexes: deployment_zone_id (string) and product_id
74
+ (string). ');
75
+ }
76
+
77
+ // config array, defaults are defined here
78
+ $this->config = array(
79
+ 'staging' => TRUE,
80
+ 'subject_type' => 'product',
81
+ 'latency_timeout' => 1000,
82
+ 'current_page_url' => $this->_getCurrentUrl(),
83
+ 'bot_detection' => TRUE, // for some clients who are behind a CDN or something they may want to include SEO content with every request
84
+ 'include_display_integration_code' => FALSE,
85
+ 'client_name' => $params['deployment_zone_id'],
86
+ 'internal_file_path' => FALSE,
87
+ 'bot_list' => 'msnbot|google|teoma|bingbot|yandexbot|yahoo', // used in regex to determine if request is a bot or not
88
+ );
89
+
90
+ // merge passed in params with defualts for config.
91
+ $this->config = array_merge($this->config, $params);
92
+
93
+ // setup the reviews object
94
+ $this->reviews = new Reviews($this->config);
95
+
96
+ // setup the questions object
97
+ $this->questions = new Questions($this->config);
98
+
99
+ }
100
+
101
+ // since this is used to set the default for an optional config option it is
102
+ // included in the BV class.
103
+ public function _getCurrentUrl(){
104
+ // depending on protocal set the
105
+ // beginging of url and defualt port
106
+ if(isset($_SERVER["HTTPS"])){
107
+ $url = 'https://';
108
+ $defaultPort = '443';
109
+ }else{
110
+ $url = 'http://';
111
+ $defaultPort = '80';
112
+ }
113
+
114
+ $url .= $_SERVER["SERVER_NAME"];
115
+
116
+ // if there is a port other than the defaultPort being used it needs to be included
117
+ if ($_SERVER["SERVER_PORT"] != $defaultPort){
118
+ $url .= ":".$_SERVER["SERVER_PORT"];
119
+ }
120
+
121
+ $url .= $_SERVER["REQUEST_URI"];
122
+
123
+ return $url;
124
+ }
125
+ } // end of BV class
126
+
127
+ // Most shared functionatly is here so when we add support for questions
128
+ // and answers it should be minimal changes. Just need to create an answers
129
+ // class which inherits from Base.
130
+ class Base{
131
+
132
+ public function __construct($params = array())
133
+ {
134
+ if ( ! $params)
135
+ {
136
+ throw new Exception('BV Base Class missing config array.');
137
+ }
138
+
139
+ $this->config = $params;
140
+
141
+ // setup bv (internal) defaults
142
+ $this->bv_config['seo-domain']['staging'] = 'seo-stg.bazaarvoice.com';
143
+ $this->bv_config['seo-domain']['production'] = 'seo.bazaarvoice.com';
144
+ }
145
+
146
+ /**
147
+ * Render SEO
148
+ *
149
+ * Method used to do all the work to fetch, parse, and then return
150
+ * the SEO payload. This is set as protected so classes inheriting
151
+ * from the base class can invoke it or replace it if needed.
152
+ *
153
+ * @access protected
154
+ * @return string
155
+ */
156
+ protected function _renderSEO()
157
+ {
158
+ // we will return a payload of a string
159
+ $pay_load = '';
160
+
161
+ // we only want to render SEO when it's a search engine bot
162
+ if ($this->_isBot())
163
+ {
164
+
165
+ // get the page number of SEO content to load
166
+ $page_number = $this->_getPageNumber();
167
+
168
+ // build the URL to access the SEO content for
169
+ // this product / page combination
170
+ $seo_url = $this->_buildSeoUrl($page_number);
171
+
172
+ // make call to get SEO payload from cloud
173
+ $seo_content = $this->_fetchSeoContent($seo_url);
174
+
175
+ // replace tokens for pagination URLs with page_url
176
+ $seo_content = $this->_replaceTokens($seo_content);
177
+
178
+ // if debug mode is on we want to include more debug data
179
+ if (isset($_GET['bvreveal']))
180
+ {
181
+ if($_GET['bvreveal'] == 'debug')
182
+ {
183
+ $printable_config = $this->config;
184
+ unset($printable_config['cloud_key']);
185
+ $seo_content .= $this->_buildComment('Config options: '.print_r($printable_config, TRUE));
186
+ }
187
+ }
188
+
189
+ $pay_load = $seo_content;
190
+ }
191
+ else
192
+ {
193
+ $pay_load = $this->_buildComment('Bot not detected, JavaScript-only');
194
+ }
195
+
196
+ return $pay_load;
197
+ }
198
+
199
+
200
+ // --------------------------------------------------------------------
201
+ /* Private methods. Internal workings of SDK. */
202
+ //--------------------------------------------------------------------
203
+
204
+ /**
205
+ * isBot
206
+ *
207
+ * Helper method to determine if current request is a bot or not. Will
208
+ * use the configured regex string which can be overriden with params.
209
+ *
210
+ * @access private
211
+ * @return bool
212
+ */
213
+ private function _isBot()
214
+ {
215
+ // we need to check the user agent string to see if this is a bot,
216
+ // unless the bvreveal parameter is there or we have disabled bot
217
+ // detection through the bot_detection flag
218
+ if(isset($_GET['bvreveal']) OR ! $this->config['bot_detection']){
219
+ return TRUE;
220
+ }
221
+
222
+ // search the user agent string for an indictation if this is a search bot or not
223
+ return preg_match('/('.$this->config['bot_list'].')/i', $_SERVER['HTTP_USER_AGENT']);
224
+ }
225
+
226
+ /**
227
+ * getPageNumber
228
+ *
229
+ * Helper method to pull from the URL the page of SEO we need to view.
230
+ *
231
+ * @access private
232
+ * @return int
233
+ */
234
+ private function _getPageNumber()
235
+ {
236
+ // default to page 1 if a page is not specified in the URL
237
+ $page_number = 1;
238
+
239
+ // some implementations wil use bvpage query parameter like ?bvpage=2
240
+ if (isset($_GET['bvpage'])){
241
+ $page_number = (int) $_GET['bvpage'];
242
+
243
+ // remove the bvpage parameter from the current URL so we don't keep appending it
244
+ $seo_param = str_replace('/', '\/', $_GET['bvrrp']); // need to escape slashses for regex
245
+ $this->config['current_page_url'] = preg_replace('/[?&]bvrrp='.$seo_param.'/', '', $this->config['current_page_url']);
246
+ }
247
+ // other implementations use the bvrrp, bvqap, or bvsyp parameter ?bvrrp=1234-en_us/reviews/product/2/ASF234.htm
248
+ else if(isset($_GET['bvrrp']) OR isset($_GET['bvqap']) OR isset($_GET['bvsyp']) ){
249
+ if(isset($_GET['bvrrp']))
250
+ {
251
+ $bvparam = $_GET['bvrrp'];
252
+ }
253
+ else if(isset($_GET['bvqap']))
254
+ {
255
+ $bvparam = $_GET['bvqap'];
256
+ }
257
+ else
258
+ {
259
+ $bvparam = $_GET['bvsyp'];
260
+ }
261
+
262
+ preg_match('/\/(\d+?)\/[^\/]+$/', $_SERVER['QUERY_STRING'], $page_number);
263
+ $page_number = max(1, (int) $page_number[1]);
264
+
265
+ // remove the bvrrp parameter from the current URL so we don't keep appending it
266
+ $seo_param = str_replace('/', '\/', $bvparam); // need to escape slashses for regex
267
+ $this->config['current_page_url'] = preg_replace('/[?&]bvrrp='.$seo_param.'/', '', $this->config['current_page_url']);
268
+ }
269
+
270
+ return $page_number;
271
+ }// end of _getPageNumber()
272
+
273
+ /**
274
+ * buildSeoUrl
275
+ *
276
+ * Helper method to that builds the URL to the SEO payload
277
+ *
278
+ * @access private
279
+ * @param int (page number)
280
+ * @return string
281
+ */
282
+ private function _buildSeoUrl($page_number){
283
+ // are we pointing at staging or production?
284
+ if($this->config['staging']){
285
+ $hostname = $this->bv_config['seo-domain']['staging'];
286
+ }else{
287
+ $hostname = $this->bv_config['seo-domain']['production'];
288
+ }
289
+
290
+ // dictates order of URL
291
+ $url_parts = array(
292
+ 'http://'.$hostname,
293
+ $this->config['cloud_key'],
294
+ $this->config['deployment_zone_id'],
295
+ $this->config['bv_product'],
296
+ $this->config['subject_type'],
297
+ $page_number,
298
+ urlencode($this->config['product_id']).'.htm'
299
+ );
300
+
301
+ // if our SEO content source is a file path
302
+ // we need to remove the first two sections
303
+ // and prepend the passed in file path
304
+ if($this->config['internal_file_path'])
305
+ {
306
+ unset($url_parts[0]);
307
+ unset($url_parts[1]);
308
+
309
+ return $this->config['internal_file_path'].implode("/", $url_parts);
310
+ }
311
+
312
+ // implode will convert array to a string with / in between each value in array
313
+ return implode("/", $url_parts);
314
+ }
315
+
316
+ private function _fetchSeoContent($resource)
317
+ {
318
+ if($this->config['internal_file_path'])
319
+ {
320
+ return $this->_fetchFileContent($resource);
321
+ }
322
+ else
323
+ {
324
+ return $this->_fetchCloudContent($resource);
325
+ }
326
+ }
327
+
328
+ /**
329
+ * fetchFileContent
330
+ *
331
+ * Helper method that will take in a file path and return it's payload while
332
+ * handling the possible errors or exceptions that can happen.
333
+ *
334
+ * @access private
335
+ * @param string (valid file path)
336
+ * @return string (contents of file)
337
+ */
338
+ private function _fetchFileContent($path){
339
+ return file_get_contents($path);
340
+ }
341
+
342
+
343
+ /**
344
+ * fetchCloudContent
345
+ *
346
+ * Helper method that will take in a URL and return it's payload while
347
+ * handling the possible errors or exceptions that can happen.
348
+ *
349
+ * @access private
350
+ * @param string (valid url)
351
+ * @return string
352
+ */
353
+ private function _fetchCloudContent($url){
354
+
355
+ // is cURL installed yet?
356
+ // if ( ! function_exists('curl_init')){
357
+ // return '<!-- curl library is not installed -->';
358
+ // }
359
+
360
+ // create a new cURL resource handle
361
+ $ch = curl_init();
362
+
363
+ curl_setopt($ch, CURLOPT_URL, $url); // Set URL to download
364
+ curl_setopt($ch, CURLOPT_REFERER, $this->config['current_page_url']); // Set a referer as coming from the current page url
365
+ curl_setopt($ch, CURLOPT_HEADER, 0); // Include header in result? (0 = yes, 1 = no)
366
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Should cURL return or print out the data? (true = return, false = print)
367
+ curl_setopt($ch, CURLOPT_TIMEOUT, ($this->config['latency_timeout'] / 1000)); // Timeout in seconds
368
+
369
+ // make the request to the given URL and then store the response, request info, and error number
370
+ // so we can use them later
371
+ $request = array(
372
+ 'response' => curl_exec($ch),
373
+ 'info' => curl_getinfo($ch),
374
+ 'error_number' => curl_errno($ch),
375
+ 'error_message' => curl_error($ch)
376
+ );
377
+
378
+ // Close the cURL resource, and free system resources
379
+ curl_close($ch);
380
+
381
+ // see if we got any errors with the connection
382
+ if($request['error_number'] != 0){
383
+ $msg = 'Error - '.$request['error_message'];
384
+ $this->_buildComment($msg);
385
+ }
386
+
387
+ // see if we got a status code of something other than 200
388
+ if($request['info']['http_code'] != 200){
389
+ $msg = 'HTTP status code of '.$request['info']['http_code'].' was returned';
390
+ return $this->_buildComment($msg);
391
+ }
392
+
393
+ // if we are here we got a response so let's return it
394
+ $msg = 'timer '.($request['info']['total_time'] * 1000).'ms';
395
+ return $request['response'].$this->_buildComment($msg);
396
+ }
397
+
398
+ /**
399
+ * replaceTokens
400
+ *
401
+ * After we have an SEO payload we need to replace the {INSERT_PAGE_URI}
402
+ * tokens with the current page url so pagination works.
403
+ *
404
+ * @access private
405
+ * @param string (valid url)
406
+ * @return string
407
+ */
408
+
409
+ private function _replaceTokens($content){
410
+ // determine if query string exists in current page url
411
+ if (parse_url($this->config['current_page_url'], PHP_URL_QUERY) != ''){
412
+ // append an amperstand, because the URL already has a ? mark
413
+ $page_url_query_prefix = '&';
414
+ } else {
415
+ // append a question mark, since this URL currently has no query
416
+ $page_url_query_prefix = '?';
417
+ }
418
+
419
+ $content = str_replace('{INSERT_PAGE_URI}', $this->config['current_page_url'] . $page_url_query_prefix, $content);
420
+
421
+ return $content;
422
+ }
423
+
424
+ private function _buildComment($msg){
425
+ return "\n".'<!--BVSEO|dp: '.$this->config['deployment_zone_id'].'|sdk: v1.0-p|msg: '.$msg.' -->';
426
+ }
427
+
428
+ } // end of Base class
429
+
430
+
431
+ class Reviews extends Base{
432
+
433
+ function __construct($params = array())
434
+ {
435
+ // call Base Class constructor
436
+ parent::__construct($params);
437
+
438
+ // since we are in the reviews class
439
+ // we need to set the bv_product config
440
+ // to reviews so we get reviews in our
441
+ // SEO request
442
+ $this->config['bv_product'] = 'reviews';
443
+
444
+ // for reviews subject type will always
445
+ // need to be product
446
+ $this->config['subject_type'] = 'product';
447
+ }
448
+
449
+ public function renderSeo()
450
+ {
451
+ $pay_load = $this->_renderSeo();
452
+
453
+ // if they want to power display integration as well
454
+ // then we need to include the JS integration code
455
+ // regardless of if it's a bot or not
456
+ if($this->config['include_display_integration_code'])
457
+ {
458
+ $pay_load .= '
459
+ <script>
460
+ $BV.ui("rr", "show_reviews", {
461
+ productId: "'.$this->config['product_id'].'"
462
+ });
463
+ </script>
464
+ ';
465
+ }
466
+
467
+ return $pay_load;
468
+
469
+ }
470
+ } // end of Reviews class
471
+
472
+
473
+ class Questions extends Base{
474
+
475
+ function __construct($params = array())
476
+ {
477
+ // call Base Class constructor
478
+ parent::__construct($params);
479
+
480
+ // since we are in the questions class
481
+ // we need to set the bv_product config
482
+ // to questions so we get questions in our
483
+ // SEO request
484
+ $this->config['bv_product'] = 'questions';
485
+ }
486
+
487
+ public function renderSeo()
488
+ {
489
+ $pay_load = $this->_renderSeo();
490
+
491
+ // if they want to power display integration as well
492
+ // then we need to include the JS integration code
493
+ // regardless of if it's a bot or not
494
+ if($this->config['include_display_integration_code'])
495
+ {
496
+
497
+ $pay_load .= '
498
+ <script>
499
+ $BV.ui("qa", "show_questions", {
500
+ productId: "'.$this->config['product_id'].'"
501
+ });
502
+ </script>
503
+ ';
504
+ }
505
+
506
+ return $pay_load;
507
+
508
+ }
509
+ } // end of Questions class
510
+
511
+ // end of bvsdk.php
package.xml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>Bazaarvoice_Connector</name>
4
+ <version>6.0.3</version>
5
+ <stability>stable</stability>
6
+ <license>Bazaarvoice, Inc.</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>Integration extension for Bazaarvoice customers</summary>
10
+ <description>See www.bazaarvoice.com for further details on our offerings</description>
11
+ <notes>Plugin corresponds to version 6.0.2 of the Bazaarvoice platform</notes>
12
+ <authors><author><name>BV DTS</name><user>bvdts</user><email>dts@bazaarvoice.com</email></author></authors>
13
+ <date>2014-03-22</date>
14
+ <time>12:55:01</time>
15
+ <contents><target name="magelocal"><dir name="Bazaarvoice"><dir name="Connector"><dir name="Block"><file name="Bazaarvoice.php" hash="473e92e58fd86c823fb55765ed473414"/><file name="Ratings.php" hash="6822db584f2ee777beb8d39c662e8465"/><file name="Reviews.php" hash="b10079b4b57e8738f7a0f3e4a119b44a"/><dir name="Roi"><file name="Beacon.php" hash="060af59693b1471a98e6b065bad6d93d"/></dir><file name="Submissioncontainer.php" hash="23d04ee9ece011d107bb8b914653a24d"/></dir><dir name="Helper"><file name="Data.php" hash="e088fc56bc227516c34e8614ac572ba2"/></dir><dir name="Model"><file name="ExportProductFeed.php" hash="c118c7b0b6a9c2e2dee9554db2b54b8e"/><file name="ExportPurchaseFeed.php" hash="8d9400ea078aa22e162b9265542ec1bf"/><dir name="Mysql4"><file name="Setup.php" hash="884c886c1e9fa395f05e7872ba6478a2"/></dir><dir name="ProductFeed"><file name="Brand.php" hash="b2ab72e79d7e3afe23b38bc3b64ba115"/><file name="Category.php" hash="c08fe52655879ca6d47caed30c657d7d"/><file name="Product.php" hash="31c7c3c0617326642eb6157613572702"/></dir><file name="RetrieveInlineRatingsFeed.php" hash="d9b5548838a43917de916dbd5266b48a"/><file name="RetrieveSmartSEOPackage.php" hash="eab3badfb13d16965d280ca13109abe6"/><dir name="Source"><file name="AuthenticationMethod.php" hash="582d6c76372bac64728e6e4d68f959e4"/><file name="Environment.php" hash="1e575c9adb480df80e4a8a917960bd55"/><file name="FeedGenerationScope.php" hash="b2450e4c0c69b0da328f1d0d7d67012a"/><file name="TriggeringEvent.php" hash="fa47f3a2fcec92d9603f21541c853035"/></dir></dir><dir name="controllers"><file name="FeedController.php" hash="933a1555d97ac2cec8ea52306cf63102"/><file name="IndexController.php" hash="16b9a353153d40ebc32e759112ca6d6f"/></dir><dir name="etc"><file name="adminhtml.xml" hash="39cf8642bfc219709849618519a1c767"/><file name="config.xml" hash="add14265cecfb9e3e0698db626e23df0"/><file name="system.xml" hash="4ab043c44acad63a53bd9f47d3825fae"/></dir><dir name="sql"><dir name="bazaarvoice_setup"><file name="mysql4-install-0.1.0.php" hash="d721fefe4be8cee31e6bd25b8252dca6"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Bazaarvoice_Connector.xml" hash="c517b52d29fec93a83a8010451368ee1"/></dir></target><target name="magedesign"><dir name="frontend"><dir name="enterprise"><dir name="default"><dir name="template"><dir name="bazaarvoice"><dir name="display"><file name="headerincludes.phtml" hash="0faf96303bfe38e1c4130dfdbaff9c87"/><file name="headerproduct.phtml" hash="0d468a860f9d5ad8ae03c77e04c6089c"/><dir name="qa"><file name="questions.phtml" hash="ba5dbc762b3d43e31e492f2937bb3f3b"/><file name="questionsummary.phtml" hash="c6044994c1d355730d7e5cfd3a6e3125"/></dir><dir name="rr"><file name="ratings.phtml" hash="56047515b347d0250d44e4e2eb0a923c"/><file name="reviews.phtml" hash="7d2ad8b18910010d22240c9521131290"/><file name="reviewsummary.phtml" hash="75e823088dd75387fc94e6028d8236a5"/></dir></dir><dir name="submit"><file name="roi_beacon.phtml" hash="494b9a44ceb14b1f376611d30ac18645"/><file name="submissioncontainer.phtml" hash="3f3f22b922c1aa5b87f7bbf4fbeea51c"/></dir></dir></dir><dir name="layout"><file name="bazaarvoice.xml" hash="aaf26cfadf47936077b764356b0e1ab2"/></dir></dir></dir></dir></target><target name="magelocale"><dir name="en_US"><dir name="template"><dir name="email"><file name="bazaarvoice_notification.html" hash="9a0e90ac62d926dad4db13719f3c8b73"/></dir></dir></dir></target><target name="mageskin"><dir name="frontend"><dir name="base"><dir name="default"><dir name="images"><dir name="bazaarvoice"><file name="rating-0_0.gif" hash="f50bd3f45f69a753614b2e76f53bdafc"/><file name="rating-1_0.gif" hash="c691e11e3250a18939aec523734d9b67"/><file name="rating-1_1.gif" hash="26377f1337bb6fb9e340292243a6f780"/><file name="rating-1_2.gif" hash="5c51583dc52d901c61d9470d5faeb2a4"/><file name="rating-1_3.gif" hash="3948c716d18ea0389ce9e57c347e7b6d"/><file name="rating-1_4.gif" hash="2211d8586bda467cb8fcc617670b94df"/><file name="rating-1_5.gif" hash="3fa9480c8b86f85749147fa0e8144b05"/><file name="rating-1_6.gif" hash="a577c79e7ea0c6c59ac15251c39de515"/><file name="rating-1_7.gif" hash="b5b52fa267632eda6ba5b3be56319397"/><file name="rating-1_8.gif" hash="205170e1ffbfcc81569286a9e1a88eb5"/><file name="rating-1_9.gif" hash="63709f7b2a2e2f14ae442dbef6513f25"/><file name="rating-2_0.gif" hash="4eec2468b5e41dc03d198ed6fe084a53"/><file name="rating-2_1.gif" hash="155cab7b16f4cfef3e94b99ca297cedc"/><file name="rating-2_2.gif" hash="2e2dc606fd83853bdf90a3beb901cf3e"/><file name="rating-2_3.gif" hash="638632f37a750558722c0bf1a79f2546"/><file name="rating-2_4.gif" hash="6b0a85c21066c6402b9f8914284b999f"/><file name="rating-2_5.gif" hash="c4792dac3b9d5a914a72a4200f931c6e"/><file name="rating-2_6.gif" hash="1c7ac3f4e3721d4779721973cfaaa8db"/><file name="rating-2_7.gif" hash="21b680dce6ffef505532afea7fea1452"/><file name="rating-2_8.gif" hash="136ac6b284d1a2b9452a06eea993c1fa"/><file name="rating-2_9.gif" hash="d13af6920569aa85da6dfb0a139d560a"/><file name="rating-3_0.gif" hash="6b30e597cc23aec52dbd2be978d52351"/><file name="rating-3_1.gif" hash="cb544d168a949100fb5ee117adbd765b"/><file name="rating-3_2.gif" hash="75124c4b4dfc5cbcf5ae3ccfa7bdf906"/><file name="rating-3_3.gif" hash="0693b6a471361957da1dc8ee2e9af5ec"/><file name="rating-3_4.gif" hash="1a6e3cff41a61e1bbed9296badb94392"/><file name="rating-3_5.gif" hash="7f63ecf505414386267fad2e92617a9f"/><file name="rating-3_6.gif" hash="8b9b9ccebc3537cffd2bed75c60eaa9e"/><file name="rating-3_7.gif" hash="7f83f3996a738d1fd6763204cd964376"/><file name="rating-3_8.gif" hash="219a1f2dbd45bcb58a58f460c9491bbf"/><file name="rating-3_9.gif" hash="e4114607ca469db2fd5f87ac21c4f00a"/><file name="rating-4_0.gif" hash="a15541525186bf6911202e0f64daa4a6"/><file name="rating-4_1.gif" hash="818971c067beb397247095f5eedbac29"/><file name="rating-4_2.gif" hash="5b9599176771adfbf8c52c7dfa04e565"/><file name="rating-4_3.gif" hash="18dc68db736819e17ab5cf0d5725d99c"/><file name="rating-4_4.gif" hash="56b124f1a2e599918b462ce29cd1cd96"/><file name="rating-4_5.gif" hash="2044f11b1f7005f66f14219c5fce1020"/><file name="rating-4_6.gif" hash="3166f044e7f73f9b3e75bda4507eaa35"/><file name="rating-4_7.gif" hash="20546d3ebee7a364927e9da9274996a7"/><file name="rating-4_8.gif" hash="9dab0f19785d1592a96c5c295842f308"/><file name="rating-4_9.gif" hash="19a47143b04aceae85def246059fba33"/><file name="rating-5_0.gif" hash="e43b403663785255d2f023ca35566ac3"/></dir></dir></dir></dir></dir></target><target name="magelib"><dir name="Bazaarvoice"><file name="bvseosdk.php" hash="01c63530a4b80a7a70555b5005a4ba5c"/></dir></target><target name="mageweb"><dir name="shell"><file name="bv_export_order_feed.php" hash="309995ede2f85d95a0b91d8845c06ae5"/><file name="bv_export_product_feed.php" hash="1696c363c97bf9943560045b297c98fb"/></dir></target></contents>
16
+ <compatible/>
17
+ <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
18
+ </package>
shell/bv_export_order_feed.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ *
4
+ */
5
+
6
+ require 'app/Mage.php';
7
+
8
+ if (!Mage::isInstalled()) {
9
+ echo "Application is not installed yet, please complete install wizard first.";
10
+ exit;
11
+ }
12
+
13
+ // Only for urls
14
+ // Don't remove this
15
+ $_SERVER['SCRIPT_NAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_NAME']);
16
+ $_SERVER['SCRIPT_FILENAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_FILENAME']);
17
+
18
+ Mage::app('admin')->setUseSessionInUrl(false);
19
+
20
+ umask(0);
21
+
22
+ try {
23
+ // Log mem usage
24
+ echo "\n" . 'Memory usage: ' . memory_get_usage() . "\n";
25
+
26
+ // Create model
27
+ $exportModel = Mage::getModel('bazaarvoice/exportPurchaseFeed');
28
+
29
+ // Call export
30
+ $exportModel->exportPurchaseFeed();
31
+
32
+ // Log mem usage
33
+ echo 'Memory usage: ' . memory_get_usage() . "\n";
34
+
35
+ } catch (Exception $e) {
36
+ Mage::printException($e);
37
+ }
38
+
shell/bv_export_product_feed.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ *
4
+ */
5
+
6
+ require 'app/Mage.php';
7
+
8
+ if (!Mage::isInstalled()) {
9
+ echo "Application is not installed yet, please complete install wizard first.";
10
+ exit;
11
+ }
12
+
13
+ // Only for urls
14
+ // Don't remove this
15
+ $_SERVER['SCRIPT_NAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_NAME']);
16
+ $_SERVER['SCRIPT_FILENAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_FILENAME']);
17
+
18
+ Mage::app('admin')->setUseSessionInUrl(false);
19
+
20
+ umask(0);
21
+
22
+ try {
23
+ // Log mem usage
24
+ echo "\n" . 'Memory usage: ' . memory_get_usage() . "\n";
25
+
26
+ // Create model
27
+ $exportModel = Mage::getModel('bazaarvoice/exportProductFeed');
28
+
29
+ // Call export
30
+ $exportModel->exportDailyProductFeed();
31
+
32
+ // Log mem usage
33
+ echo 'Memory usage: ' . memory_get_usage() . "\n";
34
+
35
+ } catch (Exception $e) {
36
+ Mage::printException($e);
37
+ }
38
+
skin/frontend/base/default/images/bazaarvoice/rating-0_0.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_0.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_1.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_2.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_3.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_4.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_5.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_6.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_7.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_8.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-1_9.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_0.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_1.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_2.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_3.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_4.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_5.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_6.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_7.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_8.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-2_9.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_0.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_1.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_2.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_3.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_4.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_5.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_6.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_7.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_8.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-3_9.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_0.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_1.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_2.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_3.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_4.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_5.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_6.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_7.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_8.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-4_9.gif ADDED
Binary file
skin/frontend/base/default/images/bazaarvoice/rating-5_0.gif ADDED
Binary file