Bootic - Version 1.0.0

Version Notes

First release

Download this release

Release Info

Developer Pierre Vigier
Extension Bootic
Version 1.0.0
Comparing to
See all releases


Version 1.0.0

Files changed (152) hide show
  1. app/code/community/Bootic/.DS_Store +0 -0
  2. app/code/community/Bootic/Bootic/.DS_Store +0 -0
  3. app/code/community/Bootic/Bootic/Block/.DS_Store +0 -0
  4. app/code/community/Bootic/Bootic/Block/Adminhtml/.DS_Store +0 -0
  5. app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog.php +9 -0
  6. app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog/Category.php +53 -0
  7. app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog/Tab/General.php +29 -0
  8. app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog/Tabs.php +32 -0
  9. app/code/community/Bootic/Bootic/Block/Adminhtml/Connect/Profile.php +24 -0
  10. app/code/community/Bootic/Bootic/Block/Adminhtml/Connect/Profile/.DS_Store +0 -0
  11. app/code/community/Bootic/Bootic/Block/Adminhtml/Connect/Profile/Form.php +402 -0
  12. app/code/community/Bootic/Bootic/Block/Adminhtml/Log.php +9 -0
  13. app/code/community/Bootic/Bootic/Block/Adminhtml/Log/Error.php +22 -0
  14. app/code/community/Bootic/Bootic/Block/Adminhtml/Log/Grid.php +69 -0
  15. app/code/community/Bootic/Bootic/Block/Adminhtml/Product.php +174 -0
  16. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Banner.php +9 -0
  17. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Create.php +28 -0
  18. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Create/Form.php +70 -0
  19. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit.php +50 -0
  20. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Form.php +22 -0
  21. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Design.php +124 -0
  22. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Design/Preview.php +55 -0
  23. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/General.php +134 -0
  24. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Settings.php +114 -0
  25. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Social.php +84 -0
  26. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tabs.php +46 -0
  27. app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Preview.php +14 -0
  28. app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Account/Create.php +24 -0
  29. app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Account/Create/Form.php +67 -0
  30. app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Button/Cron.php +51 -0
  31. app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Createaccount.php +52 -0
  32. app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Tabs.php +94 -0
  33. app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Testapiconnection.php +60 -0
  34. app/code/community/Bootic/Bootic/Block/Adminhtml/Widget/Grid/Column/Renderer/Log/Message.php +29 -0
  35. app/code/community/Bootic/Bootic/Block/Payment/Form.php +5 -0
  36. app/code/community/Bootic/Bootic/Block/Payment/Info.php +5 -0
  37. app/code/community/Bootic/Bootic/Exception.php +21 -0
  38. app/code/community/Bootic/Bootic/Helper/Abstract.php +25 -0
  39. app/code/community/Bootic/Bootic/Helper/Account.php +23 -0
  40. app/code/community/Bootic/Bootic/Helper/Api.php +58 -0
  41. app/code/community/Bootic/Bootic/Helper/Category.php +282 -0
  42. app/code/community/Bootic/Bootic/Helper/Connect.php +22 -0
  43. app/code/community/Bootic/Bootic/Helper/Data.php +57 -0
  44. app/code/community/Bootic/Bootic/Helper/Lists.php +162 -0
  45. app/code/community/Bootic/Bootic/Helper/Log.php +29 -0
  46. app/code/community/Bootic/Bootic/Helper/Message.php +57 -0
  47. app/code/community/Bootic/Bootic/Helper/Orders.php +568 -0
  48. app/code/community/Bootic/Bootic/Helper/Product.php +1007 -0
  49. app/code/community/Bootic/Bootic/Helper/Product/Data.php +129 -0
  50. app/code/community/Bootic/Bootic/Helper/Product/Inventory.php +58 -0
  51. app/code/community/Bootic/Bootic/Helper/Product/Type/Configurable.php +239 -0
  52. app/code/community/Bootic/Bootic/Helper/Storefront.php +51 -0
  53. app/code/community/Bootic/Bootic/Model/Category.php +12 -0
  54. app/code/community/Bootic/Bootic/Model/Category/Mapping.php +12 -0
  55. app/code/community/Bootic/Bootic/Model/Cron.php +43 -0
  56. app/code/community/Bootic/Bootic/Model/Log.php +12 -0
  57. app/code/community/Bootic/Bootic/Model/Message.php +15 -0
  58. app/code/community/Bootic/Bootic/Model/Message/Observer.php +19 -0
  59. app/code/community/Bootic/Bootic/Model/Mysql4/Category.php +152 -0
  60. app/code/community/Bootic/Bootic/Model/Mysql4/Category/Collection.php +12 -0
  61. app/code/community/Bootic/Bootic/Model/Mysql4/Category/Mapping.php +18 -0
  62. app/code/community/Bootic/Bootic/Model/Mysql4/Category/Mapping/Collection.php +15 -0
  63. app/code/community/Bootic/Bootic/Model/Mysql4/Log.php +15 -0
  64. app/code/community/Bootic/Bootic/Model/Mysql4/Log/Collection.php +21 -0
  65. app/code/community/Bootic/Bootic/Model/Mysql4/Message.php +17 -0
  66. app/code/community/Bootic/Bootic/Model/Mysql4/Order/Data.php +17 -0
  67. app/code/community/Bootic/Bootic/Model/Mysql4/Product/Data.php +18 -0
  68. app/code/community/Bootic/Bootic/Model/Mysql4/Product/Data/Collection.php +12 -0
  69. app/code/community/Bootic/Bootic/Model/Order/Data.php +38 -0
  70. app/code/community/Bootic/Bootic/Model/Order/Observer.php +41 -0
  71. app/code/community/Bootic/Bootic/Model/Payment.php +15 -0
  72. app/code/community/Bootic/Bootic/Model/Product/Data.php +122 -0
  73. app/code/community/Bootic/Bootic/Model/Product/Observer.php +29 -0
  74. app/code/community/Bootic/Bootic/Model/Profile.php +57 -0
  75. app/code/community/Bootic/Bootic/Model/Session/Observer.php +15 -0
  76. app/code/community/Bootic/Bootic/Model/Shipping/Flatrate.php +37 -0
  77. app/code/community/Bootic/Bootic/Model/Stock/Observer.php +42 -0
  78. app/code/community/Bootic/Bootic/Model/Storefront.php +21 -0
  79. app/code/community/Bootic/Bootic/Model/System/Config/Commission.php +18 -0
  80. app/code/community/Bootic/Bootic/Model/System/Config/Connection.php +27 -0
  81. app/code/community/Bootic/Bootic/Model/System/Config/Observer.php +16 -0
  82. app/code/community/Bootic/Bootic/Model/System/Config/Source/Attribute.php +26 -0
  83. app/code/community/Bootic/Bootic/Model/System/Config/Source/Image.php +28 -0
  84. app/code/community/Bootic/Bootic/Model/System/Config/Source/Store.php +12 -0
  85. app/code/community/Bootic/Bootic/controllers/Adminhtml/AbstractController.php +134 -0
  86. app/code/community/Bootic/Bootic/controllers/Adminhtml/Catalog/CategoryController.php +57 -0
  87. app/code/community/Bootic/Bootic/controllers/Adminhtml/Catalog/ProductController.php +25 -0
  88. app/code/community/Bootic/Bootic/controllers/Adminhtml/CatalogController.php +48 -0
  89. app/code/community/Bootic/Bootic/controllers/Adminhtml/ConnectController.php +142 -0
  90. app/code/community/Bootic/Bootic/controllers/Adminhtml/StorefrontController.php +196 -0
  91. app/code/community/Bootic/Bootic/controllers/Adminhtml/System/Config/AccountController.php +133 -0
  92. app/code/community/Bootic/Bootic/controllers/Adminhtml/System/Config/TestapiconnectionController.php +31 -0
  93. app/code/community/Bootic/Bootic/controllers/Adminhtml/System/CronController.php +31 -0
  94. app/code/community/Bootic/Bootic/controllers/IndexController.php +209 -0
  95. app/code/community/Bootic/Bootic/etc/adminhtml.xml +72 -0
  96. app/code/community/Bootic/Bootic/etc/config.xml +277 -0
  97. app/code/community/Bootic/Bootic/etc/system.xml +305 -0
  98. app/code/community/Bootic/Bootic/sql/.DS_Store +0 -0
  99. app/code/community/Bootic/Bootic/sql/bootic_setup/mysql4-install-1.0.0.php +86 -0
  100. app/design/adminhtml/default/default/layout/bootic.xml +69 -0
  101. app/design/adminhtml/default/default/template/bootic/adminhtml/system/config/button/cron.phtml +68 -0
  102. app/design/adminhtml/default/default/template/bootic/adminhtml/system/config/createaccount.phtml +17 -0
  103. app/design/adminhtml/default/default/template/bootic/adminhtml/system/config/testapiconnection.phtml +59 -0
  104. app/design/adminhtml/default/default/template/bootic/bootic.phtml +18 -0
  105. app/design/adminhtml/default/default/template/bootic/catalog/category.phtml +176 -0
  106. app/design/adminhtml/default/default/template/bootic/catalog/tab/general.phtml +10 -0
  107. app/design/adminhtml/default/default/template/bootic/log/error.phtml +38 -0
  108. app/design/adminhtml/default/default/template/bootic/products.phtml +7 -0
  109. app/design/adminhtml/default/default/template/bootic/storefront/banners.phtml +41 -0
  110. app/design/adminhtml/default/default/template/bootic/storefront/preview.phtml +62 -0
  111. app/etc/modules/Bootic_Bootic.xml +29 -0
  112. js/bootic/prototype-color-picker/.DS_Store +0 -0
  113. js/bootic/prototype-color-picker/css/prototype_colorpicker.css +183 -0
  114. js/bootic/prototype-color-picker/images/blank.gif +0 -0
  115. js/bootic/prototype-color-picker/images/colorpicker_background.png +0 -0
  116. js/bootic/prototype-color-picker/images/colorpicker_extra.png +0 -0
  117. js/bootic/prototype-color-picker/images/colorpicker_extra_background.png +0 -0
  118. js/bootic/prototype-color-picker/images/colorpicker_hex.png +0 -0
  119. js/bootic/prototype-color-picker/images/colorpicker_hsb_b.png +0 -0
  120. js/bootic/prototype-color-picker/images/colorpicker_hsb_h.png +0 -0
  121. js/bootic/prototype-color-picker/images/colorpicker_hsb_s.png +0 -0
  122. js/bootic/prototype-color-picker/images/colorpicker_indic.gif +0 -0
  123. js/bootic/prototype-color-picker/images/colorpicker_overlay.png +0 -0
  124. js/bootic/prototype-color-picker/images/colorpicker_rgb_b.png +0 -0
  125. js/bootic/prototype-color-picker/images/colorpicker_rgb_g.png +0 -0
  126. js/bootic/prototype-color-picker/images/colorpicker_rgb_r.png +0 -0
  127. js/bootic/prototype-color-picker/images/colorpicker_select.gif +0 -0
  128. js/bootic/prototype-color-picker/images/colorpicker_submit.png +0 -0
  129. js/bootic/prototype-color-picker/images/custom_background.png +0 -0
  130. js/bootic/prototype-color-picker/images/custom_hex.png +0 -0
  131. js/bootic/prototype-color-picker/images/custom_hsb_b.png +0 -0
  132. js/bootic/prototype-color-picker/images/custom_hsb_h.png +0 -0
  133. js/bootic/prototype-color-picker/images/custom_hsb_s.png +0 -0
  134. js/bootic/prototype-color-picker/images/custom_indic.gif +0 -0
  135. js/bootic/prototype-color-picker/images/custom_rgb_b.png +0 -0
  136. js/bootic/prototype-color-picker/images/custom_rgb_g.png +0 -0
  137. js/bootic/prototype-color-picker/images/custom_rgb_r.png +0 -0
  138. js/bootic/prototype-color-picker/images/custom_submit.png +0 -0
  139. js/bootic/prototype-color-picker/images/select.png +0 -0
  140. js/bootic/prototype-color-picker/images/select2.png +0 -0
  141. js/bootic/prototype-color-picker/images/slider.png +0 -0
  142. js/bootic/prototype-color-picker/index.html +215 -0
  143. js/bootic/prototype-color-picker/js/prototype.1.6.js +4874 -0
  144. js/bootic/prototype-color-picker/js/prototype_colorpicker.js +462 -0
  145. lib/Bootic/Api/Client.php +563 -0
  146. lib/Bootic/Api/Exception.php +8 -0
  147. lib/Bootic/Api/Exception/UnsupportedDataType.php +8 -0
  148. lib/Bootic/Api/Result.php +211 -0
  149. lib/Bootic/File/Uploader.php +72 -0
  150. lib/Bootic/Tests/Fixtures/account_create.json +5 -0
  151. lib/Bootic/Tests/Fixtures/order_list.json +141 -0
  152. package.xml +27 -0
app/code/community/Bootic/.DS_Store ADDED
Binary file
app/code/community/Bootic/Bootic/.DS_Store ADDED
Binary file
app/code/community/Bootic/Bootic/Block/.DS_Store ADDED
Binary file
app/code/community/Bootic/Bootic/Block/Adminhtml/.DS_Store ADDED
Binary file
app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Catalog extends Mage_Adminhtml_Block_Widget_Form
7
+ {
8
+
9
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog/Category.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Catalog_Category extends Mage_Adminhtml_Block_Widget_Container
7
+ {
8
+ public function __construct(){
9
+
10
+ parent::__construct();
11
+
12
+ if (!$this->hasData('template')) {
13
+ $this->setTemplate('bootic/catalog/category.phtml');
14
+ }
15
+
16
+ $this->_addButton('reset', array(
17
+ 'label' => Mage::helper('adminhtml')->__('Reset'),
18
+ 'onclick' => 'setLocation(window.location.href)',
19
+ ), -1);
20
+
21
+ $this->_addButton('save', array(
22
+ 'label' => Mage::helper('adminhtml')->__('Save'),
23
+ 'onclick' => 'editForm.submit();',
24
+ 'class' => 'save',
25
+ ), 1);
26
+
27
+ }
28
+
29
+ public function getHeaderText()
30
+ {
31
+ return Mage::helper('bootic')->__('Match Magento categories with Bootic categories');
32
+ }
33
+
34
+ /**
35
+ * Return Magento categories
36
+ */
37
+ public function getMagentoCategories()
38
+ {
39
+ return Mage::helper('bootic/category')->getMagentoCategories();
40
+ }
41
+
42
+ public function getJsonFormattedBooticCategories()
43
+ {
44
+ $json = Mage::helper('bootic/category')->getJsonFormattedBooticCategories();
45
+
46
+ return $json;
47
+ }
48
+
49
+ public function getCategoryMapping()
50
+ {
51
+ return Mage::registry('category_mapping');
52
+ }
53
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog/Tab/General.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Catalog_Tab_General extends Mage_Adminhtml_Block_Widget
7
+ {
8
+ public function __construct(){
9
+
10
+ parent::__construct();
11
+ $this->setHtmlId('general');
12
+ $this->setTemplate('bootic/catalog/tab/general.phtml');
13
+
14
+ }
15
+
16
+ protected function _prepareLayout()
17
+ {
18
+ // Main Grid
19
+ $block = $this->getLayout()->createBlock('bootic/adminhtml_product');
20
+ $block->setTemplate('bootic/products.phtml');
21
+ $this->setChild('bootic_products', $block);
22
+
23
+ // last logs table
24
+ $lastErrorsBlock = $this->getLayout()->createBlock('bootic/adminhtml_log_error');
25
+ $lastErrorsBlock->setTemplate('bootic/log/error.phtml');
26
+ $this->setChild('bootic_last_errors', $lastErrorsBlock);
27
+
28
+ }
29
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Catalog/Tabs.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Catalog_Tabs extends Mage_Adminhtml_Block_Widget_Tabs
7
+ {
8
+ public function __construct() {
9
+
10
+ parent::__construct();
11
+ $this->setId('bootic_catalog_tab');
12
+ $this->setDestElementId('bootic_catalog_tab_content');
13
+ $this->setTemplate('widget/tabshoriz.phtml');
14
+
15
+ }
16
+
17
+ protected function _beforeToHtml(){
18
+
19
+ $this->addTab('general', array(
20
+ 'label' => Mage::helper('bootic')->__('General'),
21
+ 'content' => $this->getLayout()->createBlock('bootic/adminhtml_catalog_tab_general')->toHtml(),
22
+ 'active' => true
23
+ ));
24
+
25
+ $this->addTab('log', array(
26
+ 'label' => Mage::helper('bootic')->__('Log'),
27
+ 'content' => $this->getLayout()->createBlock('bootic/adminhtml_log_grid')->toHtml()
28
+ ));
29
+
30
+ return parent::_beforeToHtml();
31
+ }
32
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Connect/Profile.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Connect_Profile extends Mage_Adminhtml_Block_Widget_Form_Container
7
+ {
8
+ public function __construct()
9
+ {
10
+ parent::__construct();
11
+
12
+ $this->_objectId = 'id';
13
+ $this->_blockGroup = 'bootic';
14
+ $this->_controller = 'adminhtml_connect';
15
+ $this->_mode = 'profile';
16
+
17
+ $this->_removeButton('back');
18
+ }
19
+
20
+ public function getHeaderText()
21
+ {
22
+ return Mage::helper('bootic')->__('Edit your profile');
23
+ }
24
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Connect/Profile/.DS_Store ADDED
Binary file
app/code/community/Bootic/Bootic/Block/Adminhtml/Connect/Profile/Form.php ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Connect_Profile_Form extends Mage_Adminhtml_Block_Widget_Form
7
+ {
8
+ protected $_countries = array(
9
+ "" => "-- Please Select --",
10
+ "11" => "Afghanistan",
11
+ "12" => "Albania",
12
+ "13" => "Algeria",
13
+ "14" => "Andorra",
14
+ "15" => "Angola",
15
+ "16" => "Antigua and Barbuda",
16
+ "17" => "Argentina",
17
+ "18" => "Armenia",
18
+ "19" => "Australia",
19
+ "20" => "Austria",
20
+ "21" => "Azerbaijan",
21
+ "22" => "Bahamas, The",
22
+ "23" => "Bahrain",
23
+ "24" => "Bangladesh",
24
+ "25" => "Barbados",
25
+ "26" => "Belarus",
26
+ "27" => "Belgium",
27
+ "28" => "Belize",
28
+ "29" => "Benin",
29
+ "30" => "Bhutan",
30
+ "31" => "Bolivia",
31
+ "32" => "Bosnia and Herzegovina",
32
+ "33" => "Botswana",
33
+ "34" => "Brazil",
34
+ "35" => "Brunei",
35
+ "36" => "Bulgaria",
36
+ "37" => "Burkina Faso",
37
+ "38" => "Burma",
38
+ "39" => "Burundi",
39
+ "40" => "Cambodia",
40
+ "41" => "Cameroon",
41
+ "10" => "Canada",
42
+ "42" => "Cape Verde",
43
+ "43" => "Central African Republic",
44
+ "44" => "Chad",
45
+ "45" => "Chile",
46
+ "5" => "China",
47
+ "46" => "Colombia",
48
+ "47" => "Comoros",
49
+ "48" => "Congo, Democratic Republic of the",
50
+ "49" => "Congo, Republic of the",
51
+ "50" => "Costa Rica",
52
+ "51" => "Cote d Ivoire",
53
+ "52" => "Croatia",
54
+ "53" => "Cuba",
55
+ "54" => "Cyprus",
56
+ "55" => "Czech Republic",
57
+ "56" => "Denmark",
58
+ "57" => "Djibouti",
59
+ "58" => "Dominica",
60
+ "59" => "Dominican Republic",
61
+ "61" => "Ecuador",
62
+ "62" => "Egypt",
63
+ "63" => "El Salvador",
64
+ "64" => "Equatorial Guinea",
65
+ "65" => "Eritrea",
66
+ "66" => "Estonia",
67
+ "67" => "Ethiopia",
68
+ "68" => "Fiji",
69
+ "69" => "Finland",
70
+ "1" => "France",
71
+ "70" => "Gabon",
72
+ "71" => "Gambia, The",
73
+ "72" => "Georgia",
74
+ "73" => "Germany",
75
+ "74" => "Ghana",
76
+ "75" => "Greece",
77
+ "76" => "Grenada",
78
+ "77" => "Guatemala",
79
+ "78" => "Guinea",
80
+ "79" => "Guinea-Bissau",
81
+ "80" => "Guyana",
82
+ "81" => "Haiti",
83
+ "82" => "Holy See",
84
+ "83" => "Honduras",
85
+ "84" => "Hong Kong",
86
+ "85" => "Hungary",
87
+ "86" => "Iceland",
88
+ "87" => "India",
89
+ "88" => "Indonesia",
90
+ "89" => "Iran",
91
+ "90" => "Iraq",
92
+ "7" => "Ireland",
93
+ "91" => "Israel",
94
+ "92" => "Italy",
95
+ "93" => "Jamaica",
96
+ "94" => "Japan",
97
+ "95" => "Jordan",
98
+ "96" => "Kazakhstan",
99
+ "97" => "Kenya",
100
+ "98" => "Kiribati",
101
+ "99" => "Korea, North",
102
+ "100" => "Korea, South",
103
+ "101" => "Kosovo",
104
+ "102" => "Kuwait",
105
+ "103" => "Kyrgyzstan",
106
+ "104" => "Laos",
107
+ "105" => "Latvia",
108
+ "106" => "Lebanon",
109
+ "107" => "Lesotho",
110
+ "108" => "Liberia",
111
+ "109" => "Libya",
112
+ "110" => "Liechtenstein",
113
+ "111" => "Lithuania",
114
+ "112" => "Luxembourg",
115
+ "113" => "Macau",
116
+ "114" => "Macedonia",
117
+ "115" => "Madagascar",
118
+ "116" => "Malawi",
119
+ "117" => "Malaysia",
120
+ "118" => "Maldives",
121
+ "119" => "Mali",
122
+ "120" => "Malta",
123
+ "121" => "Marshall Islands",
124
+ "122" => "Mauritania",
125
+ "123" => "Mauritius",
126
+ "124" => "Mexico",
127
+ "125" => "Micronesia",
128
+ "126" => "Moldova",
129
+ "127" => "Monaco",
130
+ "128" => "Mongolia",
131
+ "129" => "Montenegro",
132
+ "130" => "Morocco",
133
+ "131" => "Mozambique",
134
+ "132" => "Namibia",
135
+ "133" => "Nauru",
136
+ "134" => "Nepal",
137
+ "135" => "Netherlands",
138
+ "136" => "Netherlands Antilles",
139
+ "137" => "New Zealand",
140
+ "138" => "Nicaragua",
141
+ "139" => "Niger",
142
+ "140" => "Nigeria",
143
+ "141" => "North Korea",
144
+ "142" => "Norway",
145
+ "143" => "Oman",
146
+ "144" => "Pakistan",
147
+ "145" => "Palau",
148
+ "146" => "Palestinian Territories",
149
+ "147" => "Panama",
150
+ "148" => "Papua New Guinea",
151
+ "149" => "Paraguay",
152
+ "150" => "Peru",
153
+ "151" => "Philippines",
154
+ "152" => "Poland",
155
+ "153" => "Portugal",
156
+ "154" => "Qatar",
157
+ "155" => "Romania",
158
+ "8" => "Russia",
159
+ "156" => "Rwanda",
160
+ "157" => "Saint Kitts and Nevis",
161
+ "158" => "Saint Lucia",
162
+ "159" => "Saint Vincent and the Grenadines",
163
+ "160" => "Samoa ",
164
+ "161" => "San Marino",
165
+ "162" => "Sao Tome and Principe",
166
+ "163" => "Saudi Arabia",
167
+ "164" => "Senegal",
168
+ "165" => "Serbia",
169
+ "166" => "Seychelles",
170
+ "167" => "Sierra Leone",
171
+ "6" => "Singapore",
172
+ "168" => "Slovakia",
173
+ "169" => "Slovenia",
174
+ "170" => "Solomon Islands",
175
+ "171" => "Somalia",
176
+ "172" => "South Africa",
177
+ "173" => "South Korea",
178
+ "174" => "South Sudan",
179
+ "9" => "Spain",
180
+ "175" => "Sri Lanka",
181
+ "176" => "Sudan",
182
+ "177" => "Suriname",
183
+ "178" => "Swaziland ",
184
+ "179" => "Sweden",
185
+ "180" => "Switzerland",
186
+ "181" => "Syria",
187
+ "182" => "Taiwan",
188
+ "183" => "Tajikistan",
189
+ "184" => "Tanzania",
190
+ "185" => "Thailand ",
191
+ "60" => "Timor-Leste",
192
+ "187" => "Togo",
193
+ "188" => "Tonga",
194
+ "189" => "Trinidad and Tobago",
195
+ "190" => "Tunisia",
196
+ "191" => "Turkey",
197
+ "192" => "Turkmenistan",
198
+ "193" => "Tuvalu",
199
+ "194" => "Uganda",
200
+ "195" => "Ukraine",
201
+ "196" => "United Arab Emirates",
202
+ "4" => "United Kingdom",
203
+ "197" => "Uruguay",
204
+ "2" => "USA",
205
+ "198" => "Uzbekistan",
206
+ "199" => "Vanuatu",
207
+ "200" => "Venezuela",
208
+ "201" => "Vietnam",
209
+ "202" => "Yemen",
210
+ "203" => "Zambia",
211
+ "204" => "Zimbabwe"
212
+ );
213
+
214
+ protected $_regions = array(
215
+ "" => "-- Please select --",
216
+ "1" => "Alabama",
217
+ "2" => "Alaska",
218
+ "3" => "Arizona",
219
+ "4" => "Arkansas",
220
+ "5" => "California",
221
+ "6" => "Colorado",
222
+ "7" => "Connecticut",
223
+ "8" => "Delaware",
224
+ "51" => "District of Columbia",
225
+ "9" => "Florida",
226
+ "10" => "Georgia",
227
+ "11" => "Hawaii",
228
+ "12" => "Idaho",
229
+ "13" => "Illinois",
230
+ "14" => "Indiana",
231
+ "15" => "Iowa",
232
+ "16" => "Kansas",
233
+ "17" => "Kentucky",
234
+ "18" => "Louisiana",
235
+ "19" => "Maine",
236
+ "20" => "Maryland",
237
+ "21" => "Massachusetts",
238
+ "22" => "Michigan",
239
+ "23" => "Minnesota",
240
+ "24" => "Mississippi",
241
+ "25" => "Missouri",
242
+ "26" => "Montana",
243
+ "27" => "Nebraska",
244
+ "28" => "Nevada",
245
+ "29" => "New Hampshire",
246
+ "30" => "New Jersey",
247
+ "31" => "New Mexico",
248
+ "32" => "New York",
249
+ "33" => "North Carolina",
250
+ "34" => "North Dakota",
251
+ "35" => "Ohio",
252
+ "36" => "Oklahoma",
253
+ "37" => "Oregon",
254
+ "38" => "Pennsylvania",
255
+ "39" => "Rhode Island",
256
+ "40" => "South Carolina",
257
+ "41" => "South Dakota",
258
+ "42" => "Tennessee",
259
+ "43" => "Texas",
260
+ "44" => "Utah",
261
+ "45" => "Vermont",
262
+ "46" => "Virginia",
263
+ "47" => "Washington",
264
+ "48" => "West Virginia",
265
+ "49" => "Wisconsin",
266
+ "50" => "Wyoming",
267
+ "0" => "Not applicable"
268
+ );
269
+
270
+
271
+ protected function _prepareForm()
272
+ {
273
+ $helper = Mage::helper('bootic');
274
+
275
+ $form = new Varien_Data_Form(array(
276
+ 'id' => 'edit_form',
277
+ 'action' => $this->getUrl('*/*/save'),
278
+ 'method' => 'post',
279
+ 'enctype' => 'multipart/form-data'
280
+ ));
281
+
282
+ $form->setUseContainer(true);
283
+ $this->setForm($form);
284
+
285
+ $fieldset = $form->addFieldset('connect_profile_form', array('legend' => $helper->__('Profile')));
286
+
287
+ $fieldset->addField('name', 'text', array(
288
+ 'label' => $helper->__('Name'),
289
+ 'class' => 'required-entry',
290
+ 'required' => true,
291
+ 'name' => 'name',
292
+ ));
293
+
294
+ $fieldset->addField('company', 'text', array(
295
+ 'label' => $helper->__('Company'),
296
+ 'name' => 'company',
297
+ ));
298
+
299
+ $fieldset->addField('address', 'text', array(
300
+ 'label' => $helper->__('Address line 1'),
301
+ 'name' => 'address'
302
+ ));
303
+
304
+ $fieldset->addField('address2', 'text', array(
305
+ 'label' => $helper->__('Address line 2'),
306
+ 'name' => 'address2'
307
+ ));
308
+
309
+ $countries = array();
310
+ foreach ($this->_countries as $code => $country) {
311
+ $countries[] = array(
312
+ 'value' => $code,
313
+ 'label' => $helper->__($country)
314
+ );
315
+ }
316
+
317
+ $fieldset->addField('country', 'select', array(
318
+ 'label' => $helper->__('Country'),
319
+ 'name' => 'country',
320
+ 'class' => 'required-entry',
321
+ 'required' => true,
322
+ 'values' => $countries
323
+ ));
324
+
325
+ $regions = array();
326
+ foreach ($this->_regions as $code => $region) {
327
+ $regions[] = array(
328
+ 'value' => $code,
329
+ 'label' => $helper->__($region)
330
+ );
331
+ }
332
+
333
+ $fieldset->addField('region', 'select', array(
334
+ 'label' => $helper->__('State'),
335
+ 'name' => 'region',
336
+ 'class' => 'required-entry',
337
+ 'required' => true,
338
+ 'values' => $regions,
339
+ ));
340
+
341
+ $fieldset->addField('city', 'text', array(
342
+ 'label' => $helper->__('City'),
343
+ 'name' => 'city',
344
+ ));
345
+
346
+ $fieldset->addField('post_code', 'text', array(
347
+ 'label' => $helper->__('Postal Code'),
348
+ 'name' => 'post_code'
349
+ ));
350
+
351
+ $fieldset->addField('phone_number', 'text', array(
352
+ 'label' => $helper->__('Phone'),
353
+ 'name' => 'phone_number'
354
+ ));
355
+
356
+ $fieldset->addField('ssn_tax_id', 'text', array(
357
+ 'label' => $helper->__('Tax ID (or SSN for individuals)'),
358
+ 'name' => 'ssn_tax_id'
359
+ ));
360
+
361
+ $fieldset->addField('show_phone_number', 'select', array(
362
+ 'label' => Mage::helper('bootic')->__('Display my phone number on storefront:'),
363
+ 'name' => 'show_phone_number',
364
+ 'values' => array(
365
+ 0 => array(
366
+ 'value' => 1,
367
+ 'label' => 'Yes'
368
+ ),
369
+ 1 => array(
370
+ 'value' => 0,
371
+ 'label' => 'No'
372
+ )
373
+ )
374
+ ));
375
+
376
+ $fieldset2 = $form->addFieldset('connect_profile_image_form', array('legend' => $helper->__('Profile Image')));
377
+
378
+ $fieldset2->addField('picture', 'image', array(
379
+ 'label' => $helper->__('Profile Image'),
380
+ 'name' => 'picture'
381
+ ));
382
+
383
+ $fieldset3 = $form->addFieldset('connect_profile_paypal_form', array('legend' => $helper->__('Paypal Account')));
384
+
385
+ $fieldset3->addField('payment_merchant_paypal_account', 'text', array(
386
+ 'label' => $helper->__('Paypal Account for Bootic withdrawal:'),
387
+ 'name' => 'payment_merchant_paypal_account'
388
+ ));
389
+
390
+ $this->setChild(
391
+ 'form_after',
392
+ $this->getLayout()->createBlock('adminhtml/widget_form_element_dependence')
393
+ ->addFieldMap('country', 'country')
394
+ ->addFieldMap('region', 'region')
395
+ ->addFieldDependence('region', 'country', '2')
396
+ );
397
+
398
+ $form->setValues(Mage::getSingleton('bootic/profile')->getData());
399
+
400
+ return parent::_prepareForm();
401
+ }
402
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Log.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Log extends Mage_Adminhtml_Block_Widget_Form
7
+ {
8
+
9
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Log/Error.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Log_Error extends Mage_Adminhtml_Block_Widget
7
+ {
8
+ public function getErrors(){
9
+
10
+ $timestamp = Mage::getModel('core/date')->timestamp() - 3600 * 24;
11
+
12
+ $collection = mage::getModel('bootic/log')
13
+ ->getCollection()
14
+ ->addFieldToFilter('status', array('neq' => 'success'))
15
+ ->addAttributeToSort('id', 'desc')
16
+ ->addFieldToFilter('date', array('gt'=>date('Y-m-d H:i:s', $timestamp)));
17
+
18
+ $collection->getSelect()->limit(5);
19
+
20
+ return $collection;
21
+ }
22
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Log/Grid.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Log_Grid extends Mage_Adminhtml_Block_Widget_Grid
7
+ {
8
+ public function __construct() {
9
+ parent::__construct();
10
+ $this->setId('Log');
11
+ $this->_parentTemplate = $this->getTemplate();
12
+ $this->setEmptyText('No Logs');
13
+ $this->setSaveParametersInSession(true);
14
+ }
15
+
16
+ /**
17
+ * Load collection
18
+ *
19
+ * @return unknown
20
+ */
21
+ protected function _prepareCollection() {
22
+ $collection = mage::getModel('bootic/log')->getCollection()->addAttributeToSort('id', 'desc');
23
+ $this->setCollection($collection);
24
+ return parent::_prepareCollection();
25
+ }
26
+
27
+ /**
28
+ * Grid configuration
29
+ *
30
+ * @return unknown
31
+ */
32
+ protected function _prepareColumns() {
33
+
34
+ $this->addColumn('id', array(
35
+ 'header'=> Mage::helper('bootic')->__('Id'),
36
+ 'index' => 'id'
37
+ ));
38
+
39
+ $this->addColumn('product_id', array(
40
+ 'header'=> Mage::helper('bootic')->__('Product Id'),
41
+ 'index' => 'product_id'
42
+ ));
43
+
44
+ $this->addColumn('date', array(
45
+ 'header'=> Mage::helper('bootic')->__('Date'),
46
+ 'index' => 'date'
47
+ ));
48
+
49
+ $this->addColumn('status', array(
50
+ 'header'=> Mage::helper('bootic')->__('Status'),
51
+ 'index' => 'status'
52
+ ));
53
+
54
+ $this->addColumn('message', array(
55
+ 'header'=> Mage::helper('bootic')->__('Message'),
56
+ 'index' => 'message',
57
+ 'width' => '800',
58
+ 'renderer' => 'Bootic_Bootic_Block_Adminhtml_Widget_Grid_Column_Renderer_Log_Message'
59
+ ));
60
+
61
+ return parent::_prepareColumns();
62
+
63
+ }
64
+
65
+ public function getGridParentHtml() {
66
+ $templateName = Mage::getDesign()->getTemplateFilename($this->_parentTemplate, array('_relative'=>true));
67
+ return $this->fetchView($templateName);
68
+ }
69
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Product.php ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Product extends Mage_Adminhtml_Block_Widget_Grid
7
+ {
8
+ public function __construct()
9
+ {
10
+ parent::__construct();
11
+
12
+ $this->setId('ProductGrid');
13
+ $this->_parentTemplate = $this->getTemplate();
14
+ $this->setEmptyText('No Products');
15
+ $this->setSaveParametersInSession(true);
16
+ $this->setDefaultSort('entity_id');
17
+ $this->setDefaultDir('DESC');
18
+ }
19
+
20
+ protected function _prepareCollection()
21
+ {
22
+ $collection = mage::getResourceModel('catalog/product_collection')
23
+ ->addAttributeToSelect('*')
24
+ ->addAttributeToFilter('type_id', array('nin' => array('bundle', 'virtual', 'grouped', 'downloadable')))
25
+ ->addAttributeToFilter('status', 1)
26
+ ->joinTable(
27
+ 'bootic/product_data',
28
+ 'magento_product_id=entity_id',
29
+ array(
30
+ 'bootic_product_id' => 'bootic_product_id',
31
+ 'bootic_stock_id' => 'bootic_stock_id',
32
+ 'bootic_status' => 'bootic_status',
33
+ 'creation_time' => 'creation_time',
34
+ 'update_time' => 'update_time',
35
+ 'upload_failures' => 'upload_failures'
36
+ ),
37
+ null,
38
+ 'left'
39
+ )
40
+ ;
41
+
42
+ if (Mage::helper('catalog')->isModuleEnabled('Mage_CatalogInventory')) {
43
+ $collection->joinField('qty',
44
+ 'cataloginventory/stock_item',
45
+ 'qty',
46
+ 'product_id=entity_id',
47
+ '{{table}}.stock_id=1',
48
+ 'left');
49
+ }
50
+
51
+ $this->setCollection($collection);
52
+
53
+ return parent::_prepareCollection();
54
+ }
55
+
56
+ protected function _prepareColumns()
57
+ {
58
+ $this->addColumn('id', array(
59
+ 'header'=> Mage::helper('bootic')->__('ID'),
60
+ 'width' => '50px',
61
+ 'type' => 'number',
62
+ 'index' => 'entity_id',
63
+ ));
64
+
65
+ $this->addColumn('sku', array(
66
+ 'header'=> Mage::helper('bootic')->__('Sku'),
67
+ 'index' => 'sku',
68
+ ));
69
+
70
+ $this->addColumn('name', array(
71
+ 'header'=> Mage::helper('bootic')->__('Product'),
72
+ 'index' => 'name'
73
+ ));
74
+
75
+ $sets = Mage::getResourceModel('eav/entity_attribute_set_collection')
76
+ ->setEntityTypeFilter(Mage::getModel('catalog/product')->getResource()->getTypeId())
77
+ ->load()
78
+ ->toOptionHash();
79
+
80
+ $this->addColumn('set_name',
81
+ array(
82
+ 'header'=> Mage::helper('catalog')->__('Attrib. Set Name'),
83
+ 'width' => '100px',
84
+ 'index' => 'attribute_set_id',
85
+ 'type' => 'options',
86
+ 'options' => $sets,
87
+ ));
88
+
89
+ $options = Mage::getSingleton('catalog/product_type')->getOptionArray();
90
+ foreach (array('virtual', 'grouped', 'bundle', 'downloadable') as $type) {
91
+ unset($options[$type]);
92
+ }
93
+
94
+ $this->addColumn('type',
95
+ array(
96
+ 'header'=> Mage::helper('catalog')->__('Type'),
97
+ 'width' => '140px',
98
+ 'index' => 'type_id',
99
+ 'type' => 'options',
100
+ 'options' => $options,
101
+ ));
102
+
103
+ $this->addColumn('price', array(
104
+ 'header' => Mage::helper('bootic')->__('Price excl tax'),
105
+ 'index' => 'price',
106
+ 'type' => 'price',
107
+ 'align' => 'right',
108
+ 'currency_code' => Mage::getStoreConfig('currency/options/base')
109
+ ));
110
+
111
+ $this->addColumn('visibility',
112
+ array(
113
+ 'header' => Mage::helper('bootic')->__('Visibility'),
114
+ 'width' => '150px',
115
+ 'index' => 'visibility',
116
+ 'type' => 'options',
117
+ 'align' => 'center',
118
+ 'options' => Mage::getModel('catalog/product_visibility')->getOptionArray(),
119
+ ));
120
+
121
+ $this->addColumn('qty', array(
122
+ 'header' => Mage::helper('bootic')->__('Qty'),
123
+ 'width' => '100px',
124
+ 'type' => 'number',
125
+ 'index' => 'qty'
126
+ ));
127
+
128
+ $this->addColumn('status', array(
129
+ 'header' => Mage::helper('bootic')->__('Bootic Status'),
130
+ 'width' => '160px',
131
+ 'index' => 'bootic_status',
132
+ 'type' => 'options',
133
+ 'sortable' => true,
134
+ 'options' => Mage::getModel('bootic/product_data')->getOptionArray(),
135
+ // 'filter' => 'Bootic_Bootic_Block_Adminhtml_Widget_Grid_Column_Filter_Product_Status',
136
+ 'default' => 0
137
+ ));
138
+
139
+ return parent::_prepareColumns();
140
+
141
+ }
142
+
143
+ public function getGridParentHtml()
144
+ {
145
+ $templateName = Mage::getDesign()->getTemplateFilename($this->_parentTemplate, array('_relative'=>true));
146
+ return $this->fetchView($templateName);
147
+ }
148
+
149
+ protected function _prepareMassaction(){
150
+
151
+ $this->setMassactionIdField('id');
152
+ $this->getMassactionBlock()->setFormFieldName('ids');
153
+
154
+ $this->getMassactionBlock()->addItem('add_selection', array(
155
+ 'label'=> Mage::helper('bootic')->__('Add to Bootic'),
156
+ 'url' => $this->getUrl('bootic/adminhtml_catalog_product/massAddProducts'),
157
+ ));
158
+
159
+ // $this->getMassactionBlock()->addItem('reset_selection', array(
160
+ // 'label'=> Mage::helper('bootic')->__('Set to Not Created'),
161
+ // 'url' => $this->getUrl('bootic/adminhtml_catalog_product/massResetProducts'),
162
+ // ));
163
+
164
+ return $this;
165
+ }
166
+
167
+ public function getRowUrl($row)
168
+ {
169
+ return $this->getUrl('adminhtml/catalog_product/edit', array(
170
+ 'store' => $this->getRequest()->getParam('store'),
171
+ 'id' => $row->getId())
172
+ );
173
+ }
174
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Banner.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * (c) Newcode <contact@newcodestudio.com>
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Banner extends Mage_Core_Block_Template
7
+ {
8
+
9
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Create.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Create extends Mage_Adminhtml_Block_Widget_Form_Container
7
+ {
8
+ public function __construct()
9
+ {
10
+ $this->_objectId = 'id';
11
+ $this->_blockGroup = 'bootic';
12
+ $this->_controller = 'adminhtml_storefront';
13
+ $this->_mode = 'create';
14
+
15
+ parent::__construct();
16
+
17
+ $this->removeButton('back');
18
+ $this->removeButton('reset');
19
+ $this->_updateButton('save', 'label', Mage::helper('bootic')->__('Create Storefront'));
20
+ }
21
+
22
+ public function getHeaderText()
23
+ {
24
+ $headerText = Mage::helper('bootic')->__('Create Storefront');
25
+
26
+ return $headerText;
27
+ }
28
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Create/Form.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Create_Form extends Mage_Adminhtml_Block_Widget_Form
7
+ {
8
+ public function _prepareForm()
9
+ {
10
+ $form = new Varien_Data_Form(array(
11
+ 'id' => 'edit_form',
12
+ 'action' => $this->getUrl('*/*/create'),
13
+ 'method' => 'post'
14
+ ));
15
+
16
+ $fieldset = $form->addFieldset('name_fieldset', array('legend' => Mage::helper('bootic')->__('Name')));
17
+
18
+ $fieldset->addField('note', 'note', array(
19
+ 'name' => 'note',
20
+ 'text' => Mage::helper('bootic')->__('Please start by filling this simple form to create your storefront. On the next step, you will be able to configure it more thoroughly.')
21
+ ));
22
+
23
+ $fieldset->addField('name', 'text', array(
24
+ 'label' => Mage::helper('bootic')->__('Name your new Storefront:'),
25
+ 'class' => 'required-entry',
26
+ 'required' => true,
27
+ 'name' => 'name',
28
+ ));
29
+
30
+ $url = $fieldset->addField('url', 'text', array(
31
+ 'label' => Mage::helper('bootic')->__('URL:'),
32
+ 'required' => true,
33
+ 'name' => 'url',
34
+ ));
35
+
36
+ $comment = '<p class="note"><span>'.Mage::helper('bootic')->__('Your storefront URL on Bootic').'</span></p>';
37
+ $js = "<script type='text/javascript'>
38
+ $('url').insert({
39
+ before: 'http://bootic.com/ '
40
+ });
41
+
42
+ $('name').observe('blur', respondToBlur);
43
+
44
+ function respondToBlur(event)
45
+ {
46
+ if ($('url').getValue() == '') {
47
+ $('url').setValue($('name').getValue())
48
+ }
49
+ }
50
+
51
+ </script>";
52
+ $url->setAfterElementHtml($comment . $js);
53
+
54
+ $fieldset->addField('template', 'select', array(
55
+ 'label' => Mage::helper('bootic')->__('Template:'),
56
+ 'name' => 'template',
57
+ 'values' => Mage::helper('bootic/storefront')->getAvailableTemplatesValues()
58
+ ));
59
+
60
+ $fieldset->addField('color_theme', 'hidden', array(
61
+ 'name' => 'color_theme',
62
+ 'value' => '0CA3DB'
63
+ ));
64
+
65
+ $form->setUseContainer(true);
66
+ $this->setForm($form);
67
+
68
+ return parent::_prepareForm();
69
+ }
70
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
7
+ {
8
+ public function __construct()
9
+ {
10
+ $this->_objectId = 'id';
11
+ $this->_blockGroup = 'bootic';
12
+ $this->_controller = 'adminhtml_storefront';
13
+ $this->_mode = 'edit';
14
+
15
+ parent::__construct();
16
+
17
+ $this->removeButton('back');
18
+ $this->_updateButton('save', 'label', Mage::helper('bootic')->__('Save Storefront'));
19
+ }
20
+
21
+ public function getHeaderText()
22
+ {
23
+ $headerText = Mage::helper('bootic')->__('Edit Storefront');
24
+
25
+ return $headerText;
26
+ }
27
+
28
+ /**
29
+ * Prepare layout
30
+ *
31
+ * @return Mage_Core_Block_Abstract
32
+ */
33
+ protected function _prepareLayout()
34
+ {
35
+ $color = Mage::getSingleton('bootic/storefront')->getColor_theme() ? Mage::getSingleton('bootic/storefront')->getColor_theme() : null;
36
+
37
+ $this->_formScripts[] = "
38
+ var cp = new colorPicker('storefront_color_theme', {
39
+ color:'#" . $color . "',
40
+ previewElement:'color-preview',
41
+ onChange: function (e) {
42
+ var val = '#' + e.HSBToHex(e.color);
43
+ $('previewDiv').setStyle({ backgroundColor: val });
44
+ }
45
+ });
46
+ ";
47
+
48
+ return parent::_prepareLayout();
49
+ }
50
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Form.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
7
+ {
8
+ public function _prepareForm()
9
+ {
10
+ $form = new Varien_Data_Form(array(
11
+ 'id' => 'edit_form',
12
+ 'action' => $this->getUrl('*/*/save'),
13
+ 'method' => 'post',
14
+ 'enctype' => 'multipart/form-data'
15
+ ));
16
+
17
+ $form->setUseContainer(true);
18
+ $this->setForm($form);
19
+
20
+ return parent::_prepareForm();
21
+ }
22
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Design.php ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Tab_Design
7
+ extends Mage_Adminhtml_Block_Widget_Form
8
+ implements Mage_Adminhtml_Block_Widget_Tab_Interface
9
+ {
10
+ protected function _prepareForm()
11
+ {
12
+ $form = new Varien_Data_Form();
13
+
14
+ $form->setHtmlIdPrefix('storefront_');
15
+
16
+ $fieldset = $form->addFieldset('design_fieldset', array('legend' => Mage::helper('bootic')->__('Design')));
17
+
18
+ $fieldset->addField('template', 'select', array(
19
+ 'label' => Mage::helper('bootic')->__('Template:'),
20
+ 'name' => 'template',
21
+ 'values' => Mage::helper('bootic/storefront')->getAvailableTemplatesValues()
22
+ ));
23
+
24
+ $fieldset->addField('color_theme', 'text', array(
25
+ 'name' => 'color_theme',
26
+ 'label' => Mage::helper('bootic')->__('Color:'),
27
+ 'style' => "width:100px;float:left;margin-right:10px;",
28
+ 'after_element_html' => '<div id="color-preview" style="display:block;width:15px;height:15px;float:left;padding:1px;border:1px solid #AAA;"></div>'
29
+ ));
30
+
31
+ $bannerFieldset = $form->addFieldset('banner_fieldset', array('legend' => Mage::helper('bootic')->__('Storefront Banner')));
32
+
33
+ $bannerFieldset->addField('banner', 'hidden', array(
34
+ 'label' => Mage::helper('bootic')->__('Default Banner'),
35
+ 'name' => 'banner',
36
+ 'note' => Mage::helper('bootic')->__('You can pick up from'),
37
+ 'after_element_html' => $this->renderBannerSelector() . $this->renderPreview()
38
+ ));
39
+
40
+ $bannerFieldset->addField('banner_url', 'image', array(
41
+ 'label' => Mage::helper('bootic')->__('Custom Banner'),
42
+ 'name' => 'banner_url',
43
+ 'note' => Mage::helper('bootic')->__('Image has to be either png, gif or jpg and be exactly 996*180px'),
44
+ ));
45
+
46
+ $form->addFieldset('preview_fieldset', array(
47
+ 'legend' => Mage::helper('bootic')->__('Storefront Preview'),
48
+ ));
49
+
50
+ // $previewFieldset->addType('preview','Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Tab_Design_Preview');
51
+ //
52
+ // $previewFieldset->addField('preview_field', 'preview', array(
53
+ // 'name' => 'preview_field',
54
+ // 'required' => false,
55
+ // ));
56
+
57
+ if (Mage::getSingleton('bootic/storefront')->getData('color_theme') == null) {
58
+ Mage::getSingleton('bootic/storefront')->setData('color_theme', '000000');
59
+ }
60
+ $form->setValues(Mage::getSingleton('bootic/storefront')->getData());
61
+ $this->setForm($form);
62
+
63
+ return parent::_prepareForm();
64
+ }
65
+
66
+ /**
67
+ * Return Tab label
68
+ *
69
+ * @return string
70
+ */
71
+ public function getTabLabel()
72
+ {
73
+ return Mage::helper('bootic')->__('Storefront Information');
74
+ }
75
+
76
+ /**
77
+ * Return Tab title
78
+ *
79
+ * @return string
80
+ */
81
+ public function getTabTitle()
82
+ {
83
+ return Mage::helper('bootic')->__('Storefront Information');
84
+ }
85
+
86
+ /**
87
+ * Can show tab in tabs
88
+ *
89
+ * @return boolean
90
+ */
91
+ public function canShowTab()
92
+ {
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * Tab is hidden
98
+ *
99
+ * @return boolean
100
+ */
101
+ public function isHidden()
102
+ {
103
+ return false;
104
+ }
105
+
106
+ private function renderBannerSelector()
107
+ {
108
+ return $this
109
+ ->getLayout()
110
+ ->getBlock('bootic_storefront_banner')
111
+ ->toHtml()
112
+ ;
113
+ }
114
+
115
+ private function renderPreview()
116
+ {
117
+ return $this
118
+ ->getLayout()
119
+ ->getBlock('bootic_storefront_preview')
120
+ ->toHtml()
121
+ ;
122
+
123
+ }
124
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Design/Preview.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * (c) Newcode <contact@newcodestudio.com>
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Tab_Design_Preview extends Varien_Data_Form_Element_Abstract
7
+ {
8
+ public function __construct($attributes=array())
9
+ {
10
+ parent::__construct($attributes);
11
+ $this->setType('label');
12
+ }
13
+
14
+ public function getElementHtml()
15
+ {
16
+ $html = $this->getJs();
17
+ $html.= $this->getEscapedValue();
18
+ $html.= $this->getBold() ? '</strong>' : '';
19
+ $html.= $this->getAfterElementHtml();
20
+ return $html;
21
+ }
22
+
23
+ private function getOptions()
24
+ {
25
+ $options = Mage::helper('bootic/storefront')->getStoreFrontOptions(Mage::getStoreConfig('bootic/account/storefront_id'));
26
+
27
+ return $options->getData();
28
+ }
29
+
30
+ private function getJs()
31
+ {
32
+ $js =
33
+ '<script type="text/javascript">
34
+ var storeOptions = ' . json_encode($this->getOptions()) . ';
35
+ var templates = storeOptions.templates;
36
+ var color = "#" + $("storefront_color_theme").value;
37
+ var previewImg = new Element("img");
38
+
39
+ templates.each(function (template) {
40
+ if (template.current_template == true) {
41
+ previewImg.src = template.preview;
42
+ $("storefront_preview_fieldset").insert({
43
+ top: previewImg
44
+ });
45
+ }
46
+ })
47
+
48
+ $("storefront_preview_fieldset").setStyle({ backgroundColor: color });
49
+
50
+ </script>'
51
+ ;
52
+
53
+ return $js;
54
+ }
55
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/General.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Tab_General
7
+ extends Mage_Adminhtml_Block_Widget_Form
8
+ implements Mage_Adminhtml_Block_Widget_Tab_Interface
9
+ {
10
+ /**
11
+ * Load Wysiwyg on demand and Prepare layout
12
+ */
13
+ protected function _prepareLayout()
14
+ {
15
+ parent::_prepareLayout();
16
+ if (Mage::getSingleton('cms/wysiwyg_config')->isEnabled()) {
17
+ $this->getLayout()->getBlock('head')->setCanLoadTinyMce(true);
18
+ }
19
+ }
20
+
21
+ protected function _prepareForm()
22
+ {
23
+ $storeData = Mage::getSingleton('bootic/storefront')->getData();
24
+ $_bootic = Mage::helper('bootic')->getBootic();
25
+
26
+ $form = new Varien_Data_Form();
27
+
28
+ $form->setHtmlIdPrefix('storefront_');
29
+
30
+ $form->addField('shop_id', 'hidden', array(
31
+ 'name' => 'shop_id'
32
+ ));
33
+
34
+ $fieldset = $form->addFieldset('general_fieldset', array('legend'=>Mage::helper('bootic')->__('General')));
35
+
36
+
37
+ if ($storeData['online'] == true) {
38
+ $fieldset->addField('note', 'note', array(
39
+ 'name' => 'note',
40
+ 'text' => '<a href="' . $_bootic->getUri('server_main') . $storeData['url'] . '" target="_blank">' . Mage::helper('bootic')->__('Preview your storefront here') . '</a>'
41
+ ));
42
+ }
43
+
44
+ $fieldset->addField('name', 'text', array(
45
+ 'label' => Mage::helper('bootic')->__('Name your new Storefront:'),
46
+ 'class' => 'required-entry',
47
+ 'required' => true,
48
+ 'name' => 'name',
49
+ ));
50
+
51
+ $fieldset->addField('description', 'editor', array(
52
+ 'label' => Mage::helper('bootic')->__('Describe your storefront:'),
53
+ 'style' => 'height:15em',
54
+ 'config' => Mage::getSingleton('cms/wysiwyg_config')->getConfig(),
55
+ 'wysiwyg' => true,
56
+ 'name' => 'description',
57
+ ));
58
+
59
+ $url = $fieldset->addField('url', 'text', array(
60
+ 'label' => Mage::helper('bootic')->__('URL:'),
61
+ 'required' => true,
62
+ 'name' => 'url',
63
+ ));
64
+
65
+ $comment = '<p class="note"><span>'.Mage::helper('bootic')->__('Your storefront URL on Bootic').'</span></p>';
66
+ $js = "<script type='text/javascript'>
67
+ $('storefront_url').insert({
68
+ before: 'http://bootic.com/ '
69
+ });
70
+ </script>";
71
+ $url->setAfterElementHtml($comment . $js);
72
+
73
+ $fieldset->addField('online', 'select', array(
74
+ 'label' => Mage::helper('bootic')->__('Online:'),
75
+ 'name' => 'online',
76
+ 'values' => array(
77
+ 0 => array(
78
+ 'value' => 1,
79
+ 'label' => 'Yes'
80
+ ),
81
+ 1 => array(
82
+ 'value' => 0,
83
+ 'label' => 'No'
84
+ )
85
+ )
86
+ ));
87
+
88
+ $form->setValues($storeData);
89
+ $this->setForm($form);
90
+
91
+ return parent::_prepareForm();
92
+ }
93
+
94
+ /**
95
+ * Return Tab label
96
+ *
97
+ * @return string
98
+ */
99
+ public function getTabLabel()
100
+ {
101
+ return Mage::helper('bootic')->__('Storefront Information');
102
+ }
103
+
104
+ /**
105
+ * Return Tab title
106
+ *
107
+ * @return string
108
+ */
109
+ public function getTabTitle()
110
+ {
111
+ return Mage::helper('bootic')->__('Storefront Information');
112
+ }
113
+
114
+ /**
115
+ * Can show tab in tabs
116
+ *
117
+ * @return boolean
118
+ */
119
+ public function canShowTab()
120
+ {
121
+ return true;
122
+ }
123
+
124
+ /**
125
+ * Tab is hidden
126
+ *
127
+ * @return boolean
128
+ */
129
+ public function isHidden()
130
+ {
131
+ return false;
132
+ }
133
+
134
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Settings.php ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Tab_Settings
7
+ extends Mage_Adminhtml_Block_Widget_Form
8
+ implements Mage_Adminhtml_Block_Widget_Tab_Interface
9
+ {
10
+ protected function _prepareForm()
11
+ {
12
+ $form = new Varien_Data_Form();
13
+
14
+ $form->setHtmlIdPrefix('storefront_');
15
+
16
+ $fieldset = $form->addFieldset('settings_fieldset', array('legend' => Mage::helper('bootic')->__('Settings')));
17
+
18
+ $fieldset->addField('free_delivery_at_and_above', 'text', array(
19
+ 'label' => Mage::helper('bootic')->__('Delivery free if purchase is higher than:'),
20
+ 'name' => 'free_delivery_at_and_above',
21
+ 'after_element_html' => '<strong>[USD]</strong>'
22
+ ));
23
+
24
+ $fieldset->addField('cumulative_delivery_cost', 'select', array(
25
+ 'label' => Mage::helper('bootic')->__('Cumulative delivery cost:'),
26
+ 'name' => 'cumulative_delivery_cost',
27
+ 'values' => array(
28
+ 0 => array(
29
+ 'value' => 1,
30
+ 'label' => 'Yes'
31
+ ),
32
+ 1 => array(
33
+ 'value' => 0,
34
+ 'label' => 'No'
35
+ )
36
+ )
37
+ ));
38
+
39
+ // $fieldset->addField('transferable', 'select', array(
40
+ // 'label' => Mage::helper('bootic')->__('Transferable:'),
41
+ // 'name' => 'transferable',
42
+ // 'values' => array(
43
+ // 0 => array(
44
+ // 'value' => 1,
45
+ // 'label' => 'Yes'
46
+ // ),
47
+ // 1 => array(
48
+ // 'value' => 0,
49
+ // 'label' => 'No'
50
+ // )
51
+ // )
52
+ // ));
53
+
54
+ $fieldset->addField('indexable', 'select', array(
55
+ 'label' => Mage::helper('bootic')->__('Indexable:'),
56
+ 'name' => 'indexable',
57
+ 'values' => array(
58
+ 0 => array(
59
+ 'value' => 1,
60
+ 'label' => 'Yes'
61
+ ),
62
+ 1 => array(
63
+ 'value' => 0,
64
+ 'label' => 'No'
65
+ )
66
+ )
67
+ ));
68
+
69
+ $form->setValues(Mage::getSingleton('bootic/storefront')->getData());
70
+ $this->setForm($form);
71
+
72
+ return parent::_prepareForm();
73
+ }
74
+
75
+ /**
76
+ * Return Tab label
77
+ *
78
+ * @return string
79
+ */
80
+ public function getTabLabel()
81
+ {
82
+ return Mage::helper('bootic')->__('Storefront Settings');
83
+ }
84
+
85
+ /**
86
+ * Return Tab title
87
+ *
88
+ * @return string
89
+ */
90
+ public function getTabTitle()
91
+ {
92
+ return Mage::helper('bootic')->__('Storefront Settings');
93
+ }
94
+
95
+ /**
96
+ * Can show tab in tabs
97
+ *
98
+ * @return boolean
99
+ */
100
+ public function canShowTab()
101
+ {
102
+ return true;
103
+ }
104
+
105
+ /**
106
+ * Tab is hidden
107
+ *
108
+ * @return boolean
109
+ */
110
+ public function isHidden()
111
+ {
112
+ return false;
113
+ }
114
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tab/Social.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Tab_Social
7
+ extends Mage_Adminhtml_Block_Widget_Form
8
+ implements Mage_Adminhtml_Block_Widget_Tab_Interface
9
+ {
10
+ protected function _prepareForm()
11
+ {
12
+ $form = new Varien_Data_Form();
13
+
14
+ $form->setHtmlIdPrefix('storefront_');
15
+
16
+ $fieldset = $form->addFieldset('social_fieldset', array('legend' => Mage::helper('bootic')->__('Social')));
17
+
18
+ $fieldset->addField('follow_google_url', 'text', array(
19
+ 'name' => 'follow_google_url',
20
+ 'label' => Mage::helper('bootic')->__('Google+:')
21
+ ));
22
+
23
+ $fieldset->addField('follow_facebook_url', 'text', array(
24
+ 'name' => 'follow_facebook_url',
25
+ 'label' => Mage::helper('bootic')->__('Facebook:')
26
+ ));
27
+
28
+ $fieldset->addField('follow_twitter_url', 'text', array(
29
+ 'name' => 'follow_twitter_url',
30
+ 'label' => Mage::helper('bootic')->__('Twitter:')
31
+ ));
32
+
33
+ $fieldset->addField('follow_pinterest_url', 'text', array(
34
+ 'name' => 'follow_pinterest_url',
35
+ 'label' => Mage::helper('bootic')->__('Pinterest:')
36
+ ));
37
+
38
+ $form->setValues(Mage::getSingleton('bootic/storefront')->getData());
39
+ $this->setForm($form);
40
+
41
+ return parent::_prepareForm();
42
+ }
43
+
44
+ /**
45
+ * Return Tab label
46
+ *
47
+ * @return string
48
+ */
49
+ public function getTabLabel()
50
+ {
51
+ return Mage::helper('bootic')->__('Storefront Information');
52
+ }
53
+
54
+ /**
55
+ * Return Tab title
56
+ *
57
+ * @return string
58
+ */
59
+ public function getTabTitle()
60
+ {
61
+ return Mage::helper('bootic')->__('Storefront Information');
62
+ }
63
+
64
+ /**
65
+ * Can show tab in tabs
66
+ *
67
+ * @return boolean
68
+ */
69
+ public function canShowTab()
70
+ {
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * Tab is hidden
76
+ *
77
+ * @return boolean
78
+ */
79
+ public function isHidden()
80
+ {
81
+ return false;
82
+ }
83
+
84
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Edit/Tabs.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs
7
+ {
8
+ public function __construct()
9
+ {
10
+ parent::__construct();
11
+ $this->setId('storefront_tabs');
12
+ $this->setDestElementId('edit_form');
13
+ $this->setTitle(Mage::helper('bootic')->__('Storefront Information'));
14
+ }
15
+
16
+ protected function _beforeToHtml()
17
+ {
18
+ $helper = Mage::helper('bootic');
19
+
20
+ $this->addTab('general_section', array(
21
+ 'label' => $helper->__('General'),
22
+ 'title' => $helper->__('General informations'),
23
+ 'content' => $this->getLayout()->createBlock('bootic/adminhtml_storefront_edit_tab_general')->toHtml()
24
+ ));
25
+
26
+ $this->addTab('settings_section', array(
27
+ 'label' => $helper->__('Settings'),
28
+ 'title' => $helper->__('Settings'),
29
+ 'content' => $this->getLayout()->createBlock('bootic/adminhtml_storefront_edit_tab_settings')->toHtml()
30
+ ));
31
+
32
+ $this->addTab('social_section', array(
33
+ 'label' => $helper->__('Social'),
34
+ 'title' => $helper->__('Social'),
35
+ 'content' => $this->getLayout()->createBlock('bootic/adminhtml_storefront_edit_tab_social')->toHtml()
36
+ ));
37
+
38
+ $this->addTab('Design_section', array(
39
+ 'label' => $helper->__('Design'),
40
+ 'title' => $helper->__('Design'),
41
+ 'content' => $this->getLayout()->createBlock('bootic/adminhtml_storefront_edit_tab_design')->toHtml()
42
+ ));
43
+
44
+ return parent::_beforeToHtml();
45
+ }
46
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Storefront/Preview.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * (c) Newcode <contact@newcodestudio.com>
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Storefront_Preview extends Mage_Core_Block_Template
7
+ {
8
+ public function getOptions()
9
+ {
10
+ $options = Mage::helper('bootic/storefront')->getStoreFrontOptions(Mage::getStoreConfig('bootic/account/storefront_id'));
11
+
12
+ return $options->getData();
13
+ }
14
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Account/Create.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_System_Config_Account_Create extends Mage_Adminhtml_Block_Widget_Form_Container
7
+ {
8
+ public function __construct()
9
+ {
10
+ parent::__construct();
11
+
12
+ $this->_objectId = 'id';
13
+ $this->_blockGroup = 'bootic';
14
+ $this->_controller = 'adminhtml_system_config_account';
15
+ $this->_mode = 'create';
16
+
17
+ $this->_removeButton('back');
18
+ }
19
+
20
+ public function getHeaderText()
21
+ {
22
+ return Mage::helper('bootic')->__('Create your account');
23
+ }
24
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Account/Create/Form.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_System_Config_Account_Create_Form extends Mage_Adminhtml_Block_Widget_Form
7
+ {
8
+ protected function _prepareForm()
9
+ {
10
+ $helper = Mage::helper('bootic');
11
+
12
+ $form = new Varien_Data_Form(array(
13
+ 'id' => 'edit_form',
14
+ 'action' => $this->getUrl('*/*/create'),
15
+ 'method' => 'post',
16
+ 'enctype' => 'multipart/form-data'
17
+ ));
18
+
19
+ $form->setUseContainer(true);
20
+ $this->setForm($form);
21
+
22
+ $fieldset = $form->addFieldset('account_create_form', array('legend' => $helper->__('Account Information')));
23
+
24
+ $fieldset->addField('email', 'text', array(
25
+ 'name' => 'email',
26
+ 'label' => Mage::helper('adminhtml')->__('Email'),
27
+ 'id' => 'customer_email',
28
+ 'title' => Mage::helper('adminhtml')->__('User Email'),
29
+ 'class' => 'required-entry validate-email',
30
+ 'required' => true,
31
+ ));
32
+
33
+ $fieldset->addField('password', 'password', array(
34
+ 'name' => 'password',
35
+ 'label' => Mage::helper('adminhtml')->__('Password'),
36
+ 'id' => 'customer_pass',
37
+ 'title' => Mage::helper('adminhtml')->__('Password'),
38
+ 'class' => 'input-text required-entry validate-password',
39
+ 'required' => true,
40
+ ));
41
+
42
+ $fieldset->addField('confirmation', 'password', array(
43
+ 'name' => 'password_confirmation',
44
+ 'label' => Mage::helper('adminhtml')->__('Password Confirmation'),
45
+ 'id' => 'confirmation',
46
+ 'title' => Mage::helper('adminhtml')->__('Password Confirmation'),
47
+ 'class' => 'input-text required-entry validate-cpassword',
48
+ 'required' => true,
49
+ ));
50
+
51
+ $fieldset->addField('selling_agreement', 'checkbox', array(
52
+ 'name' => 'selling_agreement',
53
+ 'label' => Mage::helper('bootic')->__('Terms & Conditions'),
54
+ 'id' => 'selling_agreement',
55
+ 'style' => "width:10px;float:left;margin-right:10px;margin-top:4px;",
56
+ 'after_element_html' => '<div id="selling-agreement-link" style="float:left;padding:1px;width:270px;">By using Bootic.com, you agree to the terms listed in our <a href="https://secure.bootic.com/_vendor/footer/selling_agreement" target="_blank">Selling Agreement.</a></div>'
57
+ ));
58
+
59
+ $data = Mage::getSingleton('adminhtml/session')->getFormData();
60
+ $data['selling_agreement'] = 1;
61
+
62
+ $form->setValues($data);
63
+ $this->setForm($form);
64
+
65
+ return parent::_prepareForm();
66
+ }
67
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Button/Cron.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ class Bootic_Bootic_Block_Adminhtml_System_Config_Button_Cron extends Mage_Adminhtml_Block_System_Config_Form_Field
5
+ {
6
+ /**
7
+ * Set template to itself
8
+ *
9
+ * @return Bootic_Bootic_Block_Adminhtml_System_Config_Button_Cron
10
+ */
11
+ protected function _prepareLayout()
12
+ {
13
+ parent::_prepareLayout();
14
+ if (!$this->getTemplate()) {
15
+ $this->setTemplate('bootic/adminhtml/system/config/button/cron.phtml');
16
+ }
17
+
18
+ return $this;
19
+ }
20
+
21
+ /**
22
+ * Unset some non-related element parameters
23
+ *
24
+ * @param Varien_Data_Form_Element_Abstract $element
25
+ * @return string
26
+ */
27
+ public function render(Varien_Data_Form_Element_Abstract $element)
28
+ {
29
+ $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
30
+ return parent::render($element);
31
+ }
32
+
33
+ /**
34
+ * Get the button and scripts contents
35
+ *
36
+ * @param Varien_Data_Form_Element_Abstract $element
37
+ * @return string
38
+ */
39
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
40
+ {
41
+ $originalData = $element->getOriginalData();
42
+ $this->addData(array(
43
+ 'button_label' => Mage::helper('bootic')->__($originalData['button_label']),
44
+ 'cron_type' => $originalData['cron_type'],
45
+ 'html_id' => $element->getHtmlId(),
46
+ 'ajax_url' => Mage::getSingleton('adminhtml/url')->getUrl($originalData['button_url']),
47
+ ));
48
+
49
+ return $this->_toHtml();
50
+ }
51
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Createaccount.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_System_Config_Createaccount extends Mage_Adminhtml_Block_System_Config_Form_Field
7
+ {
8
+ /**
9
+ * Set template to itself
10
+ *
11
+ * @return Mage_Adminhtml_Block_Customer_System_Config_Validatevat
12
+ */
13
+ protected function _prepareLayout()
14
+ {
15
+ parent::_prepareLayout();
16
+
17
+ if (!$this->getTemplate()) {
18
+ $this->setTemplate('bootic/adminhtml/system/config/createaccount.phtml');
19
+ }
20
+ return $this;
21
+ }
22
+
23
+ /**
24
+ * Unset some non-related element parameters
25
+ *
26
+ * @param Varien_Data_Form_Element_Abstract $element
27
+ * @return string
28
+ */
29
+ public function render(Varien_Data_Form_Element_Abstract $element)
30
+ {
31
+ $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
32
+ return parent::render($element);
33
+ }
34
+
35
+ /**
36
+ * Get the button and scripts contents
37
+ *
38
+ * @param Varien_Data_Form_Element_Abstract $element
39
+ * @return string
40
+ */
41
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
42
+ {
43
+ $originalData = $element->getOriginalData();
44
+ $this->addData(array(
45
+ 'button_label' => Mage::helper('bootic')->__($originalData['button_label']),
46
+ 'html_id' => $element->getHtmlId(),
47
+ 'redirect_url' => Mage::getSingleton('adminhtml/url')->getUrl('bootic/adminhtml_system_config_account/index')
48
+ ));
49
+
50
+ return $this->_toHtml();
51
+ }
52
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Tabs.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_System_Config_Tabs extends Mage_Adminhtml_Block_System_Config_Tabs
7
+ {
8
+ /**
9
+ * Let's override the left hand column
10
+ *
11
+ */
12
+ public function initTabs()
13
+ {
14
+ $current = 'bootic';
15
+ $websiteCode = $this->getRequest()->getParam('website');
16
+ $storeCode = $this->getRequest()->getParam('store');
17
+
18
+ $url = Mage::getModel('adminhtml/url');
19
+
20
+ $configFields = Mage::getSingleton('adminhtml/config');
21
+ $sections = $configFields->getSections($current);
22
+ $tabs = (array)$configFields->getTabs()->children();
23
+
24
+
25
+ $sections = (array)$sections;
26
+
27
+ usort($sections, array($this, '_sort'));
28
+ usort($tabs, array($this, '_sort'));
29
+
30
+ foreach ($tabs as $tab) {
31
+ $helperName = $configFields->getAttributeModule($tab);
32
+ $label = Mage::helper($helperName)->__((string)$tab->label);
33
+
34
+ $this->addTab($tab->getName(), array(
35
+ 'label' => $label,
36
+ 'class' => (string) $tab->class
37
+ ));
38
+ }
39
+
40
+
41
+ foreach ($sections as $section) {
42
+ Mage::dispatchEvent('adminhtml_block_system_config_init_tab_sections_before', array('section' => $section));
43
+ $hasChildren = $configFields->hasChildren($section, $websiteCode, $storeCode);
44
+
45
+ //$code = $section->getPath();
46
+ $code = $section->getName();
47
+
48
+ $sectionAllowed = $this->checkSectionPermissions($code);
49
+ if ((empty($current) && $sectionAllowed)) {
50
+
51
+ $current = $code;
52
+ $this->getRequest()->setParam('section', $current);
53
+ }
54
+
55
+ $helperName = $configFields->getAttributeModule($section);
56
+
57
+ $label = Mage::helper($helperName)->__((string)$section->label);
58
+
59
+ if ($code == $current) {
60
+ if (!$this->getRequest()->getParam('website') && !$this->getRequest()->getParam('store')) {
61
+ $this->_addBreadcrumb($label);
62
+ } else {
63
+ $this->_addBreadcrumb($label, '', $url->getUrl('*/*/*', array('section'=>$code)));
64
+ }
65
+ }
66
+ if ( $sectionAllowed && $hasChildren) {
67
+ $this->addSection($code, (string)$section->tab, array(
68
+ 'class' => (string)$section->class,
69
+ 'label' => $label,
70
+ 'url' => $url->getUrl('adminhtml/system_config/edit', array('_current'=>true, 'section'=>$code)),
71
+ ));
72
+ }
73
+
74
+ if ($code == $current) {
75
+ $this->setActiveTab($section->tab);
76
+ $this->setActiveSection($code);
77
+ }
78
+ }
79
+
80
+ /*
81
+ * Set last sections
82
+ */
83
+ foreach ($this->getTabs() as $tab) {
84
+ $sections = $tab->getSections();
85
+ if ($sections) {
86
+ $sections->getLastItem()->setIsLast(true);
87
+ }
88
+ }
89
+
90
+ Mage::helper('adminhtml')->addPageHelpUrl($current.'/');
91
+
92
+ return $this;
93
+ }
94
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/System/Config/Testapiconnection.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_System_Config_Testapiconnection extends Mage_Adminhtml_Block_System_Config_Form_Field
7
+ {
8
+ /**
9
+ * Set template to itself
10
+ *
11
+ * @return Mage_Adminhtml_Block_Customer_System_Config_Validatevat
12
+ */
13
+ protected function _prepareLayout()
14
+ {
15
+ parent::_prepareLayout();
16
+
17
+ $displayCreateAccount = true;
18
+ if (Mage::getStoreConfig('bootic/account/password') && Mage::getStoreConfig('bootic/account/email')) {
19
+ $displayCreateAccount = false;
20
+ }
21
+
22
+ $this->setDisplayCreateAccount($displayCreateAccount);
23
+
24
+ if (!$this->getTemplate()) {
25
+ $this->setTemplate('bootic/adminhtml/system/config/testapiconnection.phtml');
26
+ }
27
+ return $this;
28
+ }
29
+
30
+ /**
31
+ * Unset some non-related element parameters
32
+ *
33
+ * @param Varien_Data_Form_Element_Abstract $element
34
+ * @return string
35
+ */
36
+ public function render(Varien_Data_Form_Element_Abstract $element)
37
+ {
38
+ $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
39
+ return parent::render($element);
40
+ }
41
+
42
+ /**
43
+ * Get the button and scripts contents
44
+ *
45
+ * @param Varien_Data_Form_Element_Abstract $element
46
+ * @return string
47
+ */
48
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
49
+ {
50
+ $originalData = $element->getOriginalData();
51
+ $this->addData(array(
52
+ 'button_label' => Mage::helper('bootic')->__($originalData['button_label']),
53
+ 'html_id' => $element->getHtmlId(),
54
+ 'ajax_url' => Mage::getSingleton('adminhtml/url')->getUrl('bootic/adminhtml_system_config_testapiconnection/test'),
55
+ 'redirect_url' => Mage::getSingleton('adminhtml/url')->getUrl('bootic/adminhtml_system_config_account/index')
56
+ ));
57
+
58
+ return $this->_toHtml();
59
+ }
60
+ }
app/code/community/Bootic/Bootic/Block/Adminhtml/Widget/Grid/Column/Renderer/Log/Message.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Block_Adminhtml_Widget_Grid_Column_Renderer_Log_Message
7
+ extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract
8
+ {
9
+ public function render(Varien_Object $row) {
10
+
11
+ switch ($row->getStatus()) {
12
+ case 'error':
13
+ $color = 'red';
14
+ break;
15
+ case 'warning':
16
+ $color = 'orange';
17
+ break;
18
+ default:
19
+ $color = 'green';
20
+ break;
21
+ }
22
+
23
+ return '<span style="color:'.$color.'">'.$row->getMessage().'</span>';
24
+ }
25
+
26
+ public function renderExport(Varien_Object $row) {
27
+ return $row->getMessage();
28
+ }
29
+ }
app/code/community/Bootic/Bootic/Block/Payment/Form.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+ class Bootic_Bootic_Block_Payment_Form extends Mage_Payment_Block_Form
3
+ {
4
+
5
+ }
app/code/community/Bootic/Bootic/Block/Payment/Info.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+ class Bootic_Bootic_Block_Payment_Info extends Mage_Payment_Block_Info
3
+ {
4
+
5
+ }
app/code/community/Bootic/Bootic/Exception.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Exception extends Zend_Exception
7
+ {
8
+ /** @var boolean */
9
+ protected $_isWarning;
10
+
11
+ public function __construct($msg = '', $code = 0, Exception $previous = null, $isWarning = false)
12
+ {
13
+ $this->_isWarning = $isWarning;
14
+ parent::__construct($msg, $code, $previous);
15
+ }
16
+
17
+ public function isWarning()
18
+ {
19
+ return $this->_isWarning;
20
+ }
21
+ }
app/code/community/Bootic/Bootic/Helper/Abstract.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ abstract class Bootic_Bootic_Helper_Abstract extends Mage_Core_Helper_Abstract
7
+ {
8
+ /** @var Bootic_Api_Client */
9
+ protected static $_bootic;
10
+
11
+ /**
12
+ * Instantiates, authenticates and returns Bootic API Client
13
+ */
14
+ public function getBootic()
15
+ {
16
+ if (!self::$_bootic) {
17
+ self::$_bootic = new Bootic_Api_Client(new Zend_Rest_Client());
18
+ self::$_bootic->authenticateByApiKey(
19
+ Mage::getStoreConfig('bootic/account/api_key')
20
+ );
21
+ }
22
+
23
+ return self::$_bootic;
24
+ }
25
+ }
app/code/community/Bootic/Bootic/Helper/Account.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Account extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ /**
9
+ * Creates an account on Bootic, this method is different from the rest of the API
10
+ * as we don't need to authenticate before querying
11
+ *
12
+ * @param $email
13
+ * @param $password
14
+ * @return Bootic_Api_Result
15
+ */
16
+ public function createAccount($email, $password)
17
+ {
18
+ $_bootic = new Bootic_Api_Client(new Zend_Rest_Client());
19
+ $result = $_bootic->createAccount($email,$password);
20
+
21
+ return $result;
22
+ }
23
+ }
app/code/community/Bootic/Bootic/Helper/Api.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Api extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ /**
9
+ * The User Id returned by Bootic API
10
+ *
11
+ * @var int
12
+ */
13
+ protected $user_id;
14
+
15
+ /**
16
+ * Instantiates, authenticates and returns Bootic API Client
17
+ */
18
+ private function _getBootic($email, $password)
19
+ {
20
+ if (!self::$_bootic) {
21
+ self::$_bootic = new Bootic_Api_Client(new Zend_Rest_Client());
22
+ $result = self::$_bootic->authenticateByEmailAndPassword($email, $password);
23
+
24
+ $this->user_id = $result->getData('user_id');
25
+ }
26
+
27
+ return self::$_bootic;
28
+ }
29
+
30
+ /**
31
+ * Creates an Api Key for the extension to use
32
+ *
33
+ * @param $email
34
+ * @param $password
35
+ * @return array|mixed
36
+ */
37
+ public function createKey($email, $password)
38
+ {
39
+ $apiKeyName = 'magento';
40
+ $apiKeyDescription = 'Magento:' . Mage::getVersion();
41
+
42
+ $result = $this
43
+ ->_getBootic($email, $password)
44
+ ->createKey($apiKeyName, $apiKeyDescription);
45
+
46
+ return $result->getData('api_key');
47
+ }
48
+
49
+ /**
50
+ * Classic User Id getter
51
+ *
52
+ * @return int
53
+ */
54
+ public function getuserId()
55
+ {
56
+ return $this->user_id;
57
+ }
58
+ }
app/code/community/Bootic/Bootic/Helper/Category.php ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Category extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ /** @var Array */
9
+ protected $_booticCategories;
10
+
11
+ /**
12
+ * Lists all original categories on Bootic
13
+ *
14
+ * @param int $safe
15
+ * @param bool $empty
16
+ * @return array|mixed
17
+ */
18
+ public function listOrigCategories()
19
+ {
20
+ return $this->getBootic()->listCategories();
21
+ }
22
+
23
+ /**
24
+ * @return Array
25
+ */
26
+ public function getBooticCategories()
27
+ {
28
+ if (isset($this->_booticCategories)) {
29
+ return $this->_booticCategories;
30
+ }
31
+
32
+ $booticCategoryCollection = Mage::getResourceModel('bootic/category_collection')->load();
33
+
34
+ $booticCategories = array();
35
+ foreach ($booticCategoryCollection as $booticCategory) {
36
+ $booticCategories[$booticCategory->getId()] = Mage::getModel('bootic/category')->load($booticCategory->getId());
37
+ }
38
+
39
+ $this->_booticCategories = $booticCategories;
40
+
41
+ return $this->_booticCategories;
42
+ }
43
+
44
+ public function getJsonFormattedBooticCategories()
45
+ {
46
+ $origBooticCategories = $this->getBooticCategories();
47
+
48
+ $tree = $this->_buildBooticCategoryTree($origBooticCategories);
49
+
50
+ foreach ($tree as &$subTree) {
51
+ $temp = array();
52
+ $temp['id'] = $subTree['category_id'];
53
+ $temp['label'] = $subTree['name'];
54
+ if (count($subTree['children']) > 0) {
55
+ $flatTree = $this->_flatenBooticTree($subTree);
56
+
57
+ usort($flatTree, function($a, $b) {
58
+ if ($a['label'] < $b['label']) {
59
+ return -1;
60
+ } else {
61
+ return 1;
62
+ }
63
+ });
64
+
65
+ $temp['children'] = $flatTree;
66
+ }
67
+
68
+ $subTree = $temp;
69
+ }
70
+
71
+ return Zend_Json::encode($tree);
72
+ }
73
+
74
+ /**
75
+ * @param $categories
76
+ * @param $level
77
+ * @return array
78
+ */
79
+ private function _flatenBooticTree($tree, $firstLevel = true, $parentLabel = null)
80
+ {
81
+ $result = array();
82
+
83
+ $preLabel = ($parentLabel) ? $parentLabel : $tree['name'];
84
+
85
+ foreach ($tree['children'] as $key => $category) {
86
+ $label = $firstLevel ? $category['name'] : $preLabel . ' -> ' . $category['name'];
87
+ $result[$key] = array(
88
+ 'id' => $category['category_id'],
89
+ 'label' => $label
90
+ );
91
+
92
+ if (count($category['children']) > 0) {
93
+ $flatTree = $this->_flatenBooticTree($category, false, $label);
94
+ foreach($flatTree as $k => $v)
95
+ {
96
+ $result[$k] = $v;
97
+ }
98
+ }
99
+ }
100
+
101
+ return $result;
102
+ }
103
+
104
+ private function _buildBooticCategoryTree($categories)
105
+ {
106
+ $tree = array();
107
+ foreach ($categories as $key => &$category) {
108
+ $children = array();
109
+ foreach ($category->getChildren() as $childrenId) {
110
+ $children[$childrenId] = $categories[$childrenId];
111
+ }
112
+
113
+ $category->setChildren($children);
114
+
115
+ if (count($category->getChildren()) > 0 && count($category->getParents()) == 0) {
116
+ $tree[$key] = $category;
117
+ }
118
+ }
119
+
120
+ return $this->_extractData($tree);
121
+ }
122
+
123
+ private function _extractData($tree)
124
+ {
125
+ if (is_array($tree)) {
126
+ foreach ($tree as &$item) {
127
+ $item = $item->getData();
128
+
129
+ if (count($item['children']) > 0) {
130
+ foreach ($item['children'] as &$child) {
131
+ $child = $this->_extractData($child);
132
+ }
133
+ }
134
+ }
135
+ } else {
136
+ $tree = $tree->getData();
137
+
138
+ if (count($tree['children']) > 0) {
139
+ foreach ($tree['children'] as &$child) {
140
+ $child = $this->_extractData($child);
141
+ }
142
+ }
143
+ }
144
+
145
+ return $tree;
146
+ }
147
+
148
+ /**
149
+ * Gets Selected Magento Categories
150
+ *
151
+ * @return array
152
+ */
153
+ public function getMagentoCategories()
154
+ {
155
+ try {
156
+ $rootCategory = $this->getRootCategory();
157
+ return $this->_createCategoryTree($rootCategory);
158
+ } catch (Exception $e) {
159
+ return false;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Gets the selected root category from config
165
+ *
166
+ * @return Mage_Core_Model_Abstract
167
+ */
168
+ public function getRootCategory()
169
+ {
170
+ $rootCategoryId = Mage::getStoreConfig('bootic/product/root_category');
171
+ if ($rootCategoryId) {
172
+ return Mage::getModel('catalog/category')->load($rootCategoryId);
173
+ } else {
174
+ Mage::throwException('Root category is not set in system/configuration/bootic');
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Creates Category Tree from selected root category
180
+ *
181
+ * @param $rootCategory
182
+ * @return array
183
+ */
184
+ private function _createCategoryTree($rootCategory)
185
+ {
186
+ $tree = Mage::getResourceSingleton('catalog/category_tree')->load();
187
+ $root = $tree->getNodeById($rootCategory->getId());
188
+
189
+ $collection = Mage::getModel('catalog/category')
190
+ ->getCollection()
191
+ ->addAttributeToSelect('is_active')
192
+ ->addAttributeToSelect('name');
193
+
194
+ $tree->addCollectionData($collection, true);
195
+
196
+ $arrayTree = $this->_nodeToArray($root);
197
+
198
+ return $this->_flatenTreeArray($arrayTree);
199
+ }
200
+
201
+ /**
202
+ * Create the Tree array
203
+ *
204
+ * @param Varien_Data_Tree_Node $node
205
+ * @return array
206
+ */
207
+ private function _nodeToArray(Varien_Data_Tree_Node $node)
208
+ {
209
+ $result = array();
210
+ $result['category_id'] = $node->getId();
211
+ $result['parent_id'] = $node->getParentId();
212
+ $result['name'] = $node->getName();
213
+ $result['is_active'] = $node->getIsActive();
214
+ $result['position'] = $node->getPosition();
215
+ $result['level'] = $node->getLevel();
216
+ $result['children'] = array();
217
+
218
+ foreach ($node->getChildren() as $child) {
219
+ $result['children'][] = $this->_nodeToArray($child);
220
+ }
221
+
222
+ return $result;
223
+ }
224
+
225
+ private function _flatenTreeArray(array $arrayTree)
226
+ {
227
+ $result = array();
228
+ $arrow = ($arrayTree['level'] == 2) ? '' : '> ';
229
+
230
+ if ($arrayTree['level'] != 1) {
231
+ $result[] = array(
232
+ 'id' => $arrayTree['category_id'],
233
+ 'value' => str_repeat("|-------", (int) ($arrayTree['level'] - 2)) . $arrow . $arrayTree['name']
234
+ );
235
+ }
236
+
237
+ if (count($arrayTree['children']) > 0) {
238
+ foreach ($arrayTree['children'] as $child) {
239
+ $result = array_merge($result, $this->_flatenTreeArray($child));
240
+ }
241
+ }
242
+
243
+ return $result;
244
+ }
245
+
246
+ /**
247
+ * Gets categories from Bootic and stores them locally
248
+ */
249
+ public function pullBooticCategories()
250
+ {
251
+ $result = $this->listOrigCategories();
252
+ $booticCategories = $result->getData();
253
+
254
+ foreach ($booticCategories as $booticCategory) {
255
+ $category = Mage::getModel('bootic/category')->load($booticCategory['id']);
256
+ $category->setId($booticCategory['id']);
257
+ $category->setName($booticCategory['name']);
258
+ $category->setParents($booticCategory['parents']);
259
+ $category->save();
260
+ }
261
+ }
262
+
263
+
264
+ /**
265
+ * Get collection of product categories with additional matching bootic field
266
+ *
267
+ * @param Mage_Catalog_Model_Product $product
268
+ * @return Mage_Catalog_Model_Resource_Category_Collection
269
+ */
270
+ public function getMappedCategoryCollection($product)
271
+ {
272
+ $collection = Mage::getResourceModel('catalog/product')->getCategoryCollection($product)
273
+ ->joinField('bootic_category_id',
274
+ 'bootic/category_mapping',
275
+ 'bootic_category_id',
276
+ 'magento_category_id=entity_id',
277
+ null)
278
+ ->addAttributeToSort('level', 'DESC');
279
+
280
+ return $collection;
281
+ }
282
+ }
app/code/community/Bootic/Bootic/Helper/Connect.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Connect extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ public function getProfile()
9
+ {
10
+ return $this->getBootic()->getProfile();
11
+ }
12
+
13
+ public function editProfile(array $profile)
14
+ {
15
+ return $this->getBootic()->editProfile($profile);
16
+ }
17
+
18
+ public function editProfilePicture(array $data)
19
+ {
20
+ return $this->getBootic()->editProfilePicture($data);
21
+ }
22
+ }
app/code/community/Bootic/Bootic/Helper/Data.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Data extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ public function testApiConnection($email, $password)
9
+ {
10
+ // Default response
11
+ $response = new Varien_Object(array(
12
+ 'is_connected' => false,
13
+ 'request_success' => false
14
+ ));
15
+
16
+ if (!$this->canTestApiConnection($email)) {
17
+ return $response;
18
+ }
19
+
20
+ $booticClient = new Bootic_Api_Client(new Zend_Rest_Client());
21
+ try {
22
+ $result = $booticClient->authenticateByEmailAndPassword($email, $password);
23
+
24
+ $auth = $result->getData('auth');
25
+
26
+ if (empty($auth)) {
27
+ $response->setIsConnected(false);
28
+ } else {
29
+ $response->setIsConnected(true);
30
+ $response->setRequestSuccess(true);
31
+ }
32
+ } catch (\Exception $e) {
33
+ $response->setIsConnected(false);
34
+ }
35
+
36
+ return $response;
37
+ }
38
+
39
+ /**
40
+ * Check if parameters are valid to test API connection
41
+ *
42
+ * @param string $emai
43
+ *
44
+ * @return boolean
45
+ */
46
+ public function canTestApiConnection($email)
47
+ {
48
+ $result = true;
49
+
50
+ // TODO check if email is properly formatted
51
+ if (!is_string($email)) {
52
+ $result = false;
53
+ }
54
+
55
+ return $result;
56
+ }
57
+ }
app/code/community/Bootic/Bootic/Helper/Lists.php ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Lists.php
4
+ * @copyright Copyright (c) 2012 by Bootic.
5
+ */
6
+ class Bootic_Bootic_Helper_Lists extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ /**
9
+ * instance cache of regions
10
+ * @var array
11
+ */
12
+ protected $regions = null;
13
+
14
+ /**
15
+ * instance cache of countries
16
+ * @var array
17
+ */
18
+ protected $countries = null;
19
+
20
+ /**
21
+ * instance cache of shipping companies
22
+ * @var array
23
+ */
24
+ protected $shippingCompanies = null;
25
+
26
+ /**
27
+ * get region label by $regionId
28
+ * @param str $regionId
29
+ * @return string|NULL
30
+ */
31
+ public function getRegionLabel($regionId)
32
+ {
33
+ $regionData = $this->getRegionById($regionId);
34
+ if (is_array($regionData)) {
35
+ return $regionData['name'];
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * get region data by ID
42
+ * @param array|null $regionId
43
+ */
44
+ public function getRegionById($regionId)
45
+ {
46
+ foreach ($this->getRegions() as $region) {
47
+ if ($region['id'] == $regionId) {
48
+ return $region;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * get country label
56
+ * @param str $countryId
57
+ */
58
+ public function getCountryLabel($countryId)
59
+ {
60
+ $countryData = $this->getCountryById($countryId);
61
+ if (is_array($countryData)) {
62
+ return $countryData['name'];
63
+ }
64
+ return null;
65
+ }
66
+
67
+ /**
68
+ * get country data
69
+ * @param str $countryId
70
+ */
71
+ public function getCountryById($countryId)
72
+ {
73
+ foreach ($this->getCountries() as $country) {
74
+ if ($country['id'] == $countryId) {
75
+ return $country;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * get region list
83
+ * @return array
84
+ */
85
+ public function getRegions()
86
+ {
87
+ if ($this->regions === null) {
88
+ $result = $this->getBootic()->getCommonList(Bootic_Api_Client::COMMON_LIST_REGION);
89
+ if ($result->isSuccess()) {
90
+ $this->regions = $result->getData();
91
+ }
92
+ }
93
+ return $this->regions;
94
+ }
95
+
96
+ /**
97
+ * get country list
98
+ * @return array
99
+ */
100
+ public function getCountries()
101
+ {
102
+ if ($this->countries === null) {
103
+ $result = $this->getBootic()->getCommonList(Bootic_Api_Client::COMMON_LIST_COUNTRY);
104
+ if ($result->isSuccess()) {
105
+ $this->countries = $result->getData();
106
+ }
107
+ }
108
+ return $this->countries;
109
+ }
110
+
111
+ /**
112
+ * get bootic carried ID from a magento carrier code
113
+ * @param str $code
114
+ */
115
+ public function getIdByCarrierCode($code)
116
+ {
117
+ $companyName = $this->carrierCodeToCompanyName($code);
118
+ foreach ($this->getShippingCompanies() as $company) {
119
+ if (strtolower($company['name']) == strtolower($companyName)) {
120
+ return $company['id'];
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * get carrier name based on a magento carrier code
127
+ * @param str $code
128
+ * @return string
129
+ */
130
+ public function carrierCodeToCompanyName($code)
131
+ {
132
+ $name = 'Other';
133
+ switch ($code) {
134
+ case 'fedex':
135
+ $name = 'FedEx';
136
+ break;
137
+ case 'usps':
138
+ $name = 'USPS';
139
+ break;
140
+ case 'ups':
141
+ $name = 'UPS';
142
+ break;
143
+
144
+ }
145
+ return $name;
146
+ }
147
+
148
+ /**
149
+ * get shipping companies
150
+ * @return array
151
+ */
152
+ public function getShippingCompanies()
153
+ {
154
+ if ($this->shippingCompanies === null) {
155
+ $result = $this->getBootic()->getCommonList(Bootic_Api_Client::COMMON_LIST_SHIPPING_COMPANY);
156
+ if ($result->isSuccess()) {
157
+ $this->shippingCompanies = $result->getData();
158
+ }
159
+ }
160
+ return $this->shippingCompanies;
161
+ }
162
+ }
app/code/community/Bootic/Bootic/Helper/Log.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Log extends Mage_Core_Helper_Abstract
7
+ {
8
+ /**
9
+ * Logs Info about a product upload
10
+ *
11
+ * @param $productId
12
+ * @param string $status
13
+ * @param string $message
14
+ * @return false|Mage_Core_Model_Abstract
15
+ */
16
+ public function addLog($productId, $status = 'success', $message = '')
17
+ {
18
+ $log = Mage::getModel('bootic/log');
19
+
20
+ $log->setProductId($productId);
21
+ $log->setStatus($status);
22
+ $log->setMessage($message);
23
+ $log->setDate(date('Y-m-d H:i:s', Mage::getModel('core/date')->timestamp()));
24
+
25
+ $log->save();
26
+
27
+ return $log;
28
+ }
29
+ }
app/code/community/Bootic/Bootic/Helper/Message.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Message extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ /**
9
+ * Pulls all account messages and store them locally if need be
10
+ */
11
+ public function pullUnreadMessages()
12
+ {
13
+ try {
14
+ $result = $this->getBootic()->getAccountMessages();
15
+ } catch (Exception $e) {
16
+ // We do nothing
17
+ return;
18
+ }
19
+
20
+ $messages = array_reverse($result->getData());
21
+
22
+ foreach ($messages as $message) {
23
+ $booticMessage = Mage::getModel('bootic/message')->load($message['id']);
24
+
25
+ if ($booticMessage->isObjectNew()) {
26
+ $content = 'From <strong>' . $message['user_name'] . '</strong><br/>';
27
+ $content .= $message['content'];
28
+
29
+ $magentoMessage = Mage::getModel('adminnotification/inbox');
30
+ $magentoMessage->setSeverity(2);
31
+ $magentoMessage->setTitle($message['subject']);
32
+ $magentoMessage->setDescription($content);
33
+ $magentoMessage->setDateAdded($message['date']);
34
+ $magentoMessage->save();
35
+
36
+ $booticMessage->setMagentoMessageId($magentoMessage->getId());
37
+ $booticMessage->setBooticMessageId($message['id']);
38
+ $booticMessage->save();
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Marks messages as read
45
+ * @param $ids
46
+ */
47
+ public function markMessageAsRead($ids)
48
+ {
49
+ if (is_array($ids)) {
50
+ $ids = implode(',', $ids);
51
+ }
52
+
53
+ $this->getBootic()->markMessageAsRead(array(
54
+ 'msg_ids' => $ids
55
+ ));
56
+ }
57
+ }
app/code/community/Bootic/Bootic/Helper/Orders.php ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @copyright Copyright (c) 2012 by Bootic.
5
+ */
6
+
7
+
8
+ class Bootic_Bootic_Helper_Orders extends Bootic_Bootic_Helper_Abstract
9
+ {
10
+ /* order statuses */
11
+ const ORDER_CANCELLED = '-201';
12
+ const CANCELLED = '-101';
13
+ //only canceled products are below 0
14
+ const PRE_ORDERED = '0';
15
+ //only ordered products are above 0
16
+ const ORDERED = '101';
17
+ const NO_STOCK__PENDING = '201';
18
+ const NO_STOCK__ORDERED = '202';
19
+ const IN_DELIVERY__PENDING = '301';
20
+ const IN_DELIVERY__SENT = '302';
21
+ const IN_DELIVERY__LOST = '303';
22
+ const SOURCE_REQUEST_CANCELLATION = '351';
23
+ const DELIVERED__ACCEPTED = '401';
24
+ const DELIVERED__REFUSED = '402';
25
+ const DELIVERED__RETURN_WANTED = '450';
26
+ const DELIVERED__RETURNED = '451';
27
+ const REFUSED__DELIVERED_BACK = '501';
28
+ const RETURNED__DELIVERED_BACK = '502';
29
+ const COMPLETED = '601';
30
+
31
+ /**
32
+ * Checks for orders that need to be processed
33
+ * and process its.
34
+ *
35
+ * @return array|Mage_Sales_Model_Order[]
36
+ */
37
+ public function processPendingOrders()
38
+ {
39
+ Mage::log('process pending orders...');
40
+ $newOrders = array();
41
+ $orderList = $this->fetchPendingOrders();
42
+ Mage::log('got this many: '.count($orderList));
43
+ foreach ($orderList as $order) {
44
+ try {
45
+ //try to load existing
46
+ $orderData = Mage::getModel('bootic/order_data')->load($order['basket'], 'bootic_order_id');
47
+ if (!$orderData->getOrderId()) {
48
+ if ($order['status_code'] == self::ORDERED) {
49
+ $newOrders[] = $this->createOrder($order);
50
+ } else {
51
+ Mage::log("will not create order unless its new! {$order['status_code']}");
52
+ }
53
+ }
54
+ } catch(Exception $e) {
55
+ Mage::logException($e);
56
+ }
57
+ }
58
+ return $newOrders;
59
+ }
60
+
61
+ /**
62
+ * Fetches list of orders from Bootic
63
+ * that need to be processed
64
+ * re-organizes them by basket ID
65
+ *
66
+ * @return array|mixed
67
+ */
68
+ public function fetchPendingOrders()
69
+ {
70
+ Mage::log('fetch pending orders...');
71
+ $orderResult = $this->getBootic()->getOrderList();
72
+ $grouped = array();
73
+ if ($orderResult->isSuccess() && $orderResult->hasData('transactions')) {
74
+ Mage::log('we got transactions');
75
+ $transactions = $orderResult->getData('transactions');
76
+ foreach ($transactions as $transaction) {
77
+ if (!isset($grouped[$transaction['basket']])) {
78
+ $orderDetails = array(
79
+ 'basket' => $transaction['basket'],
80
+ 'shop_name' => $transaction['shop_name'],
81
+ 'producer_currency_name' => $transaction['producer_currency_name'],
82
+ 'buyer_email' => $transaction['buyer_email'],
83
+ 'producer' => $transaction['producer'],
84
+ 'buyer' => $transaction['buyer'],
85
+ 'shop_deleted' => $transaction['shop_deleted'],
86
+ 'survey_feedback_id' => $transaction['survey_feedback_id'],
87
+ 'status' => $transaction['status'],
88
+ 'shop_online' => $transaction['shop_online'],
89
+ 'shipping_company' => $transaction['shipping_company'],
90
+ 'shop_url' => $transaction['shop_url'],
91
+ 'marketer' => $transaction['marketer'],
92
+ 'shipping_tracking_number' => $transaction['shipping_tracking_number'],
93
+ 'transaction_date' => $transaction['transaction_date'],
94
+ 'shop_banner' => $transaction['shop_banner'],
95
+ 'status_order' => $transaction['status_order'],
96
+ 'status_code' => $transaction['status_code'],
97
+ 'shop' => $transaction['shop'],
98
+ 'buyer_name' => $transaction['buyer_name'],
99
+ 'shipping_address' => array(
100
+ 'name' => $transaction['shipping_name'],
101
+ 'address' => $transaction['shipping_address'],
102
+ 'address2' => $transaction['shipping_address2'],
103
+ 'city' => $transaction['shipping_city'],
104
+ 'post_code' => $transaction['shipping_post_code'],
105
+ 'region' => $transaction['shipping_region'],
106
+ 'phone' => $transaction['buyer_phone'],
107
+ 'country' => $transaction['shipping_country']
108
+ ),
109
+ 'billing_address' => array(
110
+ 'name' => $transaction['billing_name'],
111
+ 'address' => $transaction['billing_address'],
112
+ 'address2' => $transaction['billing_address2'],
113
+ 'city' => $transaction['billing_city'],
114
+ 'post_code' => $transaction['billing_post_code'],
115
+ 'region' => $transaction['billing_region'],
116
+ 'phone' => $transaction['buyer_phone'],
117
+ 'country' => $transaction['billing_country']
118
+ ),
119
+ 'items' => array(),
120
+ 'transactions' => array()
121
+ );
122
+ $grouped[$transaction['basket']] = $orderDetails;
123
+ }
124
+ $grouped[$transaction['basket']]['transactions'][] = $transaction['transaction'];
125
+ $grouped[$transaction['basket']]['items'][] = array(//@TODO we should have API do the math for us!
126
+ 'transaction_id' => $transaction['transaction'],
127
+ 'item_id' => $transaction['product'],
128
+ 'quantity' => $transaction['quantity'],
129
+ 'price' => $transaction['wage_producer_total'] / $transaction['quantity'],
130
+ 'tax' => $transaction['transaction_wage_tax_total'] / $transaction['quantity'],
131
+ 'tax_rate' => $transaction['transaction_tax_rate'] * 100
132
+ );
133
+ // $grouped[$transaction['basket']]['shipping_amount'] += (double)$transaction['shipping_fee_compensation'];
134
+ $grouped[$transaction['basket']]['shipping_amount'] = (double)$transaction['shipping_fees']['producer_credit'];
135
+ }
136
+ }
137
+ return $grouped;
138
+ }
139
+
140
+ /**
141
+ * Creates a new Order in Magento from the
142
+ * order data returned by Bootic API
143
+ *
144
+ * @param array $order The Bootic order Data
145
+ * @return Mage_Sales_Model_Order
146
+ */
147
+ public function createOrder($order)
148
+ {
149
+ //create customer
150
+ $customer = $this->createOrGetCustomer($order);
151
+
152
+ Mage::Log(sprintf('Creating order for transaction #%s', $order['basket']));
153
+ Mage::Log(sprintf('Customer is "%s %s <%s>"',
154
+ $customer->getFirstname(),
155
+ $customer->getLastname(),
156
+ $customer->getEmail())
157
+ );
158
+ // Mage::Log($order);
159
+ /* @var $listHelper Bootic_Bootic_Helper_Lists */
160
+ $listHelper = Mage::helper('bootic/lists');
161
+ //create order
162
+ /* @var @newOrder Mage_Sales_Model_Order */
163
+ $newOrder = Mage::getModel('sales/order');
164
+ $newOrder->reset();
165
+ $newOrder->setCustomerId($customer->getId());
166
+ $newOrder->setCustomerGroupId($customer->getGroupId());
167
+ $newOrder->setCustomerFirstname($customer->getFirstname());
168
+ $newOrder->setCustomerLastname($customer->getLastname());
169
+ $newOrder->setCustomerIsGuest(0);
170
+ $newOrder->setCustomerEmail($customer->getemail());
171
+ //use default store currency
172
+ $currency = Mage::app()->getStore($this->getStoreId())->getCurrentCurrency();
173
+ $newOrder->setStoreId($this->getStoreId());
174
+ $newOrder->setOrderCurrencyCode($currency->getCode());
175
+ $newOrder->setBaseCurrencyCode($currency->getCode());
176
+ $newOrder->setStoreCurrencyCode($currency->getCode());
177
+ $newOrder->setStoreToBaseRate(1);
178
+
179
+ //shipping address
180
+ /* @var $shippingAddress Mage_Sales_Model_Order_Address */
181
+ $shippingAddress = Mage::getModel('sales/order_address');
182
+ $shippingAddress->setOrder($newOrder);
183
+ $shippingAddress->setId(null);
184
+ //12 is default quote address entity type
185
+ $shippingAddress->setEntityTypeId(12);
186
+ $shippingAddress->setFirstname($order['shipping_address']['name']);
187
+ $shippingAddress->setLastname('');
188
+
189
+ $shippingAddress->setStreet($order['shipping_address']['address']);
190
+
191
+ $shippingAddress->setCity($order['shipping_address']['city']);
192
+ $shippingAddress->setPostcode($order['shipping_address']['post_code']);
193
+ $shippingAddress->setRegion($listHelper->getRegionLabel($order['shipping_address']['region']));
194
+ $shippingAddress->setEmail($customer->getEmail());
195
+ $shippingAddress->setTelephone($order['shipping_address']['phone']);
196
+ $shippingAddress->setCompany('');
197
+ $shippingAddress->setCountryId($listHelper->getCountryLabel($order['shipping_address']['country']));
198
+ $newOrder->setShippingAddress($shippingAddress);
199
+
200
+ //billing address
201
+ /* @var $billingAddress Mage_Sales_Model_Order_Address */
202
+ $billingAddress = Mage::getModel('sales/order_address');
203
+ $billingAddress->setOrder($newOrder);
204
+ $billingAddress->setId(null);
205
+
206
+ $billingAddress->setEntityTypeId(12);
207
+ $billingAddress->setFirstname($order['billing_address']['name']);
208
+ $billingAddress->setLastname('');
209
+ $billingAddress->setStreet($order['billing_address']['address']);
210
+ $billingAddress->setCity($order['billing_address']['city']);
211
+ $billingAddress->setPostcode($order['billing_address']['post_code']);
212
+ $billingAddress->setRegion($listHelper->getRegionLabel($order['billing_address']['region']));
213
+ $billingAddress->setCountryId($listHelper->getCountryLabel($order['billing_address']['country']));
214
+ $billingAddress->setEmail($customer->getEmail());
215
+ $billingAddress->setTelephone($order['billing_address']['phone']);
216
+ $billingAddress->setcompany('');
217
+
218
+ $newOrder->setBillingAddress($billingAddress);
219
+
220
+ //Payment method
221
+ /* @var Mage_Sales_Model_Order_Payment */
222
+ $payment = Mage::getModel('sales/order_payment');
223
+ $payment->setMethod('bootic_payment_method');
224
+ $newOrder->setPayment($payment);
225
+
226
+ //shipping method
227
+ $shippingTaxAmount = 0.00;
228
+ $shippingAmount = $order['shipping_amount'];
229
+ $newOrder->setShippingMethod('bootic_flat_shipping');
230
+ $newOrder->setShippingDescription('Bootic Flat Shipping');
231
+ $newOrder->setShippingAmount((double) $shippingAmount);
232
+ $newOrder->setBaseShippingAmount((double) $shippingAmount);
233
+ $newOrder->setShippingTaxAmount((double) $shippingTaxAmount);
234
+ $newOrder->setBaseShippingTaxAmount((double) $shippingTaxAmount);
235
+
236
+ //init order totals
237
+ $newOrder
238
+ ->setGrandTotal($shippingAmount + $shippingTaxAmount)
239
+ ->setBaseGrandTotal($shippingAmount + $shippingTaxAmount)
240
+ ->setTaxAmount($shippingTaxAmount)
241
+ ->setBaseTaxAmount($shippingTaxAmount);
242
+
243
+ foreach ($order['items'] as $item) {
244
+ /* @var $product Mage_Catalog_Model_Product */
245
+ $product = Mage::helper('bootic/product')->loadByBooticId($item['item_id']);
246
+ if (!$product) {
247
+ Mage::log('skipping: '.$item['item_id']);
248
+ continue;
249
+ }
250
+ //set price and tax
251
+ $price_excl_tax = $item['price'];
252
+ $price_incl_tax = $item['price'] + $item['tax'];
253
+ $tax = $item['tax'];
254
+ $qty = $item['quantity'];
255
+ $taxRate = $item['tax_rate'];
256
+ $taxTotal = $tax * $qty;
257
+ $htTotal = $price_excl_tax * $qty;
258
+ $newOrderItem = Mage::getModel('sales/order_item')
259
+ ->setProductId($product->getId())
260
+ ->setSku($product->getSku())
261
+ ->setName($product->getName())
262
+ ->setWeight($product->getWeight())
263
+ ->setTaxClassId($product->getTaxClassId())
264
+ ->setCost($product->getCost())
265
+ ->setOriginalPrice($price_excl_tax)
266
+ ->setBaseOriginalPrice($price_excl_tax)
267
+ ->setIsQtyDecimal(0)
268
+ ->setProduct($product)
269
+ ->setPrice((double) $price_excl_tax)
270
+ ->setBasePrice((double) $price_excl_tax)
271
+ ->setQtyOrdered($qty)
272
+ ->setTotalQty($qty)//used to track quantity to decrement from inventory
273
+ ->setTaxAmount($taxTotal)
274
+ ->setBaseTaxAmount($taxTotal)
275
+ ->setTaxPercent($taxRate)
276
+ ->setRowTotal($htTotal)
277
+ ->setBaseRowTotal($htTotal)
278
+ ->setRowWeight($product->getWeight() * $qty)
279
+ ->setBaseTaxBeforeDiscount($taxTotal)
280
+ ->setTaxBeforeDiscount($taxTotal);
281
+
282
+ //add product
283
+ $newOrder->addItem($newOrderItem);
284
+ $newOrder
285
+ ->setSubtotal($newOrder->getSubtotal() + $price_excl_tax * $qty)
286
+ ->setBaseSubtotal($newOrder->getBaseSubtotal() + $price_excl_tax * $qty)
287
+ ->setGrandTotal($newOrder->getGrandTotal() + (($tax + $price_excl_tax) * $qty))
288
+ ->setBaseGrandTotal($newOrder->getBaseGrandTotal() + (($tax + $price_excl_tax) * $qty))
289
+ ->setTaxAmount($newOrder->getTaxAmount() + $tax * $qty)
290
+ ->setBaseTaxAmount($newOrder->getBaseTaxAmount() + $tax * $qty);
291
+ //done with product
292
+ }
293
+ //save order
294
+ $newOrder->setstatus('processing');
295
+ $newOrder->setstate('new');
296
+ $newOrder->addStatusToHistory(
297
+ 'pending',
298
+ 'Bootic Order from shop:' . $order['shop_name'] . ' #' . $order['basket']
299
+ );
300
+ $newOrder->setInvoiceComments('Bootic Order ' . $order['shop_name'] . ' #' . $order['basket']);
301
+ $this->_updateProductStock($newOrder);
302
+ $newOrder->save();
303
+ if ($newOrder->getId()) {
304
+ Mage::getModel('bootic/order_data')
305
+ ->setOrderId($newOrder->getId())
306
+ ->setBooticOrderId($order['basket'])
307
+ ->setInSync(true)
308
+ ->setTransactions($order['transactions'])
309
+ ->setLastStatus(self::ORDERED)
310
+ ->save();
311
+ }
312
+
313
+ return $newOrder;
314
+ }
315
+
316
+ /**
317
+ * Fetch and returns customer if it already exists,
318
+ * Create a new one otherwise.
319
+ *
320
+ * @param array $order
321
+ * @return Mage_Customer_Model_Customer
322
+ */
323
+ public function createOrGetCustomer($order)
324
+ {
325
+ $email = $order['buyer_email'];
326
+ $storeId = $this->getStoreId();
327
+ $webSiteId = $this->getWebsiteId();
328
+ $customer = Mage::getModel('customer/customer')
329
+ ->setWebsiteId($webSiteId)
330
+ ->loadByEmail($email);
331
+
332
+ if ($customer->getId()) {
333
+ return $customer;
334
+ }
335
+
336
+ list($firstname, $lastname) = sscanf($order['buyer_name'], '%s %s');
337
+
338
+ $customer = Mage::getModel('customer/customer');
339
+ $customer
340
+ ->setStoreId($storeId)
341
+ ->setWebsiteId($webSiteId)
342
+ ->setFirstname($firstname)
343
+ ->setLastname($lastname)
344
+ ->setEmail($email)
345
+ ->save();
346
+
347
+ return $customer;
348
+ }
349
+
350
+ /**
351
+ * @return int
352
+ */
353
+ public function getStoreId()
354
+ {
355
+ return Mage::getStoreConfig('bootic/system/store_id');
356
+ }
357
+
358
+ /**
359
+ * @return int
360
+ */
361
+ public function getWebsiteId($storeId = null)
362
+ {
363
+ $storeId = null == $storeId ? $this->getStoreId() : $storeId;
364
+
365
+ return Mage::getModel('core/store')->load($storeId)->getWebsiteId();
366
+ }
367
+ /**
368
+ * set order data to out of sync
369
+ * @param Mage_Sales_Model_Order $order
370
+ */
371
+ public function setOutOfSync(Mage_Sales_Model_Order $order)
372
+ {
373
+ /* @var $orderData Bootic_Bootic_Model_Order_Data */
374
+ $orderData = Mage::getModel('bootic/order_data')->load($order->getId());
375
+ if ($orderData->getBooticOrderId()) {
376
+ $orderData->setInSync(false)->save();
377
+ }
378
+ }
379
+ /**
380
+ * syncrhonize order status
381
+ */
382
+ public function syncOrders()
383
+ {
384
+ /* @var $collection Mage_Sales_Model_Mysql4_Order_Collection */
385
+ $collection = Mage::getResourceModel('sales/order_collection')->addAttributeToSelect('*');
386
+ $collection->join(
387
+ 'bootic/order_data',
388
+ 'entity_id=order_id',
389
+ array(
390
+ 'bootic_order_id' => 'bootic_order_id',
391
+ 'magento_order_id' => 'order_id',
392
+ )
393
+ );
394
+
395
+ $collection->addAttributeToFilter('in_sync', false);
396
+
397
+ foreach ($collection as $order) {
398
+ /* @var $order Mage_Sales_Model_Order */
399
+ /* @var $orderData Bootic_Bootic_Model_Order_Data */
400
+ $orderData = Mage::getModel('bootic/order_data')->load($order->getId());
401
+ Mage::log($order->getState());
402
+ try {
403
+ switch ($order->getState()) {
404
+ case Mage_Sales_Model_Order::STATE_CANCELED:
405
+ $this->_syncCanceledOrder($order, $orderData);
406
+ break;
407
+ case Mage_Sales_Model_Order::STATE_PROCESSING:
408
+ if ($this->allItemsShipped($order)) {
409
+ $this->_syncShippedOrder($order, $orderData);
410
+ }
411
+ break;
412
+ }
413
+ } catch (Exception $e) {
414
+ Mage::log($e->getMessage());
415
+ }
416
+ }
417
+ }
418
+ /**
419
+ * pull order status from orders marked as shipped
420
+ */
421
+ public function pullShippedOrderStatus()
422
+ {
423
+ /* @var $collection Mage_Sales_Model_Resource_Order_Collection */
424
+ $collection = Mage::getResourceModel('sales/order_collection')->addAttributeToSelect('*');
425
+ $collection->join(
426
+ array(
427
+ 'order_data' => 'bootic/order_data'
428
+ ),
429
+ 'order_data.order_id=entity_id',
430
+ array(
431
+ 'bootic_order_id' => 'bootic_order_id',
432
+ 'magento_order_id' => 'order_id',
433
+ )
434
+ );
435
+ $collection->addAttributeToFilter('last_status', self::IN_DELIVERY__SENT);
436
+ foreach ($collection as $order) {
437
+ /* @var $order Mage_Sales_Model_Order */
438
+ /* @var $orderData Bootic_Bootic_Model_Order_Data */
439
+ $orderData = Mage::getModel('bootic/order_data')->load($order->getId());
440
+ try {
441
+ if ($order->getState() == Mage_Sales_Model_Order::STATE_PROCESSING) {
442
+ $details = $this->getBootic()->getOrderDetails($orderData->getBooticOrderId());
443
+ if (true) {//@TODO if status complete
444
+ if ($this->_invoiceOrder($order)) {
445
+ $orderData->setInSync(true)->setLastStatus(self::COMPLETED)->save();
446
+ }
447
+ }
448
+ }
449
+ }catch (Exception $e) {
450
+ Mage::log($e->getMessage());
451
+ }
452
+ }
453
+ }
454
+ /**
455
+ * sync cnaceled order
456
+ * @param Mage_Sales_Model_Order $order
457
+ * @param Bootic_Bootic_Model_Order_Data $orderData
458
+ */
459
+ protected function _syncCanceledOrder(Mage_Sales_Model_Order $order, Bootic_Bootic_Model_Order_Data $orderData)
460
+ {
461
+ foreach ($orderData->getTransactions() as $transaction) {
462
+ $result = $this->getBootic()->updateTransactionStatus($transaction, array('status' => self::CANCELLED));
463
+ if ($result->getSuccess()) {
464
+ $orderData->setInSync(true)->setLastSyncStatus(self::CANCELLED)->save();//@TODO only save if success num == trans num?
465
+ }
466
+ }
467
+ }
468
+
469
+ /**
470
+ * sync shipped order
471
+ * @param Mage_Sales_Model_Order $order
472
+ * @param Bootic_Bootic_Model_Order_Data $orderData
473
+ */
474
+ protected function _syncShippedOrder(Mage_Sales_Model_Order $order, Bootic_Bootic_Model_Order_Data $orderData)
475
+ {
476
+ /* @var $shipment Mage_Sales_Model_Order_Shipment */
477
+ $shipment = $order->getShipmentsCollection()->getFirstItem();
478
+ /* @var $track Mage_Sales_Model_Order_Shipment_Track */
479
+ $track = $shipment->getTracksCollection()->getFirstItem();
480
+
481
+ $carrierId = Mage::helper('bootic/lists')->getIdByCarrierCode($track->getCarrierCode());
482
+ // Here for Magento 1.5 compatibility
483
+ $tracking = $track->getTrackNumber() ? $track->getTrackNumber() : $track->getNumber();
484
+
485
+ foreach ($orderData->getTransactions() as $transaction) {
486
+ $params = array(
487
+ 'status' => self::IN_DELIVERY__SENT,
488
+ 'tracking_no' => $tracking,
489
+ 'shipping_company_id' => $carrierId
490
+ );
491
+
492
+ if ($carrierId == 4) {
493
+ $params['custom_shipping_company'] = $track->getCarrierCode();
494
+ }
495
+
496
+ $result = $this->getBootic()->updateTransactionStatus($transaction, $params);
497
+ if ($result->getSuccess()) {
498
+ $orderData->setInSync(true)->setLastStatus(self::IN_DELIVERY__SENT)->save();
499
+ }
500
+ }
501
+ }
502
+ /**
503
+ * check if all items have been shipped
504
+ * @param Mage_Sales_Model_Order $order
505
+ */
506
+ public function allItemsShipped(Mage_Sales_Model_Order $order)
507
+ {
508
+ $shipmentComplete = true;
509
+ foreach ($order->getAllItems() as $item) {
510
+ /* @var $item Mage_Sales_Model_Order_Shipment_Item */
511
+ if ($item->getQtyToShip()>0 && !$item->getIsVirtual()
512
+ && !$item->getLockedDoShip())
513
+ {
514
+ $shipmentComplete = false;
515
+ }
516
+ }
517
+ return $shipmentComplete;
518
+ }
519
+ /**
520
+ * update product stock quantity
521
+ * @param Mage_Catalog_Model_Product $product
522
+ * @param unknown_type $qty
523
+ */
524
+ protected function _updateProductStock(Mage_Sales_Model_Order $order)
525
+ {
526
+ /* @var $helper Bootic_Bootic_Helper_Product_Inventory */
527
+ $helper = Mage::helper('bootic/product_inventory');
528
+ try {
529
+ $helper->subtractOrderInventory($order);
530
+ $helper->reindexOrderInventory($order);
531
+ } catch (Exception $e) {
532
+ Mage::logException($e);
533
+ }
534
+ }
535
+ /**
536
+ * invoice order
537
+ * @param Mage_Sales_Model_Order $order
538
+ */
539
+ protected function _invoiceOrder(Mage_Sales_Model_Order $order) {
540
+ try {
541
+ /* @var $convertor Mage_Sales_Model_Convert_Order */
542
+ $convertor = Mage::getModel('sales/convert_order');
543
+ $invoice = $convertor->toInvoice($order);
544
+ foreach ($order->getAllItems() as $orderItem) {
545
+ $invoiceItem = $convertor->itemToInvoiceItem($orderItem);
546
+ $invoiceItem->setQty($orderItem->getQtyOrdered());
547
+ $invoice->addItem($invoiceItem);
548
+ }
549
+ $invoice->collectTotals();
550
+ $invoice->register();
551
+ $invoice->getOrder()->setIsInProcess(true);
552
+ $transactionSave = Mage::getModel('core/resource_transaction')
553
+ ->addObject($invoice)
554
+ ->addObject($invoice->getOrder())
555
+ ->save();
556
+ $invoice->save();
557
+ //validate payment
558
+ $payment = $order->getPayment();
559
+ $payment->pay($invoice);
560
+ $payment->save();
561
+ $order->save();
562
+ return true;
563
+ } catch (Exception $e) {
564
+ Mage::logException($e);
565
+ return false;
566
+ }
567
+ }
568
+ }
app/code/community/Bootic/Bootic/Helper/Product.php ADDED
@@ -0,0 +1,1007 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Product extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ /** @var array */
9
+ protected $_booticAttributes;
10
+
11
+ /** @var int */
12
+ protected $_limit;
13
+
14
+ /**
15
+ * Constructor
16
+ */
17
+ public function __construct()
18
+ {
19
+ $this->_limit = (Mage::getStoreConfig('bootic/product/limit')) ? Mage::getStoreConfig('bootic/product/limit') : 50;
20
+ }
21
+
22
+ /**
23
+ * Queues a collection of products for cron to upload them to Bootic
24
+ *
25
+ * @param Mage_Core_Controller_Request_Http $request
26
+ */
27
+ public function massCreateProducts(Mage_Core_Controller_Request_Http $request)
28
+ {
29
+ $ids = $request->__get('ids');
30
+
31
+ $addedToQueue = array();
32
+ $alreadyQueued = array();
33
+ $configurableChildrenInQueue = array();
34
+ $configurableChildrenNeedParent = array();
35
+ $notProcessable = array();
36
+
37
+ foreach ($ids as $id) {
38
+ $product = Mage::getModel('catalog/product')->load($id);
39
+
40
+ if ($product->isConfigurable()) {
41
+ $childrenIds = Mage::getModel('catalog/product_type_configurable')->getChildrenIds($product->getId());
42
+
43
+ foreach ($childrenIds[0] as $childId) {
44
+ $childProduct = Mage::getModel('catalog/product')->load($childId);
45
+
46
+ $this->_queueProduct($childProduct, $addedToQueue, $alreadyQueued);
47
+
48
+ if (!in_array($childId, $ids)) {
49
+ // We remove the children from the errors list
50
+ if (($key = array_search($childId, $configurableChildrenNeedParent)) !== false) {
51
+ unset($configurableChildrenNeedParent[$key]);
52
+ }
53
+ $configurableChildrenInQueue[] = $childId;
54
+ }
55
+ }
56
+
57
+ $this->_queueProduct($product, $addedToQueue, $alreadyQueued);
58
+
59
+ } elseif ($product->getTypeId() == 'simple' || $product->getTypeId() == 'downloadable') {
60
+ $parentIds = Mage::getModel('catalog/product_type_configurable')->getParentIdsByChild($product->getId());
61
+ $product->setParentIds($parentIds);
62
+
63
+ if (count($parentIds) > 0) {
64
+ foreach ($parentIds as $parentId) {
65
+ $parent = Mage::getModel('catalog/product')->load($parentId);
66
+
67
+ if (
68
+ Mage::helper('bootic/product_data')->isNotCreated($parent) &&
69
+ !in_array($parentId, $ids)
70
+ ) {
71
+ $errorStatus = Mage::getModel('bootic/product_data')->getStatusError();
72
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $errorStatus);
73
+ $configurableChildrenNeedParent[] = $product;
74
+ } else {
75
+ $parentData = Mage::getModel('bootic/product_data')->load($parent->getId());
76
+ $parentStatus = $parentData->getBooticStatus();
77
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $parentStatus);
78
+ }
79
+ }
80
+ } else {
81
+ $this->_queueProduct($product, $addedToQueue, $alreadyQueued);
82
+ }
83
+ } else {
84
+ $notProcessable[] = $id;
85
+ }
86
+ }
87
+
88
+ if (count($alreadyQueued) > 0) {
89
+ Mage::getSingleton('adminhtml/session')->addNotice(count($alreadyQueued) . ' products were skipped because they were already queued.');
90
+ }
91
+
92
+ if (count($addedToQueue) > 0) {
93
+ Mage::getSingleton('adminhtml/session')->addSuccess(count($addedToQueue) . ' products were successfully queued.');
94
+ }
95
+
96
+ if (count($configurableChildrenInQueue) > 0) {
97
+ Mage::getSingleton('adminhtml/session')->addNotice(count($configurableChildrenInQueue) . ' configurable products children were automatically added to the queue because you added their parents.');
98
+ }
99
+
100
+ if (count($configurableChildrenNeedParent) > 0) {
101
+ // For these ones, we also add a log to make it easier for users to retrieve the products
102
+ foreach($configurableChildrenNeedParent as $configurableChild) {
103
+ $message = 'Please add parent products with ids ' . implode(',', $configurableChild->getParentIds());
104
+ Mage::helper('bootic/log')->addLog($configurableChild->getId(), 'error', $message);
105
+ }
106
+ }
107
+
108
+ if (count($notProcessable) > 0) {
109
+ Mage::getSingleton('adminhtml/session')->addError(count($notProcessable) . ' products have types that can\'t be currently processed on Bootic.');
110
+ }
111
+
112
+ return;
113
+ }
114
+
115
+ /**
116
+ * Queues a unique product
117
+ *
118
+ * @param Mage_Catalog_Model_Product $product
119
+ * @param $addedToQueue
120
+ * @param $alreadyQueued
121
+ */
122
+ protected function _queueProduct(Mage_Catalog_Model_Product $product, &$addedToQueue, &$alreadyQueued)
123
+ {
124
+ if (Mage::helper('bootic/product_data')->isQueueable($product)) {
125
+ Mage::helper('bootic/product_data')->updateProductStatus($product, Mage::getModel('bootic/product_data')->getStatusProcessing());
126
+ $addedToQueue[] = $product->getId();
127
+ } else {
128
+ $alreadyQueued[] = $product->getId();
129
+ }
130
+ }
131
+
132
+ /**
133
+ * load a product product model by bootic ID
134
+ * @param str $id
135
+ * @return Mage_Catalog_Model_Product | null
136
+ */
137
+ public function loadByBooticId($id)
138
+ {
139
+ /* @var $collection Mage_Catalog_Model_Resource_Product_Collection */
140
+ $collection = Mage::getResourceModel('catalog/product_collection')
141
+ ->addAttributeToSelect('*')
142
+ ->joinTable(
143
+ 'bootic/product_data',
144
+ 'magento_product_id=entity_id',
145
+ array (
146
+ 'bootic_product_id' => 'bootic_product_id',
147
+ )
148
+ )
149
+ ->addAttributeToFilter('bootic_product_id', $id)
150
+ ;
151
+
152
+ Mage::getModel('cataloginventory/stock_item')->addCatalogInventoryToProductCollection($collection);
153
+ $collection->load();
154
+
155
+ if ($collection->getSize()) {
156
+ $product = $collection->getFirstItem();
157
+ Mage::dispatchEvent('catalog_product_load_after', array('product' => $product));
158
+
159
+ return $product;
160
+ }
161
+
162
+ return null;
163
+ }
164
+
165
+ /**
166
+ * Uploads a batch of n products
167
+ */
168
+ public function uploadProducts()
169
+ {
170
+ $collection = Mage::getResourceModel('catalog/product_collection')
171
+ ->addAttributeToSelect('*')
172
+ ->addAttributeToSort('entity_id', 'ASC')
173
+ ->joinTable(
174
+ 'bootic/product_data',
175
+ 'magento_product_id=entity_id',
176
+ array(
177
+ 'bootic_product_id' => 'bootic_product_id',
178
+ 'bootic_stock_id' => 'bootic_stock_id',
179
+ 'bootic_status' => 'bootic_status',
180
+ 'creation_time' => 'creation_time',
181
+ 'update_time' => 'update_time',
182
+ 'is_info_synced' => 'is_info_synced',
183
+ 'is_stock_synced' => 'is_stock_synced',
184
+ 'upload_failures' => 'upload_failures'
185
+ ),
186
+ "{{table}}.bootic_status=". Mage::getModel('bootic/product_data')->getStatusProcessing() .""
187
+ )
188
+ ->load()
189
+ ;
190
+
191
+ $count = 1;
192
+ foreach ($collection as $product) {
193
+ if ($count > $this->_limit) {
194
+ break;
195
+ }
196
+
197
+ if ($this->_isProductUploadable($product)) {
198
+ $booticProductId = $product->getBooticProductId();
199
+
200
+ try {
201
+ if (empty($booticProductId)) {
202
+ if ($product->isConfigurable()) {
203
+ $this->makeConfigurableProduct($product);
204
+ } else {
205
+ $this->makeSimpleProduct($product);
206
+ }
207
+ } else {
208
+ if ($product->isConfigurable()) {
209
+ $this->editConfigurableProduct($product);
210
+ } else {
211
+ $this->editSimpleProduct($product);
212
+ }
213
+ }
214
+
215
+ // Everything went well, we flag product as ready to go
216
+ $statusPending = Mage::getModel('bootic/product_data')->getStatusPendingApproval();
217
+ $options = array(
218
+ 'upload_failures' => 0,
219
+ 'is_info_synced' => true
220
+ );
221
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $statusPending, $options);
222
+
223
+ Mage::helper('bootic/log')->addLog($product->getId(), 'success', 'The product ' . $product->getName() . ' was succesfully created.');
224
+ $count ++;
225
+
226
+ } catch (Bootic_Bootic_Exception $e) {
227
+ // If an error occured on one of the calls, we flag product as errored and log the errors
228
+ $statusError = Mage::getModel('bootic/product_data')->getStatusError();
229
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $statusError);
230
+
231
+ Mage::helper('bootic/log')->addLog($product->getId(), 'error', $e->getMessage());
232
+ $count ++;
233
+
234
+ } catch (Bootic_Api_Exception $e) {
235
+ // Probably a network error or an API downtime
236
+ // We try 5 times and then we notify Admin
237
+ $statusProcessing = Mage::getModel('bootic/product_data')->getStatusProcessing();
238
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
239
+ $productData->setBooticStatus($statusProcessing);
240
+ $productData->incrementUploadFailures();
241
+ $productData->save();
242
+
243
+ // If 5 Api exceptions occur, we dispatch this event to notify the admin
244
+ if ($productData->getUploadFailures() > 4) {
245
+ $statusError = Mage::getModel('bootic/product_data')->getStatusError();
246
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $statusError);
247
+
248
+ Mage::dispatchEvent('bootic_product_upload_failure', array('product' => $product));
249
+ }
250
+
251
+ $count ++;
252
+
253
+ } catch (Exception $e) {
254
+ // If anything else weird happens, we re-queue the product
255
+ $statusProcessing = Mage::getModel('bootic/product_data')->getStatusProcessing();
256
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $statusProcessing);
257
+
258
+ $count ++;
259
+
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Edits a batch of n products
267
+ */
268
+ public function editProducts()
269
+ {
270
+ $collection = Mage::getResourceModel('catalog/product_collection')
271
+ ->addAttributeToSelect('*')
272
+ ->addAttributeToSort('entity_id', 'ASC')
273
+ ->joinTable(
274
+ 'bootic/product_data',
275
+ 'magento_product_id=entity_id',
276
+ array(
277
+ 'bootic_product_id' => 'bootic_product_id',
278
+ 'bootic_stock_id' => 'bootic_stock_id',
279
+ 'bootic_status' => 'bootic_status',
280
+ 'creation_time' => 'creation_time',
281
+ 'update_time' => 'update_time',
282
+ 'is_info_synced' => 'is_info_synced',
283
+ 'is_stock_synced' => 'is_stock_synced',
284
+ 'upload_failures' => 'upload_failures'
285
+ ),
286
+ "({{table}}.bootic_status=". Mage::getModel('bootic/product_data')->getStatusCreated() .""
287
+ . " OR {{table}}.bootic_status=". Mage::getModel('bootic/product_data')->getStatusPendingApproval() .""
288
+ . " OR {{table}}.bootic_status=". Mage::getModel('bootic/product_data')->getStatusIncomplete() .")"
289
+ . " AND {{table}}.is_info_synced=0"
290
+ )
291
+ ->load()
292
+ ;
293
+
294
+ $count = 1;
295
+ foreach ($collection as $product) {
296
+ if ($count > $this->_limit) {
297
+ break;
298
+ }
299
+
300
+ try {
301
+ if ($product->isConfigurable()) {
302
+ $this->editConfigurableProduct($product);
303
+ } else {
304
+ $this->editSimpleProduct($product);
305
+ }
306
+
307
+ // Everything went well, we set flag the product data to created and synced
308
+ $statusCreated = Mage::getModel('bootic/product_data')->getStatusCreated();
309
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $statusCreated);
310
+ Mage::helper('bootic/product_data')->setInfoSync($product, true);
311
+
312
+ $count ++;
313
+
314
+ } catch (Bootic_Bootic_Exception $e) {
315
+ // If update fails or get warning, we show admin the product has errors
316
+ $statusException = ($e->isWarning()) ? Mage::getModel('bootic/product_data')->getStatusIncomplete() : Mage::getModel('bootic/product_data')->getStatusError();
317
+ $status = ($e->isWarning()) ? 'warning' : 'error';
318
+ Mage::helper('bootic/product_data')->updateProductStatus($product, $statusException);
319
+
320
+ Mage::helper('bootic/log')->addLog($product->getId(), $status, $e->getMessage());
321
+
322
+ // If it's an error / then we disable the product and Admin will have to fix it
323
+ if (!$e->isWarning()) {
324
+ $this->getBootic()->editProduct(array(
325
+ 'product_id' => $product->getBooticProductId(),
326
+ 'available' => false
327
+ ));
328
+ }
329
+
330
+ } catch (Exception $e) {
331
+ // If anything else weird happens, we simply disregard
332
+ Mage::log('Failed editing a product on Bootic');
333
+ }
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Tests is the product is uploadable to Bootic
339
+ * Only Simple, Downloadable and Configurable products can be uploaded
340
+ *
341
+ * @param Mage_Catalog_Model_Product $product
342
+ * @return bool
343
+ */
344
+ protected function _isProductUploadable(Mage_Catalog_Model_Product $product)
345
+ {
346
+ $parents = Mage::getModel('catalog/product_type_configurable')->getParentIdsByChild($product->getId());
347
+
348
+ $result = true;
349
+
350
+ if (!$product->isConfigurable() && $product->getTypeId() != 'simple' && $product->getType != 'downloadable') {
351
+ Mage::helper('bootic/log')->addLog($product->getId(), 'error', 'This type of product cannot currently be uploaded to Bootic.');
352
+ $result = false;
353
+ } elseif (count($parents) > 0) {
354
+ $result = false;
355
+ }
356
+
357
+ return $result;
358
+ }
359
+
360
+ /**
361
+ * Adds a simple product to Bootic
362
+ *
363
+ * @param Mage_Catalog_Model_Product $product
364
+ */
365
+ public function makeSimpleProduct(Mage_Catalog_Model_Product $product)
366
+ {
367
+ $attributes = $product->getAttributes();
368
+ $personalizations = array();
369
+ foreach ($attributes as $attribute) {
370
+ if ($attribute->getIsVisibleOnFront()) {
371
+ $personalizations[] = $attribute->getFrontendLabel() .': ' .$attribute->getFrontend()->getValue($product) . '<br/>';
372
+ }
373
+ }
374
+ $product->setPersonalizations($personalizations);
375
+
376
+ // We prepare the array to be uploaded
377
+ $p = $this->_prepareBooticProductArray($product);
378
+
379
+ // Here we go
380
+ $result = $this->getBootic()->addProduct($p);
381
+
382
+ if (!$result->isSuccess()) {
383
+ throw new Bootic_Bootic_Exception($result->getErrorMessage());
384
+
385
+ } else {
386
+ $stockCombinations = $result->getData('stock_combinations');
387
+ $valid = true;
388
+ $stockId = $stockCombinations[0]['stock_id'];
389
+ $productId = $result->getData('product_id');
390
+ $sku = $product->getSku();
391
+ $stock = Mage::getModel('cataloginventory/stock_item')->loadByProduct($product)->getQty();
392
+
393
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
394
+ $productData->setBooticProductId($productId);
395
+ $productData->setBooticStockId($stockId );
396
+
397
+ try {
398
+ $this->updateProductStock($productId, $stockId, $sku, $stock, $valid);
399
+ $productData->setIsStockSynced(true);
400
+ $productData->save();
401
+
402
+ } catch (Bootic_Api_Exception $e) {
403
+ // If an error occurs during this call, product stock gets set to out of sync
404
+ $productData->setIsStockSynced(false);
405
+ $productData->save();
406
+ }
407
+
408
+ // If product upload call originally had warnings, we notify the admin
409
+ if ($result->hasWarning()) {
410
+ $warningMessages = $result->getWarningMessages();
411
+
412
+ // We remove the first message which is not intended for Magento Admins
413
+ array_shift($warningMessages);
414
+ $message = implode(' ', $warningMessages);
415
+
416
+ throw new Bootic_Bootic_Exception($message);
417
+ }
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Edits a simple product on Bootic
423
+ *
424
+ * @param Mage_Catalog_Model_Product $product
425
+ * @throws Bootic_Bootic_Exception
426
+ */
427
+ public function editSimpleProduct(Mage_Catalog_Model_Product $product)
428
+ {
429
+ $attributes = $product->getAttributes();
430
+ $personalizations = array();
431
+ foreach ($attributes as $attribute) {
432
+ if ($attribute->getIsVisibleOnFront()) {
433
+ $personalizations[] = $attribute->getFrontendLabel() .': ' .$attribute->getFrontend()->getValue($product) . '<br/>';
434
+ }
435
+ }
436
+ $product->setPersonalizations($personalizations);
437
+
438
+ // We prepare the array to be uploaded
439
+ $p = $this->_prepareBooticProductArray($product);
440
+
441
+ if ($product->getBooticProductId() == null) {
442
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
443
+ $product->setBooticProductId($productData->getBooticProductId());
444
+ }
445
+
446
+ $p = array_merge(array('product_id' => $product->getBooticProductId()), $p);
447
+
448
+ // Here we go
449
+ $result = $this->getBootic()->editProduct($p);
450
+
451
+ if (!$result->isSuccess()) {
452
+ throw new Bootic_Bootic_Exception($result->getErrorMessage());
453
+
454
+ } elseif ($result->hasWarning()) {
455
+ $warningMessages = $result->getWarningMessages();
456
+
457
+ // We remove the first message which is not intended for Magento Admins
458
+ array_shift($warningMessages);
459
+ $message = implode(' ', $warningMessages);
460
+
461
+ throw new Bootic_Bootic_Exception($message, 0, null, true);
462
+ }
463
+ }
464
+
465
+ /**
466
+ * Adds a configurable product to Bootic
467
+ *
468
+ * @param Mage_Catalog_Model_Product $product
469
+ * @return bool
470
+ * @throws Exception
471
+ */
472
+ public function makeConfigurableProduct(Mage_Catalog_Model_Product $product)
473
+ {
474
+ $productAttributes = $this->_getConfigurableProductAttributes($product, $productOptions, $matchedAttributes);
475
+
476
+ $attributes = $product->getAttributes();
477
+ $personalizations = array();
478
+ foreach ($attributes as $attribute) {
479
+ if ($attribute->getIsVisibleOnFront() && !in_array($attribute, $productOptions['attributes'])) {
480
+ $personalizations[] = $attribute->getFrontendLabel() .': ' .$attribute->getFrontend()->getValue($product) . '<br/>';
481
+ }
482
+ }
483
+ $product->setPersonalizations($personalizations);
484
+
485
+ // We prepare array to get uploaded
486
+ $p = $this->_prepareBooticProductArray($product, $productAttributes);
487
+
488
+ // Here we go
489
+ $result = $this->getBootic()->addProduct($p);
490
+
491
+ if (!$result->isSuccess()) {
492
+ throw new Bootic_Bootic_Exception($result->getErrorMessage());
493
+
494
+ } else {
495
+ $stockCombinations = $result->getData('stock_combinations');
496
+ $productId = $result->getData('product_id');
497
+ $stockInSync = true;
498
+
499
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
500
+ $productData->setBooticProductId($result->getData('product_id'));
501
+
502
+ foreach ($stockCombinations as $stockCombination) {
503
+ $productArray = array();
504
+
505
+ foreach ($stockCombination['elements'] as $element) {
506
+
507
+ // now we loop through Mage matchedAttributes array to find a matching
508
+ foreach ($matchedAttributes as $attribute) {
509
+
510
+ foreach ($attribute['options'] as $option) {
511
+ if ($option['label'] == $element['value']) {
512
+ $productArray[] = $option['products'];
513
+ }
514
+ }
515
+ }
516
+ }
517
+
518
+ $prod = array();
519
+ foreach ($productArray as $products) {
520
+ $prod = empty($prod) ? $products : array_intersect($prod, $products);
521
+ }
522
+
523
+ $valid = false;
524
+ $stockId = $stockCombination['stock_id'];
525
+ $sku = '';
526
+ $stock = '';
527
+
528
+ // If product exists, we set its stock combination
529
+ if (!empty($prod)) {
530
+ $_product = Mage::getModel('catalog/product')->load(current($prod));
531
+ $_productData = Mage::getModel('bootic/product_data')->load($_product->getId());
532
+ $_productData->setBooticProductId($productId);
533
+ $_productData->setBooticStockId($stockId);
534
+
535
+ $valid = true;
536
+ $sku = $_product->getSku();
537
+ $stock = Mage::getModel('cataloginventory/stock_item')->loadByProduct($_product)->getQty();
538
+
539
+ try {
540
+ $this->updateProductStock($productId, $stockId, $sku, $stock, $valid);
541
+ $_productData->setIsStockSynced(true);
542
+ $_productData->save();
543
+
544
+ } catch (Bootic_Api_Exception $e) {
545
+ // If an error occurs during this call, product stock gets set to out of sync
546
+ $_productData->setIsStockSynced(false);
547
+ $_productData->save();
548
+ }
549
+
550
+ // If not, we just set its stock to not valid - no big deal if this one fails
551
+ } else {
552
+ try {
553
+ $this->updateProductStock($productId, $stockId, $sku, $stock, $valid);
554
+ } catch (Exception $e) {
555
+ $stockInSync = false;
556
+ }
557
+ }
558
+ }
559
+
560
+ $productData->setIsStockSynced($stockInSync);
561
+ $productData->save();
562
+
563
+ // If product upload call originally had warnings, we notify the admin
564
+ if ($result->hasWarning()) {
565
+ $warningMessages = $result->getWarningMessages();
566
+
567
+ // We remove the first message which is not intended for Magento Admins
568
+ array_shift($warningMessages);
569
+ $message = implode(' ', $warningMessages);
570
+
571
+ throw new Bootic_Bootic_Exception($message);
572
+ }
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Edit a configurable product on Bootic
578
+ *
579
+ * @param Mage_Catalog_Model_Product $product
580
+ * @throws Bootic_Bootic_Exception
581
+ */
582
+ public function editConfigurableProduct(Mage_Catalog_Model_Product $product)
583
+ {
584
+ $productAttributes = $this->_getConfigurableProductAttributes($product, $productOptions, $matchedAttributes);
585
+
586
+ $attributes = $product->getAttributes();
587
+ $personalizations = array();
588
+ foreach ($attributes as $attribute) {
589
+ if ($attribute->getIsVisibleOnFront() && !in_array($attribute, $productOptions['attributes'])) {
590
+ $personalizations[] = $attribute->getFrontendLabel() .': ' .$attribute->getFrontend()->getValue($product) . '<br/>';
591
+ }
592
+ }
593
+ $product->setPersonalizations($personalizations);
594
+
595
+ // We prepare array to get uploaded
596
+ $p = $this->_prepareBooticProductArray($product, $productAttributes);
597
+
598
+ if ($product->getBooticProductId() == null) {
599
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
600
+ $product->setBooticProductId($productData->getBooticProductId());
601
+ }
602
+
603
+ $p = array_merge(array('product_id' => $product->getBooticProductId()), $p);
604
+
605
+ // Here we go
606
+ $result = $this->getBootic()->editProduct($p);
607
+
608
+ if (!$result->isSuccess()) {
609
+ throw new Bootic_Bootic_Exception($result->getErrorMessage());
610
+ } elseif ($result->hasWarning()) {
611
+ $warningMessages = $result->getWarningMessages();
612
+
613
+ // We remove the first message which is not intended for Magento Admins
614
+ array_shift($warningMessages);
615
+ $message = implode(' ', $warningMessages);
616
+
617
+ throw new Bootic_Bootic_Exception($message, 0, null, true);
618
+ }
619
+ }
620
+
621
+ protected function _getConfigurableProductAttributes(Mage_Catalog_Model_Product $product, &$productOptions, &$matchedAttributes)
622
+ {
623
+ $productOptions = Mage::helper('bootic/product_type_configurable')->getOptions($product);
624
+
625
+ $matchedAttributes = array();
626
+ foreach ($productOptions['attributes'] as $attribute) {
627
+ $exists = false;
628
+ foreach ($this->getBooticAttributes() as $booticAttribute) {
629
+ if ($attribute['code'] == $booticAttribute['name']) {
630
+ $matchedAttributes[$booticAttribute['id']] = $attribute;
631
+ $exists = true;
632
+ break;
633
+ }
634
+ }
635
+
636
+ if (!$exists) {
637
+ $booticAttribute = $this->createProductAttribute($attribute['code']);
638
+ $matchedAttributes[$booticAttribute['id']] = $attribute;
639
+ }
640
+ }
641
+
642
+ // We prepare the string for bootic's product_attributes property
643
+ // should look something like 'attributeId,price(i.e. 2.00),qty;attributeId,price,etc...'
644
+ $productAttributes = '';
645
+ foreach ($matchedAttributes as $id => $attribute) {
646
+ foreach ($attribute['options'] as $option) {
647
+ $productAttributes .= $id . ',';
648
+ $productAttributes .= number_format($option['price'], 2) . ',';
649
+ $productAttributes .= $option['label'] . ';';
650
+ }
651
+ }
652
+
653
+ return $productAttributes;
654
+ }
655
+
656
+ /**
657
+ * Updates stock for a product
658
+ *
659
+ * @param null $stockId
660
+ * @param null $sku
661
+ * @param null $stock
662
+ * @param bool $valid
663
+ * @return bool
664
+ * @throws Exception
665
+ */
666
+ public function updateProductStock($productId = null, $stockId = null, $sku = null, $stock = null, $valid = false)
667
+ {
668
+ $p['product_id'] = (int)$productId;
669
+ $p['stock_id'] = $stockId;
670
+ $p['sku'] = $sku;
671
+ $p['stock'] = (int)$stock;
672
+ $p['valid'] = $valid;
673
+
674
+ $result = $this->getBootic()->updateProductStock($p);
675
+
676
+ if (!$result->isSuccess()) {
677
+ throw new Bootic_Bootic_Exception($result->getErrorMessage());
678
+ }
679
+
680
+ return $result;
681
+ }
682
+
683
+ /**
684
+ * Lists all attributes existing on Bootic
685
+ *
686
+ * @return array|mixed
687
+ */
688
+ public function listProductAvailableAttributes()
689
+ {
690
+ $result = $this->getBootic()->listProductAvailableAttributes();
691
+
692
+ return $result->getData();
693
+ }
694
+
695
+ /**
696
+ * Creates a new attribute on Bootic
697
+ *
698
+ * @param $attributeCode
699
+ * @return array|bool|mixed
700
+ * @throws Exception
701
+ */
702
+ public function createProductAttribute($attributeCode)
703
+ {
704
+ if (is_null($attributeCode)) {
705
+ throw new Bootic_Bootic_Exception('Attribute code cannot be empty');
706
+ }
707
+
708
+ $exist = false;
709
+ $existingAttributes = $this->getBooticAttributes();
710
+
711
+ foreach ($existingAttributes as $existingAttribute) {
712
+ if ($attributeCode == $existingAttribute['name']) {
713
+ $exist = true;
714
+ }
715
+ }
716
+
717
+ if (!$exist) {
718
+ $result = $this->getBootic()->createProductAttribute(array(
719
+ 'name' => $attributeCode
720
+ ));
721
+
722
+ if ($result->isSuccess()) {
723
+ $this->addBooticAttribute($result->getData('attribute'));
724
+ return $result->getData('attribute');
725
+ }
726
+ }
727
+
728
+ return false;
729
+ }
730
+
731
+ /**
732
+ * Gets all locally stored Bootic attributes
733
+ *
734
+ * @return array
735
+ */
736
+ public function getBooticAttributes()
737
+ {
738
+ if (is_null($this->_booticAttributes)) {
739
+ $this->_booticAttributes = $this->listProductAvailableAttributes();
740
+ }
741
+
742
+ return $this->_booticAttributes;
743
+ }
744
+
745
+ /**
746
+ * Add a new attribute to the local stored Bootic attributes
747
+ *
748
+ * @param array $attribute
749
+ * @return Bootic_Bootic_Helper_Product
750
+ */
751
+ public function addBooticAttribute(array $attribute)
752
+ {
753
+ $this->_booticAttributes[] = $attribute;
754
+ return $this;
755
+ }
756
+
757
+ /**
758
+ * Prepares and formats the product array to be uploaded to Bootic
759
+ *
760
+ * @param $product
761
+ * @param null $product_attributes
762
+ * @return array
763
+ */
764
+ private function _prepareBooticProductArray(Mage_Catalog_Model_Product $_product, $product_attributes = null)
765
+ {
766
+ /** @var $product Mage_Catalog_Model_Product */
767
+ $product = Mage::getModel('catalog/product')->load($_product->getId());
768
+
769
+ $p['product_name'] = $product->getName();
770
+
771
+ // Here we add all the attributes to the long description
772
+ $p['long_desc'] = $product->getDescription();
773
+ $productPersonalizations = $_product->getPersonalizations();
774
+ foreach ($productPersonalizations as $personalization) {
775
+ $p['long_desc'] .= '<p>' . $personalization . '</p>';
776
+ }
777
+
778
+ // Truncate the string
779
+ $shortDesc = Mage::helper('core/string')->truncate($product->getShortDescription(), 120, '...');
780
+ // Replace line breaks with spaces
781
+ $shortDesc = preg_replace("/[\n\r\t]/"," ", $shortDesc);
782
+ // Make sure everything is UTF8
783
+ $p['short_desc'] = Mage::helper('core/string')->cleanString($shortDesc);
784
+ $p['price'] = floatval($product->getPrice());
785
+ $p['in_stock'] = Mage::getModel('cataloginventory/stock_item')->loadByProduct($product)->getIsInStock();
786
+ $p['supplier_reference'] = $product->getSku();
787
+
788
+ // Category Mapping
789
+ $categoryCollection = Mage::helper('bootic/category')->getMappedCategoryCollection($product);
790
+
791
+ if (count($categoryCollection) == 0) {
792
+ throw new Bootic_Bootic_Exception('Product\'s Magento category needs to be mapped to a Bootic category in Bootic > Catalog > Category Mapping');
793
+ }
794
+
795
+ $i = 1;
796
+ $secondaryCategories = null;
797
+ foreach ($categoryCollection as $category) {
798
+ if ($i == 1) {
799
+ $p['category'] = (int) $category->getBooticCategoryId();
800
+ } else {
801
+ $secondaryCategories .= (int) $category->getBooticCategoryId() . ',';
802
+ }
803
+ $i++;
804
+ }
805
+
806
+ if ($secondaryCategories) {
807
+ $secondaryCategories = rtrim($secondaryCategories, ',');
808
+ $p['secondary_category'] = $secondaryCategories;
809
+ }
810
+
811
+ $p['can_others_sell'] = true;
812
+ $p['others_can_edit_price'] = false;
813
+ $p['others_can_edit_content'] = false;
814
+ $p['monthly_sales_req_for_bonus'] = Mage::getStoreConfig('bootic/sales/monthly_sales_req_for_bonus');
815
+ $p['bonus_amount'] = Mage::getStoreConfig('bootic/sales/bonus_amount');
816
+ $p['commission'] = Mage::getStoreConfig('bootic/sales/commission');
817
+
818
+ // We get the Main image
819
+ $mainImage = Mage::getStoreConfig('bootic/product/image') ? Mage::getStoreConfig('bootic/product/image') : 'image';
820
+ $img = file_get_contents(str_replace(':8080', '', $product->getMediaConfig()->getMediaUrl($product->getData($mainImage))));
821
+ $imgEncoded = base64_encode($img);
822
+ $p['upload_file_method'] = 'base64';
823
+ $p['product_image_1'] = $imgEncoded;
824
+
825
+ // We get the product's image gallery - Bootic limits to 4 additional images
826
+ $gallery = $product->getMediaGalleryImages();
827
+ $i = 2;
828
+ foreach ($gallery as $image) {
829
+ if ($i > 5) break;
830
+ $img = file_get_contents(str_replace(':8080', '', Mage::helper('catalog/image')->init($product, 'thumbnail', $image->getFile())->__toString()));
831
+ $imgEncoded = base64_encode($img);
832
+ $index = 'product_image_' . $i;
833
+ $p[$index] = $imgEncoded;
834
+ $i++;
835
+ }
836
+
837
+ // Default options - non editable from Magento
838
+ $p['need_quotation'] = false;
839
+ $p['publish'] = true;
840
+ $p['available'] = true;
841
+ $p['category_linked'] = true;
842
+ $p['position_in_shop'] = 500;
843
+ $p['active_in_shop'] = true;
844
+
845
+ // Options from config
846
+ if ($brandName = Mage::getStoreConfig('bootic/product/brand_name')) {
847
+ $p['brand_name'] = $product->getResource()->getAttribute($brandName)->getFrontend()->getValue($product);
848
+ }
849
+
850
+ if ($warrantyMonths = Mage::getStoreConfig('bootic/product/warranty_months')) {
851
+ $p['warranty_months'] = $product->getResource()->getAttribute($warrantyMonths)->getFrontend()->getValue($product);
852
+ }
853
+
854
+ if ($isbn = Mage::getStoreConfig('bootic/product/isbn')) {
855
+ $p['isbn'] = $product->getResource()->getAttribute($isbn)->getFrontend()->getValue($product);
856
+ }
857
+
858
+ if ($ean = Mage::getStoreConfig('bootic/product/ean')) {
859
+ $p['ean13'] = $product->getResource()->getAttribute($ean)->getFrontend()->getValue($product);
860
+ }
861
+
862
+ if ($upc = Mage::getStoreConfig('bootic/product/upc')) {
863
+ $p['upc'] = $product->getResource()->getAttribute($upc)->getFrontend()->getValue($product);
864
+ }
865
+
866
+ if ($pkgWidth = Mage::getStoreConfig('bootic/product/pkg_width')) {
867
+ $p['pkg_width'] = $product->getResource()->getAttribute($pkgWidth)->getFrontend()->getValue($product);
868
+ }
869
+
870
+ if ($pkgLength = Mage::getStoreConfig('bootic/product/pkg_length')) {
871
+ $p['pkg_length'] = $product->getResource()->getAttribute($pkgLength)->getFrontend()->getValue($product);
872
+ }
873
+
874
+ if ($pkgHeight = Mage::getStoreConfig('bootic/product/pkg_height')) {
875
+ $p['pkg_height'] = $product->getResource()->getAttribute($pkgHeight)->getFrontend()->getValue($product);
876
+ }
877
+
878
+ if ($pkgWeight = Mage::getStoreConfig('bootic/product/pkg_weight')) {
879
+ $p['pkg_weight'] = $product->getResource()->getAttribute($pkgWeight)->getFrontend()->getValue($product);
880
+ }
881
+
882
+ if (!is_null($product_attributes)) {
883
+ $p['product_attributes'] = $product_attributes;
884
+ }
885
+
886
+ return $p;
887
+ }
888
+
889
+ /**
890
+ * Checks the status of a product on Bootic
891
+ * Pending Approval, Accepted, Denied
892
+ */
893
+ public function checkProductsStatus()
894
+ {
895
+ $collection = Mage::getResourceModel('catalog/product_collection')
896
+ ->addAttributeToSelect('*')
897
+ ->addAttributeToSort('entity_id', 'ASC')
898
+ ->joinTable(
899
+ 'bootic/product_data',
900
+ 'magento_product_id=entity_id',
901
+ array(
902
+ 'bootic_product_id' => 'bootic_product_id',
903
+ 'bootic_stock_id' => 'bootic_stock_id',
904
+ 'bootic_status' => 'bootic_status',
905
+ 'creation_time' => 'creation_time',
906
+ 'update_time' => 'update_time',
907
+ 'is_info_synced' => 'is_info_synced',
908
+ 'is_stock_synced' => 'is_stock_synced',
909
+ 'upload_failures' => 'upload_failures'
910
+ ),
911
+ "{{table}}.bootic_status=". Mage::getModel('bootic/product_data')->getStatusPendingApproval() .""
912
+ )
913
+ ->load()
914
+ ;
915
+
916
+ foreach ($collection as $product) {
917
+ // If product has parents, no need to query the API as things will happen at the parent level
918
+ if ($product->getTypeId() == 'simple') {
919
+ $parentIds = Mage::getModel('catalog/product_type_configurable')->getParentIdsByChild($product->getId());
920
+ if (count($parentIds) > 0) {
921
+ continue;
922
+ }
923
+ }
924
+
925
+ $approved = null;
926
+ $approval_remark = null;
927
+
928
+ $productInfo = $this->getBootic()->getProductInfo(array(
929
+ 'product_id' => $product->getBooticProductId()
930
+ ));
931
+
932
+ $approved = $productInfo->getData('approved');
933
+ $approval_remark = $productInfo->getData('approval_remark');
934
+
935
+ switch ($approved) {
936
+ // Case 1: Approved
937
+ case 1:
938
+ Mage::helper('bootic/product_data')->updateProductStatus($product, Mage::getModel('bootic/product_data')->getStatusCreated());
939
+ Mage::helper('bootic/log')->addLog($product->getId(), 'success', Mage::helper('bootic')->__('The product was approved'));
940
+ break;
941
+ // Cases -1: Rejected ; -2: Definitely Rejected
942
+ case -1:
943
+ case -2:
944
+ Mage::helper('bootic/product_data')->updateProductStatus($product, Mage::getModel('bootic/product_data')->getStatusNotApproved());
945
+ Mage::helper('bootic/log')->addLog($product->getId(), 'error', Mage::helper('bootic')->__('The product was denied. ') . $approval_remark);
946
+ break;
947
+ // Case -3: Deleted , we clear Bootic's product data
948
+ case -3:
949
+ $id = $product->getId();
950
+ Mage::helper('bootic/product_data')->resetProduct($id);
951
+ Mage::helper('bootic/log')->addLog($product->getId(), 'warning', Mage::helper('bootic')->__('The product was deleted from Bootic'));
952
+ break;
953
+ default:
954
+ // We do nothing
955
+ }
956
+ }
957
+ }
958
+
959
+ /**
960
+ * Synchronizes products stocks
961
+ */
962
+ public function syncProductsStocks()
963
+ {
964
+ $collection = Mage::getResourceModel('catalog/product_collection')
965
+ ->addAttributeToSelect('*')
966
+ ->addAttributeToSort('entity_id', 'ASC')
967
+ ->joinTable(
968
+ 'bootic/product_data',
969
+ 'magento_product_id=entity_id',
970
+ array(
971
+ 'bootic_product_id' => 'bootic_product_id',
972
+ 'bootic_stock_id' => 'bootic_stock_id',
973
+ 'bootic_status' => 'bootic_status',
974
+ 'creation_time' => 'creation_time',
975
+ 'update_time' => 'update_time',
976
+ 'is_info_synced' => 'is_info_synced',
977
+ 'is_stock_synced' => 'is_stock_synced',
978
+ 'upload_failures' => 'upload_failures'
979
+ ),
980
+ "{{table}}.is_stock_synced = 0"
981
+ . " AND {{table}}.bootic_product_id != 0"
982
+ )
983
+ ->load()
984
+ ;
985
+
986
+ foreach ($collection as $product) {
987
+ // At this point, we should not have configurable products, but in case we do
988
+ // we skip them
989
+ if ($product->isConfigurable()) {
990
+ continue;
991
+ }
992
+
993
+ $productId = $product->getBooticProductId();
994
+ $stockId = $product->getBooticStockId();
995
+ $sku = $product->getSku();
996
+ $stock = Mage::getModel('cataloginventory/stock_item')->loadByProduct($product)->getQty();
997
+ $valid = true;
998
+
999
+ try {
1000
+ $this->updateProductStock($productId, $stockId, $sku, $stock, $valid);
1001
+ Mage::helper('bootic/product_data')->setStockSync($product, true);
1002
+ } catch (Exception $e) {
1003
+ // Here we just do nothing to let the system retry on its own on a next cron run
1004
+ }
1005
+ }
1006
+ }
1007
+ }
app/code/community/Bootic/Bootic/Helper/Product/Data.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Product_Data extends Mage_Core_Helper_Abstract
7
+ {
8
+ /**
9
+ * Sets a collection of products to status: 'Not Created'
10
+ *
11
+ * @param Mage_Core_Controller_Request_Http $request
12
+ */
13
+ public function resetProducts(Mage_Core_Controller_Request_Http $request)
14
+ {
15
+ $ids = $request->__get('ids');
16
+
17
+ if (!is_array($ids)) {
18
+ $ids = array($ids);
19
+ }
20
+
21
+ foreach ($ids as $id) {
22
+ if($id == '' || $id === null) {
23
+ continue;
24
+ }
25
+
26
+ $this->resetProduct($id);
27
+ }
28
+ }
29
+
30
+ public function resetProduct($id)
31
+ {
32
+ $notCreated = Mage::getModel('bootic/product_data')->getStatusNotCreated();
33
+ $productData = Mage::getModel('bootic/product_data')->load($id);
34
+
35
+ $productData->setMagentoProductId($id);
36
+ $productData->setBooticStatus($notCreated);
37
+ $productData->setBooticProductId(0);
38
+ $productData->setBooticStockId(0);
39
+ $productData->setIsInfoSynced(0);
40
+ $productData->setIsStockSynced(0);
41
+ $productData->setUpdateTime(date('Y-m-d H:i:s', Mage::getModel('core/date')->timestamp()));
42
+ $productData->save();
43
+ }
44
+
45
+ public function updateProductsStatus(array $ids, $status, $options = array())
46
+ {
47
+ foreach ($ids as $id) {
48
+ if($id == '' || $id === null) {
49
+ continue;
50
+ }
51
+
52
+ $productData = Mage::getModel('bootic/product_data')->load((int) $id);
53
+ $productData->setMagentoProductId($id);
54
+ $productData->setBooticStatus($status);
55
+ $productData->setUpdateTime(date('Y-m-d H:i:s', Mage::getModel('core/date')->timestamp()));
56
+ $productData->addData($options);
57
+ $productData->save();
58
+ }
59
+ }
60
+
61
+ public function updateProductStatus(Mage_Catalog_Model_Product $product, $status, $options = array())
62
+ {
63
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
64
+
65
+ $productData->setMagentoProductId($product->getId());
66
+ $productData->setBooticStatus($status);
67
+ $productData->setUpdateTime(date('Y-m-d H:i:s', Mage::getModel('core/date')->timestamp()));
68
+ $productData->addData($options);
69
+ $productData->save();
70
+
71
+ if ($product->isConfigurable()) {
72
+ $childrenIds = $product->getTypeInstance(true)->getChildrenIds($product->getId());
73
+ $this->updateProductsStatus($childrenIds[0], $status, $options);
74
+ }
75
+
76
+ return $product;
77
+ }
78
+
79
+ public function isNotCreated(Mage_Catalog_Model_Product $product)
80
+ {
81
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
82
+
83
+ if ($productData->getData('bootic_status') == Mage::getModel('bootic/product_data')->getStatusNotCreated()) {
84
+ return true;
85
+ }
86
+
87
+ return false;
88
+ }
89
+
90
+ public function setInfoSync(Mage_Catalog_Model_Product $product, $inSync = true)
91
+ {
92
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
93
+
94
+ $productData->setIsInfoSynced($inSync);
95
+ $productData->setUpdateTime(date('Y-m-d H:i:s', Mage::getModel('core/date')->timestamp()));
96
+ $productData->save();
97
+
98
+ return $product;
99
+ }
100
+
101
+ public function setStockSync(Mage_Catalog_Model_Product $product, $inSync = true)
102
+ {
103
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
104
+
105
+ $productData->setIsStockSynced($inSync);
106
+ $productData->setUpdateTime(date('Y-m-d H:i:s', Mage::getModel('core/date')->timestamp()));
107
+ $productData->save();
108
+
109
+ return $product;
110
+ }
111
+
112
+ public function isQueueable(Mage_Catalog_Model_Product $product)
113
+ {
114
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
115
+
116
+ $unvalidStatus = array(
117
+ Mage::getModel('bootic/product_data')->getStatusProcessing(),
118
+ Mage::getModel('bootic/product_data')->getStatusPendingApproval(),
119
+ Mage::getModel('bootic/product_data')->getStatusNotApproved(),
120
+ Mage::getModel('bootic/product_data')->getStatusCreated(),
121
+ );
122
+
123
+ if (!in_array($productData->getData('bootic_status'), $unvalidStatus)) {
124
+ return true;
125
+ }
126
+
127
+ return false;
128
+ }
129
+ }
app/code/community/Bootic/Bootic/Helper/Product/Inventory.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Product Inventory Helper
4
+ * take advantage of catalog inventory observer methods
5
+ * and reuse in context of orders
6
+ */
7
+ class Bootic_Bootic_Helper_Product_Inventory extends Mage_CatalogInventory_Model_Observer
8
+ {
9
+ /**
10
+ * subtract order inventory
11
+ * @param Mage_Sales_Model_Order $order
12
+ * @see parent::subtractQuoteInventory
13
+ */
14
+ public function subtractOrderInventory(Mage_Sales_Model_Order $order)
15
+ {
16
+ $items = $this->_getProductsQty($order->getAllItems());
17
+ /**
18
+ * Remember items
19
+ */
20
+ $this->_itemsForReindex = Mage::getSingleton('cataloginventory/stock')->registerProductsSale($items);
21
+ }
22
+
23
+ /**
24
+ * Refresh stock index for specific stock items after succesful order placement
25
+ *
26
+ * @param $observer
27
+ */
28
+ public function reindexOrderInventory(Mage_Sales_Model_Order $order)
29
+ {
30
+ // Reindex order ids
31
+ $productIds = array();
32
+ foreach ($order->getAllItems() as $item) {
33
+ $productIds[$item->getProductId()] = $item->getProductId();
34
+ $children = $item->getChildrenItems();
35
+ if ($children) {
36
+ foreach ($children as $childItem) {
37
+ $productIds[$childItem->getProductId()] = $childItem->getProductId();
38
+ }
39
+ }
40
+ }
41
+
42
+ if( count($productIds)) {
43
+ Mage::getResourceSingleton('cataloginventory/indexer_stock')->reindexProducts($productIds);
44
+ }
45
+
46
+ // Reindex previously remembered items
47
+ $productIds = array();
48
+ foreach ($this->_itemsForReindex as $item) {
49
+ $item->save();
50
+ $productIds[] = $item->getProductId();
51
+ }
52
+ Mage::getResourceSingleton('catalog/product_indexer_price')->reindexProductIds($productIds);
53
+
54
+ $this->_itemsForReindex = array(); // Clear list of remembered items - we don't need it anymore
55
+
56
+ return $this;
57
+ }
58
+ }
app/code/community/Bootic/Bootic/Helper/Product/Type/Configurable.php ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Product_Type_Configurable extends Mage_Core_Helper_Abstract
7
+ {
8
+ /**
9
+ * Prices
10
+ *
11
+ * @var array
12
+ */
13
+ protected $_prices = array();
14
+
15
+ /**
16
+ * Prepared prices
17
+ *
18
+ * @var array
19
+ */
20
+ protected $_resPrices = array();
21
+
22
+ /**
23
+ * Get allowed attributes
24
+ *
25
+ * @return array
26
+ */
27
+ public function getAllowAttributes(Mage_Catalog_Model_Product $currentProduct)
28
+ {
29
+ return $currentProduct->getTypeInstance(true)->getConfigurableAttributes($currentProduct);
30
+ }
31
+
32
+ /**
33
+ * Get Allowed Products
34
+ *
35
+ * @return array
36
+ */
37
+ public function getAllowProducts(Mage_Catalog_Model_Product $currentProduct)
38
+ {
39
+ $products = array();
40
+ $allProducts = $currentProduct->getTypeInstance(true)->getUsedProducts(null, $currentProduct);
41
+ foreach ($allProducts as $product) {
42
+ if ($product->getStatus() == Mage_Catalog_Model_Product_Status::STATUS_ENABLED) {
43
+ $products[] = $product;
44
+ }
45
+ }
46
+
47
+ return $products;
48
+ }
49
+
50
+ /**
51
+ * Creates array of product options
52
+ *
53
+ * @return array
54
+ */
55
+ public function getOptions(Mage_Catalog_Model_Product $currentProduct)
56
+ {
57
+ $attributes = array();
58
+ $options = array();
59
+
60
+ foreach ($this->getAllowProducts($currentProduct) as $product) {
61
+ $productId = $product->getId();
62
+
63
+ foreach ($this->getAllowAttributes($currentProduct) as $attribute) {
64
+ $productAttribute = $attribute->getProductAttribute();
65
+ $productAttributeId = $productAttribute->getId();
66
+ $attributeValue = $product->getData($productAttribute->getAttributeCode());
67
+ if (!isset($options[$productAttributeId])) {
68
+ $options[$productAttributeId] = array();
69
+ }
70
+
71
+ if (!isset($options[$productAttributeId][$attributeValue])) {
72
+ $options[$productAttributeId][$attributeValue] = array();
73
+ }
74
+ $options[$productAttributeId][$attributeValue][] = $productId;
75
+ }
76
+ }
77
+
78
+ $this->_resPrices = array(
79
+ $this->_preparePrice($currentProduct, $currentProduct->getFinalPrice())
80
+ );
81
+
82
+ foreach ($this->getAllowAttributes($currentProduct) as $attribute) {
83
+ $productAttribute = $attribute->getProductAttribute();
84
+ $attributeId = $productAttribute->getId();
85
+ $info = array(
86
+ 'id' => $productAttribute->getId(),
87
+ 'code' => $productAttribute->getAttributeCode(),
88
+ 'label' => $attribute->getLabel(),
89
+ 'options' => array()
90
+ );
91
+
92
+ $optionPrices = array();
93
+ $prices = $attribute->getPrices();
94
+ if (is_array($prices)) {
95
+ foreach ($prices as $value) {
96
+ if (!$this->_validateAttributeValue($attributeId, $value, $options)) {
97
+ continue;
98
+ }
99
+ $currentProduct->setConfigurablePrice(
100
+ $this->_preparePrice($currentProduct, $value['pricing_value'], $value['is_percent'])
101
+ );
102
+ $currentProduct->setParentId(true);
103
+ $configurablePrice = $currentProduct->getConfigurablePrice();
104
+
105
+ if (isset($options[$attributeId][$value['value_index']])) {
106
+ $productsIndex = $options[$attributeId][$value['value_index']];
107
+ } else {
108
+ $productsIndex = array();
109
+ }
110
+
111
+ $info['options'][] = array(
112
+ 'id' => $value['value_index'],
113
+ 'label' => $value['label'],
114
+ 'price' => $configurablePrice,
115
+ 'oldPrice' => $this->_prepareOldPrice($currentProduct, $value['pricing_value'], $value['is_percent']),
116
+ 'products' => $productsIndex,
117
+ );
118
+ $optionPrices[] = $configurablePrice;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Prepare formated values for options choose
124
+ */
125
+ foreach ($optionPrices as $optionPrice) {
126
+ foreach ($optionPrices as $additional) {
127
+ $this->_preparePrice($currentProduct, abs($additional - $optionPrice));
128
+ }
129
+ }
130
+ if ($this->_validateAttributeInfo($info)) {
131
+ $attributes[$attributeId] = $info;
132
+ }
133
+ }
134
+
135
+ $config = array(
136
+ 'attributes' => $attributes,
137
+ 'basePrice' => $this->_convertPrice($currentProduct->getFinalPrice()),
138
+ 'oldPrice' => $this->_convertPrice($currentProduct->getPrice()),
139
+ 'productId' => $currentProduct->getId(),
140
+ );
141
+
142
+ return $config;
143
+ }
144
+
145
+ /**
146
+ * Calculation real price
147
+ *
148
+ * @param float $price
149
+ * @param bool $isPercent
150
+ * @return mixed
151
+ */
152
+ protected function _preparePrice(Mage_Catalog_Model_Product $currentProduct, $price, $isPercent = false)
153
+ {
154
+ if ($isPercent && !empty($price)) {
155
+ $price = $currentProduct->getTypeInstance(true)->getFinalPrice() * $price / 100;
156
+ }
157
+
158
+ return $this->_convertPrice($price, true);
159
+ }
160
+
161
+ /**
162
+ * Calculation price before special price
163
+ *
164
+ * @param float $price
165
+ * @param bool $isPercent
166
+ * @return mixed
167
+ */
168
+ protected function _prepareOldPrice(Mage_Catalog_Model_Product $currentProduct, $price, $isPercent = false)
169
+ {
170
+ if ($isPercent && !empty($price)) {
171
+ $price = $currentProduct->getTypeInstance(true)->getPrice() * $price / 100;
172
+ }
173
+
174
+ return $this->_convertPrice($price, true);
175
+ }
176
+
177
+ /**
178
+ * Convert price from default currency to current currency
179
+ *
180
+ * @param float $price
181
+ * @param boolean $round
182
+ * @return float
183
+ */
184
+ protected function _convertPrice($price, $round = false)
185
+ {
186
+ if (empty($price)) {
187
+ return 0;
188
+ }
189
+
190
+ $price = $this->getCurrentStore()->convertPrice($price);
191
+ if ($round) {
192
+ $price = $this->getCurrentStore()->roundPrice($price);
193
+ }
194
+
195
+ return $price;
196
+ }
197
+
198
+ /**
199
+ * retrieve current store
200
+ *
201
+ * @return Mage_Core_Model_Store
202
+ */
203
+ public function getCurrentStore()
204
+ {
205
+ return Mage::app()->getStore();
206
+ }
207
+
208
+ /**
209
+ * Validating of super product option value
210
+ *
211
+ * @param array $attributeId
212
+ * @param array $value
213
+ * @param array $options
214
+ * @return boolean
215
+ */
216
+ protected function _validateAttributeValue($attributeId, &$value, &$options)
217
+ {
218
+ if(isset($options[$attributeId][$value['value_index']])) {
219
+ return true;
220
+ }
221
+
222
+ return false;
223
+ }
224
+
225
+ /**
226
+ * Validation of super product option
227
+ *
228
+ * @param array $info
229
+ * @return boolean
230
+ */
231
+ protected function _validateAttributeInfo(&$info)
232
+ {
233
+ if(count($info['options']) > 0) {
234
+ return true;
235
+ }
236
+
237
+ return false;
238
+ }
239
+ }
app/code/community/Bootic/Bootic/Helper/Storefront.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Helper_Storefront extends Bootic_Bootic_Helper_Abstract
7
+ {
8
+ public function getStoreFrontList()
9
+ {
10
+ return $this->getBootic()->getStoreFrontList();
11
+ }
12
+
13
+ public function createStorefront(array $data)
14
+ {
15
+ // Our storefront is always created in online state
16
+ $data['online'] = true;
17
+
18
+ return $this->getBootic()->createStorefront($data);
19
+ }
20
+
21
+ public function updateStoreFront(array $data)
22
+ {
23
+ return $this->getBootic()->updateStorefront($data);
24
+ }
25
+
26
+ public function addStorefrontBanner(array $data)
27
+ {
28
+ return $this->getBootic()->addStorefrontBanner($data);
29
+ }
30
+
31
+ public function getStoreFrontOptions($storeFrontId)
32
+ {
33
+ return $this->getBootic()->getAvailableOptionsForStorefront($storeFrontId);
34
+ }
35
+
36
+ public function getAvailableTemplatesValues()
37
+ {
38
+ $result = $this->getBootic()->getCommonList('template');
39
+ $templates = $result->getData();
40
+
41
+ $values = array();
42
+ foreach ($templates as $key => $template) {
43
+ $values[$key] = array(
44
+ 'value' => $template['name'],
45
+ 'label' => $template['title']
46
+ );
47
+ }
48
+
49
+ return $values;
50
+ }
51
+ }
app/code/community/Bootic/Bootic/Model/Category.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Category extends Mage_Core_Model_Abstract
7
+ {
8
+ protected function _construct()
9
+ {
10
+ $this->_init('bootic/category');
11
+ }
12
+ }
app/code/community/Bootic/Bootic/Model/Category/Mapping.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Category_Mapping extends Mage_Core_Model_Abstract
7
+ {
8
+ protected function _construct()
9
+ {
10
+ $this->_init('bootic/category_mapping');
11
+ }
12
+ }
app/code/community/Bootic/Bootic/Model/Cron.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Cron extends Mage_Core_Model_Abstract
7
+ {
8
+ public function uploadProducts()
9
+ {
10
+ Mage::log('uploading batch of products');
11
+ Mage::helper('bootic/product')->uploadProducts();
12
+ }
13
+
14
+ public function editProducts()
15
+ {
16
+ Mage::log('editing batch of products');
17
+ Mage::helper('bootic/product')->editProducts();
18
+ }
19
+
20
+ public function checkProductsStatus()
21
+ {
22
+ Mage::log('checking products status');
23
+ Mage::helper('bootic/product')->checkProductsStatus();
24
+ }
25
+
26
+ public function syncProductsStocks()
27
+ {
28
+ Mage::log('syncing products stocks');
29
+ Mage::helper('bootic/product')->syncProductsStocks();
30
+ }
31
+
32
+ public function fetchOrders()
33
+ {
34
+ Mage::log('processing pending orders');
35
+ Mage::helper('bootic/orders')->processPendingOrders();
36
+ }
37
+
38
+ public function syncOrders()
39
+ {
40
+ Mage::log('syncing orders');
41
+ Mage::helper('bootic/orders')->syncOrders();
42
+ }
43
+ }
app/code/community/Bootic/Bootic/Model/Log.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Log extends Mage_Core_Model_Abstract
7
+ {
8
+ public function _construct()
9
+ {
10
+ $this->_init('bootic/log');
11
+ }
12
+ }
app/code/community/Bootic/Bootic/Model/Message.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Message extends Mage_Core_Model_Abstract
7
+ {
8
+ /**
9
+ * Constructor
10
+ */
11
+ public function _construct()
12
+ {
13
+ $this->_init('bootic/message');
14
+ }
15
+ }
app/code/community/Bootic/Bootic/Model/Message/Observer.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Message_Observer extends Mage_Core_Model_Abstract
7
+ {
8
+ public function markMessageAsRead($observer)
9
+ {
10
+ $params = $observer->getControllerAction()->getRequest()->getParams();
11
+ $messageId = $params['id'];
12
+
13
+ $booticMessage = Mage::getModel('bootic/message')->load($messageId, 'magento_message_id');
14
+
15
+ if (!$booticMessage->isObjectNew()) {
16
+ Mage::helper('bootic/message')->markMessageAsRead($booticMessage->getId());
17
+ }
18
+ }
19
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Category.php ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Category extends Mage_Core_Model_Mysql4_Abstract
7
+ {
8
+ protected $_isPkAutoIncrement = false;
9
+
10
+ /**
11
+ * Initialize resource model
12
+ *
13
+ */
14
+ protected function _construct()
15
+ {
16
+ $this->_init('bootic/category', 'category_id');
17
+ }
18
+
19
+ /**
20
+ * Perform operations after object load
21
+ *
22
+ * @param Mage_Core_Model_Abstract $object
23
+ * @return Bootic_Bootic_Model_Resource_Category
24
+ */
25
+ protected function _afterLoad(Mage_Core_Model_Abstract $object)
26
+ {
27
+ if ($object->getId() !== false) {
28
+ $parents = $this->lookupParentIds($object->getId());
29
+ $children = $this->lookupChildrenIds($object->getId());
30
+ $object->setData('parents', $parents);
31
+ $object->setData('children', $children);
32
+ }
33
+
34
+ return parent::_afterLoad($object);
35
+ }
36
+
37
+ /**
38
+ * Process category data before deleting
39
+ *
40
+ * @param Mage_Core_Model_Abstract $object
41
+ * @return Bootic_Bootic_Model_Resource_Category
42
+ */
43
+ protected function _beforeDelete(Mage_Core_Model_Abstract $object)
44
+ {
45
+ $condition = array(
46
+ 'category_id = ?' => (int)$object->getId(),
47
+ );
48
+
49
+ $this->_getWriteAdapter()->delete($this->getTable('bootic/category_parent'), $condition);
50
+
51
+ return parent::_beforeDelete($object);
52
+ }
53
+
54
+ /**
55
+ * Perform operations before object save
56
+ *
57
+ * @param Mage_Core_Model_Abstract $object
58
+ * @return Bootic_Bootic_Model_Resource_Category|Mage_Core_Model_Resource_Db_Abstract
59
+ */
60
+ protected function _beforeSave(Mage_Core_Model_Abstract $object)
61
+ {
62
+ if (!$object->getId()) {
63
+ $object->setCreationTime(Mage::getSingleton('core/date')->gmtDate());
64
+ }
65
+ $object->setUpdateTime(Mage::getSingleton('core/date')->gmtDate());
66
+ return $this;
67
+ }
68
+
69
+ /**
70
+ * Perform operations after object save
71
+ *
72
+ * @param Mage_Core_Model_Abstract $object
73
+ * @return Bootic_Bootic_Model_Resource_Category
74
+ */
75
+ protected function _afterSave(Mage_Core_Model_Abstract $object)
76
+ {
77
+ $oldParents = $this->lookupParentIds($object->getId());
78
+ $newParents = (array)$object->getParents();
79
+
80
+ $table = $this->getTable('bootic/category_parent');
81
+ $insert = array_diff($newParents, $oldParents);
82
+ $delete = array_diff($oldParents, $newParents);
83
+
84
+ if ($delete) {
85
+ $where = array(
86
+ 'category_id = ?' => (int) $object->getId(),
87
+ 'parent_id IN (?)' => $delete
88
+ );
89
+
90
+ $this->_getWriteAdapter()->delete($table, $where);
91
+ }
92
+
93
+ if ($insert) {
94
+ $data = array();
95
+
96
+ foreach ($insert as $parentId) {
97
+ $data[] = array(
98
+ 'category_id' => (int) $object->getId(),
99
+ 'parent_id' => (int) $parentId
100
+ );
101
+ }
102
+
103
+
104
+ $this->_getWriteAdapter()->insertMultiple($table, $data);
105
+ }
106
+
107
+ return parent::_afterSave($object);
108
+
109
+ }
110
+
111
+ /**
112
+ * Get parent category ids to which specified item is assigned
113
+ *
114
+ * @param int $id
115
+ * @return array
116
+ */
117
+ public function lookupParentIds($id)
118
+ {
119
+ $adapter = $this->_getReadAdapter();
120
+
121
+ $select = $adapter->select()
122
+ ->from($this->getTable('bootic/category_parent'), 'parent_id')
123
+ ->where('category_id = :category_id');
124
+
125
+ $binds = array(
126
+ ':category_id' => (int) $id
127
+ );
128
+
129
+ return $adapter->fetchCol($select, $binds);
130
+ }
131
+
132
+ /**
133
+ * Get children category ids to which specified item is assigned
134
+ *
135
+ * @param int $id
136
+ * @return array
137
+ */
138
+ public function lookupChildrenIds($id)
139
+ {
140
+ $adapter = $this->_getReadAdapter();
141
+
142
+ $select = $adapter->select()
143
+ ->from($this->getTable('bootic/category_parent'), 'category_id')
144
+ ->where('parent_id = :category_id');
145
+
146
+ $binds = array(
147
+ ':category_id' => (int) $id
148
+ );
149
+
150
+ return $adapter->fetchCol($select, $binds);
151
+ }
152
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Category/Collection.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Category_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
7
+ {
8
+ public function _construct()
9
+ {
10
+ $this->_init('bootic/category');
11
+ }
12
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Category/Mapping.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Category_Mapping extends Mage_Core_Model_Mysql4_Abstract
7
+ {
8
+ protected $_isPkAutoIncrement = false;
9
+
10
+ /**
11
+ * Initialize resource model
12
+ *
13
+ */
14
+ protected function _construct()
15
+ {
16
+ $this->_init('bootic/category_mapping', 'magento_category_id');
17
+ }
18
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Category/Mapping/Collection.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Category_Mapping_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
7
+ {
8
+ /**
9
+ * Initialization
10
+ */
11
+ public function _construct()
12
+ {
13
+ $this->_init('bootic/category_mapping');
14
+ }
15
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Log.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Log extends Mage_Core_Model_Mysql4_Abstract
7
+ {
8
+ /**
9
+ * Constructor
10
+ */
11
+ public function _construct()
12
+ {
13
+ $this->_init('bootic/log', 'id');
14
+ }
15
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Log/Collection.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Log_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
7
+ {
8
+ public function _construct()
9
+ {
10
+ $this->_init('bootic/log');
11
+ }
12
+
13
+ public function addAttributeToSort($attribute, $dir = 'asc')
14
+ {
15
+ if (!is_string($attribute)) {
16
+ return $this;
17
+ }
18
+ $this->setOrder($attribute, $dir);
19
+ return $this;
20
+ }
21
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Message.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Message extends Mage_Core_Model_Mysql4_Abstract
7
+ {
8
+ protected $_isPkAutoIncrement = false;
9
+
10
+ /**
11
+ * Constructor
12
+ */
13
+ public function _construct()
14
+ {
15
+ $this->_init('bootic/message', 'bootic_message_id');
16
+ }
17
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Order/Data.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ class Bootic_Bootic_Model_Mysql4_Order_Data extends Mage_Core_Model_Mysql4_Abstract
6
+ {
7
+ protected $_isPkAutoIncrement = false;
8
+
9
+ /**
10
+ * Initialize resource model
11
+ *
12
+ */
13
+ protected function _construct()
14
+ {
15
+ $this->_init('bootic/order_data', 'order_id');
16
+ }
17
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Product/Data.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Product_Data extends Mage_Core_Model_Mysql4_Abstract
7
+ {
8
+ protected $_isPkAutoIncrement = false;
9
+
10
+ /**
11
+ * Initialize resource model
12
+ *
13
+ */
14
+ protected function _construct()
15
+ {
16
+ $this->_init('bootic/product_data', 'magento_product_id');
17
+ }
18
+ }
app/code/community/Bootic/Bootic/Model/Mysql4/Product/Data/Collection.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Mysql4_Product_Data_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
7
+ {
8
+ public function _construct()
9
+ {
10
+ $this->_init('bootic/product_data');
11
+ }
12
+ }
app/code/community/Bootic/Bootic/Model/Order/Data.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Order_Data extends Mage_Core_Model_Abstract
7
+ {
8
+ protected function _construct()
9
+ {
10
+ $this->_init('bootic/order_data');
11
+ }
12
+ /**
13
+ * get transactions
14
+ * @return array
15
+ */
16
+ public function getTransactions()
17
+ {
18
+ return explode(',', $this->_getData('transactions'));
19
+ }
20
+ /**
21
+ * set order transactions
22
+ * @param array $transactions
23
+ * @return Bootic_Bootic_Model_Order_Data
24
+ */
25
+ public function setTransactions(array $transactions)
26
+ {
27
+ $this->setData('transactions', implode(',', $transactions));
28
+ return $this;
29
+ }
30
+ /**
31
+ * before save
32
+ * @see Mage_Core_Model_Abstract::_beforeSave
33
+ */
34
+ protected function _beforeSave()
35
+ {
36
+ $this->setUpdatedAt(date('Y-m-d H:i:s', Mage::getModel('Core/Date')->timestamp()));
37
+ }
38
+ }
app/code/community/Bootic/Bootic/Model/Order/Observer.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Order_Observer
7
+ {
8
+ /**
9
+ * handle order canceled event
10
+ * @param Varien_Event_Observer $observer
11
+ */
12
+ public function orderCanceled(Varien_Event_Observer $observer)
13
+ {
14
+ /* @var $order Mage_Sales_Model_Order */
15
+ $order = $observer->getOrder();
16
+ Mage::helper('bootic/orders')->setOutOfSync($order);
17
+ }
18
+
19
+ /**
20
+ * handle order shipped event
21
+ * check if all items are marked as shipped
22
+ * if so set order as out of sync to capture shipped status
23
+ * @param Varien_Event_Observer $observer
24
+ */
25
+ public function orderShipped(Varien_Event_Observer $observer)
26
+ {
27
+ try {
28
+ /* @var $shipment Mage_Sales_Model_Order_Shipment */
29
+ $shipment = $observer->getShipment();
30
+ /* @var $order Mage_Sales_Model_Order */
31
+ $order = $shipment->getOrder();
32
+ /* @var $helper Bootic_Bootic_Helper_Orders */
33
+ $helper = Mage::helper('bootic/orders');
34
+ if ($helper->allItemsShipped($order)) {
35
+ $helper->setOutOfSync($order);
36
+ }
37
+ } catch(Exception $e) {
38
+ Mage::logException($e);
39
+ }
40
+ }
41
+ }
app/code/community/Bootic/Bootic/Model/Payment.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Bootic_Bootic_Model_Payment extends Mage_Payment_Model_Method_Abstract
3
+ {
4
+ protected $_code = 'bootic_payment_method';
5
+ protected $_formBlockType = 'bootic/payment_form';
6
+ protected $_infoBlockType = 'bootic/payment_info';
7
+
8
+ public function getInformation() {
9
+ return $this->getConfigData('information');
10
+ }
11
+
12
+ public function getAddress() {
13
+ return $this->getConfigData('address');
14
+ }
15
+ }
app/code/community/Bootic/Bootic/Model/Product/Data.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Product_Data extends Mage_Core_Model_Abstract
7
+ {
8
+ /**
9
+ * Display Status "Not Created"
10
+ */
11
+ const BOOTIC_STATUS_NOT_CREATED = 0;
12
+
13
+ /**
14
+ * Display Status "Processing"
15
+ */
16
+ const BOOTIC_STATUS_PROCESSING = 1;
17
+
18
+ /**
19
+ * Display Status "Incomplete"
20
+ */
21
+ const BOOTIC_STATUS_INCOMPLETE = 2;
22
+
23
+ /**
24
+ * Display Status "Pending Approval"
25
+ */
26
+ const BOOTIC_STATUS_PENDING_APPROVAL = 3;
27
+
28
+ /**
29
+ * Display Status "Not Approved"
30
+ */
31
+ const BOOTIC_STATUS_NOT_APPROVED = 4;
32
+
33
+ /**
34
+ * Display Status "Created"
35
+ */
36
+ const BOOTIC_STATUS_CREATED = 5;
37
+
38
+ /**
39
+ * Display Status "Error"
40
+ */
41
+ const BOOTIC_STATUS_ERROR = 6;
42
+
43
+
44
+ protected function _construct()
45
+ {
46
+ $this->_init('bootic/product_data');
47
+ }
48
+
49
+ /**
50
+ * Get options as array
51
+ *
52
+ * @return array
53
+ */
54
+ static public function getOptionArray()
55
+ {
56
+ return array(
57
+ self::BOOTIC_STATUS_NOT_CREATED => Mage::helper('bootic')->__('Not Created'),
58
+ self::BOOTIC_STATUS_PROCESSING => Mage::helper('bootic')->__('Processing'),
59
+ self::BOOTIC_STATUS_INCOMPLETE => Mage::helper('bootic')->__('Incomplete'),
60
+ self::BOOTIC_STATUS_PENDING_APPROVAL => Mage::helper('bootic')->__('Pending Approval'),
61
+ self::BOOTIC_STATUS_NOT_APPROVED => Mage::helper('bootic')->__('Not Approved'),
62
+ self::BOOTIC_STATUS_CREATED => Mage::helper('bootic')->__('Created'),
63
+ self::BOOTIC_STATUS_ERROR => Mage::helper('bootic')->__('Error')
64
+ );
65
+ }
66
+
67
+ public function getStatusNotCreated()
68
+ {
69
+ return self::BOOTIC_STATUS_NOT_CREATED;
70
+ }
71
+
72
+ public function getStatusProcessing()
73
+ {
74
+ return self::BOOTIC_STATUS_PROCESSING;
75
+ }
76
+
77
+ public function getStatusIncomplete()
78
+ {
79
+ return self::BOOTIC_STATUS_INCOMPLETE;
80
+ }
81
+
82
+ public function getStatusPendingApproval()
83
+ {
84
+ return self::BOOTIC_STATUS_PENDING_APPROVAL;
85
+ }
86
+
87
+ public function getStatusNotApproved()
88
+ {
89
+ return self::BOOTIC_STATUS_NOT_APPROVED;
90
+ }
91
+
92
+ public function getStatusCreated()
93
+ {
94
+ return self::BOOTIC_STATUS_CREATED;
95
+ }
96
+
97
+ public function getStatusError()
98
+ {
99
+ return self::BOOTIC_STATUS_ERROR;
100
+ }
101
+
102
+ public function incrementUploadFailures()
103
+ {
104
+ $this->upload_failures ++;
105
+ }
106
+
107
+ public function resetUploadFailures()
108
+ {
109
+ $this->upload_failures = 0;
110
+ }
111
+
112
+ public function isProcessable(Mage_Catalog_Model_Product $product)
113
+ {
114
+ $productData = $this->load($product->getId());
115
+
116
+ if ($productData->getData('bootic_status') != self::BOOTIC_STATUS_NOT_CREATED) {
117
+ return false;
118
+ }
119
+
120
+ return true;
121
+ }
122
+ }
app/code/community/Bootic/Bootic/Model/Product/Observer.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Product_Observer extends Mage_Core_Model_Abstract
7
+ {
8
+ public function updateBooticProductInfo($observer)
9
+ {
10
+ $product = $observer->getProduct();
11
+ $parents = Mage::getModel('catalog/product_type_configurable')->getParentIdsByChild($product->getId());
12
+
13
+ // if product is configurable or does not have changes
14
+ if (count($parents) > 0 || !$product->hasDataChanges()) {
15
+ return;
16
+ }
17
+
18
+ $productData = Mage::getModel('bootic/product_data')->load($product->getId());
19
+ if (
20
+ (
21
+ $productData->getBooticStatus() == Mage::getModel('bootic/product_data')->getStatusCreated() ||
22
+ $productData->getBooticStatus() == Mage::getModel('bootic/product_data')->getStatusPendingApproval()
23
+ )
24
+ && $productData->getIsInfoSynced() == true
25
+ ) {
26
+ Mage::helper('bootic/product_data')->setInfoSync($product, false);
27
+ }
28
+ }
29
+ }
app/code/community/Bootic/Bootic/Model/Profile.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Profile extends Varien_Object
7
+ {
8
+ public function validate()
9
+ {
10
+ $errors = array();
11
+
12
+ if (!Zend_Validate::is($this->getName(), 'NotEmpty')) {
13
+ $errors[] = Mage::helper('bootic')->__('Username can\'t be empty');
14
+ }
15
+
16
+ if (!Zend_Validate::is($this->getCountry(), 'NotEmpty')) {
17
+ $errors[] = Mage::helper('bootic')->__('Country can\'t be empty');
18
+ }
19
+
20
+ if (!Zend_Validate::is($this->getRegion(), 'NotEmpty')) {
21
+ $errors[] = Mage::helper('bootic')->__('Region can\'t be empty');
22
+ }
23
+
24
+ if ($this->getPost_code() != '' && !Zend_Validate::is($this->getPost_code(), 'Int')) {
25
+ $errors[] = Mage::helper('bootic')->__('Postal code has to be an integer');
26
+ }
27
+
28
+ if ($this->getPhone_code() != '' && !Zend_Validate::is($this->getPhone_number(), 'Alnum')) {
29
+ $errors[] = Mage::helper('bootic')->__('Phone number is not valid');
30
+ }
31
+
32
+ // picture validation
33
+ if (null !== $this->getPicture()) {
34
+ $pictureValidators = new Zend_Validate();
35
+ $pictureValidators
36
+ ->addValidator(new Zend_Validate_File_FilesSize('10MB'), true)
37
+ ->addValidator(new Zend_Validate_File_IsImage(array('image/jpeg', 'image/png')), true)
38
+ // TODO: validate for a minimum and maximum size ??
39
+ // ->addValidator(new Zend_Validate_File_ImageSize(array('minwidth' => '240', 'minheight' => '240')))
40
+ ;
41
+
42
+ if (!$pictureValidators->isValid($this->getPicture())) {
43
+ $errors[] = Mage::helper('bootic')->__(
44
+ 'The selected file for profile picture is not a valid or a supported image.'
45
+ .' The accepted formats are jpeg and png.'
46
+ );
47
+ }
48
+ }
49
+
50
+
51
+ if (empty($errors)) {
52
+ return true;
53
+ }
54
+
55
+ return $errors;
56
+ }
57
+ }
app/code/community/Bootic/Bootic/Model/Session/Observer.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Session_Observer extends Mage_Core_Model_Abstract
7
+ {
8
+ /**
9
+ * Upon login, let's pull unread messages
10
+ */
11
+ public function pullUnreadMessages()
12
+ {
13
+ Mage::helper('bootic/message')->pullUnreadMessages();
14
+ }
15
+ }
app/code/community/Bootic/Bootic/Model/Shipping/Flatrate.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ class Bootic_Bootic_Model_Shipping_Flatrate extends Mage_Shipping_Model_Carrier_Abstract implements Mage_Shipping_Model_Carrier_Interface
6
+ {
7
+ protected $_code = 'bootic';
8
+ protected $_isFixed = true;
9
+
10
+ public function collectRates(Mage_Shipping_Model_Rate_Request $request)
11
+ {
12
+
13
+ $result = Mage::getModel('shipping/rate_result');
14
+
15
+ $method = Mage::getModel('shipping/rate_result_method');
16
+ $method->setCarrier($this->_code);
17
+ $method->setCarrierTitle($this->getConfigData('title'));
18
+ $method->setMethod('flat_shipping');
19
+ $method->setMethodTitle($this->getConfigData('name'));
20
+ $method->setPrice(0);
21
+ $method->setCost(0);
22
+
23
+ $result->append($method);
24
+
25
+ return $result;
26
+ }
27
+
28
+ public function getAllowedMethods()
29
+ {
30
+ return array('flat_shipping'=>$this->getConfigData('name'));
31
+ }
32
+
33
+ public function isShippingLabelsAvailable()
34
+ {
35
+ return false;
36
+ }
37
+ }
app/code/community/Bootic/Bootic/Model/Stock/Observer.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Stock_Observer extends Mage_Core_Model_Abstract
7
+ {
8
+ public function updateBooticStock($observer)
9
+ {
10
+ $productId = $observer->getItem()->getProductId();
11
+ $this->_setProductToUnsynced($productId);
12
+ }
13
+
14
+ public function updateSoldProductBooticStock($observer)
15
+ {
16
+ $items = $observer->getOrder()->getAllItems();
17
+ foreach ($items as $item) {
18
+ // Configurable products have no stockID so no need to flag them as out of sync - ever
19
+ $product = Mage::getModel('catalog/product')->load($item->getProductId());
20
+ if (!$product->isConfigurable()) {
21
+ $this->_setProductToUnsynced($item->getProductId());
22
+ }
23
+ }
24
+ }
25
+
26
+ protected function _setProductToUnsynced($id)
27
+ {
28
+ $productData = Mage::getModel('bootic/product_data')->load($id);
29
+
30
+ if (
31
+ (
32
+ $productData->getBooticStatus() == Mage::getModel('bootic/product_data')->getStatusCreated() ||
33
+ $productData->getBooticStatus() == Mage::getModel('bootic/product_data')->getStatusPendingApproval()
34
+ )
35
+ && $productData->getIsStockSynced() == true
36
+ ) {
37
+ $productData->setIsStockSynced(false);
38
+ $productData->setUpdateTime(date('Y-m-d H:i:s', Mage::getModel('core/date')->timestamp()));
39
+ $productData->save();
40
+ }
41
+ }
42
+ }
app/code/community/Bootic/Bootic/Model/Storefront.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_Storefront extends Varien_Object
7
+ {
8
+ public function validate()
9
+ {
10
+ $errors = array();
11
+
12
+ if (!Zend_Validate::is($this->getName(), 'NotEmpty')) {
13
+ $errors[] = Mage::helper('bootic')->__('Storefront name can\'t be empty');
14
+ }
15
+
16
+ if (empty($errors)) {
17
+ return true;
18
+ }
19
+ return $errors;
20
+ }
21
+ }
app/code/community/Bootic/Bootic/Model/System/Config/Commission.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_System_Config_Commission extends Mage_Core_Model_Config_Data
7
+ {
8
+ public function save()
9
+ {
10
+ $commission = $this->getValue();
11
+ $commission = preg_replace('#[^0-9]#','',$commission);
12
+ if ((0 > (int)$commission) || (100 < (int)$commission)) {
13
+ Mage::throwException("Commission is a percentage and its value must range from 0 to 100.");
14
+ }
15
+
16
+ return parent::save();
17
+ }
18
+ }
app/code/community/Bootic/Bootic/Model/System/Config/Connection.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_System_Config_Connection extends Mage_Core_Model_Config_Data
7
+ {
8
+ public function save()
9
+ {
10
+ $email = $this->getFieldsetDataValue('email');
11
+ $password = $this->getFieldsetDataValue('password');
12
+
13
+ try {
14
+ $apiKey = Mage::helper('bootic/api')->createKey($email, $password);
15
+ $userId = Mage::helper('bootic/api')->getUserId();
16
+
17
+ $mageConfig = new Mage_Core_Model_Config();
18
+ $mageConfig->saveConfig('bootic/account/user_id', $userId, 'default', 0);
19
+ $mageConfig->saveConfig('bootic/account/api_key', $apiKey, 'default', 0);
20
+ $mageConfig->saveConfig('bootic/account/connect', 'existing', 'default', 0);
21
+ } catch (Bootic_Api_Exception $e) {
22
+ Mage::throwException(Mage::helper('bootic')->__('Your configuration settings could not be saved. Your Bootic login and passwords are not valid.'));
23
+ }
24
+
25
+ parent::save();
26
+ }
27
+ }
app/code/community/Bootic/Bootic/Model/System/Config/Observer.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_System_Config_Observer extends Mage_Core_Model_Abstract
7
+ {
8
+ public function redirectToCategoryMapping($observer)
9
+ {
10
+ if (Mage::getSingleton('adminhtml/session')->hasData('bootic_category_redirect')) {
11
+ $redirectUrl = Mage::getSingleton('adminhtml/url')->getUrl(Mage::getSingleton('adminhtml/session')->getData('bootic_category_redirect'));
12
+ Mage::getSingleton('adminhtml/session')->unsetData('bootic_category_redirect');
13
+ return $observer->getControllerAction()->getResponse()->setRedirect($redirectUrl);
14
+ }
15
+ }
16
+ }
app/code/community/Bootic/Bootic/Model/System/Config/Source/Attribute.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_System_Config_Source_Attribute
7
+ {
8
+ public function toOptionArray()
9
+ {
10
+ $collection = Mage::getResourceModel('catalog/product_attribute_collection')->addVisibleFilter();
11
+
12
+ $options[] = array(
13
+ 'value' => '',
14
+ 'label' => '',
15
+ );
16
+
17
+ foreach ($collection as $attribute) {
18
+ $options[] = array(
19
+ 'value' => $attribute->getAttributeCode(),
20
+ 'label' => $attribute->getName(),
21
+ );
22
+ }
23
+
24
+ return $options;
25
+ }
26
+ }
app/code/community/Bootic/Bootic/Model/System/Config/Source/Image.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Model_System_Config_Source_Image
7
+ {
8
+ public function toOptionArray()
9
+ {
10
+ $collection = Mage::getResourceModel('catalog/product_attribute_collection')
11
+ ->addVisibleFilter()
12
+ ;
13
+
14
+ $options[] = array(
15
+ 'value' => '',
16
+ 'label' => '',
17
+ );
18
+
19
+ foreach ($collection as $attribute) {
20
+ $options[] = array(
21
+ 'value' => $attribute->getAttributeCode(),
22
+ 'label' => $attribute->getName(),
23
+ );
24
+ }
25
+
26
+ return $options;
27
+ }
28
+ }
app/code/community/Bootic/Bootic/Model/System/Config/Source/Store.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Bootic_Bootic_Model_System_Config_Source_Store
4
+ {
5
+ public function toOptionArray()
6
+ {
7
+ return $stores = Mage::getModel('core/store')
8
+ ->getCollection()
9
+ ->toOptionArray()
10
+ ;
11
+ }
12
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/AbstractController.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Adminhtml_AbstractController extends Mage_Adminhtml_Controller_action
7
+ {
8
+ public function preDispatch()
9
+ {
10
+ parent::preDispatch();
11
+
12
+ $request = $this->getRequest();
13
+ $controller = $request->getControllerName();
14
+
15
+ if (!$this->isBooticAccountConfigured() || !$this->isBooticAccountValid())
16
+ {
17
+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
18
+
19
+ $this->_getSession()->addError(Mage::helper('bootic')->__('You must create an account or provide your email and password before using the Bootic extension.'));
20
+ $this->_redirect('adminhtml/system_config/edit', array('section' => 'bootic'));
21
+
22
+ return;
23
+ } elseif (!$this->isBooticProfileConfigured() && $controller != 'adminhtml_connect') {
24
+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
25
+
26
+ if ($controller != 'adminhtml_storefront') {
27
+ $this->_getSession()->addError(Mage::helper('bootic')->__('You must fill in your profile before being able to sell products on Bootic.'));
28
+ } else {
29
+ $this->_getSession()->addError(Mage::helper('bootic')->__('You must fill in your profile before creating your storefront.'));
30
+ }
31
+
32
+ $this->_redirect('bootic/adminhtml_connect/index');
33
+ // If storefront has not been created yet, we take user to the storefront page
34
+ } elseif (!$this->isBooticStorefrontCreated() && ($controller != 'adminhtml_connect' && $controller != 'adminhtml_storefront')) {
35
+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
36
+
37
+ $this->_getSession()->addError(Mage::helper('bootic')->__('You must create a storefront to sell products on Bootic.'));
38
+ $this->_redirect('bootic/adminhtml_storefront/new');
39
+ }
40
+ }
41
+
42
+ protected function isBooticAccountConfigured()
43
+ {
44
+ $_accountEmail = Mage::getStoreConfig('bootic/account/email');
45
+ $_accountPassword = Mage::getStoreConfig('bootic/account/password');
46
+
47
+ if (
48
+ !isset($_accountEmail)
49
+ || $_accountEmail == null
50
+ || !isset($_accountPassword)
51
+ || $_accountPassword == null
52
+ ) {
53
+ return false;
54
+ }
55
+
56
+ return true;
57
+ }
58
+
59
+ protected function isBooticAccountValid()
60
+ {
61
+ $_accountEmail = Mage::getStoreConfig('bootic/account/email');
62
+ $_accountPassword = Mage::getStoreConfig('bootic/account/password');
63
+
64
+ //TODO Change this, no need to query API?
65
+ try {
66
+ $_bootic = new Bootic_Api_Client(new Zend_Rest_Client());
67
+ $_bootic->authenticateByEmailAndPassword($_accountEmail, $_accountPassword);
68
+
69
+ return true;
70
+ } catch (Bootic_Api_Exception $e) {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ protected function isBooticProfileConfigured()
76
+ {
77
+ $_profileUpdated = Mage::getStoreConfig('bootic/account/profile_updated');
78
+
79
+ if ($_profileUpdated === null) {
80
+ // Check if user profile has been filled in
81
+ $profile = Mage::helper('bootic/connect')->getProfile();
82
+
83
+ $mageConfig = new Mage_Core_Model_Config();
84
+
85
+ // Profile has never be filled in on Bootic, user has to do it
86
+ if ($profile->getData('demo') == true) {
87
+ $_profileUpdated = false;
88
+
89
+ $mageConfig->saveConfig('bootic/account/profile_updated', 0, 'default', 0);
90
+
91
+ // Profile has been filled in bootic
92
+ } else {
93
+ $_profileUpdated = false;
94
+
95
+ $mageConfig->saveConfig('bootic/account/profile_updated', true, 'default', 0);
96
+ }
97
+
98
+ // Here we have to reinit configuration
99
+ Mage::getConfig()->reinit();
100
+ Mage::app()->reinitStores();
101
+ }
102
+
103
+ return $_profileUpdated;
104
+ }
105
+
106
+ protected function isBooticStorefrontCreated()
107
+ {
108
+ $_storefrontCreated = Mage::getStoreConfig('bootic/account/storefront_created');
109
+
110
+ if ($_storefrontCreated === null) {
111
+ $result = Mage::helper('bootic/storefront')->getStoreFrontList();
112
+ $stores = $result->getData();
113
+
114
+ $mageConfig = new Mage_Core_Model_Config();
115
+
116
+ if (!is_array($stores) || count($stores) == 0) {
117
+ $_storefrontCreated = false;
118
+
119
+ $mageConfig->saveConfig('bootic/account/storefront_created', 0, 'default', 0);
120
+
121
+ } else {
122
+ $_storefrontCreated = true;
123
+
124
+ $mageConfig->saveConfig('bootic/account/storefront_created', true, 'default', 0);
125
+ }
126
+
127
+ // Here we have to reinit configuration
128
+ Mage::getConfig()->reinit();
129
+ Mage::app()->reinitStores();
130
+ }
131
+
132
+ return $_storefrontCreated;
133
+ }
134
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/Catalog/CategoryController.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Adminhtml_Catalog_CategoryController extends Mage_Adminhtml_Controller_action
7
+ {
8
+ public function indexAction()
9
+ {
10
+ try {
11
+ Mage::helper('bootic/category')->getRootCategory();
12
+ } catch (Exception $e) {
13
+ Mage::getSingleton('adminhtml/session')
14
+ ->addError(Mage::helper('bootic')->__('Please select a Root Category and save the configuration.'))
15
+ ->addData(array('bootic_category_redirect' => 'bootic/adminhtml_catalog_category/index'));
16
+
17
+ $this->_redirect('adminhtml/system_config/edit', array('section' => 'bootic'));
18
+
19
+ return;
20
+ }
21
+
22
+ Mage::helper('bootic/category')->pullBooticCategories();
23
+
24
+ $collection = Mage::getResourceModel('bootic/category_mapping_collection')->load();
25
+
26
+ $mapping = array();
27
+ foreach ($collection as $match) {
28
+ $mapping[$match->getId()] = $match->getBooticCategoryId();
29
+ }
30
+
31
+ Mage::register('category_mapping', $mapping);
32
+
33
+ $this->_title($this->__('Bootic Category Mapping'));
34
+ $this
35
+ ->loadLayout()
36
+ ->_setActiveMenu('bootic/bootic')
37
+ ->renderLayout();
38
+ }
39
+
40
+ public function saveAction()
41
+ {
42
+ if ($data = $this->getRequest()->getPost()) {
43
+ $session = Mage::getSingleton('adminhtml/session');
44
+
45
+ $mapping = Mage::getModel('bootic/category_mapping');
46
+ foreach ($data['category'] as $magentoId => $booticId) {
47
+ $mapping->setId($magentoId);
48
+ $mapping->setBooticCategoryId($booticId);
49
+ $mapping->save();
50
+ }
51
+
52
+ $session->addSuccess(Mage::helper('bootic')->__('Categories mapping was successfully saved'));
53
+ }
54
+
55
+ $this->_redirect('*/*/');
56
+ }
57
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/Catalog/ProductController.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Adminhtml_Catalog_ProductController extends Mage_Adminhtml_Controller_action
7
+ {
8
+ public function massAddProductsAction()
9
+ {
10
+ try {
11
+ Mage::helper('bootic/product')->massCreateProducts($this->getRequest());
12
+ $this->_redirectReferer();
13
+ } catch(Exception $e) {
14
+ Mage::getSingleton('adminhtml/session')->addError($e->getMessage().' : '.$e->getTraceAsString());
15
+ $this->_redirectReferer();
16
+ }
17
+ }
18
+
19
+ public function massResetProductsAction()
20
+ {
21
+ Mage::helper('bootic/product_data')->resetProducts($this->getRequest());
22
+ Mage::getSingleton('adminhtml/session')->addSuccess('Products set to Not Created.');
23
+ $this->_redirectReferer();
24
+ }
25
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/CatalogController.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Open Software License (OSL 3.0)
8
+ * that is bundled with this package in the file LICENSE.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * @category Bootic
16
+ * @package Bootic_Bootic
17
+ * @copyright Copyright (c) 2012 Bootic (http://www.bootic.com)
18
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
19
+ */
20
+
21
+ require_once('Bootic/Bootic/controllers/Adminhtml/AbstractController.php');
22
+
23
+ class Bootic_Bootic_Adminhtml_CatalogController extends Bootic_Bootic_Adminhtml_AbstractController
24
+ {
25
+ public function indexAction()
26
+ {
27
+ $collection = mage::getResourceModel('catalog/product_collection')
28
+ ->addAttributeToSelect('*')
29
+ ->addAttributeToFilter('type_id', array('nin' => array('bundle', 'virtual', 'grouped')))
30
+ ->addAttributeToFilter('status', 1)
31
+ ;
32
+
33
+ $session = Mage::getSingleton('adminhtml/session');
34
+ foreach ($collection as $product) {
35
+ if ($product->getBooticStatus() === Bootic_Bootic_Model_Product_Data::BOOTIC_STATUS_ERROR) {
36
+ $session->addError(Mage::helper('bootic')->__('Some products have errors. Please look at the logs to fix the issues and add them to Bootic again.'));
37
+ break;
38
+ }
39
+ }
40
+
41
+ $this->_title($this->__('Bootic Catalog'));
42
+
43
+ $this
44
+ ->loadLayout()
45
+ ->_setActiveMenu('bootic/bootic')
46
+ ->renderLayout();
47
+ }
48
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/ConnectController.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Open Software License (OSL 3.0)
8
+ * that is bundled with this package in the file LICENSE.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * @category Bootic
16
+ * @package Bootic_Bootic
17
+ * @copyright Copyright (c) 2012 Bootic (http://www.bootic.com)
18
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
19
+ */
20
+
21
+ require_once('Bootic/Bootic/controllers/Adminhtml/AbstractController.php');
22
+
23
+ class Bootic_Bootic_Adminhtml_ConnectController extends Bootic_Bootic_Adminhtml_AbstractController
24
+ {
25
+
26
+ public function indexAction()
27
+ {
28
+ $this->_title($this->__('Bootic Connect'));
29
+ $this
30
+ ->loadLayout()
31
+ ->_setActiveMenu('bootic/bootic')
32
+ ->_addBreadcrumb(Mage::helper('bootic')->__('Profile'), Mage::helper('bootic')->__('Profile'))
33
+ ;
34
+
35
+ $session = Mage::getSingleton('adminhtml/session');
36
+ $_p = $session->hasData('form_data') ? $session->getFormData() : Mage::helper('bootic/connect')->getProfile()->getData();
37
+
38
+ $profile = Mage::getSingleton('bootic/profile');
39
+ $profile->addData($_p);
40
+
41
+ $this->renderLayout();
42
+ }
43
+
44
+ public function saveAction()
45
+ {
46
+ if ($data = $this->getRequest()->getPost()) {
47
+
48
+ $session = Mage::getSingleton('adminhtml/session');
49
+
50
+ $profile = Mage::getSingleton('bootic/profile');
51
+
52
+ if (isset($data['picture']['delete']) && $data['picture']['delete'] == 1) {
53
+ Mage::helper('bootic/connect')->editProfilePicture(array('use_default_avatar' => true));
54
+ unset($data['picture']);
55
+ } else {
56
+ $uploader = new Bootic_File_Uploader('picture');
57
+ $uploader
58
+ ->setAllowedExtensions(array('jpg', 'jpeg', 'png'))
59
+ ->setAllowRenameFiles(false)
60
+ ->setFilesDispersion(false)
61
+ ;
62
+
63
+ $profile->setPicture($uploader->getTempFileName());
64
+ unset($data['picture']);
65
+ }
66
+
67
+ // If outside of the US, we force the region to the default empty value
68
+ if ($data['country'] != 2) {
69
+ $data['region'] = 0;
70
+ }
71
+
72
+ $profile = Mage::getSingleton('bootic/profile')->addData($data);
73
+
74
+ $validate = $profile->validate();
75
+ if ($validate === true) {
76
+ try {
77
+ $dataArray = $profile->toArray();
78
+ unset($dataArray['picture']);
79
+ $result = Mage::helper('bootic/connect')->editProfile($dataArray);
80
+
81
+ $success = true;
82
+ if (!$result->isSuccess()) {
83
+ $success = false;
84
+ $session->addError($result->getErrorMessage());
85
+ }
86
+
87
+ // updating picture requires a different API call
88
+ if (null !== $profile->getPicture()) {
89
+ $binary = file_get_contents($profile->getPicture());
90
+ $result = Mage::helper('bootic/connect')->editProfilePicture(array('image_base64' => base64_encode($binary)));
91
+ if (!$result->isSuccess()) {
92
+ $success = false;
93
+ $session->addError($result->getErrorMessage());
94
+ }
95
+ }
96
+
97
+ if (!$success) {
98
+ $this->_redirect('*/*/');
99
+
100
+ return;
101
+ }
102
+
103
+ $mageConfig = new Mage_Core_Model_Config();
104
+ $mageConfig->saveConfig('bootic/account/profile_updated', true, 'default', 0);
105
+
106
+ // Here we have to reinit configuration
107
+ Mage::getConfig()->reinit();
108
+ Mage::app()->reinitStores();
109
+
110
+ if ($session->hasData('account_creation')) {
111
+ $session->unsetData('account_creation');
112
+ $session->addSuccess(Mage::helper('bootic')->__('Profile was successfully saved. Please create a Storefront now.'));
113
+ $this->_redirect('bootic/adminhtml_storefront/index');
114
+ } else {
115
+ $session->addSuccess(Mage::helper('bootic')->__('Profile was successfully saved'));
116
+ $this->_redirect('*/*/');
117
+ }
118
+
119
+ return;
120
+ } catch (Exception $e) {
121
+ $session->addError($e->getMessage());
122
+ $this->_redirect('*/*/');
123
+
124
+ return;
125
+ }
126
+ }
127
+ else {
128
+ $session->setFormData($data);
129
+ if (is_array($validate)) {
130
+ foreach ($validate as $errorMessage) {
131
+ $session->addError($errorMessage);
132
+ }
133
+ }
134
+ else {
135
+ $session->addError($this->__('Unable to update your profile.'));
136
+ }
137
+ }
138
+ }
139
+
140
+ $this->_redirect('*/*/');
141
+ }
142
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/StorefrontController.php ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Open Software License (OSL 3.0)
8
+ * that is bundled with this package in the file LICENSE.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * @category Bootic
16
+ * @package Bootic_Bootic
17
+ * @copyright Copyright (c) 2012 Bootic (http://www.bootic.com)
18
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
19
+ */
20
+
21
+ require_once('Bootic/Bootic/controllers/Adminhtml/AbstractController.php');
22
+
23
+ class Bootic_Bootic_Adminhtml_StorefrontController extends Bootic_Bootic_Adminhtml_AbstractController
24
+ {
25
+ protected function _init()
26
+ {
27
+ $this->_title($this->__('Bootic Storefront'));
28
+
29
+ $this
30
+ ->loadLayout()
31
+ ->_setActiveMenu('bootic/bootic')
32
+ ->_addBreadcrumb(Mage::helper('bootic')->__('Storefront'), Mage::helper('bootic')->__('Storefront'));
33
+
34
+ $this->getLayout()->getBlock('head')->setCanLoadTinyMce(true);
35
+
36
+ return $this;
37
+ }
38
+
39
+ public function indexAction()
40
+ {
41
+ $result = Mage::helper('bootic/storefront')->getStoreFrontList();
42
+
43
+ $shops = $result->getData();
44
+ if (count($shops) > 0) {
45
+ $shop = reset($shops);
46
+ $options = Mage::helper('bootic/storefront')->getStoreFrontOptions($shop['shop_id']);
47
+ $shop['banners'] = $options->getData('banners');
48
+ Mage::getSingleton('bootic/storefront')->addData($shop);
49
+ } else {
50
+ $this->_redirect('*/*/new');
51
+ return;
52
+ }
53
+
54
+ $this->_init();
55
+ $this->renderLayout();
56
+ }
57
+
58
+ public function newAction()
59
+ {
60
+ $this->_init();
61
+ $this->renderLayout();
62
+ }
63
+
64
+ public function createAction()
65
+ {
66
+ if ($data = $this->getRequest()->getPost()) {
67
+
68
+ $session = Mage::getSingleton('adminhtml/session');
69
+
70
+ try {
71
+ $result = Mage::helper('bootic/storefront')->createStorefront($data);
72
+
73
+ if (!$result->isSuccess()) {
74
+ $session->addError($result->getErrorMessage());
75
+ $this->_redirect('*/*/');
76
+ return;
77
+ }
78
+
79
+ $mageConfig = new Mage_Core_Model_Config();
80
+ $mageConfig->saveConfig('bootic/account/storefront_created', true, 'default', 0);
81
+ $mageConfig->saveConfig('bootic/account/storefront_id', $result->getData('shop_id'), 'default', 0);
82
+
83
+ // Here we have to reinit configuration
84
+ Mage::getConfig()->reinit();
85
+ Mage::app()->reinitStores();
86
+ $session->addSuccess(Mage::helper('bootic')->__('Storefront was successfully created'));
87
+ } catch (Exception $e) {
88
+ $session->addError($e->getMessage());
89
+ $this->_redirect('*/*/');
90
+ return;
91
+ }
92
+ }
93
+
94
+ $this->_redirect('*/*/');
95
+ }
96
+
97
+ public function saveAction()
98
+ {
99
+ if ($data = $this->getRequest()->getPost()) {
100
+
101
+ $session = Mage::getSingleton('adminhtml/session');
102
+ $storefront = Mage::getSingleton('bootic/storefront')->addData($data);
103
+
104
+ if (empty($data['shop_id'])) {
105
+
106
+ try {
107
+ $result = Mage::helper('bootic/storefront')->createStorefront($storefront->toArray());
108
+
109
+ if (!$result->isSuccess()) {
110
+ $session->addError($result->getErrorMessage());
111
+ $this->_redirect('*/*/');
112
+ return;
113
+ }
114
+
115
+ $session->addSuccess(Mage::helper('bootic')->__('Storefront was successfully saved'));
116
+ } catch (Exception $e) {
117
+ $session->addError($e->getMessage());
118
+ $this->_redirect('*/*/');
119
+ return;
120
+ }
121
+ } else {
122
+
123
+ // @TODO: add the delete functionality, reset banner to a default one
124
+ // if (isset($data['banner']['delete']) && $data['banner']['delete'] == 1) {
125
+ // $bootic->editProfilePicture(array('use_default_avatar' => true));
126
+ // unset($data['picture']);
127
+ // }
128
+
129
+ if (isset($_FILES['banner_url']['name']) && (file_exists($_FILES['banner_url']['tmp_name']))) {
130
+ try {
131
+ $im = file_get_contents($_FILES['banner_url']['tmp_name']);
132
+ $imdata = base64_encode($im);
133
+
134
+ $result = Mage::helper('bootic/storefront')->addStorefrontBanner(array(
135
+ 'image_base64' => $imdata,
136
+ 'shop_id' => $data['shop_id']
137
+ ));
138
+
139
+ if (!$result->isSuccess()) {
140
+ $session->addError($result->getErrorMessage());
141
+ $this->_redirect('*/*/');
142
+ return;
143
+ }
144
+
145
+ $storefront->setData('banner', $result->getData('banner'));
146
+
147
+ } catch (Exception $e) {
148
+ // TODO: better error than that
149
+ $session->addError(sprintf('An error occured during picture processing with error : %s', $e->getMessage()));
150
+ }
151
+ }
152
+
153
+ $validate = $storefront->validate();
154
+ if ($validate === true) {
155
+ try {
156
+ $result = Mage::helper('bootic/storefront')->updateStorefront($storefront->toArray());
157
+
158
+ if (!$result->isSuccess()) {
159
+ $session->addError($result->getErrorMessage());
160
+ $this->_redirect('*/*/');
161
+ return;
162
+ }
163
+
164
+ $session->addSuccess(Mage::helper('bootic')->__('Storefront was successfully saved'));
165
+
166
+ // check if 'Save and Continue'
167
+ if ($this->getRequest()->getParam('back')) {
168
+ $this->_redirect('*/*/', array('_current' => true));
169
+ return;
170
+ }
171
+
172
+ $this->_redirect('*/*/');
173
+ return;
174
+ } catch (Exception $e) {
175
+ $session->addError($e->getMessage());
176
+ $this->_redirect('*/*/');
177
+ return;
178
+ }
179
+ } else {
180
+ $session->setFormData($data);
181
+ if (is_array($validate)) {
182
+ foreach ($validate as $errorMessage) {
183
+ $session->addError($errorMessage);
184
+ }
185
+ } else {
186
+ $session->addError($this->__('Unable to create your storefront.'));
187
+ }
188
+ }
189
+ }
190
+
191
+
192
+ }
193
+
194
+ $this->_redirect('*/*/');
195
+ }
196
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/System/Config/AccountController.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Adminhtml_System_Config_AccountController extends Mage_Adminhtml_Controller_Action
7
+ {
8
+ /**
9
+ * Displays the creation form
10
+ *
11
+ * @return void
12
+ */
13
+ public function indexAction()
14
+ {
15
+ $this->_title($this->__('System'))->_title($this->__('Configuration'));
16
+
17
+ $current = 'bootic';
18
+ $website = $this->getRequest()->getParam('website');
19
+ $store = $this->getRequest()->getParam('store');
20
+
21
+ Mage::getSingleton('adminhtml/config_data')
22
+ ->setSection($current)
23
+ ->setWebsite($website)
24
+ ->setStore($store);
25
+
26
+ $this->loadLayout();
27
+
28
+ $this->_setActiveMenu('system/config');
29
+ $this->getLayout()->getBlock('menu')->setAdditionalCacheKeyInfo(array($current));
30
+
31
+ $this->_addBreadcrumb(Mage::helper('adminhtml')->__('System'), Mage::helper('adminhtml')->__('System'),
32
+ $this->getUrl('*/system'));
33
+
34
+ $this->getLayout()->getBlock('left')
35
+ ->append($this->getLayout()->createBlock('bootic/adminhtml_system_config_tabs')->initTabs());
36
+
37
+ $this->renderLayout();
38
+ }
39
+
40
+ public function createAction()
41
+ {
42
+ if ($data = $this->getRequest()->getPost()) {
43
+ $email = $data['email'];
44
+ $password = $data['password'];
45
+ $passwordConfirmation = $data['password_confirmation'];
46
+ $sellingAgreement = $data['selling_agreement'];
47
+
48
+ $validate = true;
49
+ $validationErrors = array();
50
+ $session = Mage::getSingleton('adminhtml/session');
51
+
52
+ $validator = new Zend_Validate_EmailAddress();
53
+ if (!$validator->isValid($email)) {
54
+ $validate = false;
55
+ foreach ($validator->getMessages() as $message) {
56
+ $validationErrors[] = $message;
57
+ }
58
+ }
59
+
60
+ if (strlen($password) < 4) {
61
+ $validate = false;
62
+ $validationErrors[] = Mage::helper('bootic')->__('Password is too short.');
63
+ }
64
+
65
+ if ($password !== $passwordConfirmation) {
66
+ $validate = false;
67
+ $validationErrors[] = Mage::helper('bootic')->__('Passwords don\'t match.');
68
+ }
69
+
70
+ if (!isset($sellingAgreement)) {
71
+ $validate = false;
72
+ $validationErrors[] = Mage::helper('bootic')->__('Please accept our terms and conditions.');
73
+ }
74
+
75
+ if ($validate === true) {
76
+ try {
77
+ /** @var Bootic_Api_Result $result */
78
+ $result = Mage::helper('bootic/account')->createAccount($email, $password);
79
+
80
+ if (!$result->isSuccess()) {
81
+ $session->setFormData(array('email' => $email));
82
+ $errorMessage = $result->getErrorMessage();
83
+
84
+ foreach ((array) $errorMessage as $message) {
85
+ $session->addError($message);
86
+ }
87
+ } else {
88
+ $userId = $result->getData('new_user_id');
89
+
90
+ // We create a new API Key
91
+ // TODO Rollback user creation if this step fails?
92
+ $apiKey = Mage::helper('bootic/api')->createKey($email, $password);
93
+
94
+ $mageConfig = new Mage_Core_Model_Config();
95
+ $mageConfig->saveConfig('bootic/account/email', $email, 'default', 0);
96
+ $mageConfig->saveConfig('bootic/account/password', $password, 'default', 0);
97
+ $mageConfig->saveConfig('bootic/account/user_id', $userId, 'default', 0);
98
+ $mageConfig->saveConfig('bootic/account/api_key', $apiKey, 'default', 0);
99
+ $mageConfig->saveConfig('bootic/account/connect', 'new', 'default', 0);
100
+ $mageConfig->saveConfig('bootic/account/profile_updated', false, 'default', 0);
101
+ $mageConfig->saveConfig('bootic/account/storefront_created', false, 'default', 0);
102
+
103
+ // Here we have to reinit configuration
104
+ Mage::getConfig()->reinit();
105
+ Mage::app()->reinitStores();
106
+
107
+ $session->addSuccess(Mage::helper('bootic')->__('Your account was successfully created on Bootic. Please update your profile now.'));
108
+ $session->addData(array('account_creation' => true));
109
+ $this->_redirect('bootic/adminhtml_connect/index');
110
+
111
+ return;
112
+ }
113
+ } catch (Exception $e) {
114
+ $session->addError($e->getMessage());
115
+ $this->_redirect('*/*/');
116
+
117
+ return;
118
+ }
119
+ } else {
120
+ $session->setFormData(array('email' => $email));
121
+ if (is_array($validationErrors)) {
122
+ foreach ($validationErrors as $errorMessage) {
123
+ $session->addError($errorMessage);
124
+ }
125
+ } else {
126
+ $session->addError($this->__('Unable to create your account.'));
127
+ }
128
+ }
129
+ }
130
+
131
+ $this->_redirect('*/*/');
132
+ }
133
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/System/Config/TestapiconnectionController.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_Adminhtml_System_Config_TestapiconnectionController extends Mage_Adminhtml_Controller_Action
7
+ {
8
+ /**
9
+ * Perform connection credentials test
10
+ *
11
+ * @return Varien_Object
12
+ */
13
+ protected function _test()
14
+ {
15
+ return Mage::helper('bootic')->testApiConnection(
16
+ $this->getRequest()->getParam('email'),
17
+ $this->getRequest()->getParam('password')
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Check whether connection can be established
23
+ *
24
+ * @return void
25
+ */
26
+ public function testAction()
27
+ {
28
+ $result = $this->_test();
29
+ $this->getResponse()->setBody((int)$result->getIsConnected());
30
+ }
31
+ }
app/code/community/Bootic/Bootic/controllers/Adminhtml/System/CronController.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Bootic_Bootic_Adminhtml_System_CronController extends Mage_Adminhtml_Controller_action
4
+ {
5
+ public function runProcessOrdersAction()
6
+ {
7
+ try {
8
+ $orders = $this->getCronObserver()->processOrders();
9
+ $response = array(
10
+ 'success' => 1,
11
+ 'orderCount' => count($orders),
12
+ );
13
+ } catch(Exception $e){
14
+ $response = array(
15
+ 'success' => 0,
16
+ 'error' => $e->getMessage(),
17
+ );
18
+ }
19
+
20
+ $this->getResponse()->setHeader('Content-type', 'application/json');
21
+ $this->getResponse()->setBody(json_encode($response));
22
+ }
23
+
24
+ /**
25
+ * @return Bootic_Bootic_Model_OrdersCronObserver
26
+ */
27
+ public function getCronObserver()
28
+ {
29
+ return Mage::getModel('bootic/OrdersCronObserver');
30
+ }
31
+ }
app/code/community/Bootic/Bootic/controllers/IndexController.php ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Bootic_IndexController extends Mage_Core_Controller_Front_Action
7
+ {
8
+ /**
9
+ * Index action
10
+ */
11
+ public function indexAction()
12
+ {
13
+ $product = Mage::getModel('catalog/product')->load(83);
14
+
15
+ $usedProducts = $product->getTypeInstance(true)->getUsedProducts(null, $product);
16
+
17
+ foreach ($usedProducts as $usedProduct) {
18
+ $arr[] = $usedProduct->getId();
19
+ }
20
+
21
+ var_dump($arr);
22
+ }
23
+
24
+ /**
25
+ * Index action
26
+ */
27
+ public function childrenAction()
28
+ {
29
+ var_dump(Mage::getModel('catalog/product_type_configurable')->getChildrenIds(83));
30
+ }
31
+
32
+ public function testAction()
33
+ {
34
+ $product = Mage::getModel('bootic/product_data')->load(83);
35
+ echo '<pre>'.print_r($product, true).'</pre>';
36
+ }
37
+
38
+ public function attributeAction()
39
+ {
40
+ $product = Mage::getModel('catalog/product')->load(93);
41
+ var_dump(Mage::helper('bootic/product')->makeConfigurableProduct($product));
42
+ }
43
+
44
+ public function categoriesAction()
45
+ {
46
+ $booticCategories = Mage::helper('bootic/product')->listCategories();
47
+
48
+ foreach ($booticCategories as $booticCategory) {
49
+ $category = Mage::getModel('bootic/category');
50
+ $category->setId($booticCategory['id']);
51
+ $category->setName($booticCategory['name']);
52
+ $category->setParents($booticCategory['parents']);
53
+ try {
54
+ $category->save();
55
+ echo 'cool<br/>';
56
+ } catch (Exception $e) {
57
+ echo $category->getCategoryId() . '<br/>';
58
+ echo $e->getMessage() . '<br/>';
59
+ }
60
+ }
61
+
62
+ // echo ('<pre>' . print_r(Mage::helper('bootic/product')->listCategories(), true) . '</pre>');
63
+
64
+ // echo ('<pre>' . print_r(Mage::getModel('bootic/category')->load(49, 'category_id'), true) . '</pre>');
65
+
66
+ // $collection = mage::getResourceModel('bootic/category_collection');
67
+ // foreach ($collection as $cat) {
68
+ // echo ('<pre>' . print_r($cat, true) . '</pre>');
69
+ // }
70
+
71
+ echo ('<pre>' . print_r(Mage::helper('bootic/category')->getBooticCategories(), true) . '</pre>');
72
+ // echo ('<pre>' . print_r(Mage::helper('bootic/category')->getBooticCategories(), true) . '</pre>');
73
+
74
+ }
75
+
76
+ public function categorycollectionAction()
77
+ {
78
+ /** @var Mage_Catalog_Model_Product $product */
79
+ $product = Mage::getModel('catalog/product')->load(166);
80
+
81
+ // $categoryCollection = $product->getCategoryCollection()->addAttributeToSort('level', 'DESC');
82
+ $categoryCollection = Mage::getModel('bootic/resource_product')->getMappedCategoryCollection($product);
83
+
84
+ foreach ($categoryCollection as $category) {
85
+ echo $category->getId() . ': ' . $category->getBooticCategoryId() . '<br/>';
86
+ }
87
+ }
88
+
89
+ public function imagesAction()
90
+ {
91
+ /** @var Mage_Catalog_Model_Product $product */
92
+ $product = Mage::getModel('catalog/product')->load(83);
93
+
94
+ $gallery = $product->getMediaGalleryImages();
95
+ foreach ($gallery as $image) {
96
+ var_dump(Mage::helper('catalog/image')->init($product, 'thumbnail', $image->getFile())->__toString());
97
+ }
98
+ }
99
+
100
+ public function productAction()
101
+ {
102
+ /** @var $product Mage_Catalog_Model_Product */
103
+ $product = Mage::getModel('catalog/product')->load(46);
104
+
105
+ $gallery = $product->getMediaGalleryImages();
106
+
107
+ foreach ($gallery as $image) {
108
+ echo 'image';
109
+ }
110
+ }
111
+
112
+ public function cronAction()
113
+ {
114
+ try {
115
+ echo Mage::helper('bootic/product')->editProducts();
116
+ } catch (Exception $e) {
117
+ var_dump($e);
118
+ }
119
+ }
120
+
121
+ public function statusAction()
122
+ {
123
+ Mage::helper('bootic/product')->checkProductsStatus();
124
+ }
125
+
126
+ public function messagesAction()
127
+ {
128
+ Mage::helper('bootic/message')->pullUnreadMessages();
129
+ }
130
+
131
+ public function testScopeAction()
132
+ {
133
+ $messages = Mage::helper('bootic/message')->getAccountMessages();
134
+
135
+ $productInfo = Mage::helper('bootic/product')->getProductInfo();
136
+ }
137
+
138
+ public function shortDescAction()
139
+ {
140
+ $p = Mage::getModel('catalog/product')->load(140);
141
+
142
+ var_dump($p->getShortDescription());
143
+
144
+ // Truncate the string
145
+ $shortDesc = Mage::helper('core/string')->truncate($p->getShortDescription(), 120, '...');
146
+ // Remove line breaks
147
+ $shortDesc = preg_replace("/[\n\r\t]/","", $shortDesc);
148
+ // Make sure everything is UTF8
149
+ $shd = Mage::helper('core/string')->cleanString($shortDesc);
150
+ var_dump($shd);
151
+ }
152
+
153
+ public function orderslistAction()
154
+ {
155
+ $orders = Mage::helper('bootic/product')->getOrdersList();
156
+
157
+ echo '<pre>' . print_r($orders, true) . '</pre>';
158
+ }
159
+
160
+ public function salesListAction()
161
+ {
162
+ $sales = Mage::helper('bootic/product')->getSalesList();
163
+
164
+ echo '<pre>' . print_r($sales, true) . '</pre>';
165
+ }
166
+
167
+ public function orderDetailsAction()
168
+ {
169
+ $details = Mage::helper('bootic/product')->getOrderDetails(5510);
170
+
171
+ echo '<pre>' . print_r($details, true) . '</pre>';
172
+ }
173
+
174
+ public function processOrdersAction()
175
+ {
176
+ Mage::helper('bootic/orders')->processPendingOrders();
177
+ }
178
+
179
+ public function syncOrdersAction()
180
+ {
181
+ Mage::helper('bootic/orders')->syncOrders();
182
+ }
183
+
184
+ public function pullShippedOrdersAction()
185
+ {
186
+ Mage::helper('bootic/orders')->pullShippedOrderStatus();
187
+ }
188
+
189
+ public function uploadAction()
190
+ {
191
+ Mage::helper('bootic/product')->uploadProducts();
192
+ }
193
+
194
+ public function editAction()
195
+ {
196
+ Mage::helper('bootic/product')->editProducts();
197
+ }
198
+
199
+ public function syncStockAction()
200
+ {
201
+ Mage::helper('bootic/product')->syncProductsStocks();
202
+ }
203
+
204
+ public function regionsAction()
205
+ {
206
+ Mage::helper('bootic/lists')->getCountries();
207
+ }
208
+
209
+ }
app/code/community/Bootic/Bootic/etc/adminhtml.xml ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <menu>
4
+ <bootic translate="title" module="bootic">
5
+ <title>Bootic</title>
6
+ <sort_order>71</sort_order>
7
+ <children>
8
+ <connect module="bootic">
9
+ <title>Profile</title>
10
+ <sort_order>0</sort_order>
11
+ <action>bootic/adminhtml_connect</action>
12
+ </connect>
13
+ <storefront module="bootic">
14
+ <title>Storefront</title>
15
+ <sort_order>1</sort_order>
16
+ <action>bootic/adminhtml_storefront</action>
17
+ </storefront>
18
+ <catalog module="bootic">
19
+ <title>Catalog</title>
20
+ <sort_order>2</sort_order>
21
+ <children>
22
+ <upload_products translate="title" module="bootic">
23
+ <title>Manage Products</title>
24
+ <action>bootic/adminhtml_catalog</action>
25
+ </upload_products>
26
+ <category translate="title" module="bootic">
27
+ <title>Category Mapping</title>
28
+ <action>bootic/adminhtml_catalog_category</action>
29
+ </category>
30
+ </children>
31
+ </catalog>
32
+ </children>
33
+ </bootic>
34
+ </menu>
35
+
36
+ <acl>
37
+ <resources>
38
+ <admin>
39
+ <children>
40
+ <bootic translate="title" module="bootic">
41
+ <title>Bootic</title>
42
+ <children>
43
+ <connect translate="title" module="bootic">
44
+ <title>Connect</title>
45
+ </connect>
46
+ <storefront translate="title" module="bootic">
47
+ <title>Storefront</title>
48
+ </storefront>
49
+ <catalog translate="title" module="bootic">
50
+ <title>Catalog</title>
51
+ </catalog>
52
+ <statistics translate="title" module="bootic">
53
+ <title>Statistics</title>
54
+ </statistics>
55
+ </children>
56
+ </bootic>
57
+ <system>
58
+ <children>
59
+ <config>
60
+ <children>
61
+ <bootic module="bootic">
62
+ <title>Bootic</title>
63
+ </bootic>
64
+ </children>
65
+ </config>
66
+ </children>
67
+ </system>
68
+ </children>
69
+ </admin>
70
+ </resources>
71
+ </acl>
72
+ </config>
app/code/community/Bootic/Bootic/etc/config.xml ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <!--
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * that is bundled with this package in the file LICENSE.txt.
10
+ * It is also available through the world-wide-web at this URL:
11
+ * http://opensource.org/licenses/osl-3.0.php
12
+ * If you did not receive a copy of the license and are unable to
13
+ * obtain it through the world-wide-web, please send an email
14
+ * to license@magentocommerce.com so we can send you a copy immediately.
15
+ *
16
+ * @category Bootic
17
+ * @package Bootic_Bootic
18
+ * @copyright Copyright (c) 2012 Bootic (http://www.bootic.com)
19
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
20
+ */
21
+ -->
22
+ <config>
23
+ <modules>
24
+ <Bootic_Bootic>
25
+ <version>1.0.0</version>
26
+ </Bootic_Bootic>
27
+ </modules>
28
+ <global>
29
+ <helpers>
30
+ <bootic>
31
+ <class>Bootic_Bootic_Helper</class>
32
+ </bootic>
33
+ </helpers>
34
+ <blocks>
35
+ <bootic>
36
+ <class>Bootic_Bootic_Block</class>
37
+ </bootic>
38
+ </blocks>
39
+ <models>
40
+ <bootic>
41
+ <class>Bootic_Bootic_Model</class>
42
+ <resourceModel>bootic_resource</resourceModel>
43
+ </bootic>
44
+ <bootic_resource>
45
+ <class>Bootic_Bootic_Model_Mysql4</class>
46
+ <entities>
47
+ <log>
48
+ <table>bootic_log</table>
49
+ </log>
50
+ <product_data>
51
+ <table>bootic_product_data</table>
52
+ </product_data>
53
+ <category>
54
+ <table>bootic_category</table>
55
+ </category>
56
+ <category_parent>
57
+ <table>bootic_category_parent</table>
58
+ </category_parent>
59
+ <category_mapping>
60
+ <table>bootic_category_mapping</table>
61
+ </category_mapping>
62
+ <message>
63
+ <table>bootic_message</table>
64
+ </message>
65
+ <order_data>
66
+ <table>bootic_order_data</table>
67
+ </order_data>
68
+ </entities>
69
+ </bootic_resource>
70
+ </models>
71
+ <resources>
72
+ <bootic_setup>
73
+ <setup>
74
+ <module>Bootic_Bootic</module>
75
+ </setup>
76
+ <connection>
77
+ <use>core_setup</use>
78
+ </connection>
79
+ </bootic_setup>
80
+ <bootic_write>
81
+ <connection>
82
+ <use>core_write</use>
83
+ </connection>
84
+ </bootic_write>
85
+ <bootic_read>
86
+ <connection>
87
+ <use>core_read</use>
88
+ </connection>
89
+ </bootic_read>
90
+ </resources>
91
+ <events>
92
+ <controller_action_postdispatch_adminhtml_system_config_save>
93
+ <observers>
94
+ <bootic_config_changed>
95
+ <type>singleton</type>
96
+ <class>Bootic_Bootic_Model_System_Config_Observer</class>
97
+ <method>redirectToCategoryMapping</method>
98
+ </bootic_config_changed>
99
+ </observers>
100
+ </controller_action_postdispatch_adminhtml_system_config_save>
101
+ <catalog_product_save_before>
102
+ <observers>
103
+ <bootic_product_edit>
104
+ <type>singleton</type>
105
+ <class>Bootic_Bootic_Model_Product_Observer</class>
106
+ <method>updateBooticProductInfo</method>
107
+ </bootic_product_edit>
108
+ </observers>
109
+ </catalog_product_save_before>
110
+ <cataloginventory_stock_item_save_after>
111
+ <observers>
112
+ <bootic_product_stock_edit>
113
+ <type>singleton</type>
114
+ <class>Bootic_Bootic_Model_Stock_Observer</class>
115
+ <method>updateBooticStock</method>
116
+ </bootic_product_stock_edit>
117
+ </observers>
118
+ </cataloginventory_stock_item_save_after>
119
+ <sales_order_save_after>
120
+ <observers>
121
+ <bootic_product_sold>
122
+ <type>singleton</type>
123
+ <class>Bootic_Bootic_Model_Stock_Observer</class>
124
+ <method>updateSoldProductBooticStock</method>
125
+ </bootic_product_sold>
126
+ </observers>
127
+ </sales_order_save_after>
128
+ <admin_session_user_login_success>
129
+ <observers>
130
+ <bootic_admin_login>
131
+ <type>singleton</type>
132
+ <class>Bootic_Bootic_Model_Session_Observer</class>
133
+ <method>pullUnreadMessages</method>
134
+ </bootic_admin_login>
135
+ </observers>
136
+ </admin_session_user_login_success>
137
+ <controller_action_postdispatch_adminhtml_notification_markAsRead>
138
+ <observers>
139
+ <bootic_message_read>
140
+ <type>singleton</type>
141
+ <class>Bootic_Bootic_Model_Message_Observer</class>
142
+ <method>markMessageAsRead</method>
143
+ </bootic_message_read>
144
+ </observers>
145
+ </controller_action_postdispatch_adminhtml_notification_markAsRead>
146
+ <order_cancel_after>
147
+ <observers>
148
+ <bootic_order_cancel>
149
+ <type>singleton</type>
150
+ <class>Bootic_Bootic_Model_Order_Observer</class>
151
+ <method>orderCanceled</method>
152
+ </bootic_order_cancel>
153
+ </observers>
154
+ </order_cancel_after>
155
+ <sales_order_shipment_save_after>
156
+ <observers>
157
+ <bootic_order_shipped>
158
+ <type>singleton</type>
159
+ <class>Bootic_Bootic_Model_Order_Observer</class>
160
+ <method>orderShipped</method>
161
+ </bootic_order_shipped>
162
+ </observers>
163
+ </sales_order_shipment_save_after>
164
+ </events>
165
+ </global>
166
+ <frontend>
167
+ <routers>
168
+ <bootic>
169
+ <use>standard</use>
170
+ <args>
171
+ <module>Bootic_Bootic</module>
172
+ <frontName>bootic</frontName>
173
+ </args>
174
+ </bootic>
175
+ </routers>
176
+ </frontend>
177
+ <admin>
178
+ <routers>
179
+ <bootic>
180
+ <use>admin</use>
181
+ <args>
182
+ <module>Bootic_Bootic</module>
183
+ <frontName>bootic</frontName>
184
+ </args>
185
+ </bootic>
186
+ </routers>
187
+ </admin>
188
+ <adminhtml>
189
+ <layout>
190
+ <updates>
191
+ <bootic>
192
+ <file>bootic.xml</file>
193
+ </bootic>
194
+ </updates>
195
+ </layout>
196
+ </adminhtml>
197
+ <crontab>
198
+ <jobs>
199
+ <bootic_bootic_upload_products>
200
+ <schedule>
201
+ <cron_expr>0 */1 * * *</cron_expr>
202
+ </schedule>
203
+ <run>
204
+ <model>bootic/cron::uploadProducts</model>
205
+ </run>
206
+ </bootic_bootic_upload_products>
207
+ <bootic_bootic_edit_products>
208
+ <schedule>
209
+ <cron_expr>0 */2 * * *</cron_expr>
210
+ </schedule>
211
+ <run>
212
+ <model>bootic/cron::editProducts</model>
213
+ </run>
214
+ </bootic_bootic_edit_products>
215
+ <bootic_bootic_check_products_status>
216
+ <schedule>
217
+ <cron_expr>0 */2 * * *</cron_expr>
218
+ </schedule>
219
+ <run>
220
+ <model>bootic/cron::checkProductsStatus</model>
221
+ </run>
222
+ </bootic_bootic_check_products_status>
223
+ <bootic_bootic_sync_products_stocks>
224
+ <schedule>
225
+ <cron_expr>0 */1 * * *</cron_expr>
226
+ </schedule>
227
+ <run>
228
+ <model>bootic/cron::syncProductsStocks</model>
229
+ </run>
230
+ </bootic_bootic_sync_products_stocks>
231
+ <bootic_bootic_fetch_orders>
232
+ <schedule>
233
+ <cron_expr>0 */1 * * *</cron_expr>
234
+ </schedule>
235
+ <run>
236
+ <model>bootic/cron::fetchOrders</model>
237
+ </run>
238
+ </bootic_bootic_fetch_orders>
239
+ <bootic_bootic_sync_orders>
240
+ <schedule>
241
+ <cron_expr>*/20 * * * *</cron_expr>
242
+ </schedule>
243
+ <run>
244
+ <model>bootic/cron::syncOrders</model>
245
+ </run>
246
+ </bootic_bootic_sync_orders>
247
+ </jobs>
248
+ </crontab>
249
+ <default>
250
+ <bootic>
251
+ <sales>
252
+ <monthly_sales_req_for_bonus>0</monthly_sales_req_for_bonus>
253
+ <bonus_amount>0</bonus_amount>
254
+ <commission>10</commission>
255
+ </sales>
256
+ <product>
257
+ <limit>50</limit>
258
+ </product>
259
+ </bootic>
260
+ <carriers>
261
+ <bootic>
262
+ <active>1</active>
263
+ <model>bootic/shipping_flatrate</model>
264
+ <title>Bootic Shipping</title>
265
+ <name>Default Rate</name>
266
+ </bootic>
267
+ </carriers>
268
+ <payment>
269
+ <bootic_payment_method>
270
+ <active>0</active>
271
+ <model>bootic/payment</model>
272
+ <allowspecific>0</allowspecific>
273
+ <title>Bootic Payment</title>
274
+ </bootic_payment_method>
275
+ </payment>
276
+ </default>
277
+ </config>
app/code/community/Bootic/Bootic/etc/system.xml ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <sections>
4
+ <bootic translate="label" module="bootic">
5
+ <label>Bootic</label>
6
+ <tab>sales</tab>
7
+ <frontend_type>text</frontend_type>
8
+ <sort_order>600</sort_order>
9
+ <show_in_default>1</show_in_default>
10
+ <show_in_website>0</show_in_website>
11
+ <show_in_store>0</show_in_store>
12
+ <groups>
13
+ <account translate="button_label" module="bootic">
14
+ <label>Account</label>
15
+ <frontend_type>text</frontend_type>
16
+ <sort_order>0</sort_order>
17
+ <show_in_default>1</show_in_default>
18
+ <show_in_website>0</show_in_website>
19
+ <show_in_store>0</show_in_store>
20
+ <expanded>true</expanded>
21
+ <fields>
22
+ <heading_credentials translate="label" module="bootic">
23
+ <label>Your Bootic account credentials</label>
24
+ <frontend_model>adminhtml/system_config_form_field_heading</frontend_model>
25
+ <sort_order>3</sort_order>
26
+ <show_in_default>1</show_in_default>
27
+ <show_in_website>0</show_in_website>
28
+ <show_in_store>0</show_in_store>
29
+ </heading_credentials>
30
+ <email translate="label" module="bootic">
31
+ <label>Email</label>
32
+ <frontend_type>text</frontend_type>
33
+ <sort_order>5</sort_order>
34
+ <show_in_default>1</show_in_default>
35
+ <show_in_website>0</show_in_website>
36
+ <show_in_store>0</show_in_store>
37
+ </email>
38
+ <password translate="label" module="bootic">
39
+ <label>Password</label>
40
+ <frontend_type>password</frontend_type>
41
+ <backend_model>bootic/system_config_connection</backend_model>
42
+ <sort_order>15</sort_order>
43
+ <show_in_default>1</show_in_default>
44
+ <show_in_website>0</show_in_website>
45
+ <show_in_store>0</show_in_store>
46
+ </password>
47
+ <connection_test translate="button_label">
48
+ <label></label>
49
+ <button_label>Test Bootic API connection</button_label>
50
+ <frontend_model>bootic/adminhtml_system_config_testapiconnection</frontend_model>
51
+ <sort_order>20</sort_order>
52
+ <show_in_default>1</show_in_default>
53
+ <show_in_website>0</show_in_website>
54
+ <show_in_store>0</show_in_store>
55
+ </connection_test>
56
+ </fields>
57
+ </account>
58
+ <system translate="label" module="bootic">
59
+ <label>System</label>
60
+ <frontend_type>text</frontend_type>
61
+ <sort_order>100</sort_order>
62
+ <show_in_default>1</show_in_default>
63
+ <show_in_website>1</show_in_website>
64
+ <show_in_store>1</show_in_store>
65
+ <fields>
66
+ <!-- ORDERS -->
67
+ <!--<heading_orders translate="label">-->
68
+ <!--<label>Orders import (from Bootic)</label>-->
69
+ <!--<frontend_model>adminhtml/system_config_form_field_heading</frontend_model>-->
70
+ <!--<sort_order>105</sort_order>-->
71
+ <!--<show_in_default>1</show_in_default>-->
72
+ <!--<show_in_website>1</show_in_website>-->
73
+ <!--</heading_orders>-->
74
+ <store_id translate="label">
75
+ <label>Store</label>
76
+ <frontend_type>select</frontend_type>
77
+ <source_model>bootic/system_config_source_store</source_model>
78
+ <sort_order>30</sort_order>
79
+ <show_in_default>1</show_in_default>
80
+ <show_in_website>0</show_in_website>
81
+ <show_in_store>0</show_in_store>
82
+ </store_id>
83
+ <!--<run_cron_orders translate="label button_label comment">-->
84
+ <!--<label>Orders processing job</label>-->
85
+ <!--<frontend_model>bootic/adminhtml_system_config_button_cron</frontend_model>-->
86
+ <!--<cron_type>orders</cron_type>-->
87
+ <!--<button_label>Run</button_label>-->
88
+ <!--<button_url>bootic/adminhtml_system_cron/runProcessOrders</button_url>-->
89
+ <!--<sort_order>110</sort_order>-->
90
+ <!--<show_in_default>1</show_in_default>-->
91
+ <!--<show_in_website>1</show_in_website>-->
92
+ <!--<show_in_store>1</show_in_store>-->
93
+ <!--<comment>Run cron manually</comment>-->
94
+ <!--</run_cron_orders>-->
95
+ </fields>
96
+ </system>
97
+ <product translate="label" module="bootic">
98
+ <label>Product</label>
99
+ <comment>&lt;strong&gt;Note:&lt;/strong&gt; in case no match can be found leave these values empty or create new attributes specifically for Bootic.</comment>
100
+ <frontend_type>text</frontend_type>
101
+ <sort_order>10</sort_order>
102
+ <show_in_default>1</show_in_default>
103
+ <show_in_website>0</show_in_website>
104
+ <show_in_store>0</show_in_store>
105
+ <fields>
106
+ <heading_upload translate="label">
107
+ <label>Upload</label>
108
+ <frontend_model>adminhtml/system_config_form_field_heading</frontend_model>
109
+ <sort_order>0</sort_order>
110
+ <show_in_default>1</show_in_default>
111
+ <show_in_website>0</show_in_website>
112
+ <show_in_store>0</show_in_store>
113
+ </heading_upload>
114
+ <limit translate="label">
115
+ <label>Limit of products per cron run</label>
116
+ <comment>&lt;strong&gt;Note:&lt;/strong&gt; default is 50 - reduce this limit if you experience temporary slowdowns on your site.</comment>
117
+ <frontend_type>text</frontend_type>
118
+ <sort_order>1</sort_order>
119
+ <show_in_default>1</show_in_default>
120
+ <show_in_website>0</show_in_website>
121
+ <show_in_store>0</show_in_store>
122
+ </limit>
123
+ <heading_general translate="label">
124
+ <label>General</label>
125
+ <frontend_model>adminhtml/system_config_form_field_heading</frontend_model>
126
+ <sort_order>5</sort_order>
127
+ <show_in_default>1</show_in_default>
128
+ <show_in_website>0</show_in_website>
129
+ <show_in_store>0</show_in_store>
130
+ </heading_general>
131
+ <brand_name translate="label">
132
+ <label>Brand Name</label>
133
+ <frontend_type>select</frontend_type>
134
+ <source_model>bootic/system_config_source_attribute</source_model>
135
+ <sort_order>6</sort_order>
136
+ <show_in_default>1</show_in_default>
137
+ <show_in_website>0</show_in_website>
138
+ <show_in_store>0</show_in_store>
139
+ </brand_name>
140
+ <warranty_months translate="label">
141
+ <label>Warranty in Months</label>
142
+ <frontend_type>select</frontend_type>
143
+ <source_model>bootic/system_config_source_attribute</source_model>
144
+ <sort_order>10</sort_order>
145
+ <show_in_default>1</show_in_default>
146
+ <show_in_website>0</show_in_website>
147
+ <show_in_store>0</show_in_store>
148
+ </warranty_months>
149
+ <isbn translate="label">
150
+ <label>ISBN</label>
151
+ <frontend_type>select</frontend_type>
152
+ <source_model>bootic/system_config_source_attribute</source_model>
153
+ <sort_order>20</sort_order>
154
+ <show_in_default>1</show_in_default>
155
+ <show_in_website>0</show_in_website>
156
+ <show_in_store>0</show_in_store>
157
+ </isbn>
158
+ <ean translate="label">
159
+ <label>EAN13</label>
160
+ <frontend_type>select</frontend_type>
161
+ <source_model>bootic/system_config_source_attribute</source_model>
162
+ <sort_order>30</sort_order>
163
+ <show_in_default>1</show_in_default>
164
+ <show_in_website>0</show_in_website>
165
+ <show_in_store>0</show_in_store>
166
+ </ean>
167
+ <upc translate="label">
168
+ <label>UPC</label>
169
+ <frontend_type>select</frontend_type>
170
+ <source_model>bootic/system_config_source_attribute</source_model>
171
+ <sort_order>40</sort_order>
172
+ <show_in_default>1</show_in_default>
173
+ <show_in_website>0</show_in_website>
174
+ <show_in_store>0</show_in_store>
175
+ </upc>
176
+ <pkg_width translate="label">
177
+ <label>Package Width in inches</label>
178
+ <frontend_type>select</frontend_type>
179
+ <source_model>bootic/system_config_source_attribute</source_model>
180
+ <sort_order>50</sort_order>
181
+ <show_in_default>1</show_in_default>
182
+ <show_in_website>0</show_in_website>
183
+ <show_in_store>0</show_in_store>
184
+ </pkg_width>
185
+ <pkg_height translate="label">
186
+ <label>Package Height in inches</label>
187
+ <frontend_type>select</frontend_type>
188
+ <source_model>bootic/system_config_source_attribute</source_model>
189
+ <sort_order>60</sort_order>
190
+ <show_in_default>1</show_in_default>
191
+ <show_in_website>0</show_in_website>
192
+ <show_in_store>0</show_in_store>
193
+ </pkg_height>
194
+ <pkg_length translate="label">
195
+ <label>Package Length in inches</label>
196
+ <frontend_type>select</frontend_type>
197
+ <source_model>bootic/system_config_source_attribute</source_model>
198
+ <sort_order>70</sort_order>
199
+ <show_in_default>1</show_in_default>
200
+ <show_in_website>0</show_in_website>
201
+ <show_in_store>0</show_in_store>
202
+ </pkg_length>
203
+ <pkg_weight translate="label">
204
+ <label>Package Weight in pounds</label>
205
+ <frontend_type>select</frontend_type>
206
+ <source_model>bootic/system_config_source_attribute</source_model>
207
+ <sort_order>80</sort_order>
208
+ <show_in_default>1</show_in_default>
209
+ <show_in_website>0</show_in_website>
210
+ <show_in_store>0</show_in_store>
211
+ </pkg_weight>
212
+ <heading_categories translate="label">
213
+ <label>Categories</label>
214
+ <frontend_model>adminhtml/system_config_form_field_heading</frontend_model>
215
+ <sort_order>100</sort_order>
216
+ <show_in_default>1</show_in_default>
217
+ <show_in_website>0</show_in_website>
218
+ <show_in_store>0</show_in_store>
219
+ </heading_categories>
220
+ <root_category translate="label">
221
+ <label>Root category</label>
222
+ <frontend_type>select</frontend_type>
223
+ <source_model>adminhtml/system_config_source_category</source_model>
224
+ <sort_order>110</sort_order>
225
+ <show_in_default>1</show_in_default>
226
+ <show_in_website>0</show_in_website>
227
+ <show_in_store>0</show_in_store>
228
+ </root_category>
229
+ <heading_image translate="label">
230
+ <label>Image</label>
231
+ <frontend_model>adminhtml/system_config_form_field_heading</frontend_model>
232
+ <sort_order>200</sort_order>
233
+ <show_in_default>1</show_in_default>
234
+ <show_in_website>0</show_in_website>
235
+ <show_in_store>0</show_in_store>
236
+ </heading_image>
237
+ <image translate="label">
238
+ <label>Main Image</label>
239
+ <frontend_type>select</frontend_type>
240
+ <source_model>bootic/system_config_source_image</source_model>
241
+ <sort_order>210</sort_order>
242
+ <show_in_default>1</show_in_default>
243
+ <show_in_website>0</show_in_website>
244
+ <show_in_store>0</show_in_store>
245
+ </image>
246
+ </fields>
247
+ </product>
248
+ <sales translate="label" module="bootic">
249
+ <label>Sales</label>
250
+ <comment>&lt;strong&gt;Note:&lt;/strong&gt; Define default sales options values.</comment>
251
+ <frontend_type>text</frontend_type>
252
+ <sort_order>20</sort_order>
253
+ <show_in_default>1</show_in_default>
254
+ <show_in_website>0</show_in_website>
255
+ <show_in_store>0</show_in_store>
256
+ <fields>
257
+ <monthly_sales_req_for_bonus translate="label">
258
+ <label>Monthly Sales Required for Bonus</label>
259
+ <frontend_type>text</frontend_type>
260
+ <sort_order>0</sort_order>
261
+ <show_in_default>1</show_in_default>
262
+ <show_in_website>0</show_in_website>
263
+ <show_in_store>0</show_in_store>
264
+ </monthly_sales_req_for_bonus>
265
+ <bonus_amount translate="label">
266
+ <label>Bonus Amount</label>
267
+ <frontend_type>text</frontend_type>
268
+ <sort_order>10</sort_order>
269
+ <show_in_default>1</show_in_default>
270
+ <show_in_website>0</show_in_website>
271
+ <show_in_store>0</show_in_store>
272
+ </bonus_amount>
273
+ <commission translate="label">
274
+ <label>Commission for other vendors on your products</label>
275
+ <backend_model>bootic/system_config_commission</backend_model>
276
+ <frontend_type>text</frontend_type>
277
+ <sort_order>50</sort_order>
278
+ <show_in_default>1</show_in_default>
279
+ <show_in_website>0</show_in_website>
280
+ <show_in_store>0</show_in_store>
281
+ </commission>
282
+ </fields>
283
+ </sales>
284
+ <delivery translate="label" module="bootic">
285
+ <label>Delivery options</label>
286
+ <frontend_type>text</frontend_type>
287
+ <sort_order>30</sort_order>
288
+ <show_in_default>1</show_in_default>
289
+ <show_in_website>0</show_in_website>
290
+ <show_in_store>0</show_in_store>
291
+ <fields>
292
+ <delivery_time_days translate="label">
293
+ <label>How many days (average) will normal shipping take?</label>
294
+ <frontend_type>text</frontend_type>
295
+ <sort_order>0</sort_order>
296
+ <show_in_default>1</show_in_default>
297
+ <show_in_website>0</show_in_website>
298
+ <show_in_store>0</show_in_store>
299
+ </delivery_time_days>
300
+ </fields>
301
+ </delivery>
302
+ </groups>
303
+ </bootic>
304
+ </sections>
305
+ </config>
app/code/community/Bootic/Bootic/sql/.DS_Store ADDED
Binary file
app/code/community/Bootic/Bootic/sql/bootic_setup/mysql4-install-1.0.0.php ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ $installer = $this;
7
+
8
+ //$installer->installEntities();
9
+
10
+ $installer->startSetup();
11
+ $installer->run("
12
+ DROP TABLE IF EXISTS {$this->getTable('bootic_log')};
13
+ CREATE TABLE {$this->getTable('bootic_log')} (
14
+ `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
15
+ `product_id` INT(11) NOT NULL,
16
+ `date` DATETIME NULL,
17
+ `status` VARCHAR(255) NOT NULL,
18
+ `message` VARCHAR(2550) NULL,
19
+ PRIMARY KEY (`id`)
20
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
21
+
22
+ DROP TABLE IF EXISTS {$this->getTable('bootic_category')};
23
+ CREATE TABLE {$this->getTable('bootic_category')}
24
+ (
25
+ `category_id` SMALLINT(6) NOT NULL,
26
+ `name` VARCHAR(255) NOT NULL,
27
+ `creation_time` TIMESTAMP,
28
+ `update_time` TIMESTAMP,
29
+ PRIMARY KEY (`category_id`)
30
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
31
+
32
+ DROP TABLE IF EXISTS {$this->getTable('bootic_category_parent')};
33
+ CREATE TABLE {$this->getTable('bootic_category_parent')}
34
+ (
35
+ `category_id` SMALLINT(6) NOT NULL,
36
+ `parent_id` SMALLINT(6) NOT NULL,
37
+ PRIMARY KEY (`category_id`, `parent_id`)
38
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
39
+
40
+ DROP TABLE IF EXISTS {$this->getTable('bootic_category_mapping')};
41
+ CREATE TABLE {$this->getTable('bootic_category_mapping')}
42
+ (
43
+ `magento_category_id` SMALLINT(6) NOT NULL,
44
+ `bootic_category_id` SMALLINT(6) NOT NULL,
45
+ PRIMARY KEY (`magento_category_id`)
46
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
47
+
48
+ DROP TABLE IF EXISTS {$this->getTable('bootic_product_data')};
49
+ CREATE TABLE {$this->getTable('bootic_product_data')}
50
+ (
51
+ `magento_product_id` INT(11) NOT NULL,
52
+ `bootic_product_id` INT(11),
53
+ `bootic_stock_id` varchar(50),
54
+ `bootic_status` SMALLINT(1),
55
+ `is_info_synced` TINYINT(1) unsigned NOT NULL default '0',
56
+ `is_stock_synced` TINYINT(1) unsigned NOT NULL default '0',
57
+ `update_time` TIMESTAMP,
58
+ `creation_time` TIMESTAMP,
59
+ `upload_failures` SMALLINT(1),
60
+ PRIMARY KEY (`magento_product_id`)
61
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
62
+
63
+ DROP TABLE IF EXISTS {$this->getTable('bootic_message')};
64
+ CREATE TABLE {$this->getTable('bootic_message')}
65
+ (
66
+ `magento_message_id` int(10) NOT NULL,
67
+ `bootic_message_id` int(10) NOT NULL,
68
+ PRIMARY KEY (`magento_message_id`)
69
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
70
+
71
+ DROP TABLE IF EXISTS {$this->getTable('bootic_order_data')};
72
+ CREATE TABLE {$this->getTable('bootic_order_data')}
73
+ (
74
+ `order_id` int(10) NOT NULL,
75
+ `bootic_order_id` int(10) NOT NULL,
76
+ `transactions` text NOT NULL,
77
+ `in_sync` SMALLINT(1) DEFAULT 0,
78
+ `last_status` varchar(10),
79
+ `updated_at` timestamp NULL DEFAULT NULL ,
80
+ PRIMARY KEY (`order_id`),
81
+ UNIQUE KEY `UNQ_BOOTIC_ORDER_ID` (`bootic_order_id`)
82
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
83
+
84
+ ");
85
+
86
+ $installer->endSetup();
app/design/adminhtml/default/default/layout/bootic.xml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <!--
3
+ /**
4
+ * @copyright Copyright (c) 2012 by Bootic.
5
+ */
6
+ -->
7
+
8
+ <layout>
9
+
10
+ <bootic_adminhtml_connect_index>
11
+ <update handle="editor"/>
12
+ <reference name="content">
13
+ <block type="bootic/adminhtml_connect_profile" name="bootic_connect" />
14
+ </reference>
15
+ </bootic_adminhtml_connect_index>
16
+
17
+ <bootic_adminhtml_storefront_index>
18
+ <block type="bootic/adminhtml_storefront_banner" name="bootic_storefront_banner" template="bootic/storefront/banners.phtml" />
19
+ <block type="bootic/adminhtml_storefront_preview" name="bootic_storefront_preview" template="bootic/storefront/preview.phtml" />
20
+ <reference name="content">
21
+ <block type="bootic/adminhtml_storefront_edit" name="bootic_storefront_edit" />
22
+ </reference>
23
+ <reference name="left">
24
+ <block type="bootic/adminhtml_storefront_edit_tabs" name="bootic_storefront_edit_tabs"/>
25
+ </reference>
26
+ <reference name="head">
27
+ <action method="addItem">
28
+ <type>js</type>
29
+ <name>bootic/prototype-color-picker/js/prototype_colorpicker.js</name>
30
+ </action>
31
+ <action method="addItem">
32
+ <type>js_css</type>
33
+ <name>bootic/prototype-color-picker/css/prototype_colorpicker.css</name>
34
+ </action>
35
+ </reference>
36
+ </bootic_adminhtml_storefront_index>
37
+
38
+ <bootic_adminhtml_storefront_new>
39
+ <reference name="content">
40
+ <block type="bootic/adminhtml_storefront_create" name="bootic_storefront_create"/>
41
+ </reference>
42
+ </bootic_adminhtml_storefront_new>
43
+
44
+ <bootic_adminhtml_catalog_index>
45
+ <reference name="messages">
46
+ <action method="addNotice" translate="message">
47
+ <message>Bootic only supports simple and configurable products. Products that are chidlren of configurable products cannot be uploaded individually.</message>
48
+ </action>
49
+ </reference>
50
+ <reference name="content">
51
+ <block type="bootic/adminhtml_catalog" name="bootic_catalog" template="bootic/bootic.phtml">
52
+ <block type="bootic/adminhtml_catalog_tabs" name="bootic_adminhtml_catalog_tabs"/>
53
+ </block>
54
+ </reference>
55
+ </bootic_adminhtml_catalog_index>
56
+
57
+ <bootic_adminhtml_catalog_category_index>
58
+ <reference name="content">
59
+ <block type="bootic/adminhtml_catalog_category" name="bootic_catalog_category" />
60
+ </reference>
61
+ </bootic_adminhtml_catalog_category_index>
62
+
63
+ <bootic_adminhtml_system_config_account_index>
64
+ <reference name="content">
65
+ <block type="bootic/adminhtml_system_config_account_create" name="bootic_account_create" />
66
+ </reference>
67
+ </bootic_adminhtml_system_config_account_index>
68
+
69
+ </layout>
app/design/adminhtml/default/default/template/bootic/adminhtml/system/config/button/cron.phtml ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Academic Free License (AFL 3.0)
8
+ * that is bundled with this package in the file LICENSE_AFL.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/afl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * DISCLAIMER
16
+ *
17
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
18
+ * versions in the future. If you wish to customize Magento for your
19
+ * needs please refer to http://www.magentocommerce.com for more information.
20
+ *
21
+ * @category design
22
+ * @package default_default
23
+ * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
24
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
25
+ */
26
+ ?>
27
+ <?php
28
+ /**
29
+ * @see Bootic_Bootic_Block_Adminhtml_System_Config_Button_Cron
30
+ */
31
+ ?>
32
+ <script type="text/javascript">
33
+ //<![CDATA[
34
+ function executeCron() {
35
+ var elem = $('<?php echo $this->getHtmlId() ?>-button');
36
+ var cronType = '<?php echo $this->getCronType() ?>';
37
+
38
+ new Ajax.Request('<?php echo $this->getAjaxUrl() ?>', {
39
+ onSuccess: function(response) {
40
+ var result = '<?php echo $this->__('An error occured') ?>';
41
+ try {
42
+ response = response.responseText.evalJSON();
43
+ if (true == response.success) {
44
+ switch(cronType.toLowerCase()) {
45
+ case 'orders':
46
+ result = "<?php echo $this->__('Job\'s done! Orders processed:') ?> " + response.orderCount;
47
+ break;
48
+
49
+ default:
50
+ result = "<?php echo $this->__('Job\'s done!') ?>";
51
+ }
52
+
53
+ elem.removeClassName('fail').addClassName('success')
54
+ } else {
55
+ elem.removeClassName('success').addClassName('fail')
56
+ }
57
+ } catch (e) {
58
+ elem.removeClassName('success').addClassName('fail')
59
+ }
60
+ elem.update(result);
61
+ }
62
+ });
63
+ }
64
+ //]]>
65
+ </script>
66
+ <button onclick="javascript:executeCron(); return false;" class="scalable" type="button" id="<?php echo $this->getHtmlId() ?>-button">
67
+ <span id="validation_result"><?php echo $this->escapeHtml($this->getButtonLabel()) ?></span>
68
+ </button>
app/design/adminhtml/default/default/template/bootic/adminhtml/system/config/createaccount.phtml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ ?>
6
+
7
+ <script type="text/javascript">
8
+ //<![CDATA[
9
+ function goToCreateAccountPage() {
10
+ window.location = '<?php echo $this->getRedirectUrl(); ?>';
11
+ }
12
+ //]]>
13
+ </script>
14
+
15
+ <button onclick="javascript:goToCreateAccountPage(); return false;" class="scalable add" type="button" id="<?php echo $this->getHtmlId() ?>">
16
+ <span id="validation_result"><?php echo $this->escapeHtml($this->getButtonLabel()) ?></span>
17
+ </button>
app/design/adminhtml/default/default/template/bootic/adminhtml/system/config/testapiconnection.phtml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ ?>
6
+
7
+ <?php if($this->getDisplayCreateAccount() == true): ?>
8
+ <script type="text/javascript">
9
+ //<![CDATA[
10
+ var accountCreateButtonHeading = '<tr class="system-fieldset-sub-head" id="row_bootic_account_heading_creation"><td colspan="5"><h4 id="bootic_account_heading_creation"> Create an account on Bootic</h4></td></tr>';
11
+ var accountCreateButtonField = '<tr id="row_bootic_account_create"><td class="label"><label for="bootic_account_create"> Create a new account</label></td><td class="value">';
12
+ accountCreateButtonField += '<button onclick="javascript:goToCreateAccountPage(); return false;" class="scalable add" type="button" id="bootic_account_create"><span>Create account</span></button></td><td class="scope-label"></td><td class=""></td></tr>';
13
+
14
+ $('bootic_account').down('tbody').insert({top: accountCreateButtonField});
15
+ $('bootic_account').down('tbody').insert({top: accountCreateButtonHeading});
16
+
17
+ $('bootic_account_heading_credentials').update('<?php echo $this->__('Or provide your Bootic credentials if you already have an account'); ?>');
18
+
19
+ function goToCreateAccountPage() {
20
+ window.location = '<?php echo $this->getRedirectUrl(); ?>';
21
+ }
22
+ //]]>
23
+ </script>
24
+ <?php endif; ?>
25
+
26
+ <script type="text/javascript">
27
+ //<![CDATA[
28
+ function testApiConnection() {
29
+ var elem = $('<?php echo $this->getHtmlId() ?>');
30
+
31
+ params = {
32
+ email: $('bootic_account_email').value,
33
+ password: $('bootic_account_password').value
34
+ };
35
+
36
+ new Ajax.Request('<?php echo $this->getAjaxUrl() ?>', {
37
+ parameters: params,
38
+ onSuccess: function(response) {
39
+ result = '<?php echo $this->__('Cannot connect to Bootic API') ?>';
40
+ try {
41
+ response = response.responseText;
42
+ if (response == 1) {
43
+ result = '<?php echo $this->__('Successfully connected to Bootic API') ?>';
44
+ elem.removeClassName('fail').addClassName('success')
45
+ } else {
46
+ elem.removeClassName('success').addClassName('fail')
47
+ }
48
+ } catch (e) {
49
+ elem.removeClassName('success').addClassName('fail')
50
+ }
51
+ $('validation_result').update(result);
52
+ }
53
+ });
54
+ }
55
+ //]]>
56
+ </script>
57
+ <button onclick="javascript:testApiConnection(); return false;" class="scalable" type="button" id="<?php echo $this->getHtmlId() ?>">
58
+ <span id="validation_result"><?php echo $this->escapeHtml($this->getButtonLabel()) ?></span>
59
+ </button>
app/design/adminhtml/default/default/template/bootic/bootic.phtml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ ?>
6
+
7
+ <div class="content-header">
8
+ <table cellspacing="0" class="grid-header">
9
+ <tr>
10
+ <td><h3><?php echo $this->__('Bootic - Manage your products') ?></h3></td>
11
+ <td class="a-right">
12
+ </td>
13
+ </tr>
14
+ </table>
15
+ </div>
16
+
17
+ <?php echo $this->getChildHtml('bootic_adminhtml_catalog_tabs'); ?>
18
+ <div id="bootic_catalog_tab_content"></div>
app/design/adminhtml/default/default/template/bootic/catalog/category.phtml ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ ?>
6
+
7
+ <?php $mapping = $this->getCategoryMapping(); ?>
8
+
9
+ <div class="content-header">
10
+ <?php echo $this->getHeaderHtml() ?>
11
+ <p class="form-buttons"><?php echo $this->getButtonsHtml('header') ?></p>
12
+ </div>
13
+ <form id="category_form" name="category_form" action="<?php echo Mage::helper('adminhtml')->getUrl('bootic/adminhtml_catalog_category/save'); ?>" method="POST">
14
+ <input id="form_key" name="form_key" type="hidden" value="<?php echo Mage::getSingleton('core/session')->getFormKey() ?>" />
15
+ <div class="grid">
16
+ <table class="data" cellspacing="0" width="100%">
17
+ <thead>
18
+ <tr class="headings">
19
+ <th class="a-left"><?php echo $this->__('Magento Category') ?></th>
20
+ <th class="a-left" width="70%"><?php echo $this->__('Bootic Category') ?></th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+
25
+ <?php $jsonBooticCategories = $this->getJsonFormattedBooticCategories(); ?>
26
+
27
+ <?php foreach($this->getMagentoCategories() as $category): ?>
28
+ <tr>
29
+ <td><?php echo $category['value'] ?></td>
30
+ <td align="center" class="bootic-category-container" style="position: relative;">
31
+ <input type="hidden" name="category[<?php echo $category['id'] ?>]" id="category-<?php echo $category['id'] ?>" class="bootic-category" value="<?php echo ($mapping[$category['id']]) ? $mapping[$category['id']] : '' ?>">
32
+ </td>
33
+ </tr>
34
+ <?php endforeach; ?>
35
+
36
+ <script type="text/javascript">
37
+ var booticCategories = '<?php print $jsonBooticCategories ?>'.evalJSON();
38
+ var elmts = $$('.bootic-category-container');
39
+
40
+ elmts.each(function (elmt) {
41
+ var catId = parseInt(elmt.down('input[type=hidden]').value),
42
+ firstSel = new Element('select'),
43
+ defaultOpt = new Element('option')
44
+ ;
45
+
46
+ defaultOpt.value = null;
47
+ defaultOpt.text = '-- Select a category --';
48
+
49
+ firstSel
50
+ .addClassName('first')
51
+ .setStyle({
52
+ float: 'left'
53
+ })
54
+ ;
55
+
56
+ firstSel.options.add(defaultOpt);
57
+
58
+ var secSel = new Element('select'),
59
+ secDefaultOpt = new Element('option');
60
+
61
+ secSel.addClassName('second');
62
+ secSel.options.add(secDefaultOpt);
63
+ secSel['disable']();
64
+
65
+ secDefaultOpt.value = null;
66
+ secDefaultOpt.text = '-- Select an optional sub-category --';
67
+
68
+ for (i in booticCategories) {
69
+
70
+ var opt = new Element('option');
71
+ opt.value = booticCategories[i].id;
72
+ opt.text = booticCategories[i].label;
73
+
74
+ var childrenCategories = booticCategories[i].children;
75
+
76
+ if (opt.value == catId || hasChildSelected(catId, childrenCategories)) {
77
+ opt.selected = true;
78
+
79
+ if (typeof (childrenCategories) != 'undefined') {
80
+ childrenCategories.each(function(cC) {
81
+ var secOpt = new Element('option');
82
+ secOpt.value = cC['id'];
83
+ secOpt.text = cC['label'];
84
+
85
+ if (secOpt.value == catId) {
86
+ secOpt.selected = true;
87
+ }
88
+
89
+ secSel.options.add(secOpt);
90
+ });
91
+
92
+ secSel['enable']();
93
+ }
94
+ }
95
+
96
+ firstSel.options.add(opt);
97
+ }
98
+
99
+ elmt.insert(firstSel);
100
+ elmt.insert(secSel);
101
+ });
102
+
103
+ elmts.invoke('observe', 'change', function(e) {
104
+ var t = e.target;
105
+
106
+ if (t.hasClassName('first')) {
107
+ updateChildren(e);
108
+
109
+ } else {
110
+ var elmt = t.up('td');
111
+
112
+ if (t.value != 'null') {
113
+ elmt.down('input[type=hidden]').value = t.value;
114
+ } else {
115
+ elmt.down('input[type=hidden]').value = elmt.down('.first').value;
116
+ }
117
+ }
118
+ });
119
+
120
+ function hasChildSelected(catId, categories)
121
+ {
122
+ if (typeof (categories) != 'undefined') {
123
+ for (i in categories) {
124
+ if (catId == categories[i].id) {
125
+ return true;
126
+ }
127
+ }
128
+ }
129
+
130
+ return false;
131
+ }
132
+
133
+ function updateChildren(e)
134
+ {
135
+ var target = e.target,
136
+ elmt = target.up('td'),
137
+ id = target.value,
138
+ secSel = new Element('select'),
139
+ defaultOpt = new Element('option');
140
+
141
+ var selectedCategory = id ? booticCategories[id] : null;
142
+
143
+ elmt.down('input[type=hidden]').value = id;
144
+ Element.remove(target.next('select'));
145
+
146
+ defaultOpt.value = null;
147
+ defaultOpt.text = '-- Select an optional sub-category --';
148
+
149
+ secSel.addClassName('second');
150
+ secSel.options.add(defaultOpt);
151
+ secSel['disable']();
152
+
153
+ if (selectedCategory != null && typeof (selectedCategory.children) != 'undefined') {
154
+ var childrenCategories = selectedCategory.children;
155
+
156
+ childrenCategories.each(function(cC) {
157
+ var secOpt = new Element('option');
158
+ secOpt.value = cC['id'];
159
+ secOpt.text = cC['label'];
160
+ secSel.options.add(secOpt);
161
+ });
162
+
163
+ secSel['enable']();
164
+ }
165
+
166
+ elmt.insert(secSel);
167
+ }
168
+
169
+ </script>
170
+ </tbody>
171
+ </table>
172
+ </div>
173
+ </form>
174
+ <script type="text/javascript">
175
+ editForm = new varienForm('category_form');
176
+ </script>
app/design/adminhtml/default/default/template/bootic/catalog/tab/general.phtml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ ?>
6
+
7
+ <?php echo $this->getChildHtml('bootic_last_errors'); ?>
8
+
9
+ <div style="display:none;width:400px;margin:auto auto;height:50px;text-align:center;font-weight:bold;" id="save_process_message"><?php echo $this->__('Saving data, please wait......') ?></div>
10
+ <div id="product_grid"><?php echo $this->getChildHtml('bootic_products'); ?></div>
app/design/adminhtml/default/default/template/bootic/log/error.phtml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ ?>
6
+
7
+ <?php $_errors = $this->getErrors(); ?>
8
+
9
+ <?php if(count($_errors) > 0): ?>
10
+ <div class="grid">
11
+ <table class="data" cellspacing="0" width="100%" >
12
+ <thead>
13
+ <tr class="headings">
14
+ <th class="a-center"><?php echo $this->__('Id') ?></th>
15
+ <th class="a-center"><?php echo $this->__('Product Id') ?></th>
16
+ <th class="a-center"><?php echo $this->__('Date') ?></th>
17
+ <th class="a-center"><?php echo $this->__('Error') ?></th>
18
+ <th class="a-center"><?php echo $this->__('Message') ?></th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <?php $i = 0 ?>
23
+ <?php foreach ($_errors as $error): ?>
24
+ <?php $color = ($error->getStatus() == 'error') ? 'red' : 'orange'; ?>
25
+ <?php $class = ($i % 2 == 0) ? 'event pointer' : 'pointer'; ?>
26
+ <tr class="<?php echo $class; ?>">
27
+ <td class="a-center"><?php echo $error->getId(); ?></td>
28
+ <td class="a-center"><?php echo $error->getProductId(); ?></td>
29
+ <td class="a-center"><?php echo $error->getDate(); ?></td>
30
+ <td><?php echo $error->getStatus(); ?></td>
31
+ <td style="color:<?php echo $color ?>;"><?php echo $error->getMessage(); ?></td>
32
+ </tr>
33
+ <?php $i++ ?>
34
+ <?php endforeach ?>
35
+ </tbody>
36
+ </table>
37
+ </div>
38
+ <?php endif; ?>
app/design/adminhtml/default/default/template/bootic/products.phtml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+ ?>
6
+
7
+ <?php echo $this->getGridParentHtml() ?>
app/design/adminhtml/default/default/template/bootic/storefront/banners.phtml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $banners = Mage::getSingleton('bootic/storefront')->getBanners();
3
+ $selectedBannerId = Mage::getSingleton('bootic/storefront')->getBanner();
4
+
5
+ $booticClient = Mage::helper('bootic')->getBootic();
6
+ ?>
7
+
8
+ <div class="banners-container">
9
+ <?php foreach($banners as $b): ?>
10
+ <div class="banner-holder <?php if($b['id'] == $selectedBannerId) echo ' selected' ?>" id="<?php echo $b['id'] ?>">
11
+ <img src="<?php echo $b['picture'] ?>" class="banner" width="240" />
12
+ </div>
13
+ <?php endforeach; ?>
14
+ <div style="clear: both;"></div>
15
+ </div>
16
+
17
+ <style type="text/css">
18
+ .banners-container{margin-bottom: 20px;}
19
+ .banner-holder{float: left;margin: 0 10px 10px 0;cursor: pointer;padding: 5px;width: 240px;height: 43px;}
20
+ .banner-holder:hover, .banner-holder.selected{border:2px solid green;padding: 3px;}
21
+ </style>
22
+
23
+ <script type="text/javascript">
24
+ var elmts = $$('.banner-holder');
25
+ elmts.invoke('observe', 'click', function(e) {
26
+ var selected = $$('.banner-holder.selected');
27
+ if (selected.length) {
28
+ selected.invoke('removeClassName','selected');
29
+ }
30
+ this.addClassName('selected');
31
+ $('storefront_banner').value = this.identify();
32
+
33
+ updatePreviewBanner(this);
34
+ });
35
+
36
+ function updatePreviewBanner(banner) {
37
+ var img = banner.down();
38
+ $('bannerPreview').src = img.src;
39
+ }
40
+
41
+ </script>
app/design/adminhtml/default/default/template/bootic/storefront/preview.phtml ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $opts = $this->getOptions();
3
+ ?>
4
+
5
+ <style type="text/css">
6
+ .banners-container{margin-bottom: 20px;}
7
+ .banner-holder{float: left;margin: 0 10px 10px 0;cursor: pointer;padding: 5px;width: 240px;height: 43px;}
8
+ .banner-holder:hover, .banner-holder.selected{border:2px solid green;padding: 3px;}
9
+ </style>
10
+
11
+ <script type="text/javascript">
12
+ document.observe("dom:loaded", function() {
13
+ var storeOptions = <?php echo json_encode($opts) ?>;
14
+ console.log(storeOptions);
15
+
16
+ var templates = storeOptions.templates;
17
+ var color = "#" + $('storefront_color_theme').value;
18
+ var bannerSrc = $$('.banners-container .selected img')[0].src;
19
+
20
+ var template = new Element('img');
21
+ template.setStyle({ position: 'absolute' });
22
+
23
+ var banner = new Element('img');
24
+ banner.src = bannerSrc;
25
+ banner.setStyle({ position: 'absolute', width: '500px', top: '55px'});
26
+ banner.setAttribute('id', 'bannerPreview');
27
+
28
+ var previewDiv = new Element('div');
29
+ previewDiv
30
+ .addClassName('previewDiv')
31
+ .setStyle({ backgroundColor: color, width: '500px', height: '600px', margin: '0 auto', position: 'relative' })
32
+ .insert({
33
+ top: template,
34
+ bottom: banner
35
+ })
36
+ ;
37
+ previewDiv.setAttribute('id', 'previewDiv');
38
+
39
+ templates.each(function (tmpl) {
40
+ if (tmpl.current_template == true) {
41
+ template.src = tmpl.preview;
42
+ $("storefront_preview_fieldset").insert({
43
+ top: previewDiv
44
+ });
45
+ }
46
+ });
47
+
48
+ // Now we track change event on template
49
+ // for color see Bootic_Bootic_Block_Adminhtml_Storefront_Edit
50
+ // for banner see banners.phtml
51
+ $('storefront_template').observe('change', function(event) {
52
+ var templateName = event.target.value;
53
+
54
+ templates.each(function (tmpl) {
55
+ if (tmpl.name == templateName) {
56
+ template.src = tmpl.preview;
57
+ }
58
+ });
59
+ });
60
+
61
+ });
62
+ </script>
app/etc/modules/Bootic_Bootic.xml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <!--
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * that is bundled with this package in the file LICENSE.txt.
10
+ * It is also available through the world-wide-web at this URL:
11
+ * http://opensource.org/licenses/osl-3.0.php
12
+ * If you did not receive a copy of the license and are unable to
13
+ * obtain it through the world-wide-web, please send an email
14
+ * to license@magentocommerce.com so we can send you a copy immediately.
15
+ *
16
+ * @category Bootic
17
+ * @package Bootic_Bootic
18
+ * @copyright Copyright (c) 2012 Bootic (http://www.bootic.com)
19
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
20
+ */
21
+ -->
22
+ <config>
23
+ <modules>
24
+ <Bootic_Bootic>
25
+ <active>true</active>
26
+ <codePool>community</codePool>
27
+ </Bootic_Bootic>
28
+ </modules>
29
+ </config>
js/bootic/prototype-color-picker/.DS_Store ADDED
Binary file
js/bootic/prototype-color-picker/css/prototype_colorpicker.css ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .colorpicker {
2
+ width: 356px;
3
+ height: 176px;
4
+ overflow: hidden;
5
+ position: absolute;
6
+ background: url(../images/colorpicker_background.png);
7
+ font-family: Helvetica, Arial, sans-serif;
8
+ color:#EAEAEA;
9
+ }
10
+ .colorpicker_color {
11
+ width: 150px;
12
+ height: 150px;
13
+ left: 14px;
14
+ top: 13px;
15
+ position: absolute;
16
+ background: #f00;
17
+ overflow: hidden;
18
+ cursor: crosshair;
19
+ }
20
+ .colorpicker_color div {
21
+ position: absolute;
22
+ top: 0;
23
+ left: 0;
24
+ width: 150px;
25
+ height: 150px;
26
+ background: url(../images/colorpicker_overlay.png);
27
+ }
28
+ .colorpicker_color div div {
29
+ position: absolute;
30
+ top: 0;
31
+ left: 0;
32
+ width: 11px;
33
+ height: 11px;
34
+ overflow: hidden;
35
+ background: url(../images/colorpicker_select.gif);
36
+ margin: -5px 0 0 -5px;
37
+ }
38
+ .colorpicker_hue {
39
+ position: absolute;
40
+ top: 13px;
41
+ left: 171px;
42
+ width: 35px;
43
+ height: 150px;
44
+ cursor: n-resize;
45
+ }
46
+ .colorpicker_hue div {
47
+ position: absolute;
48
+ width: 35px;
49
+ height: 9px;
50
+ overflow: hidden;
51
+ background: url(../images/colorpicker_indic.gif) left top;
52
+ margin: -4px 0 0 0;
53
+ left: 0px;
54
+ }
55
+ .colorpicker_new_color {
56
+ position: absolute;
57
+ width: 60px;
58
+ height: 30px;
59
+ left: 213px;
60
+ top: 13px;
61
+ background: #f00;
62
+ }
63
+ .colorpicker_current_color {
64
+ position: absolute;
65
+ width: 60px;
66
+ height: 30px;
67
+ left: 283px;
68
+ top: 13px;
69
+ background: #f00;
70
+ }
71
+ .colorpicker input {
72
+ background-color: transparent;
73
+ border: 1px solid transparent;
74
+ position: absolute;
75
+ font-size: 10px;
76
+ color:#EAEAEA;
77
+ top: 4px;
78
+ right: 11px;
79
+ text-align: right;
80
+ margin: 0;
81
+ padding: 0;
82
+ height: 11px;
83
+ }
84
+ .colorpicker_hex {
85
+ position: absolute;
86
+ width: 72px;
87
+ height: 22px;
88
+ background: url(../images/colorpicker_hex.png) top;
89
+ left: 212px;
90
+ top: 142px;
91
+ }
92
+ .colorpicker_hex input {
93
+ right: 6px;
94
+ }
95
+ .colorpicker_field {
96
+ height: 22px;
97
+ width: 62px;
98
+ background-position: top;
99
+ position: absolute;
100
+ }
101
+ .colorpicker_field span {
102
+ position: absolute;
103
+ width: 12px;
104
+ height: 22px;
105
+ overflow: hidden;
106
+ top: 0;
107
+ right: 0;
108
+ cursor: n-resize;
109
+ }
110
+ .colorpicker_rgb_r {
111
+ background-image: url(../images/colorpicker_rgb_r.png);
112
+ top: 52px;
113
+ left: 212px;
114
+ }
115
+ .colorpicker_rgb_g {
116
+ background-image: url(../images/colorpicker_rgb_g.png);
117
+ top: 82px;
118
+ left: 212px;
119
+ }
120
+ .colorpicker_rgb_b {
121
+ background-image: url(../images/colorpicker_rgb_b.png);
122
+ top: 112px;
123
+ left: 212px;
124
+ }
125
+ .colorpicker_hsb_h {
126
+ background-image: url(../images/colorpicker_hsb_h.png);
127
+ top: 52px;
128
+ left: 282px;
129
+ }
130
+ .colorpicker_hsb_s {
131
+ background-image: url(../images/colorpicker_hsb_s.png);
132
+ top: 82px;
133
+ left: 282px;
134
+ }
135
+ .colorpicker_hsb_b {
136
+ background-image: url(../images/colorpicker_hsb_b.png);
137
+ top: 112px;
138
+ left: 282px;
139
+ }
140
+ .colorpicker_submit {
141
+ position: absolute;
142
+ width: 22px;
143
+ height: 22px;
144
+ background: url(../images/colorpicker_submit.png) top;
145
+ left: 322px;
146
+ top: 142px;
147
+ overflow: hidden;
148
+ }
149
+ .colorpicker_focus {
150
+ background-position: center;
151
+ }
152
+ .colorpicker_hex.colorpicker_focus {
153
+ background-position: bottom;
154
+ }
155
+ .colorpicker_submit.colorpicker_focus {
156
+ background-position: bottom;
157
+ }
158
+ .colorpicker_slider {
159
+ background-position: bottom;
160
+ }
161
+ .colorpicker_extra{
162
+ position: absolute;
163
+ width: 22px;
164
+ height: 22px;
165
+ background: url(../images/colorpicker_extra.png) top;
166
+ left: 292px;
167
+ top: 142px;
168
+ overflow: hidden;
169
+ }
170
+ .colorpicker_extrafill {
171
+ position: absolute;
172
+ width: 140px;
173
+ height: 110px;
174
+ padding:5px;
175
+ background: url(../images/colorpicker_extra_background.png) top;
176
+ right: 6px;
177
+ bottom: 34px;
178
+ overflow: hidden;
179
+ color:#FFF;
180
+ }
181
+ .colorpicker_extra.colorpicker_focus {
182
+ background-position: bottom;
183
+ }
js/bootic/prototype-color-picker/images/blank.gif ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_background.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_extra.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_extra_background.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_hex.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_hsb_b.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_hsb_h.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_hsb_s.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_indic.gif ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_overlay.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_rgb_b.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_rgb_g.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_rgb_r.png ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_select.gif ADDED
Binary file
js/bootic/prototype-color-picker/images/colorpicker_submit.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_background.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_hex.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_hsb_b.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_hsb_h.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_hsb_s.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_indic.gif ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_rgb_b.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_rgb_g.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_rgb_r.png ADDED
Binary file
js/bootic/prototype-color-picker/images/custom_submit.png ADDED
Binary file
js/bootic/prototype-color-picker/images/select.png ADDED
Binary file
js/bootic/prototype-color-picker/images/select2.png ADDED
Binary file
js/bootic/prototype-color-picker/images/slider.png ADDED
Binary file
js/bootic/prototype-color-picker/index.html ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
3
+ <head profile="http://purl.org/uF/hAtom/0.1/ http://purl.org/uF/2008/03/">
4
+ <title>Javascript Color Picker built on Prototype</title>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <link rel="shortcut icon" href="/images/favicon.ico" />
7
+ <link rel="apple-touch-icon" href="/images/iphone.png" />
8
+ <link rel="stylesheet" href="http://jamesroberts.name/css/style.css" type="text/css" media="screen, projection" />
9
+
10
+ <link href="css/prototype_colorpicker.css" rel="stylesheet" type="text/css" />
11
+ <script src="http://jamesroberts.name/js/protoculous-1.1.js" type="text/javascript"></script>
12
+ <script src="js/prototype_colorpicker.js" type="text/javascript"></script>
13
+
14
+ <style>
15
+ .color-preview{
16
+ width:16px;
17
+ height:16px;
18
+ border:1px solid #000;
19
+ }
20
+ pre{
21
+ text-align:left;
22
+ padding:10px;
23
+ background:#EAEAEA;
24
+ border:1px solid #AAA;
25
+ width:800px;
26
+ overflow:auto;
27
+ }
28
+ </style>
29
+
30
+ </head>
31
+ <body id="dm-body">
32
+
33
+
34
+ <table width="800" class="centered">
35
+ <tr><td>
36
+
37
+ <h1>A prototype color picker class</h1>
38
+
39
+ Download the Zip file <a href="prototype_colorpicker.zip">here</a>.
40
+
41
+ <p>The most simple, no options specified:</p>
42
+ <table cellspacing="0" cellpadding="0">
43
+ <tr>
44
+ <td>Select Your Color: </td>
45
+ <td>
46
+ #<input type="text" id="color1" name="color1" value="008a20" size="6"></td>
47
+ </td>
48
+ </tr>
49
+ </table>
50
+ <pre>
51
+ var cp1 = new colorPicker('color1');
52
+ </pre>
53
+
54
+
55
+
56
+
57
+ <p>With a preview box:</p>
58
+ <table cellspacing="0" cellpadding="0">
59
+ <tr>
60
+ <td>Select Your Color: </td>
61
+ <td>
62
+ #<input type="text" id="color2" name="color2" value="008a20" size="6"></td>
63
+ </td>
64
+ <td>&nbsp;</td>
65
+ <td>
66
+ <div id="color2-preview" class="color-preview"></div>
67
+ </td>
68
+ </tr>
69
+ </table>
70
+ <pre>
71
+ var cp2 = new colorPicker('color2',{
72
+ color:'#008a20',
73
+ previewElement:'color2-preview'
74
+ });
75
+ </pre>
76
+
77
+
78
+
79
+ <p>Inline, no popup:</p>
80
+ <table cellspacing="0" cellpadding="0">
81
+ <tr>
82
+ <td valign="top":>Select Your Color: </td>
83
+ <td>&nbsp;</td>
84
+ <td>
85
+ #<input type="text" id="color3" name="color3" value="008a20" size="6">
86
+ </td>
87
+ </tr>
88
+ </table>
89
+ <pre>
90
+ var cp3 = new colorPicker('color3',{
91
+ color:'#008a20',
92
+ flat:true
93
+ });
94
+ </pre>
95
+
96
+
97
+ <p>With lots of other options:</p>
98
+ <table cellspacing="0" cellpadding="0">
99
+ <tr>
100
+ <td>Select Your Color: </td>
101
+ <td><span title="select a color" class="icon i-color-wheel" id="color4"></span></td>
102
+ <td>
103
+ #<input type="text" id="color4-input" name="color4" value="008a20" size="6">
104
+ </td>
105
+ <td>&nbsp;</td>
106
+ <td>
107
+ <div id="color4-preview" class="color-preview"></div>
108
+ </td>
109
+ </tr>
110
+ </table>
111
+ <pre>
112
+ var cp4 = new colorPicker('color4',{
113
+ color:'#008a20',
114
+ previewElement:'color4-preview',
115
+ inputElement:'color4-input',
116
+ eventName: 'click',
117
+ onShow:function(picker){
118
+ new Effect.Appear(picker.cp);
119
+ return false;
120
+ },
121
+ onHide:function(picker){
122
+ new Effect.Fade(picker.cp);
123
+ return false;
124
+ },
125
+ origColor:'#000000',
126
+ livePreview: true,
127
+ hideOnSubmit:false,
128
+ updateOnChange:false,
129
+ flat: false,
130
+ hasExtraInfo:true,
131
+ extraInfo:function(picker){
132
+ var colors = $A([
133
+ '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
134
+ '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
135
+ 'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
136
+ 'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
137
+ 'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
138
+ ]);
139
+
140
+ var div = Builder.node('DIV').setStyle({padding:'10px 12px'});
141
+ colors.each(function(color){
142
+ var div_inner = Builder.node('DIV').setStyle({backgroundColor:'#'+color,cursor:'pointer',width:'10px',height:'10px','float':'left',border:'2px solid #'+color,margin:'1px'});
143
+ div.insert(div_inner);
144
+ div_inner.observe('click',function(ev){picker.setColor(color);});
145
+ div_inner.observe('mouseover',function(ev){ev.element().setStyle({border:'2px solid #000'});});
146
+ div_inner.observe('mouseout',function(ev){ev.element().setStyle({border:'2px solid #'+color});});
147
+ });
148
+ picker.extraInfo.update(div);
149
+ }
150
+ });
151
+ </pre>
152
+
153
+
154
+ </td></tr>
155
+ </table>
156
+
157
+ <script>
158
+
159
+ var cp1 = new colorPicker('color1');
160
+
161
+ var cp2 = new colorPicker('color2',{
162
+ color:'#008a20',
163
+ previewElement:'color2-preview'
164
+ });
165
+
166
+ var cp3 = new colorPicker('color3',{
167
+ color:'#008a20',
168
+ flat:true
169
+ });
170
+
171
+
172
+ var cp4 = new colorPicker('color4',{
173
+ color:'#008a20',
174
+ previewElement:'color4-preview',
175
+ inputElement:'color4-input',
176
+ eventName: 'click',
177
+ onShow:function(picker){
178
+ new Effect.Appear(picker.cp);
179
+ return false;
180
+ },
181
+ onHide:function(picker){
182
+ new Effect.Fade(picker.cp);
183
+ return false;
184
+ },
185
+ origColor:'#000000',
186
+ livePreview: true,
187
+ hideOnSubmit:false,
188
+ updateOnChange:false,
189
+ flat: false,
190
+ hasExtraInfo:true,
191
+ extraInfo:function(picker){
192
+ var colors = $A([
193
+ '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
194
+ '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
195
+ 'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
196
+ 'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
197
+ 'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
198
+ ]);
199
+
200
+ var div = Builder.node('DIV').setStyle({padding:'10px 12px'});
201
+ colors.each(function(color){
202
+ var div_inner = Builder.node('DIV').setStyle({backgroundColor:'#'+color,cursor:'pointer',width:'10px',height:'10px','float':'left',border:'2px solid #'+color,margin:'1px'});
203
+ div.insert(div_inner);
204
+ div_inner.observe('click',function(ev){picker.setColor(color);});
205
+ div_inner.observe('mouseover',function(ev){ev.element().setStyle({border:'2px solid #000'});});
206
+ div_inner.observe('mouseout',function(ev){ev.element().setStyle({border:'2px solid #'+color});});
207
+ });
208
+ picker.extraInfo.update(div);
209
+ }
210
+ });
211
+
212
+ </script>
213
+
214
+ </body>
215
+ </html>
js/bootic/prototype-color-picker/js/prototype.1.6.js ADDED
@@ -0,0 +1,4874 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Prototype JavaScript framework, version 1.6.1
2
+ * (c) 2005-2009 Sam Stephenson
3
+ *
4
+ * Prototype is freely distributable under the terms of an MIT-style license.
5
+ * For details, see the Prototype web site: http://www.prototypejs.org/
6
+ *
7
+ *--------------------------------------------------------------------------*/
8
+
9
+ var Prototype = {
10
+ Version: '1.6.1',
11
+
12
+ Browser: (function(){
13
+ var ua = navigator.userAgent;
14
+ var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
15
+ return {
16
+ IE: !!window.attachEvent && !isOpera,
17
+ Opera: isOpera,
18
+ WebKit: ua.indexOf('AppleWebKit/') > -1,
19
+ Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
20
+ MobileSafari: /Apple.*Mobile.*Safari/.test(ua)
21
+ }
22
+ })(),
23
+
24
+ BrowserFeatures: {
25
+ XPath: !!document.evaluate,
26
+ SelectorsAPI: !!document.querySelector,
27
+ ElementExtensions: (function() {
28
+ var constructor = window.Element || window.HTMLElement;
29
+ return !!(constructor && constructor.prototype);
30
+ })(),
31
+ SpecificElementExtensions: (function() {
32
+ if (typeof window.HTMLDivElement !== 'undefined')
33
+ return true;
34
+
35
+ var div = document.createElement('div');
36
+ var form = document.createElement('form');
37
+ var isSupported = false;
38
+
39
+ if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
40
+ isSupported = true;
41
+ }
42
+
43
+ div = form = null;
44
+
45
+ return isSupported;
46
+ })()
47
+ },
48
+
49
+ ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
50
+ JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
51
+
52
+ emptyFunction: function() { },
53
+ K: function(x) { return x }
54
+ };
55
+
56
+ if (Prototype.Browser.MobileSafari)
57
+ Prototype.BrowserFeatures.SpecificElementExtensions = false;
58
+
59
+
60
+ var Abstract = { };
61
+
62
+
63
+ var Try = {
64
+ these: function() {
65
+ var returnValue;
66
+
67
+ for (var i = 0, length = arguments.length; i < length; i++) {
68
+ var lambda = arguments[i];
69
+ try {
70
+ returnValue = lambda();
71
+ break;
72
+ } catch (e) { }
73
+ }
74
+
75
+ return returnValue;
76
+ }
77
+ };
78
+
79
+ /* Based on Alex Arnell's inheritance implementation. */
80
+
81
+ var Class = (function() {
82
+ function subclass() {};
83
+ function create() {
84
+ var parent = null, properties = $A(arguments);
85
+ if (Object.isFunction(properties[0]))
86
+ parent = properties.shift();
87
+
88
+ function klass() {
89
+ this.initialize.apply(this, arguments);
90
+ }
91
+
92
+ Object.extend(klass, Class.Methods);
93
+ klass.superclass = parent;
94
+ klass.subclasses = [];
95
+
96
+ if (parent) {
97
+ subclass.prototype = parent.prototype;
98
+ klass.prototype = new subclass;
99
+ parent.subclasses.push(klass);
100
+ }
101
+
102
+ for (var i = 0; i < properties.length; i++)
103
+ klass.addMethods(properties[i]);
104
+
105
+ if (!klass.prototype.initialize)
106
+ klass.prototype.initialize = Prototype.emptyFunction;
107
+
108
+ klass.prototype.constructor = klass;
109
+ return klass;
110
+ }
111
+
112
+ function addMethods(source) {
113
+ var ancestor = this.superclass && this.superclass.prototype;
114
+ var properties = Object.keys(source);
115
+
116
+ if (!Object.keys({ toString: true }).length) {
117
+ if (source.toString != Object.prototype.toString)
118
+ properties.push("toString");
119
+ if (source.valueOf != Object.prototype.valueOf)
120
+ properties.push("valueOf");
121
+ }
122
+
123
+ for (var i = 0, length = properties.length; i < length; i++) {
124
+ var property = properties[i], value = source[property];
125
+ if (ancestor && Object.isFunction(value) &&
126
+ value.argumentNames().first() == "$super") {
127
+ var method = value;
128
+ value = (function(m) {
129
+ return function() { return ancestor[m].apply(this, arguments); };
130
+ })(property).wrap(method);
131
+
132
+ value.valueOf = method.valueOf.bind(method);
133
+ value.toString = method.toString.bind(method);
134
+ }
135
+ this.prototype[property] = value;
136
+ }
137
+
138
+ return this;
139
+ }
140
+
141
+ return {
142
+ create: create,
143
+ Methods: {
144
+ addMethods: addMethods
145
+ }
146
+ };
147
+ })();
148
+ (function() {
149
+
150
+ var _toString = Object.prototype.toString;
151
+
152
+ function extend(destination, source) {
153
+ for (var property in source)
154
+ destination[property] = source[property];
155
+ return destination;
156
+ }
157
+
158
+ function inspect(object) {
159
+ try {
160
+ if (isUndefined(object)) return 'undefined';
161
+ if (object === null) return 'null';
162
+ return object.inspect ? object.inspect() : String(object);
163
+ } catch (e) {
164
+ if (e instanceof RangeError) return '...';
165
+ throw e;
166
+ }
167
+ }
168
+
169
+ function toJSON(object) {
170
+ var type = typeof object;
171
+ switch (type) {
172
+ case 'undefined':
173
+ case 'function':
174
+ case 'unknown': return;
175
+ case 'boolean': return object.toString();
176
+ }
177
+
178
+ if (object === null) return 'null';
179
+ if (object.toJSON) return object.toJSON();
180
+ if (isElement(object)) return;
181
+
182
+ var results = [];
183
+ for (var property in object) {
184
+ var value = toJSON(object[property]);
185
+ if (!isUndefined(value))
186
+ results.push(property.toJSON() + ': ' + value);
187
+ }
188
+
189
+ return '{' + results.join(', ') + '}';
190
+ }
191
+
192
+ function toQueryString(object) {
193
+ return $H(object).toQueryString();
194
+ }
195
+
196
+ function toHTML(object) {
197
+ return object && object.toHTML ? object.toHTML() : String.interpret(object);
198
+ }
199
+
200
+ function keys(object) {
201
+ var results = [];
202
+ for (var property in object)
203
+ results.push(property);
204
+ return results;
205
+ }
206
+
207
+ function values(object) {
208
+ var results = [];
209
+ for (var property in object)
210
+ results.push(object[property]);
211
+ return results;
212
+ }
213
+
214
+ function clone(object) {
215
+ return extend({ }, object);
216
+ }
217
+
218
+ function isElement(object) {
219
+ return !!(object && object.nodeType == 1);
220
+ }
221
+
222
+ function isArray(object) {
223
+ return _toString.call(object) == "[object Array]";
224
+ }
225
+
226
+
227
+ function isHash(object) {
228
+ return object instanceof Hash;
229
+ }
230
+
231
+ function isFunction(object) {
232
+ return typeof object === "function";
233
+ }
234
+
235
+ function isString(object) {
236
+ return _toString.call(object) == "[object String]";
237
+ }
238
+
239
+ function isNumber(object) {
240
+ return _toString.call(object) == "[object Number]";
241
+ }
242
+
243
+ function isUndefined(object) {
244
+ return typeof object === "undefined";
245
+ }
246
+
247
+ extend(Object, {
248
+ extend: extend,
249
+ inspect: inspect,
250
+ toJSON: toJSON,
251
+ toQueryString: toQueryString,
252
+ toHTML: toHTML,
253
+ keys: keys,
254
+ values: values,
255
+ clone: clone,
256
+ isElement: isElement,
257
+ isArray: isArray,
258
+ isHash: isHash,
259
+ isFunction: isFunction,
260
+ isString: isString,
261
+ isNumber: isNumber,
262
+ isUndefined: isUndefined
263
+ });
264
+ })();
265
+ Object.extend(Function.prototype, (function() {
266
+ var slice = Array.prototype.slice;
267
+
268
+ function update(array, args) {
269
+ var arrayLength = array.length, length = args.length;
270
+ while (length--) array[arrayLength + length] = args[length];
271
+ return array;
272
+ }
273
+
274
+ function merge(array, args) {
275
+ array = slice.call(array, 0);
276
+ return update(array, args);
277
+ }
278
+
279
+ function argumentNames() {
280
+ var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
281
+ .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
282
+ .replace(/\s+/g, '').split(',');
283
+ return names.length == 1 && !names[0] ? [] : names;
284
+ }
285
+
286
+ function bind(context) {
287
+ if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
288
+ var __method = this, args = slice.call(arguments, 1);
289
+ return function() {
290
+ var a = merge(args, arguments);
291
+ return __method.apply(context, a);
292
+ }
293
+ }
294
+
295
+ function bindAsEventListener(context) {
296
+ var __method = this, args = slice.call(arguments, 1);
297
+ return function(event) {
298
+ var a = update([event || window.event], args);
299
+ return __method.apply(context, a);
300
+ }
301
+ }
302
+
303
+ function curry() {
304
+ if (!arguments.length) return this;
305
+ var __method = this, args = slice.call(arguments, 0);
306
+ return function() {
307
+ var a = merge(args, arguments);
308
+ return __method.apply(this, a);
309
+ }
310
+ }
311
+
312
+ function delay(timeout) {
313
+ var __method = this, args = slice.call(arguments, 1);
314
+ timeout = timeout * 1000
315
+ return window.setTimeout(function() {
316
+ return __method.apply(__method, args);
317
+ }, timeout);
318
+ }
319
+
320
+ function defer() {
321
+ var args = update([0.01], arguments);
322
+ return this.delay.apply(this, args);
323
+ }
324
+
325
+ function wrap(wrapper) {
326
+ var __method = this;
327
+ return function() {
328
+ var a = update([__method.bind(this)], arguments);
329
+ return wrapper.apply(this, a);
330
+ }
331
+ }
332
+
333
+ function methodize() {
334
+ if (this._methodized) return this._methodized;
335
+ var __method = this;
336
+ return this._methodized = function() {
337
+ var a = update([this], arguments);
338
+ return __method.apply(null, a);
339
+ };
340
+ }
341
+
342
+ return {
343
+ argumentNames: argumentNames,
344
+ bind: bind,
345
+ bindAsEventListener: bindAsEventListener,
346
+ curry: curry,
347
+ delay: delay,
348
+ defer: defer,
349
+ wrap: wrap,
350
+ methodize: methodize
351
+ }
352
+ })());
353
+
354
+
355
+ Date.prototype.toJSON = function() {
356
+ return '"' + this.getUTCFullYear() + '-' +
357
+ (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
358
+ this.getUTCDate().toPaddedString(2) + 'T' +
359
+ this.getUTCHours().toPaddedString(2) + ':' +
360
+ this.getUTCMinutes().toPaddedString(2) + ':' +
361
+ this.getUTCSeconds().toPaddedString(2) + 'Z"';
362
+ };
363
+
364
+
365
+ RegExp.prototype.match = RegExp.prototype.test;
366
+
367
+ RegExp.escape = function(str) {
368
+ return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
369
+ };
370
+ var PeriodicalExecuter = Class.create({
371
+ initialize: function(callback, frequency) {
372
+ this.callback = callback;
373
+ this.frequency = frequency;
374
+ this.currentlyExecuting = false;
375
+
376
+ this.registerCallback();
377
+ },
378
+
379
+ registerCallback: function() {
380
+ this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
381
+ },
382
+
383
+ execute: function() {
384
+ this.callback(this);
385
+ },
386
+
387
+ stop: function() {
388
+ if (!this.timer) return;
389
+ clearInterval(this.timer);
390
+ this.timer = null;
391
+ },
392
+
393
+ onTimerEvent: function() {
394
+ if (!this.currentlyExecuting) {
395
+ try {
396
+ this.currentlyExecuting = true;
397
+ this.execute();
398
+ this.currentlyExecuting = false;
399
+ } catch(e) {
400
+ this.currentlyExecuting = false;
401
+ throw e;
402
+ }
403
+ }
404
+ }
405
+ });
406
+ Object.extend(String, {
407
+ interpret: function(value) {
408
+ return value == null ? '' : String(value);
409
+ },
410
+ specialChar: {
411
+ '\b': '\\b',
412
+ '\t': '\\t',
413
+ '\n': '\\n',
414
+ '\f': '\\f',
415
+ '\r': '\\r',
416
+ '\\': '\\\\'
417
+ }
418
+ });
419
+
420
+ Object.extend(String.prototype, (function() {
421
+
422
+ function prepareReplacement(replacement) {
423
+ if (Object.isFunction(replacement)) return replacement;
424
+ var template = new Template(replacement);
425
+ return function(match) { return template.evaluate(match) };
426
+ }
427
+
428
+ function gsub(pattern, replacement) {
429
+ var result = '', source = this, match;
430
+ replacement = prepareReplacement(replacement);
431
+
432
+ if (Object.isString(pattern))
433
+ pattern = RegExp.escape(pattern);
434
+
435
+ if (!(pattern.length || pattern.source)) {
436
+ replacement = replacement('');
437
+ return replacement + source.split('').join(replacement) + replacement;
438
+ }
439
+
440
+ while (source.length > 0) {
441
+ if (match = source.match(pattern)) {
442
+ result += source.slice(0, match.index);
443
+ result += String.interpret(replacement(match));
444
+ source = source.slice(match.index + match[0].length);
445
+ } else {
446
+ result += source, source = '';
447
+ }
448
+ }
449
+ return result;
450
+ }
451
+
452
+ function sub(pattern, replacement, count) {
453
+ replacement = prepareReplacement(replacement);
454
+ count = Object.isUndefined(count) ? 1 : count;
455
+
456
+ return this.gsub(pattern, function(match) {
457
+ if (--count < 0) return match[0];
458
+ return replacement(match);
459
+ });
460
+ }
461
+
462
+ function scan(pattern, iterator) {
463
+ this.gsub(pattern, iterator);
464
+ return String(this);
465
+ }
466
+
467
+ function truncate(length, truncation) {
468
+ length = length || 30;
469
+ truncation = Object.isUndefined(truncation) ? '...' : truncation;
470
+ return this.length > length ?
471
+ this.slice(0, length - truncation.length) + truncation : String(this);
472
+ }
473
+
474
+ function strip() {
475
+ return this.replace(/^\s+/, '').replace(/\s+$/, '');
476
+ }
477
+
478
+ function stripTags() {
479
+ return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
480
+ }
481
+
482
+ function stripScripts() {
483
+ return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
484
+ }
485
+
486
+ function extractScripts() {
487
+ var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
488
+ var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
489
+ return (this.match(matchAll) || []).map(function(scriptTag) {
490
+ return (scriptTag.match(matchOne) || ['', ''])[1];
491
+ });
492
+ }
493
+
494
+ function evalScripts() {
495
+ return this.extractScripts().map(function(script) { return eval(script) });
496
+ }
497
+
498
+ function escapeHTML() {
499
+ return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
500
+ }
501
+
502
+ function unescapeHTML() {
503
+ return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
504
+ }
505
+
506
+
507
+ function toQueryParams(separator) {
508
+ var match = this.strip().match(/([^?#]*)(#.*)?$/);
509
+ if (!match) return { };
510
+
511
+ return match[1].split(separator || '&').inject({ }, function(hash, pair) {
512
+ if ((pair = pair.split('='))[0]) {
513
+ var key = decodeURIComponent(pair.shift());
514
+ var value = pair.length > 1 ? pair.join('=') : pair[0];
515
+ if (value != undefined) value = decodeURIComponent(value);
516
+
517
+ if (key in hash) {
518
+ if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
519
+ hash[key].push(value);
520
+ }
521
+ else hash[key] = value;
522
+ }
523
+ return hash;
524
+ });
525
+ }
526
+
527
+ function toArray() {
528
+ return this.split('');
529
+ }
530
+
531
+ function succ() {
532
+ return this.slice(0, this.length - 1) +
533
+ String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
534
+ }
535
+
536
+ function times(count) {
537
+ return count < 1 ? '' : new Array(count + 1).join(this);
538
+ }
539
+
540
+ function camelize() {
541
+ var parts = this.split('-'), len = parts.length;
542
+ if (len == 1) return parts[0];
543
+
544
+ var camelized = this.charAt(0) == '-'
545
+ ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
546
+ : parts[0];
547
+
548
+ for (var i = 1; i < len; i++)
549
+ camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
550
+
551
+ return camelized;
552
+ }
553
+
554
+ function capitalize() {
555
+ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
556
+ }
557
+
558
+ function underscore() {
559
+ return this.replace(/::/g, '/')
560
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
561
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
562
+ .replace(/-/g, '_')
563
+ .toLowerCase();
564
+ }
565
+
566
+ function dasherize() {
567
+ return this.replace(/_/g, '-');
568
+ }
569
+
570
+ function inspect(useDoubleQuotes) {
571
+ var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
572
+ if (character in String.specialChar) {
573
+ return String.specialChar[character];
574
+ }
575
+ return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
576
+ });
577
+ if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
578
+ return "'" + escapedString.replace(/'/g, '\\\'') + "'";
579
+ }
580
+
581
+ function toJSON() {
582
+ return this.inspect(true);
583
+ }
584
+
585
+ function unfilterJSON(filter) {
586
+ return this.replace(filter || Prototype.JSONFilter, '$1');
587
+ }
588
+
589
+ function isJSON() {
590
+ var str = this;
591
+ if (str.blank()) return false;
592
+ str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
593
+ return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
594
+ }
595
+
596
+ function evalJSON(sanitize) {
597
+ var json = this.unfilterJSON();
598
+ try {
599
+ if (!sanitize || json.isJSON()) return eval('(' + json + ')');
600
+ } catch (e) { }
601
+ throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
602
+ }
603
+
604
+ function include(pattern) {
605
+ return this.indexOf(pattern) > -1;
606
+ }
607
+
608
+ function startsWith(pattern) {
609
+ return this.indexOf(pattern) === 0;
610
+ }
611
+
612
+ function endsWith(pattern) {
613
+ var d = this.length - pattern.length;
614
+ return d >= 0 && this.lastIndexOf(pattern) === d;
615
+ }
616
+
617
+ function empty() {
618
+ return this == '';
619
+ }
620
+
621
+ function blank() {
622
+ return /^\s*$/.test(this);
623
+ }
624
+
625
+ function interpolate(object, pattern) {
626
+ return new Template(this, pattern).evaluate(object);
627
+ }
628
+
629
+ return {
630
+ gsub: gsub,
631
+ sub: sub,
632
+ scan: scan,
633
+ truncate: truncate,
634
+ strip: String.prototype.trim ? String.prototype.trim : strip,
635
+ stripTags: stripTags,
636
+ stripScripts: stripScripts,
637
+ extractScripts: extractScripts,
638
+ evalScripts: evalScripts,
639
+ escapeHTML: escapeHTML,
640
+ unescapeHTML: unescapeHTML,
641
+ toQueryParams: toQueryParams,
642
+ parseQuery: toQueryParams,
643
+ toArray: toArray,
644
+ succ: succ,
645
+ times: times,
646
+ camelize: camelize,
647
+ capitalize: capitalize,
648
+ underscore: underscore,
649
+ dasherize: dasherize,
650
+ inspect: inspect,
651
+ toJSON: toJSON,
652
+ unfilterJSON: unfilterJSON,
653
+ isJSON: isJSON,
654
+ evalJSON: evalJSON,
655
+ include: include,
656
+ startsWith: startsWith,
657
+ endsWith: endsWith,
658
+ empty: empty,
659
+ blank: blank,
660
+ interpolate: interpolate
661
+ };
662
+ })());
663
+
664
+ var Template = Class.create({
665
+ initialize: function(template, pattern) {
666
+ this.template = template.toString();
667
+ this.pattern = pattern || Template.Pattern;
668
+ },
669
+
670
+ evaluate: function(object) {
671
+ if (object && Object.isFunction(object.toTemplateReplacements))
672
+ object = object.toTemplateReplacements();
673
+
674
+ return this.template.gsub(this.pattern, function(match) {
675
+ if (object == null) return (match[1] + '');
676
+
677
+ var before = match[1] || '';
678
+ if (before == '\\') return match[2];
679
+
680
+ var ctx = object, expr = match[3];
681
+ var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
682
+ match = pattern.exec(expr);
683
+ if (match == null) return before;
684
+
685
+ while (match != null) {
686
+ var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
687
+ ctx = ctx[comp];
688
+ if (null == ctx || '' == match[3]) break;
689
+ expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
690
+ match = pattern.exec(expr);
691
+ }
692
+
693
+ return before + String.interpret(ctx);
694
+ });
695
+ }
696
+ });
697
+ Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
698
+
699
+ var $break = { };
700
+
701
+ var Enumerable = (function() {
702
+ function each(iterator, context) {
703
+ var index = 0;
704
+ try {
705
+ this._each(function(value) {
706
+ iterator.call(context, value, index++);
707
+ });
708
+ } catch (e) {
709
+ if (e != $break) throw e;
710
+ }
711
+ return this;
712
+ }
713
+
714
+ function eachSlice(number, iterator, context) {
715
+ var index = -number, slices = [], array = this.toArray();
716
+ if (number < 1) return array;
717
+ while ((index += number) < array.length)
718
+ slices.push(array.slice(index, index+number));
719
+ return slices.collect(iterator, context);
720
+ }
721
+
722
+ function all(iterator, context) {
723
+ iterator = iterator || Prototype.K;
724
+ var result = true;
725
+ this.each(function(value, index) {
726
+ result = result && !!iterator.call(context, value, index);
727
+ if (!result) throw $break;
728
+ });
729
+ return result;
730
+ }
731
+
732
+ function any(iterator, context) {
733
+ iterator = iterator || Prototype.K;
734
+ var result = false;
735
+ this.each(function(value, index) {
736
+ if (result = !!iterator.call(context, value, index))
737
+ throw $break;
738
+ });
739
+ return result;
740
+ }
741
+
742
+ function collect(iterator, context) {
743
+ iterator = iterator || Prototype.K;
744
+ var results = [];
745
+ this.each(function(value, index) {
746
+ results.push(iterator.call(context, value, index));
747
+ });
748
+ return results;
749
+ }
750
+
751
+ function detect(iterator, context) {
752
+ var result;
753
+ this.each(function(value, index) {
754
+ if (iterator.call(context, value, index)) {
755
+ result = value;
756
+ throw $break;
757
+ }
758
+ });
759
+ return result;
760
+ }
761
+
762
+ function findAll(iterator, context) {
763
+ var results = [];
764
+ this.each(function(value, index) {
765
+ if (iterator.call(context, value, index))
766
+ results.push(value);
767
+ });
768
+ return results;
769
+ }
770
+
771
+ function grep(filter, iterator, context) {
772
+ iterator = iterator || Prototype.K;
773
+ var results = [];
774
+
775
+ if (Object.isString(filter))
776
+ filter = new RegExp(RegExp.escape(filter));
777
+
778
+ this.each(function(value, index) {
779
+ if (filter.match(value))
780
+ results.push(iterator.call(context, value, index));
781
+ });
782
+ return results;
783
+ }
784
+
785
+ function include(object) {
786
+ if (Object.isFunction(this.indexOf))
787
+ if (this.indexOf(object) != -1) return true;
788
+
789
+ var found = false;
790
+ this.each(function(value) {
791
+ if (value == object) {
792
+ found = true;
793
+ throw $break;
794
+ }
795
+ });
796
+ return found;
797
+ }
798
+
799
+ function inGroupsOf(number, fillWith) {
800
+ fillWith = Object.isUndefined(fillWith) ? null : fillWith;
801
+ return this.eachSlice(number, function(slice) {
802
+ while(slice.length < number) slice.push(fillWith);
803
+ return slice;
804
+ });
805
+ }
806
+
807
+ function inject(memo, iterator, context) {
808
+ this.each(function(value, index) {
809
+ memo = iterator.call(context, memo, value, index);
810
+ });
811
+ return memo;
812
+ }
813
+
814
+ function invoke(method) {
815
+ var args = $A(arguments).slice(1);
816
+ return this.map(function(value) {
817
+ return value[method].apply(value, args);
818
+ });
819
+ }
820
+
821
+ function max(iterator, context) {
822
+ iterator = iterator || Prototype.K;
823
+ var result;
824
+ this.each(function(value, index) {
825
+ value = iterator.call(context, value, index);
826
+ if (result == null || value >= result)
827
+ result = value;
828
+ });
829
+ return result;
830
+ }
831
+
832
+ function min(iterator, context) {
833
+ iterator = iterator || Prototype.K;
834
+ var result;
835
+ this.each(function(value, index) {
836
+ value = iterator.call(context, value, index);
837
+ if (result == null || value < result)
838
+ result = value;
839
+ });
840
+ return result;
841
+ }
842
+
843
+ function partition(iterator, context) {
844
+ iterator = iterator || Prototype.K;
845
+ var trues = [], falses = [];
846
+ this.each(function(value, index) {
847
+ (iterator.call(context, value, index) ?
848
+ trues : falses).push(value);
849
+ });
850
+ return [trues, falses];
851
+ }
852
+
853
+ function pluck(property) {
854
+ var results = [];
855
+ this.each(function(value) {
856
+ results.push(value[property]);
857
+ });
858
+ return results;
859
+ }
860
+
861
+ function reject(iterator, context) {
862
+ var results = [];
863
+ this.each(function(value, index) {
864
+ if (!iterator.call(context, value, index))
865
+ results.push(value);
866
+ });
867
+ return results;
868
+ }
869
+
870
+ function sortBy(iterator, context) {
871
+ return this.map(function(value, index) {
872
+ return {
873
+ value: value,
874
+ criteria: iterator.call(context, value, index)
875
+ };
876
+ }).sort(function(left, right) {
877
+ var a = left.criteria, b = right.criteria;
878
+ return a < b ? -1 : a > b ? 1 : 0;
879
+ }).pluck('value');
880
+ }
881
+
882
+ function toArray() {
883
+ return this.map();
884
+ }
885
+
886
+ function zip() {
887
+ var iterator = Prototype.K, args = $A(arguments);
888
+ if (Object.isFunction(args.last()))
889
+ iterator = args.pop();
890
+
891
+ var collections = [this].concat(args).map($A);
892
+ return this.map(function(value, index) {
893
+ return iterator(collections.pluck(index));
894
+ });
895
+ }
896
+
897
+ function size() {
898
+ return this.toArray().length;
899
+ }
900
+
901
+ function inspect() {
902
+ return '#<Enumerable:' + this.toArray().inspect() + '>';
903
+ }
904
+
905
+
906
+
907
+
908
+
909
+
910
+
911
+
912
+
913
+ return {
914
+ each: each,
915
+ eachSlice: eachSlice,
916
+ all: all,
917
+ every: all,
918
+ any: any,
919
+ some: any,
920
+ collect: collect,
921
+ map: collect,
922
+ detect: detect,
923
+ findAll: findAll,
924
+ select: findAll,
925
+ filter: findAll,
926
+ grep: grep,
927
+ include: include,
928
+ member: include,
929
+ inGroupsOf: inGroupsOf,
930
+ inject: inject,
931
+ invoke: invoke,
932
+ max: max,
933
+ min: min,
934
+ partition: partition,
935
+ pluck: pluck,
936
+ reject: reject,
937
+ sortBy: sortBy,
938
+ toArray: toArray,
939
+ entries: toArray,
940
+ zip: zip,
941
+ size: size,
942
+ inspect: inspect,
943
+ find: detect
944
+ };
945
+ })();
946
+ function $A(iterable) {
947
+ if (!iterable) return [];
948
+ if ('toArray' in Object(iterable)) return iterable.toArray();
949
+ var length = iterable.length || 0, results = new Array(length);
950
+ while (length--) results[length] = iterable[length];
951
+ return results;
952
+ }
953
+
954
+ function $w(string) {
955
+ if (!Object.isString(string)) return [];
956
+ string = string.strip();
957
+ return string ? string.split(/\s+/) : [];
958
+ }
959
+
960
+ Array.from = $A;
961
+
962
+
963
+ (function() {
964
+ var arrayProto = Array.prototype,
965
+ slice = arrayProto.slice,
966
+ _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
967
+
968
+ function each(iterator) {
969
+ for (var i = 0, length = this.length; i < length; i++)
970
+ iterator(this[i]);
971
+ }
972
+ if (!_each) _each = each;
973
+
974
+ function clear() {
975
+ this.length = 0;
976
+ return this;
977
+ }
978
+
979
+ function first() {
980
+ return this[0];
981
+ }
982
+
983
+ function last() {
984
+ return this[this.length - 1];
985
+ }
986
+
987
+ function compact() {
988
+ return this.select(function(value) {
989
+ return value != null;
990
+ });
991
+ }
992
+
993
+ function flatten() {
994
+ return this.inject([], function(array, value) {
995
+ if (Object.isArray(value))
996
+ return array.concat(value.flatten());
997
+ array.push(value);
998
+ return array;
999
+ });
1000
+ }
1001
+
1002
+ function without() {
1003
+ var values = slice.call(arguments, 0);
1004
+ return this.select(function(value) {
1005
+ return !values.include(value);
1006
+ });
1007
+ }
1008
+
1009
+ function reverse(inline) {
1010
+ return (inline !== false ? this : this.toArray())._reverse();
1011
+ }
1012
+
1013
+ function uniq(sorted) {
1014
+ return this.inject([], function(array, value, index) {
1015
+ if (0 == index || (sorted ? array.last() != value : !array.include(value)))
1016
+ array.push(value);
1017
+ return array;
1018
+ });
1019
+ }
1020
+
1021
+ function intersect(array) {
1022
+ return this.uniq().findAll(function(item) {
1023
+ return array.detect(function(value) { return item === value });
1024
+ });
1025
+ }
1026
+
1027
+
1028
+ function clone() {
1029
+ return slice.call(this, 0);
1030
+ }
1031
+
1032
+ function size() {
1033
+ return this.length;
1034
+ }
1035
+
1036
+ function inspect() {
1037
+ return '[' + this.map(Object.inspect).join(', ') + ']';
1038
+ }
1039
+
1040
+ function toJSON() {
1041
+ var results = [];
1042
+ this.each(function(object) {
1043
+ var value = Object.toJSON(object);
1044
+ if (!Object.isUndefined(value)) results.push(value);
1045
+ });
1046
+ return '[' + results.join(', ') + ']';
1047
+ }
1048
+
1049
+ function indexOf(item, i) {
1050
+ i || (i = 0);
1051
+ var length = this.length;
1052
+ if (i < 0) i = length + i;
1053
+ for (; i < length; i++)
1054
+ if (this[i] === item) return i;
1055
+ return -1;
1056
+ }
1057
+
1058
+ function lastIndexOf(item, i) {
1059
+ i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
1060
+ var n = this.slice(0, i).reverse().indexOf(item);
1061
+ return (n < 0) ? n : i - n - 1;
1062
+ }
1063
+
1064
+ function concat() {
1065
+ var array = slice.call(this, 0), item;
1066
+ for (var i = 0, length = arguments.length; i < length; i++) {
1067
+ item = arguments[i];
1068
+ if (Object.isArray(item) && !('callee' in item)) {
1069
+ for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
1070
+ array.push(item[j]);
1071
+ } else {
1072
+ array.push(item);
1073
+ }
1074
+ }
1075
+ return array;
1076
+ }
1077
+
1078
+ Object.extend(arrayProto, Enumerable);
1079
+
1080
+ if (!arrayProto._reverse)
1081
+ arrayProto._reverse = arrayProto.reverse;
1082
+
1083
+ Object.extend(arrayProto, {
1084
+ _each: _each,
1085
+ clear: clear,
1086
+ first: first,
1087
+ last: last,
1088
+ compact: compact,
1089
+ flatten: flatten,
1090
+ without: without,
1091
+ reverse: reverse,
1092
+ uniq: uniq,
1093
+ intersect: intersect,
1094
+ clone: clone,
1095
+ toArray: clone,
1096
+ size: size,
1097
+ inspect: inspect,
1098
+ toJSON: toJSON
1099
+ });
1100
+
1101
+ var CONCAT_ARGUMENTS_BUGGY = (function() {
1102
+ return [].concat(arguments)[0][0] !== 1;
1103
+ })(1,2)
1104
+
1105
+ if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
1106
+
1107
+ if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
1108
+ if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
1109
+ })();
1110
+ function $H(object) {
1111
+ return new Hash(object);
1112
+ };
1113
+
1114
+ var Hash = Class.create(Enumerable, (function() {
1115
+ function initialize(object) {
1116
+ this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
1117
+ }
1118
+
1119
+ function _each(iterator) {
1120
+ for (var key in this._object) {
1121
+ var value = this._object[key], pair = [key, value];
1122
+ pair.key = key;
1123
+ pair.value = value;
1124
+ iterator(pair);
1125
+ }
1126
+ }
1127
+
1128
+ function set(key, value) {
1129
+ return this._object[key] = value;
1130
+ }
1131
+
1132
+ function get(key) {
1133
+ if (this._object[key] !== Object.prototype[key])
1134
+ return this._object[key];
1135
+ }
1136
+
1137
+ function unset(key) {
1138
+ var value = this._object[key];
1139
+ delete this._object[key];
1140
+ return value;
1141
+ }
1142
+
1143
+ function toObject() {
1144
+ return Object.clone(this._object);
1145
+ }
1146
+
1147
+ function keys() {
1148
+ return this.pluck('key');
1149
+ }
1150
+
1151
+ function values() {
1152
+ return this.pluck('value');
1153
+ }
1154
+
1155
+ function index(value) {
1156
+ var match = this.detect(function(pair) {
1157
+ return pair.value === value;
1158
+ });
1159
+ return match && match.key;
1160
+ }
1161
+
1162
+ function merge(object) {
1163
+ return this.clone().update(object);
1164
+ }
1165
+
1166
+ function update(object) {
1167
+ return new Hash(object).inject(this, function(result, pair) {
1168
+ result.set(pair.key, pair.value);
1169
+ return result;
1170
+ });
1171
+ }
1172
+
1173
+ function toQueryPair(key, value) {
1174
+ if (Object.isUndefined(value)) return key;
1175
+ return key + '=' + encodeURIComponent(String.interpret(value));
1176
+ }
1177
+
1178
+ function toQueryString() {
1179
+ return this.inject([], function(results, pair) {
1180
+ var key = encodeURIComponent(pair.key), values = pair.value;
1181
+
1182
+ if (values && typeof values == 'object') {
1183
+ if (Object.isArray(values))
1184
+ return results.concat(values.map(toQueryPair.curry(key)));
1185
+ } else results.push(toQueryPair(key, values));
1186
+ return results;
1187
+ }).join('&');
1188
+ }
1189
+
1190
+ function inspect() {
1191
+ return '#<Hash:{' + this.map(function(pair) {
1192
+ return pair.map(Object.inspect).join(': ');
1193
+ }).join(', ') + '}>';
1194
+ }
1195
+
1196
+ function toJSON() {
1197
+ return Object.toJSON(this.toObject());
1198
+ }
1199
+
1200
+ function clone() {
1201
+ return new Hash(this);
1202
+ }
1203
+
1204
+ return {
1205
+ initialize: initialize,
1206
+ _each: _each,
1207
+ set: set,
1208
+ get: get,
1209
+ unset: unset,
1210
+ toObject: toObject,
1211
+ toTemplateReplacements: toObject,
1212
+ keys: keys,
1213
+ values: values,
1214
+ index: index,
1215
+ merge: merge,
1216
+ update: update,
1217
+ toQueryString: toQueryString,
1218
+ inspect: inspect,
1219
+ toJSON: toJSON,
1220
+ clone: clone
1221
+ };
1222
+ })());
1223
+
1224
+ Hash.from = $H;
1225
+ Object.extend(Number.prototype, (function() {
1226
+ function toColorPart() {
1227
+ return this.toPaddedString(2, 16);
1228
+ }
1229
+
1230
+ function succ() {
1231
+ return this + 1;
1232
+ }
1233
+
1234
+ function times(iterator, context) {
1235
+ $R(0, this, true).each(iterator, context);
1236
+ return this;
1237
+ }
1238
+
1239
+ function toPaddedString(length, radix) {
1240
+ var string = this.toString(radix || 10);
1241
+ return '0'.times(length - string.length) + string;
1242
+ }
1243
+
1244
+ function toJSON() {
1245
+ return isFinite(this) ? this.toString() : 'null';
1246
+ }
1247
+
1248
+ function abs() {
1249
+ return Math.abs(this);
1250
+ }
1251
+
1252
+ function round() {
1253
+ return Math.round(this);
1254
+ }
1255
+
1256
+ function ceil() {
1257
+ return Math.ceil(this);
1258
+ }
1259
+
1260
+ function floor() {
1261
+ return Math.floor(this);
1262
+ }
1263
+
1264
+ return {
1265
+ toColorPart: toColorPart,
1266
+ succ: succ,
1267
+ times: times,
1268
+ toPaddedString: toPaddedString,
1269
+ toJSON: toJSON,
1270
+ abs: abs,
1271
+ round: round,
1272
+ ceil: ceil,
1273
+ floor: floor
1274
+ };
1275
+ })());
1276
+
1277
+ function $R(start, end, exclusive) {
1278
+ return new ObjectRange(start, end, exclusive);
1279
+ }
1280
+
1281
+ var ObjectRange = Class.create(Enumerable, (function() {
1282
+ function initialize(start, end, exclusive) {
1283
+ this.start = start;
1284
+ this.end = end;
1285
+ this.exclusive = exclusive;
1286
+ }
1287
+
1288
+ function _each(iterator) {
1289
+ var value = this.start;
1290
+ while (this.include(value)) {
1291
+ iterator(value);
1292
+ value = value.succ();
1293
+ }
1294
+ }
1295
+
1296
+ function include(value) {
1297
+ if (value < this.start)
1298
+ return false;
1299
+ if (this.exclusive)
1300
+ return value < this.end;
1301
+ return value <= this.end;
1302
+ }
1303
+
1304
+ return {
1305
+ initialize: initialize,
1306
+ _each: _each,
1307
+ include: include
1308
+ };
1309
+ })());
1310
+
1311
+
1312
+
1313
+ var Ajax = {
1314
+ getTransport: function() {
1315
+ return Try.these(
1316
+ function() {return new XMLHttpRequest()},
1317
+ function() {return new ActiveXObject('Msxml2.XMLHTTP')},
1318
+ function() {return new ActiveXObject('Microsoft.XMLHTTP')}
1319
+ ) || false;
1320
+ },
1321
+
1322
+ activeRequestCount: 0
1323
+ };
1324
+
1325
+ Ajax.Responders = {
1326
+ responders: [],
1327
+
1328
+ _each: function(iterator) {
1329
+ this.responders._each(iterator);
1330
+ },
1331
+
1332
+ register: function(responder) {
1333
+ if (!this.include(responder))
1334
+ this.responders.push(responder);
1335
+ },
1336
+
1337
+ unregister: function(responder) {
1338
+ this.responders = this.responders.without(responder);
1339
+ },
1340
+
1341
+ dispatch: function(callback, request, transport, json) {
1342
+ this.each(function(responder) {
1343
+ if (Object.isFunction(responder[callback])) {
1344
+ try {
1345
+ responder[callback].apply(responder, [request, transport, json]);
1346
+ } catch (e) { }
1347
+ }
1348
+ });
1349
+ }
1350
+ };
1351
+
1352
+ Object.extend(Ajax.Responders, Enumerable);
1353
+
1354
+ Ajax.Responders.register({
1355
+ onCreate: function() { Ajax.activeRequestCount++ },
1356
+ onComplete: function() { Ajax.activeRequestCount-- }
1357
+ });
1358
+ Ajax.Base = Class.create({
1359
+ initialize: function(options) {
1360
+ this.options = {
1361
+ method: 'post',
1362
+ asynchronous: true,
1363
+ contentType: 'application/x-www-form-urlencoded',
1364
+ encoding: 'UTF-8',
1365
+ parameters: '',
1366
+ evalJSON: true,
1367
+ evalJS: true
1368
+ };
1369
+ Object.extend(this.options, options || { });
1370
+
1371
+ this.options.method = this.options.method.toLowerCase();
1372
+
1373
+ if (Object.isString(this.options.parameters))
1374
+ this.options.parameters = this.options.parameters.toQueryParams();
1375
+ else if (Object.isHash(this.options.parameters))
1376
+ this.options.parameters = this.options.parameters.toObject();
1377
+ }
1378
+ });
1379
+ Ajax.Request = Class.create(Ajax.Base, {
1380
+ _complete: false,
1381
+
1382
+ initialize: function($super, url, options) {
1383
+ $super(options);
1384
+ this.transport = Ajax.getTransport();
1385
+ this.request(url);
1386
+ },
1387
+
1388
+ request: function(url) {
1389
+ this.url = url;
1390
+ this.method = this.options.method;
1391
+ var params = Object.clone(this.options.parameters);
1392
+
1393
+ if (!['get', 'post'].include(this.method)) {
1394
+ params['_method'] = this.method;
1395
+ this.method = 'post';
1396
+ }
1397
+
1398
+ this.parameters = params;
1399
+
1400
+ if (params = Object.toQueryString(params)) {
1401
+ if (this.method == 'get')
1402
+ this.url += (this.url.include('?') ? '&' : '?') + params;
1403
+ else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1404
+ params += '&_=';
1405
+ }
1406
+
1407
+ try {
1408
+ var response = new Ajax.Response(this);
1409
+ if (this.options.onCreate) this.options.onCreate(response);
1410
+ Ajax.Responders.dispatch('onCreate', this, response);
1411
+
1412
+ this.transport.open(this.method.toUpperCase(), this.url,
1413
+ this.options.asynchronous);
1414
+
1415
+ if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
1416
+
1417
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
1418
+ this.setRequestHeaders();
1419
+
1420
+ this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1421
+ this.transport.send(this.body);
1422
+
1423
+ /* Force Firefox to handle ready state 4 for synchronous requests */
1424
+ if (!this.options.asynchronous && this.transport.overrideMimeType)
1425
+ this.onStateChange();
1426
+
1427
+ }
1428
+ catch (e) {
1429
+ this.dispatchException(e);
1430
+ }
1431
+ },
1432
+
1433
+ onStateChange: function() {
1434
+ var readyState = this.transport.readyState;
1435
+ if (readyState > 1 && !((readyState == 4) && this._complete))
1436
+ this.respondToReadyState(this.transport.readyState);
1437
+ },
1438
+
1439
+ setRequestHeaders: function() {
1440
+ var headers = {
1441
+ 'X-Requested-With': 'XMLHttpRequest',
1442
+ 'X-Prototype-Version': Prototype.Version,
1443
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1444
+ };
1445
+
1446
+ if (this.method == 'post') {
1447
+ headers['Content-type'] = this.options.contentType +
1448
+ (this.options.encoding ? '; charset=' + this.options.encoding : '');
1449
+
1450
+ /* Force "Connection: close" for older Mozilla browsers to work
1451
+ * around a bug where XMLHttpRequest sends an incorrect
1452
+ * Content-length header. See Mozilla Bugzilla #246651.
1453
+ */
1454
+ if (this.transport.overrideMimeType &&
1455
+ (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1456
+ headers['Connection'] = 'close';
1457
+ }
1458
+
1459
+ if (typeof this.options.requestHeaders == 'object') {
1460
+ var extras = this.options.requestHeaders;
1461
+
1462
+ if (Object.isFunction(extras.push))
1463
+ for (var i = 0, length = extras.length; i < length; i += 2)
1464
+ headers[extras[i]] = extras[i+1];
1465
+ else
1466
+ $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1467
+ }
1468
+
1469
+ for (var name in headers)
1470
+ this.transport.setRequestHeader(name, headers[name]);
1471
+ },
1472
+
1473
+ success: function() {
1474
+ var status = this.getStatus();
1475
+ return !status || (status >= 200 && status < 300);
1476
+ },
1477
+
1478
+ getStatus: function() {
1479
+ try {
1480
+ return this.transport.status || 0;
1481
+ } catch (e) { return 0 }
1482
+ },
1483
+
1484
+ respondToReadyState: function(readyState) {
1485
+ var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
1486
+
1487
+ if (state == 'Complete') {
1488
+ try {
1489
+ this._complete = true;
1490
+ (this.options['on' + response.status]
1491
+ || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1492
+ || Prototype.emptyFunction)(response, response.headerJSON);
1493
+ } catch (e) {
1494
+ this.dispatchException(e);
1495
+ }
1496
+
1497
+ var contentType = response.getHeader('Content-type');
1498
+ if (this.options.evalJS == 'force'
1499
+ || (this.options.evalJS && this.isSameOrigin() && contentType
1500
+ && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1501
+ this.evalResponse();
1502
+ }
1503
+
1504
+ try {
1505
+ (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
1506
+ Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
1507
+ } catch (e) {
1508
+ this.dispatchException(e);
1509
+ }
1510
+
1511
+ if (state == 'Complete') {
1512
+ this.transport.onreadystatechange = Prototype.emptyFunction;
1513
+ }
1514
+ },
1515
+
1516
+ isSameOrigin: function() {
1517
+ var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
1518
+ return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
1519
+ protocol: location.protocol,
1520
+ domain: document.domain,
1521
+ port: location.port ? ':' + location.port : ''
1522
+ }));
1523
+ },
1524
+
1525
+ getHeader: function(name) {
1526
+ try {
1527
+ return this.transport.getResponseHeader(name) || null;
1528
+ } catch (e) { return null; }
1529
+ },
1530
+
1531
+ evalResponse: function() {
1532
+ try {
1533
+ return eval((this.transport.responseText || '').unfilterJSON());
1534
+ } catch (e) {
1535
+ this.dispatchException(e);
1536
+ }
1537
+ },
1538
+
1539
+ dispatchException: function(exception) {
1540
+ (this.options.onException || Prototype.emptyFunction)(this, exception);
1541
+ Ajax.Responders.dispatch('onException', this, exception);
1542
+ }
1543
+ });
1544
+
1545
+ Ajax.Request.Events =
1546
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1547
+
1548
+
1549
+
1550
+
1551
+
1552
+
1553
+
1554
+
1555
+ Ajax.Response = Class.create({
1556
+ initialize: function(request){
1557
+ this.request = request;
1558
+ var transport = this.transport = request.transport,
1559
+ readyState = this.readyState = transport.readyState;
1560
+
1561
+ if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
1562
+ this.status = this.getStatus();
1563
+ this.statusText = this.getStatusText();
1564
+ this.responseText = String.interpret(transport.responseText);
1565
+ this.headerJSON = this._getHeaderJSON();
1566
+ }
1567
+
1568
+ if(readyState == 4) {
1569
+ var xml = transport.responseXML;
1570
+ this.responseXML = Object.isUndefined(xml) ? null : xml;
1571
+ this.responseJSON = this._getResponseJSON();
1572
+ }
1573
+ },
1574
+
1575
+ status: 0,
1576
+
1577
+ statusText: '',
1578
+
1579
+ getStatus: Ajax.Request.prototype.getStatus,
1580
+
1581
+ getStatusText: function() {
1582
+ try {
1583
+ return this.transport.statusText || '';
1584
+ } catch (e) { return '' }
1585
+ },
1586
+
1587
+ getHeader: Ajax.Request.prototype.getHeader,
1588
+
1589
+ getAllHeaders: function() {
1590
+ try {
1591
+ return this.getAllResponseHeaders();
1592
+ } catch (e) { return null }
1593
+ },
1594
+
1595
+ getResponseHeader: function(name) {
1596
+ return this.transport.getResponseHeader(name);
1597
+ },
1598
+
1599
+ getAllResponseHeaders: function() {
1600
+ return this.transport.getAllResponseHeaders();
1601
+ },
1602
+
1603
+ _getHeaderJSON: function() {
1604
+ var json = this.getHeader('X-JSON');
1605
+ if (!json) return null;
1606
+ json = decodeURIComponent(escape(json));
1607
+ try {
1608
+ return json.evalJSON(this.request.options.sanitizeJSON ||
1609
+ !this.request.isSameOrigin());
1610
+ } catch (e) {
1611
+ this.request.dispatchException(e);
1612
+ }
1613
+ },
1614
+
1615
+ _getResponseJSON: function() {
1616
+ var options = this.request.options;
1617
+ if (!options.evalJSON || (options.evalJSON != 'force' &&
1618
+ !(this.getHeader('Content-type') || '').include('application/json')) ||
1619
+ this.responseText.blank())
1620
+ return null;
1621
+ try {
1622
+ return this.responseText.evalJSON(options.sanitizeJSON ||
1623
+ !this.request.isSameOrigin());
1624
+ } catch (e) {
1625
+ this.request.dispatchException(e);
1626
+ }
1627
+ }
1628
+ });
1629
+
1630
+ Ajax.Updater = Class.create(Ajax.Request, {
1631
+ initialize: function($super, container, url, options) {
1632
+ this.container = {
1633
+ success: (container.success || container),
1634
+ failure: (container.failure || (container.success ? null : container))
1635
+ };
1636
+
1637
+ options = Object.clone(options);
1638
+ var onComplete = options.onComplete;
1639
+ options.onComplete = (function(response, json) {
1640
+ this.updateContent(response.responseText);
1641
+ if (Object.isFunction(onComplete)) onComplete(response, json);
1642
+ }).bind(this);
1643
+
1644
+ $super(url, options);
1645
+ },
1646
+
1647
+ updateContent: function(responseText) {
1648
+ var receiver = this.container[this.success() ? 'success' : 'failure'],
1649
+ options = this.options;
1650
+
1651
+ if (!options.evalScripts) responseText = responseText.stripScripts();
1652
+
1653
+ if (receiver = $(receiver)) {
1654
+ if (options.insertion) {
1655
+ if (Object.isString(options.insertion)) {
1656
+ var insertion = { }; insertion[options.insertion] = responseText;
1657
+ receiver.insert(insertion);
1658
+ }
1659
+ else options.insertion(receiver, responseText);
1660
+ }
1661
+ else receiver.update(responseText);
1662
+ }
1663
+ }
1664
+ });
1665
+
1666
+ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
1667
+ initialize: function($super, container, url, options) {
1668
+ $super(options);
1669
+ this.onComplete = this.options.onComplete;
1670
+
1671
+ this.frequency = (this.options.frequency || 2);
1672
+ this.decay = (this.options.decay || 1);
1673
+
1674
+ this.updater = { };
1675
+ this.container = container;
1676
+ this.url = url;
1677
+
1678
+ this.start();
1679
+ },
1680
+
1681
+ start: function() {
1682
+ this.options.onComplete = this.updateComplete.bind(this);
1683
+ this.onTimerEvent();
1684
+ },
1685
+
1686
+ stop: function() {
1687
+ this.updater.options.onComplete = undefined;
1688
+ clearTimeout(this.timer);
1689
+ (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1690
+ },
1691
+
1692
+ updateComplete: function(response) {
1693
+ if (this.options.decay) {
1694
+ this.decay = (response.responseText == this.lastText ?
1695
+ this.decay * this.options.decay : 1);
1696
+
1697
+ this.lastText = response.responseText;
1698
+ }
1699
+ this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
1700
+ },
1701
+
1702
+ onTimerEvent: function() {
1703
+ this.updater = new Ajax.Updater(this.container, this.url, this.options);
1704
+ }
1705
+ });
1706
+
1707
+
1708
+
1709
+ function $(element) {
1710
+ if (arguments.length > 1) {
1711
+ for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1712
+ elements.push($(arguments[i]));
1713
+ return elements;
1714
+ }
1715
+ if (Object.isString(element))
1716
+ element = document.getElementById(element);
1717
+ return Element.extend(element);
1718
+ }
1719
+
1720
+ if (Prototype.BrowserFeatures.XPath) {
1721
+ document._getElementsByXPath = function(expression, parentElement) {
1722
+ var results = [];
1723
+ var query = document.evaluate(expression, $(parentElement) || document,
1724
+ null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1725
+ for (var i = 0, length = query.snapshotLength; i < length; i++)
1726
+ results.push(Element.extend(query.snapshotItem(i)));
1727
+ return results;
1728
+ };
1729
+ }
1730
+
1731
+ /*--------------------------------------------------------------------------*/
1732
+
1733
+ if (!window.Node) var Node = { };
1734
+
1735
+ if (!Node.ELEMENT_NODE) {
1736
+ Object.extend(Node, {
1737
+ ELEMENT_NODE: 1,
1738
+ ATTRIBUTE_NODE: 2,
1739
+ TEXT_NODE: 3,
1740
+ CDATA_SECTION_NODE: 4,
1741
+ ENTITY_REFERENCE_NODE: 5,
1742
+ ENTITY_NODE: 6,
1743
+ PROCESSING_INSTRUCTION_NODE: 7,
1744
+ COMMENT_NODE: 8,
1745
+ DOCUMENT_NODE: 9,
1746
+ DOCUMENT_TYPE_NODE: 10,
1747
+ DOCUMENT_FRAGMENT_NODE: 11,
1748
+ NOTATION_NODE: 12
1749
+ });
1750
+ }
1751
+
1752
+
1753
+ (function(global) {
1754
+
1755
+ var SETATTRIBUTE_IGNORES_NAME = (function(){
1756
+ var elForm = document.createElement("form");
1757
+ var elInput = document.createElement("input");
1758
+ var root = document.documentElement;
1759
+ elInput.setAttribute("name", "test");
1760
+ elForm.appendChild(elInput);
1761
+ root.appendChild(elForm);
1762
+ var isBuggy = elForm.elements
1763
+ ? (typeof elForm.elements.test == "undefined")
1764
+ : null;
1765
+ root.removeChild(elForm);
1766
+ elForm = elInput = null;
1767
+ return isBuggy;
1768
+ })();
1769
+
1770
+ var element = global.Element;
1771
+ global.Element = function(tagName, attributes) {
1772
+ attributes = attributes || { };
1773
+ tagName = tagName.toLowerCase();
1774
+ var cache = Element.cache;
1775
+ if (SETATTRIBUTE_IGNORES_NAME && attributes.name) {
1776
+ tagName = '<' + tagName + ' name="' + attributes.name + '">';
1777
+ delete attributes.name;
1778
+ return Element.writeAttribute(document.createElement(tagName), attributes);
1779
+ }
1780
+ if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
1781
+ return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
1782
+ };
1783
+ Object.extend(global.Element, element || { });
1784
+ if (element) global.Element.prototype = element.prototype;
1785
+ })(this);
1786
+
1787
+ Element.cache = { };
1788
+ Element.idCounter = 1;
1789
+
1790
+ Element.Methods = {
1791
+ visible: function(element) {
1792
+ return $(element).style.display != 'none';
1793
+ },
1794
+
1795
+ toggle: function(element) {
1796
+ element = $(element);
1797
+ Element[Element.visible(element) ? 'hide' : 'show'](element);
1798
+ return element;
1799
+ },
1800
+
1801
+
1802
+ hide: function(element) {
1803
+ element = $(element);
1804
+ element.style.display = 'none';
1805
+ return element;
1806
+ },
1807
+
1808
+ show: function(element) {
1809
+ element = $(element);
1810
+ element.style.display = '';
1811
+ return element;
1812
+ },
1813
+
1814
+ remove: function(element) {
1815
+ element = $(element);
1816
+ element.parentNode.removeChild(element);
1817
+ return element;
1818
+ },
1819
+
1820
+ update: (function(){
1821
+
1822
+ var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
1823
+ var el = document.createElement("select"),
1824
+ isBuggy = true;
1825
+ el.innerHTML = "<option value=\"test\">test</option>";
1826
+ if (el.options && el.options[0]) {
1827
+ isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
1828
+ }
1829
+ el = null;
1830
+ return isBuggy;
1831
+ })();
1832
+
1833
+ var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
1834
+ try {
1835
+ var el = document.createElement("table");
1836
+ if (el && el.tBodies) {
1837
+ el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
1838
+ var isBuggy = typeof el.tBodies[0] == "undefined";
1839
+ el = null;
1840
+ return isBuggy;
1841
+ }
1842
+ } catch (e) {
1843
+ return true;
1844
+ }
1845
+ })();
1846
+
1847
+ var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
1848
+ var s = document.createElement("script"),
1849
+ isBuggy = false;
1850
+ try {
1851
+ s.appendChild(document.createTextNode(""));
1852
+ isBuggy = !s.firstChild ||
1853
+ s.firstChild && s.firstChild.nodeType !== 3;
1854
+ } catch (e) {
1855
+ isBuggy = true;
1856
+ }
1857
+ s = null;
1858
+ return isBuggy;
1859
+ })();
1860
+
1861
+ function update(element, content) {
1862
+ element = $(element);
1863
+
1864
+ if (content && content.toElement)
1865
+ content = content.toElement();
1866
+
1867
+ if (Object.isElement(content))
1868
+ return element.update().insert(content);
1869
+
1870
+ content = Object.toHTML(content);
1871
+
1872
+ var tagName = element.tagName.toUpperCase();
1873
+
1874
+ if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
1875
+ element.text = content;
1876
+ return element;
1877
+ }
1878
+
1879
+ if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) {
1880
+ if (tagName in Element._insertionTranslations.tags) {
1881
+ while (element.firstChild) {
1882
+ element.removeChild(element.firstChild);
1883
+ }
1884
+ Element._getContentFromAnonymousElement(tagName, content.stripScripts())
1885
+ .each(function(node) {
1886
+ element.appendChild(node)
1887
+ });
1888
+ }
1889
+ else {
1890
+ element.innerHTML = content.stripScripts();
1891
+ }
1892
+ }
1893
+ else {
1894
+ element.innerHTML = content.stripScripts();
1895
+ }
1896
+
1897
+ content.evalScripts.bind(content).defer();
1898
+ return element;
1899
+ }
1900
+
1901
+ return update;
1902
+ })(),
1903
+
1904
+ replace: function(element, content) {
1905
+ element = $(element);
1906
+ if (content && content.toElement) content = content.toElement();
1907
+ else if (!Object.isElement(content)) {
1908
+ content = Object.toHTML(content);
1909
+ var range = element.ownerDocument.createRange();
1910
+ range.selectNode(element);
1911
+ content.evalScripts.bind(content).defer();
1912
+ content = range.createContextualFragment(content.stripScripts());
1913
+ }
1914
+ element.parentNode.replaceChild(content, element);
1915
+ return element;
1916
+ },
1917
+
1918
+ insert: function(element, insertions) {
1919
+ element = $(element);
1920
+
1921
+ if (Object.isString(insertions) || Object.isNumber(insertions) ||
1922
+ Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
1923
+ insertions = {bottom:insertions};
1924
+
1925
+ var content, insert, tagName, childNodes;
1926
+
1927
+ for (var position in insertions) {
1928
+ content = insertions[position];
1929
+ position = position.toLowerCase();
1930
+ insert = Element._insertionTranslations[position];
1931
+
1932
+ if (content && content.toElement) content = content.toElement();
1933
+ if (Object.isElement(content)) {
1934
+ insert(element, content);
1935
+ continue;
1936
+ }
1937
+
1938
+ content = Object.toHTML(content);
1939
+
1940
+ tagName = ((position == 'before' || position == 'after')
1941
+ ? element.parentNode : element).tagName.toUpperCase();
1942
+
1943
+ childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
1944
+
1945
+ if (position == 'top' || position == 'after') childNodes.reverse();
1946
+ childNodes.each(insert.curry(element));
1947
+
1948
+ content.evalScripts.bind(content).defer();
1949
+ }
1950
+
1951
+ return element;
1952
+ },
1953
+
1954
+ wrap: function(element, wrapper, attributes) {
1955
+ element = $(element);
1956
+ if (Object.isElement(wrapper))
1957
+ $(wrapper).writeAttribute(attributes || { });
1958
+ else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
1959
+ else wrapper = new Element('div', wrapper);
1960
+ if (element.parentNode)
1961
+ element.parentNode.replaceChild(wrapper, element);
1962
+ wrapper.appendChild(element);
1963
+ return wrapper;
1964
+ },
1965
+
1966
+ inspect: function(element) {
1967
+ element = $(element);
1968
+ var result = '<' + element.tagName.toLowerCase();
1969
+ $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1970
+ var property = pair.first(), attribute = pair.last();
1971
+ var value = (element[property] || '').toString();
1972
+ if (value) result += ' ' + attribute + '=' + value.inspect(true);
1973
+ });
1974
+ return result + '>';
1975
+ },
1976
+
1977
+ recursivelyCollect: function(element, property) {
1978
+ element = $(element);
1979
+ var elements = [];
1980
+ while (element = element[property])
1981
+ if (element.nodeType == 1)
1982
+ elements.push(Element.extend(element));
1983
+ return elements;
1984
+ },
1985
+
1986
+ ancestors: function(element) {
1987
+ return Element.recursivelyCollect(element, 'parentNode');
1988
+ },
1989
+
1990
+ descendants: function(element) {
1991
+ return Element.select(element, "*");
1992
+ },
1993
+
1994
+ firstDescendant: function(element) {
1995
+ element = $(element).firstChild;
1996
+ while (element && element.nodeType != 1) element = element.nextSibling;
1997
+ return $(element);
1998
+ },
1999
+
2000
+ immediateDescendants: function(element) {
2001
+ if (!(element = $(element).firstChild)) return [];
2002
+ while (element && element.nodeType != 1) element = element.nextSibling;
2003
+ if (element) return [element].concat($(element).nextSiblings());
2004
+ return [];
2005
+ },
2006
+
2007
+ previousSiblings: function(element) {
2008
+ return Element.recursivelyCollect(element, 'previousSibling');
2009
+ },
2010
+
2011
+ nextSiblings: function(element) {
2012
+ return Element.recursivelyCollect(element, 'nextSibling');
2013
+ },
2014
+
2015
+ siblings: function(element) {
2016
+ element = $(element);
2017
+ return Element.previousSiblings(element).reverse()
2018
+ .concat(Element.nextSiblings(element));
2019
+ },
2020
+
2021
+ match: function(element, selector) {
2022
+ if (Object.isString(selector))
2023
+ selector = new Selector(selector);
2024
+ return selector.match($(element));
2025
+ },
2026
+
2027
+ up: function(element, expression, index) {
2028
+ element = $(element);
2029
+ if (arguments.length == 1) return $(element.parentNode);
2030
+ var ancestors = Element.ancestors(element);
2031
+ return Object.isNumber(expression) ? ancestors[expression] :
2032
+ Selector.findElement(ancestors, expression, index);
2033
+ },
2034
+
2035
+ down: function(element, expression, index) {
2036
+ element = $(element);
2037
+ if (arguments.length == 1) return Element.firstDescendant(element);
2038
+ return Object.isNumber(expression) ? Element.descendants(element)[expression] :
2039
+ Element.select(element, expression)[index || 0];
2040
+ },
2041
+
2042
+ previous: function(element, expression, index) {
2043
+ element = $(element);
2044
+ if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
2045
+ var previousSiblings = Element.previousSiblings(element);
2046
+ return Object.isNumber(expression) ? previousSiblings[expression] :
2047
+ Selector.findElement(previousSiblings, expression, index);
2048
+ },
2049
+
2050
+ next: function(element, expression, index) {
2051
+ element = $(element);
2052
+ if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
2053
+ var nextSiblings = Element.nextSiblings(element);
2054
+ return Object.isNumber(expression) ? nextSiblings[expression] :
2055
+ Selector.findElement(nextSiblings, expression, index);
2056
+ },
2057
+
2058
+
2059
+ select: function(element) {
2060
+ var args = Array.prototype.slice.call(arguments, 1);
2061
+ return Selector.findChildElements(element, args);
2062
+ },
2063
+
2064
+ adjacent: function(element) {
2065
+ var args = Array.prototype.slice.call(arguments, 1);
2066
+ return Selector.findChildElements(element.parentNode, args).without(element);
2067
+ },
2068
+
2069
+ identify: function(element) {
2070
+ element = $(element);
2071
+ var id = Element.readAttribute(element, 'id');
2072
+ if (id) return id;
2073
+ do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
2074
+ Element.writeAttribute(element, 'id', id);
2075
+ return id;
2076
+ },
2077
+
2078
+ readAttribute: function(element, name) {
2079
+ element = $(element);
2080
+ if (Prototype.Browser.IE) {
2081
+ var t = Element._attributeTranslations.read;
2082
+ if (t.values[name]) return t.values[name](element, name);
2083
+ if (t.names[name]) name = t.names[name];
2084
+ if (name.include(':')) {
2085
+ return (!element.attributes || !element.attributes[name]) ? null :
2086
+ element.attributes[name].value;
2087
+ }
2088
+ }
2089
+ return element.getAttribute(name);
2090
+ },
2091
+
2092
+ writeAttribute: function(element, name, value) {
2093
+ element = $(element);
2094
+ var attributes = { }, t = Element._attributeTranslations.write;
2095
+
2096
+ if (typeof name == 'object') attributes = name;
2097
+ else attributes[name] = Object.isUndefined(value) ? true : value;
2098
+
2099
+ for (var attr in attributes) {
2100
+ name = t.names[attr] || attr;
2101
+ value = attributes[attr];
2102
+ if (t.values[attr]) name = t.values[attr](element, value);
2103
+ if (value === false || value === null)
2104
+ element.removeAttribute(name);
2105
+ else if (value === true)
2106
+ element.setAttribute(name, name);
2107
+ else element.setAttribute(name, value);
2108
+ }
2109
+ return element;
2110
+ },
2111
+
2112
+ getHeight: function(element) {
2113
+ return Element.getDimensions(element).height;
2114
+ },
2115
+
2116
+ getWidth: function(element) {
2117
+ return Element.getDimensions(element).width;
2118
+ },
2119
+
2120
+ classNames: function(element) {
2121
+ return new Element.ClassNames(element);
2122
+ },
2123
+
2124
+ hasClassName: function(element, className) {
2125
+ if (!(element = $(element))) return;
2126
+ var elementClassName = element.className;
2127
+ return (elementClassName.length > 0 && (elementClassName == className ||
2128
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
2129
+ },
2130
+
2131
+ addClassName: function(element, className) {
2132
+ if (!(element = $(element))) return;
2133
+ if (!Element.hasClassName(element, className))
2134
+ element.className += (element.className ? ' ' : '') + className;
2135
+ return element;
2136
+ },
2137
+
2138
+ removeClassName: function(element, className) {
2139
+ if (!(element = $(element))) return;
2140
+ element.className = element.className.replace(
2141
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
2142
+ return element;
2143
+ },
2144
+
2145
+ toggleClassName: function(element, className) {
2146
+ if (!(element = $(element))) return;
2147
+ return Element[Element.hasClassName(element, className) ?
2148
+ 'removeClassName' : 'addClassName'](element, className);
2149
+ },
2150
+
2151
+ cleanWhitespace: function(element) {
2152
+ element = $(element);
2153
+ var node = element.firstChild;
2154
+ while (node) {
2155
+ var nextNode = node.nextSibling;
2156
+ if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
2157
+ element.removeChild(node);
2158
+ node = nextNode;
2159
+ }
2160
+ return element;
2161
+ },
2162
+
2163
+ empty: function(element) {
2164
+ return $(element).innerHTML.blank();
2165
+ },
2166
+
2167
+ descendantOf: function(element, ancestor) {
2168
+ element = $(element), ancestor = $(ancestor);
2169
+
2170
+ if (element.compareDocumentPosition)
2171
+ return (element.compareDocumentPosition(ancestor) & 8) === 8;
2172
+
2173
+ if (ancestor.contains)
2174
+ return ancestor.contains(element) && ancestor !== element;
2175
+
2176
+ while (element = element.parentNode)
2177
+ if (element == ancestor) return true;
2178
+
2179
+ return false;
2180
+ },
2181
+
2182
+ scrollTo: function(element) {
2183
+ element = $(element);
2184
+ var pos = Element.cumulativeOffset(element);
2185
+ window.scrollTo(pos[0], pos[1]);
2186
+ return element;
2187
+ },
2188
+
2189
+ getStyle: function(element, style) {
2190
+ element = $(element);
2191
+ style = style == 'float' ? 'cssFloat' : style.camelize();
2192
+ var value = element.style[style];
2193
+ if (!value || value == 'auto') {
2194
+ var css = document.defaultView.getComputedStyle(element, null);
2195
+ value = css ? css[style] : null;
2196
+ }
2197
+ if (style == 'opacity') return value ? parseFloat(value) : 1.0;
2198
+ return value == 'auto' ? null : value;
2199
+ },
2200
+
2201
+ getOpacity: function(element) {
2202
+ return $(element).getStyle('opacity');
2203
+ },
2204
+
2205
+ setStyle: function(element, styles) {
2206
+ element = $(element);
2207
+ var elementStyle = element.style, match;
2208
+ if (Object.isString(styles)) {
2209
+ element.style.cssText += ';' + styles;
2210
+ return styles.include('opacity') ?
2211
+ element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
2212
+ }
2213
+ for (var property in styles)
2214
+ if (property == 'opacity') element.setOpacity(styles[property]);
2215
+ else
2216
+ elementStyle[(property == 'float' || property == 'cssFloat') ?
2217
+ (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
2218
+ property] = styles[property];
2219
+
2220
+ return element;
2221
+ },
2222
+
2223
+ setOpacity: function(element, value) {
2224
+ element = $(element);
2225
+ element.style.opacity = (value == 1 || value === '') ? '' :
2226
+ (value < 0.00001) ? 0 : value;
2227
+ return element;
2228
+ },
2229
+
2230
+ getDimensions: function(element) {
2231
+ element = $(element);
2232
+ var display = Element.getStyle(element, 'display');
2233
+ if (display != 'none' && display != null) // Safari bug
2234
+ return {width: element.offsetWidth, height: element.offsetHeight};
2235
+
2236
+ var els = element.style;
2237
+ var originalVisibility = els.visibility;
2238
+ var originalPosition = els.position;
2239
+ var originalDisplay = els.display;
2240
+ els.visibility = 'hidden';
2241
+ if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
2242
+ els.position = 'absolute';
2243
+ els.display = 'block';
2244
+ var originalWidth = element.clientWidth;
2245
+ var originalHeight = element.clientHeight;
2246
+ els.display = originalDisplay;
2247
+ els.position = originalPosition;
2248
+ els.visibility = originalVisibility;
2249
+ return {width: originalWidth, height: originalHeight};
2250
+ },
2251
+
2252
+ makePositioned: function(element) {
2253
+ element = $(element);
2254
+ var pos = Element.getStyle(element, 'position');
2255
+ if (pos == 'static' || !pos) {
2256
+ element._madePositioned = true;
2257
+ element.style.position = 'relative';
2258
+ if (Prototype.Browser.Opera) {
2259
+ element.style.top = 0;
2260
+ element.style.left = 0;
2261
+ }
2262
+ }
2263
+ return element;
2264
+ },
2265
+
2266
+ undoPositioned: function(element) {
2267
+ element = $(element);
2268
+ if (element._madePositioned) {
2269
+ element._madePositioned = undefined;
2270
+ element.style.position =
2271
+ element.style.top =
2272
+ element.style.left =
2273
+ element.style.bottom =
2274
+ element.style.right = '';
2275
+ }
2276
+ return element;
2277
+ },
2278
+
2279
+ makeClipping: function(element) {
2280
+ element = $(element);
2281
+ if (element._overflow) return element;
2282
+ element._overflow = Element.getStyle(element, 'overflow') || 'auto';
2283
+ if (element._overflow !== 'hidden')
2284
+ element.style.overflow = 'hidden';
2285
+ return element;
2286
+ },
2287
+
2288
+ undoClipping: function(element) {
2289
+ element = $(element);
2290
+ if (!element._overflow) return element;
2291
+ element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
2292
+ element._overflow = null;
2293
+ return element;
2294
+ },
2295
+
2296
+ cumulativeOffset: function(element) {
2297
+ var valueT = 0, valueL = 0;
2298
+ do {
2299
+ valueT += element.offsetTop || 0;
2300
+ valueL += element.offsetLeft || 0;
2301
+ element = element.offsetParent;
2302
+ } while (element);
2303
+ return Element._returnOffset(valueL, valueT);
2304
+ },
2305
+
2306
+ positionedOffset: function(element) {
2307
+ var valueT = 0, valueL = 0;
2308
+ do {
2309
+ valueT += element.offsetTop || 0;
2310
+ valueL += element.offsetLeft || 0;
2311
+ element = element.offsetParent;
2312
+ if (element) {
2313
+ if (element.tagName.toUpperCase() == 'BODY') break;
2314
+ var p = Element.getStyle(element, 'position');
2315
+ if (p !== 'static') break;
2316
+ }
2317
+ } while (element);
2318
+ return Element._returnOffset(valueL, valueT);
2319
+ },
2320
+
2321
+ absolutize: function(element) {
2322
+ element = $(element);
2323
+ if (Element.getStyle(element, 'position') == 'absolute') return element;
2324
+
2325
+ var offsets = Element.positionedOffset(element);
2326
+ var top = offsets[1];
2327
+ var left = offsets[0];
2328
+ var width = element.clientWidth;
2329
+ var height = element.clientHeight;
2330
+
2331
+ element._originalLeft = left - parseFloat(element.style.left || 0);
2332
+ element._originalTop = top - parseFloat(element.style.top || 0);
2333
+ element._originalWidth = element.style.width;
2334
+ element._originalHeight = element.style.height;
2335
+
2336
+ element.style.position = 'absolute';
2337
+ element.style.top = top + 'px';
2338
+ element.style.left = left + 'px';
2339
+ element.style.width = width + 'px';
2340
+ element.style.height = height + 'px';
2341
+ return element;
2342
+ },
2343
+
2344
+ relativize: function(element) {
2345
+ element = $(element);
2346
+ if (Element.getStyle(element, 'position') == 'relative') return element;
2347
+
2348
+ element.style.position = 'relative';
2349
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
2350
+ var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
2351
+
2352
+ element.style.top = top + 'px';
2353
+ element.style.left = left + 'px';
2354
+ element.style.height = element._originalHeight;
2355
+ element.style.width = element._originalWidth;
2356
+ return element;
2357
+ },
2358
+
2359
+ cumulativeScrollOffset: function(element) {
2360
+ var valueT = 0, valueL = 0;
2361
+ do {
2362
+ valueT += element.scrollTop || 0;
2363
+ valueL += element.scrollLeft || 0;
2364
+ element = element.parentNode;
2365
+ } while (element);
2366
+ return Element._returnOffset(valueL, valueT);
2367
+ },
2368
+
2369
+ getOffsetParent: function(element) {
2370
+ if (element.offsetParent) return $(element.offsetParent);
2371
+ if (element == document.body) return $(element);
2372
+
2373
+ while ((element = element.parentNode) && element != document.body)
2374
+ if (Element.getStyle(element, 'position') != 'static')
2375
+ return $(element);
2376
+
2377
+ return $(document.body);
2378
+ },
2379
+
2380
+ viewportOffset: function(forElement) {
2381
+ var valueT = 0, valueL = 0;
2382
+
2383
+ var element = forElement;
2384
+ do {
2385
+ valueT += element.offsetTop || 0;
2386
+ valueL += element.offsetLeft || 0;
2387
+
2388
+ if (element.offsetParent == document.body &&
2389
+ Element.getStyle(element, 'position') == 'absolute') break;
2390
+
2391
+ } while (element = element.offsetParent);
2392
+
2393
+ element = forElement;
2394
+ do {
2395
+ if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
2396
+ valueT -= element.scrollTop || 0;
2397
+ valueL -= element.scrollLeft || 0;
2398
+ }
2399
+ } while (element = element.parentNode);
2400
+
2401
+ return Element._returnOffset(valueL, valueT);
2402
+ },
2403
+
2404
+ clonePosition: function(element, source) {
2405
+ var options = Object.extend({
2406
+ setLeft: true,
2407
+ setTop: true,
2408
+ setWidth: true,
2409
+ setHeight: true,
2410
+ offsetTop: 0,
2411
+ offsetLeft: 0
2412
+ }, arguments[2] || { });
2413
+
2414
+ source = $(source);
2415
+ var p = Element.viewportOffset(source);
2416
+
2417
+ element = $(element);
2418
+ var delta = [0, 0];
2419
+ var parent = null;
2420
+ if (Element.getStyle(element, 'position') == 'absolute') {
2421
+ parent = Element.getOffsetParent(element);
2422
+ delta = Element.viewportOffset(parent);
2423
+ }
2424
+
2425
+ if (parent == document.body) {
2426
+ delta[0] -= document.body.offsetLeft;
2427
+ delta[1] -= document.body.offsetTop;
2428
+ }
2429
+
2430
+ if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
2431
+ if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
2432
+ if (options.setWidth) element.style.width = source.offsetWidth + 'px';
2433
+ if (options.setHeight) element.style.height = source.offsetHeight + 'px';
2434
+ return element;
2435
+ }
2436
+ };
2437
+
2438
+ Object.extend(Element.Methods, {
2439
+ getElementsBySelector: Element.Methods.select,
2440
+
2441
+ childElements: Element.Methods.immediateDescendants
2442
+ });
2443
+
2444
+ Element._attributeTranslations = {
2445
+ write: {
2446
+ names: {
2447
+ className: 'class',
2448
+ htmlFor: 'for'
2449
+ },
2450
+ values: { }
2451
+ }
2452
+ };
2453
+
2454
+ if (Prototype.Browser.Opera) {
2455
+ Element.Methods.getStyle = Element.Methods.getStyle.wrap(
2456
+ function(proceed, element, style) {
2457
+ switch (style) {
2458
+ case 'left': case 'top': case 'right': case 'bottom':
2459
+ if (proceed(element, 'position') === 'static') return null;
2460
+ case 'height': case 'width':
2461
+ if (!Element.visible(element)) return null;
2462
+
2463
+ var dim = parseInt(proceed(element, style), 10);
2464
+
2465
+ if (dim !== element['offset' + style.capitalize()])
2466
+ return dim + 'px';
2467
+
2468
+ var properties;
2469
+ if (style === 'height') {
2470
+ properties = ['border-top-width', 'padding-top',
2471
+ 'padding-bottom', 'border-bottom-width'];
2472
+ }
2473
+ else {
2474
+ properties = ['border-left-width', 'padding-left',
2475
+ 'padding-right', 'border-right-width'];
2476
+ }
2477
+ return properties.inject(dim, function(memo, property) {
2478
+ var val = proceed(element, property);
2479
+ return val === null ? memo : memo - parseInt(val, 10);
2480
+ }) + 'px';
2481
+ default: return proceed(element, style);
2482
+ }
2483
+ }
2484
+ );
2485
+
2486
+ Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
2487
+ function(proceed, element, attribute) {
2488
+ if (attribute === 'title') return element.title;
2489
+ return proceed(element, attribute);
2490
+ }
2491
+ );
2492
+ }
2493
+
2494
+ else if (Prototype.Browser.IE) {
2495
+ Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
2496
+ function(proceed, element) {
2497
+ element = $(element);
2498
+ try { element.offsetParent }
2499
+ catch(e) { return $(document.body) }
2500
+ var position = element.getStyle('position');
2501
+ if (position !== 'static') return proceed(element);
2502
+ element.setStyle({ position: 'relative' });
2503
+ var value = proceed(element);
2504
+ element.setStyle({ position: position });
2505
+ return value;
2506
+ }
2507
+ );
2508
+
2509
+ $w('positionedOffset viewportOffset').each(function(method) {
2510
+ Element.Methods[method] = Element.Methods[method].wrap(
2511
+ function(proceed, element) {
2512
+ element = $(element);
2513
+ try { element.offsetParent }
2514
+ catch(e) { return Element._returnOffset(0,0) }
2515
+ var position = element.getStyle('position');
2516
+ if (position !== 'static') return proceed(element);
2517
+ var offsetParent = element.getOffsetParent();
2518
+ if (offsetParent && offsetParent.getStyle('position') === 'fixed')
2519
+ offsetParent.setStyle({ zoom: 1 });
2520
+ element.setStyle({ position: 'relative' });
2521
+ var value = proceed(element);
2522
+ element.setStyle({ position: position });
2523
+ return value;
2524
+ }
2525
+ );
2526
+ });
2527
+
2528
+ Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
2529
+ function(proceed, element) {
2530
+ try { element.offsetParent }
2531
+ catch(e) { return Element._returnOffset(0,0) }
2532
+ return proceed(element);
2533
+ }
2534
+ );
2535
+
2536
+ Element.Methods.getStyle = function(element, style) {
2537
+ element = $(element);
2538
+ style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
2539
+ var value = element.style[style];
2540
+ if (!value && element.currentStyle) value = element.currentStyle[style];
2541
+
2542
+ if (style == 'opacity') {
2543
+ if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2544
+ if (value[1]) return parseFloat(value[1]) / 100;
2545
+ return 1.0;
2546
+ }
2547
+
2548
+ if (value == 'auto') {
2549
+ if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
2550
+ return element['offset' + style.capitalize()] + 'px';
2551
+ return null;
2552
+ }
2553
+ return value;
2554
+ };
2555
+
2556
+ Element.Methods.setOpacity = function(element, value) {
2557
+ function stripAlpha(filter){
2558
+ return filter.replace(/alpha\([^\)]*\)/gi,'');
2559
+ }
2560
+ element = $(element);
2561
+ var currentStyle = element.currentStyle;
2562
+ if ((currentStyle && !currentStyle.hasLayout) ||
2563
+ (!currentStyle && element.style.zoom == 'normal'))
2564
+ element.style.zoom = 1;
2565
+
2566
+ var filter = element.getStyle('filter'), style = element.style;
2567
+ if (value == 1 || value === '') {
2568
+ (filter = stripAlpha(filter)) ?
2569
+ style.filter = filter : style.removeAttribute('filter');
2570
+ return element;
2571
+ } else if (value < 0.00001) value = 0;
2572
+ style.filter = stripAlpha(filter) +
2573
+ 'alpha(opacity=' + (value * 100) + ')';
2574
+ return element;
2575
+ };
2576
+
2577
+ Element._attributeTranslations = (function(){
2578
+
2579
+ var classProp = 'className';
2580
+ var forProp = 'for';
2581
+
2582
+ var el = document.createElement('div');
2583
+
2584
+ el.setAttribute(classProp, 'x');
2585
+
2586
+ if (el.className !== 'x') {
2587
+ el.setAttribute('class', 'x');
2588
+ if (el.className === 'x') {
2589
+ classProp = 'class';
2590
+ }
2591
+ }
2592
+ el = null;
2593
+
2594
+ el = document.createElement('label');
2595
+ el.setAttribute(forProp, 'x');
2596
+ if (el.htmlFor !== 'x') {
2597
+ el.setAttribute('htmlFor', 'x');
2598
+ if (el.htmlFor === 'x') {
2599
+ forProp = 'htmlFor';
2600
+ }
2601
+ }
2602
+ el = null;
2603
+
2604
+ return {
2605
+ read: {
2606
+ names: {
2607
+ 'class': classProp,
2608
+ 'className': classProp,
2609
+ 'for': forProp,
2610
+ 'htmlFor': forProp
2611
+ },
2612
+ values: {
2613
+ _getAttr: function(element, attribute) {
2614
+ return element.getAttribute(attribute);
2615
+ },
2616
+ _getAttr2: function(element, attribute) {
2617
+ return element.getAttribute(attribute, 2);
2618
+ },
2619
+ _getAttrNode: function(element, attribute) {
2620
+ var node = element.getAttributeNode(attribute);
2621
+ return node ? node.value : "";
2622
+ },
2623
+ _getEv: (function(){
2624
+
2625
+ var el = document.createElement('div');
2626
+ el.onclick = Prototype.emptyFunction;
2627
+ var value = el.getAttribute('onclick');
2628
+ var f;
2629
+
2630
+ if (String(value).indexOf('{') > -1) {
2631
+ f = function(element, attribute) {
2632
+ attribute = element.getAttribute(attribute);
2633
+ if (!attribute) return null;
2634
+ attribute = attribute.toString();
2635
+ attribute = attribute.split('{')[1];
2636
+ attribute = attribute.split('}')[0];
2637
+ return attribute.strip();
2638
+ };
2639
+ }
2640
+ else if (value === '') {
2641
+ f = function(element, attribute) {
2642
+ attribute = element.getAttribute(attribute);
2643
+ if (!attribute) return null;
2644
+ return attribute.strip();
2645
+ };
2646
+ }
2647
+ el = null;
2648
+ return f;
2649
+ })(),
2650
+ _flag: function(element, attribute) {
2651
+ return $(element).hasAttribute(attribute) ? attribute : null;
2652
+ },
2653
+ style: function(element) {
2654
+ return element.style.cssText.toLowerCase();
2655
+ },
2656
+ title: function(element) {
2657
+ return element.title;
2658
+ }
2659
+ }
2660
+ }
2661
+ }
2662
+ })();
2663
+
2664
+ Element._attributeTranslations.write = {
2665
+ names: Object.extend({
2666
+ cellpadding: 'cellPadding',
2667
+ cellspacing: 'cellSpacing'
2668
+ }, Element._attributeTranslations.read.names),
2669
+ values: {
2670
+ checked: function(element, value) {
2671
+ element.checked = !!value;
2672
+ },
2673
+
2674
+ style: function(element, value) {
2675
+ element.style.cssText = value ? value : '';
2676
+ }
2677
+ }
2678
+ };
2679
+
2680
+ Element._attributeTranslations.has = {};
2681
+
2682
+ $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2683
+ 'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
2684
+ Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
2685
+ Element._attributeTranslations.has[attr.toLowerCase()] = attr;
2686
+ });
2687
+
2688
+ (function(v) {
2689
+ Object.extend(v, {
2690
+ href: v._getAttr2,
2691
+ src: v._getAttr2,
2692
+ type: v._getAttr,
2693
+ action: v._getAttrNode,
2694
+ disabled: v._flag,
2695
+ checked: v._flag,
2696
+ readonly: v._flag,
2697
+ multiple: v._flag,
2698
+ onload: v._getEv,
2699
+ onunload: v._getEv,
2700
+ onclick: v._getEv,
2701
+ ondblclick: v._getEv,
2702
+ onmousedown: v._getEv,
2703
+ onmouseup: v._getEv,
2704
+ onmouseover: v._getEv,
2705
+ onmousemove: v._getEv,
2706
+ onmouseout: v._getEv,
2707
+ onfocus: v._getEv,
2708
+ onblur: v._getEv,
2709
+ onkeypress: v._getEv,
2710
+ onkeydown: v._getEv,
2711
+ onkeyup: v._getEv,
2712
+ onsubmit: v._getEv,
2713
+ onreset: v._getEv,
2714
+ onselect: v._getEv,
2715
+ onchange: v._getEv
2716
+ });
2717
+ })(Element._attributeTranslations.read.values);
2718
+
2719
+ if (Prototype.BrowserFeatures.ElementExtensions) {
2720
+ (function() {
2721
+ function _descendants(element) {
2722
+ var nodes = element.getElementsByTagName('*'), results = [];
2723
+ for (var i = 0, node; node = nodes[i]; i++)
2724
+ if (node.tagName !== "!") // Filter out comment nodes.
2725
+ results.push(node);
2726
+ return results;
2727
+ }
2728
+
2729
+ Element.Methods.down = function(element, expression, index) {
2730
+ element = $(element);
2731
+ if (arguments.length == 1) return element.firstDescendant();
2732
+ return Object.isNumber(expression) ? _descendants(element)[expression] :
2733
+ Element.select(element, expression)[index || 0];
2734
+ }
2735
+ })();
2736
+ }
2737
+
2738
+ }
2739
+
2740
+ else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
2741
+ Element.Methods.setOpacity = function(element, value) {
2742
+ element = $(element);
2743
+ element.style.opacity = (value == 1) ? 0.999999 :
2744
+ (value === '') ? '' : (value < 0.00001) ? 0 : value;
2745
+ return element;
2746
+ };
2747
+ }
2748
+
2749
+ else if (Prototype.Browser.WebKit) {
2750
+ Element.Methods.setOpacity = function(element, value) {
2751
+ element = $(element);
2752
+ element.style.opacity = (value == 1 || value === '') ? '' :
2753
+ (value < 0.00001) ? 0 : value;
2754
+
2755
+ if (value == 1)
2756
+ if(element.tagName.toUpperCase() == 'IMG' && element.width) {
2757
+ element.width++; element.width--;
2758
+ } else try {
2759
+ var n = document.createTextNode(' ');
2760
+ element.appendChild(n);
2761
+ element.removeChild(n);
2762
+ } catch (e) { }
2763
+
2764
+ return element;
2765
+ };
2766
+
2767
+ Element.Methods.cumulativeOffset = function(element) {
2768
+ var valueT = 0, valueL = 0;
2769
+ do {
2770
+ valueT += element.offsetTop || 0;
2771
+ valueL += element.offsetLeft || 0;
2772
+ if (element.offsetParent == document.body)
2773
+ if (Element.getStyle(element, 'position') == 'absolute') break;
2774
+
2775
+ element = element.offsetParent;
2776
+ } while (element);
2777
+
2778
+ return Element._returnOffset(valueL, valueT);
2779
+ };
2780
+ }
2781
+
2782
+ if ('outerHTML' in document.documentElement) {
2783
+ Element.Methods.replace = function(element, content) {
2784
+ element = $(element);
2785
+
2786
+ if (content && content.toElement) content = content.toElement();
2787
+ if (Object.isElement(content)) {
2788
+ element.parentNode.replaceChild(content, element);
2789
+ return element;
2790
+ }
2791
+
2792
+ content = Object.toHTML(content);
2793
+ var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
2794
+
2795
+ if (Element._insertionTranslations.tags[tagName]) {
2796
+ var nextSibling = element.next();
2797
+ var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
2798
+ parent.removeChild(element);
2799
+ if (nextSibling)
2800
+ fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
2801
+ else
2802
+ fragments.each(function(node) { parent.appendChild(node) });
2803
+ }
2804
+ else element.outerHTML = content.stripScripts();
2805
+
2806
+ content.evalScripts.bind(content).defer();
2807
+ return element;
2808
+ };
2809
+ }
2810
+
2811
+ Element._returnOffset = function(l, t) {
2812
+ var result = [l, t];
2813
+ result.left = l;
2814
+ result.top = t;
2815
+ return result;
2816
+ };
2817
+
2818
+ Element._getContentFromAnonymousElement = function(tagName, html) {
2819
+ var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
2820
+ if (t) {
2821
+ div.innerHTML = t[0] + html + t[1];
2822
+ t[2].times(function() { div = div.firstChild });
2823
+ } else div.innerHTML = html;
2824
+ return $A(div.childNodes);
2825
+ };
2826
+
2827
+ Element._insertionTranslations = {
2828
+ before: function(element, node) {
2829
+ element.parentNode.insertBefore(node, element);
2830
+ },
2831
+ top: function(element, node) {
2832
+ element.insertBefore(node, element.firstChild);
2833
+ },
2834
+ bottom: function(element, node) {
2835
+ element.appendChild(node);
2836
+ },
2837
+ after: function(element, node) {
2838
+ element.parentNode.insertBefore(node, element.nextSibling);
2839
+ },
2840
+ tags: {
2841
+ TABLE: ['<table>', '</table>', 1],
2842
+ TBODY: ['<table><tbody>', '</tbody></table>', 2],
2843
+ TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
2844
+ TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2845
+ SELECT: ['<select>', '</select>', 1]
2846
+ }
2847
+ };
2848
+
2849
+ (function() {
2850
+ var tags = Element._insertionTranslations.tags;
2851
+ Object.extend(tags, {
2852
+ THEAD: tags.TBODY,
2853
+ TFOOT: tags.TBODY,
2854
+ TH: tags.TD
2855
+ });
2856
+ })();
2857
+
2858
+ Element.Methods.Simulated = {
2859
+ hasAttribute: function(element, attribute) {
2860
+ attribute = Element._attributeTranslations.has[attribute] || attribute;
2861
+ var node = $(element).getAttributeNode(attribute);
2862
+ return !!(node && node.specified);
2863
+ }
2864
+ };
2865
+
2866
+ Element.Methods.ByTag = { };
2867
+
2868
+ Object.extend(Element, Element.Methods);
2869
+
2870
+ (function(div) {
2871
+
2872
+ if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
2873
+ window.HTMLElement = { };
2874
+ window.HTMLElement.prototype = div['__proto__'];
2875
+ Prototype.BrowserFeatures.ElementExtensions = true;
2876
+ }
2877
+
2878
+ div = null;
2879
+
2880
+ })(document.createElement('div'))
2881
+
2882
+ Element.extend = (function() {
2883
+
2884
+ function checkDeficiency(tagName) {
2885
+ if (typeof window.Element != 'undefined') {
2886
+ var proto = window.Element.prototype;
2887
+ if (proto) {
2888
+ var id = '_' + (Math.random()+'').slice(2);
2889
+ var el = document.createElement(tagName);
2890
+ proto[id] = 'x';
2891
+ var isBuggy = (el[id] !== 'x');
2892
+ delete proto[id];
2893
+ el = null;
2894
+ return isBuggy;
2895
+ }
2896
+ }
2897
+ return false;
2898
+ }
2899
+
2900
+ function extendElementWith(element, methods) {
2901
+ for (var property in methods) {
2902
+ var value = methods[property];
2903
+ if (Object.isFunction(value) && !(property in element))
2904
+ element[property] = value.methodize();
2905
+ }
2906
+ }
2907
+
2908
+ var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
2909
+
2910
+ if (Prototype.BrowserFeatures.SpecificElementExtensions) {
2911
+ if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
2912
+ return function(element) {
2913
+ if (element && typeof element._extendedByPrototype == 'undefined') {
2914
+ var t = element.tagName;
2915
+ if (t && (/^(?:object|applet|embed)$/i.test(t))) {
2916
+ extendElementWith(element, Element.Methods);
2917
+ extendElementWith(element, Element.Methods.Simulated);
2918
+ extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
2919
+ }
2920
+ }
2921
+ return element;
2922
+ }
2923
+ }
2924
+ return Prototype.K;
2925
+ }
2926
+
2927
+ var Methods = { }, ByTag = Element.Methods.ByTag;
2928
+
2929
+ var extend = Object.extend(function(element) {
2930
+ if (!element || typeof element._extendedByPrototype != 'undefined' ||
2931
+ element.nodeType != 1 || element == window) return element;
2932
+
2933
+ var methods = Object.clone(Methods),
2934
+ tagName = element.tagName.toUpperCase();
2935
+
2936
+ if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
2937
+
2938
+ extendElementWith(element, methods);
2939
+
2940
+ element._extendedByPrototype = Prototype.emptyFunction;
2941
+ return element;
2942
+
2943
+ }, {
2944
+ refresh: function() {
2945
+ if (!Prototype.BrowserFeatures.ElementExtensions) {
2946
+ Object.extend(Methods, Element.Methods);
2947
+ Object.extend(Methods, Element.Methods.Simulated);
2948
+ }
2949
+ }
2950
+ });
2951
+
2952
+ extend.refresh();
2953
+ return extend;
2954
+ })();
2955
+
2956
+ Element.hasAttribute = function(element, attribute) {
2957
+ if (element.hasAttribute) return element.hasAttribute(attribute);
2958
+ return Element.Methods.Simulated.hasAttribute(element, attribute);
2959
+ };
2960
+
2961
+ Element.addMethods = function(methods) {
2962
+ var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
2963
+
2964
+ if (!methods) {
2965
+ Object.extend(Form, Form.Methods);
2966
+ Object.extend(Form.Element, Form.Element.Methods);
2967
+ Object.extend(Element.Methods.ByTag, {
2968
+ "FORM": Object.clone(Form.Methods),
2969
+ "INPUT": Object.clone(Form.Element.Methods),
2970
+ "SELECT": Object.clone(Form.Element.Methods),
2971
+ "TEXTAREA": Object.clone(Form.Element.Methods)
2972
+ });
2973
+ }
2974
+
2975
+ if (arguments.length == 2) {
2976
+ var tagName = methods;
2977
+ methods = arguments[1];
2978
+ }
2979
+
2980
+ if (!tagName) Object.extend(Element.Methods, methods || { });
2981
+ else {
2982
+ if (Object.isArray(tagName)) tagName.each(extend);
2983
+ else extend(tagName);
2984
+ }
2985
+
2986
+ function extend(tagName) {
2987
+ tagName = tagName.toUpperCase();
2988
+ if (!Element.Methods.ByTag[tagName])
2989
+ Element.Methods.ByTag[tagName] = { };
2990
+ Object.extend(Element.Methods.ByTag[tagName], methods);
2991
+ }
2992
+
2993
+ function copy(methods, destination, onlyIfAbsent) {
2994
+ onlyIfAbsent = onlyIfAbsent || false;
2995
+ for (var property in methods) {
2996
+ var value = methods[property];
2997
+ if (!Object.isFunction(value)) continue;
2998
+ if (!onlyIfAbsent || !(property in destination))
2999
+ destination[property] = value.methodize();
3000
+ }
3001
+ }
3002
+
3003
+ function findDOMClass(tagName) {
3004
+ var klass;
3005
+ var trans = {
3006
+ "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
3007
+ "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
3008
+ "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
3009
+ "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
3010
+ "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
3011
+ "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
3012
+ "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
3013
+ "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
3014
+ "FrameSet", "IFRAME": "IFrame"
3015
+ };
3016
+ if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
3017
+ if (window[klass]) return window[klass];
3018
+ klass = 'HTML' + tagName + 'Element';
3019
+ if (window[klass]) return window[klass];
3020
+ klass = 'HTML' + tagName.capitalize() + 'Element';
3021
+ if (window[klass]) return window[klass];
3022
+
3023
+ var element = document.createElement(tagName);
3024
+ var proto = element['__proto__'] || element.constructor.prototype;
3025
+ element = null;
3026
+ return proto;
3027
+ }
3028
+
3029
+ var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
3030
+ Element.prototype;
3031
+
3032
+ if (F.ElementExtensions) {
3033
+ copy(Element.Methods, elementPrototype);
3034
+ copy(Element.Methods.Simulated, elementPrototype, true);
3035
+ }
3036
+
3037
+ if (F.SpecificElementExtensions) {
3038
+ for (var tag in Element.Methods.ByTag) {
3039
+ var klass = findDOMClass(tag);
3040
+ if (Object.isUndefined(klass)) continue;
3041
+ copy(T[tag], klass.prototype);
3042
+ }
3043
+ }
3044
+
3045
+ Object.extend(Element, Element.Methods);
3046
+ delete Element.ByTag;
3047
+
3048
+ if (Element.extend.refresh) Element.extend.refresh();
3049
+ Element.cache = { };
3050
+ };
3051
+
3052
+
3053
+ document.viewport = {
3054
+
3055
+ getDimensions: function() {
3056
+ return { width: this.getWidth(), height: this.getHeight() };
3057
+ },
3058
+
3059
+ getScrollOffsets: function() {
3060
+ return Element._returnOffset(
3061
+ window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
3062
+ window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
3063
+ }
3064
+ };
3065
+
3066
+ (function(viewport) {
3067
+ var B = Prototype.Browser, doc = document, element, property = {};
3068
+
3069
+ function getRootElement() {
3070
+ if (B.WebKit && !doc.evaluate)
3071
+ return document;
3072
+
3073
+ if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
3074
+ return document.body;
3075
+
3076
+ return document.documentElement;
3077
+ }
3078
+
3079
+ function define(D) {
3080
+ if (!element) element = getRootElement();
3081
+
3082
+ property[D] = 'client' + D;
3083
+
3084
+ viewport['get' + D] = function() { return element[property[D]] };
3085
+ return viewport['get' + D]();
3086
+ }
3087
+
3088
+ viewport.getWidth = define.curry('Width');
3089
+
3090
+ viewport.getHeight = define.curry('Height');
3091
+ })(document.viewport);
3092
+
3093
+
3094
+ Element.Storage = {
3095
+ UID: 1
3096
+ };
3097
+
3098
+ Element.addMethods({
3099
+ getStorage: function(element) {
3100
+ if (!(element = $(element))) return;
3101
+
3102
+ var uid;
3103
+ if (element === window) {
3104
+ uid = 0;
3105
+ } else {
3106
+ if (typeof element._prototypeUID === "undefined")
3107
+ element._prototypeUID = [Element.Storage.UID++];
3108
+ uid = element._prototypeUID[0];
3109
+ }
3110
+
3111
+ if (!Element.Storage[uid])
3112
+ Element.Storage[uid] = $H();
3113
+
3114
+ return Element.Storage[uid];
3115
+ },
3116
+
3117
+ store: function(element, key, value) {
3118
+ if (!(element = $(element))) return;
3119
+
3120
+ if (arguments.length === 2) {
3121
+ Element.getStorage(element).update(key);
3122
+ } else {
3123
+ Element.getStorage(element).set(key, value);
3124
+ }
3125
+
3126
+ return element;
3127
+ },
3128
+
3129
+ retrieve: function(element, key, defaultValue) {
3130
+ if (!(element = $(element))) return;
3131
+ var hash = Element.getStorage(element), value = hash.get(key);
3132
+
3133
+ if (Object.isUndefined(value)) {
3134
+ hash.set(key, defaultValue);
3135
+ value = defaultValue;
3136
+ }
3137
+
3138
+ return value;
3139
+ },
3140
+
3141
+ clone: function(element, deep) {
3142
+ if (!(element = $(element))) return;
3143
+ var clone = element.cloneNode(deep);
3144
+ clone._prototypeUID = void 0;
3145
+ if (deep) {
3146
+ var descendants = Element.select(clone, '*'),
3147
+ i = descendants.length;
3148
+ while (i--) {
3149
+ descendants[i]._prototypeUID = void 0;
3150
+ }
3151
+ }
3152
+ return Element.extend(clone);
3153
+ }
3154
+ });
3155
+ /* Portions of the Selector class are derived from Jack Slocum's DomQuery,
3156
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
3157
+ * license. Please see http://www.yui-ext.com/ for more information. */
3158
+
3159
+ var Selector = Class.create({
3160
+ initialize: function(expression) {
3161
+ this.expression = expression.strip();
3162
+
3163
+ if (this.shouldUseSelectorsAPI()) {
3164
+ this.mode = 'selectorsAPI';
3165
+ } else if (this.shouldUseXPath()) {
3166
+ this.mode = 'xpath';
3167
+ this.compileXPathMatcher();
3168
+ } else {
3169
+ this.mode = "normal";
3170
+ this.compileMatcher();
3171
+ }
3172
+
3173
+ },
3174
+
3175
+ shouldUseXPath: (function() {
3176
+
3177
+ var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
3178
+ var isBuggy = false;
3179
+ if (document.evaluate && window.XPathResult) {
3180
+ var el = document.createElement('div');
3181
+ el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
3182
+
3183
+ var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
3184
+ "//*[local-name()='li' or local-name()='LI']";
3185
+
3186
+ var result = document.evaluate(xpath, el, null,
3187
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
3188
+
3189
+ isBuggy = (result.snapshotLength !== 2);
3190
+ el = null;
3191
+ }
3192
+ return isBuggy;
3193
+ })();
3194
+
3195
+ return function() {
3196
+ if (!Prototype.BrowserFeatures.XPath) return false;
3197
+
3198
+ var e = this.expression;
3199
+
3200
+ if (Prototype.Browser.WebKit &&
3201
+ (e.include("-of-type") || e.include(":empty")))
3202
+ return false;
3203
+
3204
+ if ((/(\[[\w-]*?:|:checked)/).test(e))
3205
+ return false;
3206
+
3207
+ if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
3208
+
3209
+ return true;
3210
+ }
3211
+
3212
+ })(),
3213
+
3214
+ shouldUseSelectorsAPI: function() {
3215
+ if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
3216
+
3217
+ if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
3218
+
3219
+ if (!Selector._div) Selector._div = new Element('div');
3220
+
3221
+ try {
3222
+ Selector._div.querySelector(this.expression);
3223
+ } catch(e) {
3224
+ return false;
3225
+ }
3226
+
3227
+ return true;
3228
+ },
3229
+
3230
+ compileMatcher: function() {
3231
+ var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
3232
+ c = Selector.criteria, le, p, m, len = ps.length, name;
3233
+
3234
+ if (Selector._cache[e]) {
3235
+ this.matcher = Selector._cache[e];
3236
+ return;
3237
+ }
3238
+
3239
+ this.matcher = ["this.matcher = function(root) {",
3240
+ "var r = root, h = Selector.handlers, c = false, n;"];
3241
+
3242
+ while (e && le != e && (/\S/).test(e)) {
3243
+ le = e;
3244
+ for (var i = 0; i<len; i++) {
3245
+ p = ps[i].re;
3246
+ name = ps[i].name;
3247
+ if (m = e.match(p)) {
3248
+ this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
3249
+ new Template(c[name]).evaluate(m));
3250
+ e = e.replace(m[0], '');
3251
+ break;
3252
+ }
3253
+ }
3254
+ }
3255
+
3256
+ this.matcher.push("return h.unique(n);\n}");
3257
+ eval(this.matcher.join('\n'));
3258
+ Selector._cache[this.expression] = this.matcher;
3259
+ },
3260
+
3261
+ compileXPathMatcher: function() {
3262
+ var e = this.expression, ps = Selector.patterns,
3263
+ x = Selector.xpath, le, m, len = ps.length, name;
3264
+
3265
+ if (Selector._cache[e]) {
3266
+ this.xpath = Selector._cache[e]; return;
3267
+ }
3268
+
3269
+ this.matcher = ['.//*'];
3270
+ while (e && le != e && (/\S/).test(e)) {
3271
+ le = e;
3272
+ for (var i = 0; i<len; i++) {
3273
+ name = ps[i].name;
3274
+ if (m = e.match(ps[i].re)) {
3275
+ this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
3276
+ new Template(x[name]).evaluate(m));
3277
+ e = e.replace(m[0], '');
3278
+ break;
3279
+ }
3280
+ }
3281
+ }
3282
+
3283
+ this.xpath = this.matcher.join('');
3284
+ Selector._cache[this.expression] = this.xpath;
3285
+ },
3286
+
3287
+ findElements: function(root) {
3288
+ root = root || document;
3289
+ var e = this.expression, results;
3290
+
3291
+ switch (this.mode) {
3292
+ case 'selectorsAPI':
3293
+ if (root !== document) {
3294
+ var oldId = root.id, id = $(root).identify();
3295
+ id = id.replace(/([\.:])/g, "\\$1");
3296
+ e = "#" + id + " " + e;
3297
+ }
3298
+
3299
+ results = $A(root.querySelectorAll(e)).map(Element.extend);
3300
+ root.id = oldId;
3301
+
3302
+ return results;
3303
+ case 'xpath':
3304
+ return document._getElementsByXPath(this.xpath, root);
3305
+ default:
3306
+ return this.matcher(root);
3307
+ }
3308
+ },
3309
+
3310
+ match: function(element) {
3311
+ this.tokens = [];
3312
+
3313
+ var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
3314
+ var le, p, m, len = ps.length, name;
3315
+
3316
+ while (e && le !== e && (/\S/).test(e)) {
3317
+ le = e;
3318
+ for (var i = 0; i<len; i++) {
3319
+ p = ps[i].re;
3320
+ name = ps[i].name;
3321
+ if (m = e.match(p)) {
3322
+ if (as[name]) {
3323
+ this.tokens.push([name, Object.clone(m)]);
3324
+ e = e.replace(m[0], '');
3325
+ } else {
3326
+ return this.findElements(document).include(element);
3327
+ }
3328
+ }
3329
+ }
3330
+ }
3331
+
3332
+ var match = true, name, matches;
3333
+ for (var i = 0, token; token = this.tokens[i]; i++) {
3334
+ name = token[0], matches = token[1];
3335
+ if (!Selector.assertions[name](element, matches)) {
3336
+ match = false; break;
3337
+ }
3338
+ }
3339
+
3340
+ return match;
3341
+ },
3342
+
3343
+ toString: function() {
3344
+ return this.expression;
3345
+ },
3346
+
3347
+ inspect: function() {
3348
+ return "#<Selector:" + this.expression.inspect() + ">";
3349
+ }
3350
+ });
3351
+
3352
+ if (Prototype.BrowserFeatures.SelectorsAPI &&
3353
+ document.compatMode === 'BackCompat') {
3354
+ Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
3355
+ var div = document.createElement('div'),
3356
+ span = document.createElement('span');
3357
+
3358
+ div.id = "prototype_test_id";
3359
+ span.className = 'Test';
3360
+ div.appendChild(span);
3361
+ var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
3362
+ div = span = null;
3363
+ return isIgnored;
3364
+ })();
3365
+ }
3366
+
3367
+ Object.extend(Selector, {
3368
+ _cache: { },
3369
+
3370
+ xpath: {
3371
+ descendant: "//*",
3372
+ child: "/*",
3373
+ adjacent: "/following-sibling::*[1]",
3374
+ laterSibling: '/following-sibling::*',
3375
+ tagName: function(m) {
3376
+ if (m[1] == '*') return '';
3377
+ return "[local-name()='" + m[1].toLowerCase() +
3378
+ "' or local-name()='" + m[1].toUpperCase() + "']";
3379
+ },
3380
+ className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
3381
+ id: "[@id='#{1}']",
3382
+ attrPresence: function(m) {
3383
+ m[1] = m[1].toLowerCase();
3384
+ return new Template("[@#{1}]").evaluate(m);
3385
+ },
3386
+ attr: function(m) {
3387
+ m[1] = m[1].toLowerCase();
3388
+ m[3] = m[5] || m[6];
3389
+ return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
3390
+ },
3391
+ pseudo: function(m) {
3392
+ var h = Selector.xpath.pseudos[m[1]];
3393
+ if (!h) return '';
3394
+ if (Object.isFunction(h)) return h(m);
3395
+ return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
3396
+ },
3397
+ operators: {
3398
+ '=': "[@#{1}='#{3}']",
3399
+ '!=': "[@#{1}!='#{3}']",
3400
+ '^=': "[starts-with(@#{1}, '#{3}')]",
3401
+ '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
3402
+ '*=': "[contains(@#{1}, '#{3}')]",
3403
+ '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
3404
+ '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
3405
+ },
3406
+ pseudos: {
3407
+ 'first-child': '[not(preceding-sibling::*)]',
3408
+ 'last-child': '[not(following-sibling::*)]',
3409
+ 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
3410
+ 'empty': "[count(*) = 0 and (count(text()) = 0)]",
3411
+ 'checked': "[@checked]",
3412
+ 'disabled': "[(@disabled) and (@type!='hidden')]",
3413
+ 'enabled': "[not(@disabled) and (@type!='hidden')]",
3414
+ 'not': function(m) {
3415
+ var e = m[6], p = Selector.patterns,
3416
+ x = Selector.xpath, le, v, len = p.length, name;
3417
+
3418
+ var exclusion = [];
3419
+ while (e && le != e && (/\S/).test(e)) {
3420
+ le = e;
3421
+ for (var i = 0; i<len; i++) {
3422
+ name = p[i].name
3423
+ if (m = e.match(p[i].re)) {
3424
+ v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
3425
+ exclusion.push("(" + v.substring(1, v.length - 1) + ")");
3426
+ e = e.replace(m[0], '');
3427
+ break;
3428
+ }
3429
+ }
3430
+ }
3431
+ return "[not(" + exclusion.join(" and ") + ")]";
3432
+ },
3433
+ 'nth-child': function(m) {
3434
+ return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
3435
+ },
3436
+ 'nth-last-child': function(m) {
3437
+ return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
3438
+ },
3439
+ 'nth-of-type': function(m) {
3440
+ return Selector.xpath.pseudos.nth("position() ", m);
3441
+ },
3442
+ 'nth-last-of-type': function(m) {
3443
+ return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
3444
+ },
3445
+ 'first-of-type': function(m) {
3446
+ m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
3447
+ },
3448
+ 'last-of-type': function(m) {
3449
+ m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
3450
+ },
3451
+ 'only-of-type': function(m) {
3452
+ var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
3453
+ },
3454
+ nth: function(fragment, m) {
3455
+ var mm, formula = m[6], predicate;
3456
+ if (formula == 'even') formula = '2n+0';
3457
+ if (formula == 'odd') formula = '2n+1';
3458
+ if (mm = formula.match(/^(\d+)$/)) // digit only
3459
+ return '[' + fragment + "= " + mm[1] + ']';
3460
+ if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3461
+ if (mm[1] == "-") mm[1] = -1;
3462
+ var a = mm[1] ? Number(mm[1]) : 1;
3463
+ var b = mm[2] ? Number(mm[2]) : 0;
3464
+ predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
3465
+ "((#{fragment} - #{b}) div #{a} >= 0)]";
3466
+ return new Template(predicate).evaluate({
3467
+ fragment: fragment, a: a, b: b });
3468
+ }
3469
+ }
3470
+ }
3471
+ },
3472
+
3473
+ criteria: {
3474
+ tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
3475
+ className: 'n = h.className(n, r, "#{1}", c); c = false;',
3476
+ id: 'n = h.id(n, r, "#{1}", c); c = false;',
3477
+ attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
3478
+ attr: function(m) {
3479
+ m[3] = (m[5] || m[6]);
3480
+ return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
3481
+ },
3482
+ pseudo: function(m) {
3483
+ if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
3484
+ return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
3485
+ },
3486
+ descendant: 'c = "descendant";',
3487
+ child: 'c = "child";',
3488
+ adjacent: 'c = "adjacent";',
3489
+ laterSibling: 'c = "laterSibling";'
3490
+ },
3491
+
3492
+ patterns: [
3493
+ { name: 'laterSibling', re: /^\s*~\s*/ },
3494
+ { name: 'child', re: /^\s*>\s*/ },
3495
+ { name: 'adjacent', re: /^\s*\+\s*/ },
3496
+ { name: 'descendant', re: /^\s/ },
3497
+
3498
+ { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
3499
+ { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ },
3500
+ { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ },
3501
+ { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
3502
+ { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
3503
+ { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
3504
+ ],
3505
+
3506
+ assertions: {
3507
+ tagName: function(element, matches) {
3508
+ return matches[1].toUpperCase() == element.tagName.toUpperCase();
3509
+ },
3510
+
3511
+ className: function(element, matches) {
3512
+ return Element.hasClassName(element, matches[1]);
3513
+ },
3514
+
3515
+ id: function(element, matches) {
3516
+ return element.id === matches[1];
3517
+ },
3518
+
3519
+ attrPresence: function(element, matches) {
3520
+ return Element.hasAttribute(element, matches[1]);
3521
+ },
3522
+
3523
+ attr: function(element, matches) {
3524
+ var nodeValue = Element.readAttribute(element, matches[1]);
3525
+ return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
3526
+ }
3527
+ },
3528
+
3529
+ handlers: {
3530
+ concat: function(a, b) {
3531
+ for (var i = 0, node; node = b[i]; i++)
3532
+ a.push(node);
3533
+ return a;
3534
+ },
3535
+
3536
+ mark: function(nodes) {
3537
+ var _true = Prototype.emptyFunction;
3538
+ for (var i = 0, node; node = nodes[i]; i++)
3539
+ node._countedByPrototype = _true;
3540
+ return nodes;
3541
+ },
3542
+
3543
+ unmark: (function(){
3544
+
3545
+ var PROPERTIES_ATTRIBUTES_MAP = (function(){
3546
+ var el = document.createElement('div'),
3547
+ isBuggy = false,
3548
+ propName = '_countedByPrototype',
3549
+ value = 'x'
3550
+ el[propName] = value;
3551
+ isBuggy = (el.getAttribute(propName) === value);
3552
+ el = null;
3553
+ return isBuggy;
3554
+ })();
3555
+
3556
+ return PROPERTIES_ATTRIBUTES_MAP ?
3557
+ function(nodes) {
3558
+ for (var i = 0, node; node = nodes[i]; i++)
3559
+ node.removeAttribute('_countedByPrototype');
3560
+ return nodes;
3561
+ } :
3562
+ function(nodes) {
3563
+ for (var i = 0, node; node = nodes[i]; i++)
3564
+ node._countedByPrototype = void 0;
3565
+ return nodes;
3566
+ }
3567
+ })(),
3568
+
3569
+ index: function(parentNode, reverse, ofType) {
3570
+ parentNode._countedByPrototype = Prototype.emptyFunction;
3571
+ if (reverse) {
3572
+ for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
3573
+ var node = nodes[i];
3574
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
3575
+ }
3576
+ } else {
3577
+ for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
3578
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
3579
+ }
3580
+ },
3581
+
3582
+ unique: function(nodes) {
3583
+ if (nodes.length == 0) return nodes;
3584
+ var results = [], n;
3585
+ for (var i = 0, l = nodes.length; i < l; i++)
3586
+ if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
3587
+ n._countedByPrototype = Prototype.emptyFunction;
3588
+ results.push(Element.extend(n));
3589
+ }
3590
+ return Selector.handlers.unmark(results);
3591
+ },
3592
+
3593
+ descendant: function(nodes) {
3594
+ var h = Selector.handlers;
3595
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
3596
+ h.concat(results, node.getElementsByTagName('*'));
3597
+ return results;
3598
+ },
3599
+
3600
+ child: function(nodes) {
3601
+ var h = Selector.handlers;
3602
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
3603
+ for (var j = 0, child; child = node.childNodes[j]; j++)
3604
+ if (child.nodeType == 1 && child.tagName != '!') results.push(child);
3605
+ }
3606
+ return results;
3607
+ },
3608
+
3609
+ adjacent: function(nodes) {
3610
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
3611
+ var next = this.nextElementSibling(node);
3612
+ if (next) results.push(next);
3613
+ }
3614
+ return results;
3615
+ },
3616
+
3617
+ laterSibling: function(nodes) {
3618
+ var h = Selector.handlers;
3619
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
3620
+ h.concat(results, Element.nextSiblings(node));
3621
+ return results;
3622
+ },
3623
+
3624
+ nextElementSibling: function(node) {
3625
+ while (node = node.nextSibling)
3626
+ if (node.nodeType == 1) return node;
3627
+ return null;
3628
+ },
3629
+
3630
+ previousElementSibling: function(node) {
3631
+ while (node = node.previousSibling)
3632
+ if (node.nodeType == 1) return node;
3633
+ return null;
3634
+ },
3635
+
3636
+ tagName: function(nodes, root, tagName, combinator) {
3637
+ var uTagName = tagName.toUpperCase();
3638
+ var results = [], h = Selector.handlers;
3639
+ if (nodes) {
3640
+ if (combinator) {
3641
+ if (combinator == "descendant") {
3642
+ for (var i = 0, node; node = nodes[i]; i++)
3643
+ h.concat(results, node.getElementsByTagName(tagName));
3644
+ return results;
3645
+ } else nodes = this[combinator](nodes);
3646
+ if (tagName == "*") return nodes;
3647
+ }
3648
+ for (var i = 0, node; node = nodes[i]; i++)
3649
+ if (node.tagName.toUpperCase() === uTagName) results.push(node);
3650
+ return results;
3651
+ } else return root.getElementsByTagName(tagName);
3652
+ },
3653
+
3654
+ id: function(nodes, root, id, combinator) {
3655
+ var targetNode = $(id), h = Selector.handlers;
3656
+
3657
+ if (root == document) {
3658
+ if (!targetNode) return [];
3659
+ if (!nodes) return [targetNode];
3660
+ } else {
3661
+ if (!root.sourceIndex || root.sourceIndex < 1) {
3662
+ var nodes = root.getElementsByTagName('*');
3663
+ for (var j = 0, node; node = nodes[j]; j++) {
3664
+ if (node.id === id) return [node];
3665
+ }
3666
+ }
3667
+ }
3668
+
3669
+ if (nodes) {
3670
+ if (combinator) {
3671
+ if (combinator == 'child') {
3672
+ for (var i = 0, node; node = nodes[i]; i++)
3673
+ if (targetNode.parentNode == node) return [targetNode];
3674
+ } else if (combinator == 'descendant') {
3675
+ for (var i = 0, node; node = nodes[i]; i++)
3676
+ if (Element.descendantOf(targetNode, node)) return [targetNode];
3677
+ } else if (combinator == 'adjacent') {
3678
+ for (var i = 0, node; node = nodes[i]; i++)
3679
+ if (Selector.handlers.previousElementSibling(targetNode) == node)
3680
+ return [targetNode];
3681
+ } else nodes = h[combinator](nodes);
3682
+ }
3683
+ for (var i = 0, node; node = nodes[i]; i++)
3684
+ if (node == targetNode) return [targetNode];
3685
+ return [];
3686
+ }
3687
+ return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
3688
+ },
3689
+
3690
+ className: function(nodes, root, className, combinator) {
3691
+ if (nodes && combinator) nodes = this[combinator](nodes);
3692
+ return Selector.handlers.byClassName(nodes, root, className);
3693
+ },
3694
+
3695
+ byClassName: function(nodes, root, className) {
3696
+ if (!nodes) nodes = Selector.handlers.descendant([root]);
3697
+ var needle = ' ' + className + ' ';
3698
+ for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
3699
+ nodeClassName = node.className;
3700
+ if (nodeClassName.length == 0) continue;
3701
+ if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
3702
+ results.push(node);
3703
+ }
3704
+ return results;
3705
+ },
3706
+
3707
+ attrPresence: function(nodes, root, attr, combinator) {
3708
+ if (!nodes) nodes = root.getElementsByTagName("*");
3709
+ if (nodes && combinator) nodes = this[combinator](nodes);
3710
+ var results = [];
3711
+ for (var i = 0, node; node = nodes[i]; i++)
3712
+ if (Element.hasAttribute(node, attr)) results.push(node);
3713
+ return results;
3714
+ },
3715
+
3716
+ attr: function(nodes, root, attr, value, operator, combinator) {
3717
+ if (!nodes) nodes = root.getElementsByTagName("*");
3718
+ if (nodes && combinator) nodes = this[combinator](nodes);
3719
+ var handler = Selector.operators[operator], results = [];
3720
+ for (var i = 0, node; node = nodes[i]; i++) {
3721
+ var nodeValue = Element.readAttribute(node, attr);
3722
+ if (nodeValue === null) continue;
3723
+ if (handler(nodeValue, value)) results.push(node);
3724
+ }
3725
+ return results;
3726
+ },
3727
+
3728
+ pseudo: function(nodes, name, value, root, combinator) {
3729
+ if (nodes && combinator) nodes = this[combinator](nodes);
3730
+ if (!nodes) nodes = root.getElementsByTagName("*");
3731
+ return Selector.pseudos[name](nodes, value, root);
3732
+ }
3733
+ },
3734
+
3735
+ pseudos: {
3736
+ 'first-child': function(nodes, value, root) {
3737
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
3738
+ if (Selector.handlers.previousElementSibling(node)) continue;
3739
+ results.push(node);
3740
+ }
3741
+ return results;
3742
+ },
3743
+ 'last-child': function(nodes, value, root) {
3744
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
3745
+ if (Selector.handlers.nextElementSibling(node)) continue;
3746
+ results.push(node);
3747
+ }
3748
+ return results;
3749
+ },
3750
+ 'only-child': function(nodes, value, root) {
3751
+ var h = Selector.handlers;
3752
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
3753
+ if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
3754
+ results.push(node);
3755
+ return results;
3756
+ },
3757
+ 'nth-child': function(nodes, formula, root) {
3758
+ return Selector.pseudos.nth(nodes, formula, root);
3759
+ },
3760
+ 'nth-last-child': function(nodes, formula, root) {
3761
+ return Selector.pseudos.nth(nodes, formula, root, true);
3762
+ },
3763
+ 'nth-of-type': function(nodes, formula, root) {
3764
+ return Selector.pseudos.nth(nodes, formula, root, false, true);
3765
+ },
3766
+ 'nth-last-of-type': function(nodes, formula, root) {
3767
+ return Selector.pseudos.nth(nodes, formula, root, true, true);
3768
+ },
3769
+ 'first-of-type': function(nodes, formula, root) {
3770
+ return Selector.pseudos.nth(nodes, "1", root, false, true);
3771
+ },
3772
+ 'last-of-type': function(nodes, formula, root) {
3773
+ return Selector.pseudos.nth(nodes, "1", root, true, true);
3774
+ },
3775
+ 'only-of-type': function(nodes, formula, root) {
3776
+ var p = Selector.pseudos;
3777
+ return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
3778
+ },
3779
+
3780
+ getIndices: function(a, b, total) {
3781
+ if (a == 0) return b > 0 ? [b] : [];
3782
+ return $R(1, total).inject([], function(memo, i) {
3783
+ if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
3784
+ return memo;
3785
+ });
3786
+ },
3787
+
3788
+ nth: function(nodes, formula, root, reverse, ofType) {
3789
+ if (nodes.length == 0) return [];
3790
+ if (formula == 'even') formula = '2n+0';
3791
+ if (formula == 'odd') formula = '2n+1';
3792
+ var h = Selector.handlers, results = [], indexed = [], m;
3793
+ h.mark(nodes);
3794
+ for (var i = 0, node; node = nodes[i]; i++) {
3795
+ if (!node.parentNode._countedByPrototype) {
3796
+ h.index(node.parentNode, reverse, ofType);
3797
+ indexed.push(node.parentNode);
3798
+ }
3799
+ }
3800
+ if (formula.match(/^\d+$/)) { // just a number
3801
+ formula = Number(formula);
3802
+ for (var i = 0, node; node = nodes[i]; i++)
3803
+ if (node.nodeIndex == formula) results.push(node);
3804
+ } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3805
+ if (m[1] == "-") m[1] = -1;
3806
+ var a = m[1] ? Number(m[1]) : 1;
3807
+ var b = m[2] ? Number(m[2]) : 0;
3808
+ var indices = Selector.pseudos.getIndices(a, b, nodes.length);
3809
+ for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
3810
+ for (var j = 0; j < l; j++)
3811
+ if (node.nodeIndex == indices[j]) results.push(node);
3812
+ }
3813
+ }
3814
+ h.unmark(nodes);
3815
+ h.unmark(indexed);
3816
+ return results;
3817
+ },
3818
+
3819
+ 'empty': function(nodes, value, root) {
3820
+ for (var i = 0, results = [], node; node = nodes[i]; i++) {
3821
+ if (node.tagName == '!' || node.firstChild) continue;
3822
+ results.push(node);
3823
+ }
3824
+ return results;
3825
+ },
3826
+
3827
+ 'not': function(nodes, selector, root) {
3828
+ var h = Selector.handlers, selectorType, m;
3829
+ var exclusions = new Selector(selector).findElements(root);
3830
+ h.mark(exclusions);
3831
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
3832
+ if (!node._countedByPrototype) results.push(node);
3833
+ h.unmark(exclusions);
3834
+ return results;
3835
+ },
3836
+
3837
+ 'enabled': function(nodes, value, root) {
3838
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
3839
+ if (!node.disabled && (!node.type || node.type !== 'hidden'))
3840
+ results.push(node);
3841
+ return results;
3842
+ },
3843
+
3844
+ 'disabled': function(nodes, value, root) {
3845
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
3846
+ if (node.disabled) results.push(node);
3847
+ return results;
3848
+ },
3849
+
3850
+ 'checked': function(nodes, value, root) {
3851
+ for (var i = 0, results = [], node; node = nodes[i]; i++)
3852
+ if (node.checked) results.push(node);
3853
+ return results;
3854
+ }
3855
+ },
3856
+
3857
+ operators: {
3858
+ '=': function(nv, v) { return nv == v; },
3859
+ '!=': function(nv, v) { return nv != v; },
3860
+ '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
3861
+ '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
3862
+ '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
3863
+ '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
3864
+ '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
3865
+ '-').include('-' + (v || "").toUpperCase() + '-'); }
3866
+ },
3867
+
3868
+ split: function(expression) {
3869
+ var expressions = [];
3870
+ expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
3871
+ expressions.push(m[1].strip());
3872
+ });
3873
+ return expressions;
3874
+ },
3875
+
3876
+ matchElements: function(elements, expression) {
3877
+ var matches = $$(expression), h = Selector.handlers;
3878
+ h.mark(matches);
3879
+ for (var i = 0, results = [], element; element = elements[i]; i++)
3880
+ if (element._countedByPrototype) results.push(element);
3881
+ h.unmark(matches);
3882
+ return results;
3883
+ },
3884
+
3885
+ findElement: function(elements, expression, index) {
3886
+ if (Object.isNumber(expression)) {
3887
+ index = expression; expression = false;
3888
+ }
3889
+ return Selector.matchElements(elements, expression || '*')[index || 0];
3890
+ },
3891
+
3892
+ findChildElements: function(element, expressions) {
3893
+ expressions = Selector.split(expressions.join(','));
3894
+ var results = [], h = Selector.handlers;
3895
+ for (var i = 0, l = expressions.length, selector; i < l; i++) {
3896
+ selector = new Selector(expressions[i].strip());
3897
+ h.concat(results, selector.findElements(element));
3898
+ }
3899
+ return (l > 1) ? h.unique(results) : results;
3900
+ }
3901
+ });
3902
+
3903
+ if (Prototype.Browser.IE) {
3904
+ Object.extend(Selector.handlers, {
3905
+ concat: function(a, b) {
3906
+ for (var i = 0, node; node = b[i]; i++)
3907
+ if (node.tagName !== "!") a.push(node);
3908
+ return a;
3909
+ }
3910
+ });
3911
+ }
3912
+
3913
+ function $$() {
3914
+ return Selector.findChildElements(document, $A(arguments));
3915
+ }
3916
+
3917
+ var Form = {
3918
+ reset: function(form) {
3919
+ form = $(form);
3920
+ form.reset();
3921
+ return form;
3922
+ },
3923
+
3924
+ serializeElements: function(elements, options) {
3925
+ if (typeof options != 'object') options = { hash: !!options };
3926
+ else if (Object.isUndefined(options.hash)) options.hash = true;
3927
+ var key, value, submitted = false, submit = options.submit;
3928
+
3929
+ var data = elements.inject({ }, function(result, element) {
3930
+ if (!element.disabled && element.name) {
3931
+ key = element.name; value = $(element).getValue();
3932
+ if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
3933
+ submit !== false && (!submit || key == submit) && (submitted = true)))) {
3934
+ if (key in result) {
3935
+ if (!Object.isArray(result[key])) result[key] = [result[key]];
3936
+ result[key].push(value);
3937
+ }
3938
+ else result[key] = value;
3939
+ }
3940
+ }
3941
+ return result;
3942
+ });
3943
+
3944
+ return options.hash ? data : Object.toQueryString(data);
3945
+ }
3946
+ };
3947
+
3948
+ Form.Methods = {
3949
+ serialize: function(form, options) {
3950
+ return Form.serializeElements(Form.getElements(form), options);
3951
+ },
3952
+
3953
+ getElements: function(form) {
3954
+ var elements = $(form).getElementsByTagName('*'),
3955
+ element,
3956
+ arr = [ ],
3957
+ serializers = Form.Element.Serializers;
3958
+ for (var i = 0; element = elements[i]; i++) {
3959
+ arr.push(element);
3960
+ }
3961
+ return arr.inject([], function(elements, child) {
3962
+ if (serializers[child.tagName.toLowerCase()])
3963
+ elements.push(Element.extend(child));
3964
+ return elements;
3965
+ })
3966
+ },
3967
+
3968
+ getInputs: function(form, typeName, name) {
3969
+ form = $(form);
3970
+ var inputs = form.getElementsByTagName('input');
3971
+
3972
+ if (!typeName && !name) return $A(inputs).map(Element.extend);
3973
+
3974
+ for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
3975
+ var input = inputs[i];
3976
+ if ((typeName && input.type != typeName) || (name && input.name != name))
3977
+ continue;
3978
+ matchingInputs.push(Element.extend(input));
3979
+ }
3980
+
3981
+ return matchingInputs;
3982
+ },
3983
+
3984
+ disable: function(form) {
3985
+ form = $(form);
3986
+ Form.getElements(form).invoke('disable');
3987
+ return form;
3988
+ },
3989
+
3990
+ enable: function(form) {
3991
+ form = $(form);
3992
+ Form.getElements(form).invoke('enable');
3993
+ return form;
3994
+ },
3995
+
3996
+ findFirstElement: function(form) {
3997
+ var elements = $(form).getElements().findAll(function(element) {
3998
+ return 'hidden' != element.type && !element.disabled;
3999
+ });
4000
+ var firstByIndex = elements.findAll(function(element) {
4001
+ return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
4002
+ }).sortBy(function(element) { return element.tabIndex }).first();
4003
+
4004
+ return firstByIndex ? firstByIndex : elements.find(function(element) {
4005
+ return /^(?:input|select|textarea)$/i.test(element.tagName);
4006
+ });
4007
+ },
4008
+
4009
+ focusFirstElement: function(form) {
4010
+ form = $(form);
4011
+ form.findFirstElement().activate();
4012
+ return form;
4013
+ },
4014
+
4015
+ request: function(form, options) {
4016
+ form = $(form), options = Object.clone(options || { });
4017
+
4018
+ var params = options.parameters, action = form.readAttribute('action') || '';
4019
+ if (action.blank()) action = window.location.href;
4020
+ options.parameters = form.serialize(true);
4021
+
4022
+ if (params) {
4023
+ if (Object.isString(params)) params = params.toQueryParams();
4024
+ Object.extend(options.parameters, params);
4025
+ }
4026
+
4027
+ if (form.hasAttribute('method') && !options.method)
4028
+ options.method = form.method;
4029
+
4030
+ return new Ajax.Request(action, options);
4031
+ }
4032
+ };
4033
+
4034
+ /*--------------------------------------------------------------------------*/
4035
+
4036
+
4037
+ Form.Element = {
4038
+ focus: function(element) {
4039
+ $(element).focus();
4040
+ return element;
4041
+ },
4042
+
4043
+ select: function(element) {
4044
+ $(element).select();
4045
+ return element;
4046
+ }
4047
+ };
4048
+
4049
+ Form.Element.Methods = {
4050
+
4051
+ serialize: function(element) {
4052
+ element = $(element);
4053
+ if (!element.disabled && element.name) {
4054
+ var value = element.getValue();
4055
+ if (value != undefined) {
4056
+ var pair = { };
4057
+ pair[element.name] = value;
4058
+ return Object.toQueryString(pair);
4059
+ }
4060
+ }
4061
+ return '';
4062
+ },
4063
+
4064
+ getValue: function(element) {
4065
+ element = $(element);
4066
+ var method = element.tagName.toLowerCase();
4067
+ return Form.Element.Serializers[method](element);
4068
+ },
4069
+
4070
+ setValue: function(element, value) {
4071
+ element = $(element);
4072
+ var method = element.tagName.toLowerCase();
4073
+ Form.Element.Serializers[method](element, value);
4074
+ return element;
4075
+ },
4076
+
4077
+ clear: function(element) {
4078
+ $(element).value = '';
4079
+ return element;
4080
+ },
4081
+
4082
+ present: function(element) {
4083
+ return $(element).value != '';
4084
+ },
4085
+
4086
+ activate: function(element) {
4087
+ element = $(element);
4088
+ try {
4089
+ element.focus();
4090
+ if (element.select && (element.tagName.toLowerCase() != 'input' ||
4091
+ !(/^(?:button|reset|submit)$/i.test(element.type))))
4092
+ element.select();
4093
+ } catch (e) { }
4094
+ return element;
4095
+ },
4096
+
4097
+ disable: function(element) {
4098
+ element = $(element);
4099
+ element.disabled = true;
4100
+ return element;
4101
+ },
4102
+
4103
+ enable: function(element) {
4104
+ element = $(element);
4105
+ element.disabled = false;
4106
+ return element;
4107
+ }
4108
+ };
4109
+
4110
+ /*--------------------------------------------------------------------------*/
4111
+
4112
+ var Field = Form.Element;
4113
+
4114
+ var $F = Form.Element.Methods.getValue;
4115
+
4116
+ /*--------------------------------------------------------------------------*/
4117
+
4118
+ Form.Element.Serializers = {
4119
+ input: function(element, value) {
4120
+ switch (element.type.toLowerCase()) {
4121
+ case 'checkbox':
4122
+ case 'radio':
4123
+ return Form.Element.Serializers.inputSelector(element, value);
4124
+ default:
4125
+ return Form.Element.Serializers.textarea(element, value);
4126
+ }
4127
+ },
4128
+
4129
+ inputSelector: function(element, value) {
4130
+ if (Object.isUndefined(value)) return element.checked ? element.value : null;
4131
+ else element.checked = !!value;
4132
+ },
4133
+
4134
+ textarea: function(element, value) {
4135
+ if (Object.isUndefined(value)) return element.value;
4136
+ else element.value = value;
4137
+ },
4138
+
4139
+ select: function(element, value) {
4140
+ if (Object.isUndefined(value))
4141
+ return this[element.type == 'select-one' ?
4142
+ 'selectOne' : 'selectMany'](element);
4143
+ else {
4144
+ var opt, currentValue, single = !Object.isArray(value);
4145
+ for (var i = 0, length = element.length; i < length; i++) {
4146
+ opt = element.options[i];
4147
+ currentValue = this.optionValue(opt);
4148
+ if (single) {
4149
+ if (currentValue == value) {
4150
+ opt.selected = true;
4151
+ return;
4152
+ }
4153
+ }
4154
+ else opt.selected = value.include(currentValue);
4155
+ }
4156
+ }
4157
+ },
4158
+
4159
+ selectOne: function(element) {
4160
+ var index = element.selectedIndex;
4161
+ return index >= 0 ? this.optionValue(element.options[index]) : null;
4162
+ },
4163
+
4164
+ selectMany: function(element) {
4165
+ var values, length = element.length;
4166
+ if (!length) return null;
4167
+
4168
+ for (var i = 0, values = []; i < length; i++) {
4169
+ var opt = element.options[i];
4170
+ if (opt.selected) values.push(this.optionValue(opt));
4171
+ }
4172
+ return values;
4173
+ },
4174
+
4175
+ optionValue: function(opt) {
4176
+ return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
4177
+ }
4178
+ };
4179
+
4180
+ /*--------------------------------------------------------------------------*/
4181
+
4182
+
4183
+ Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
4184
+ initialize: function($super, element, frequency, callback) {
4185
+ $super(callback, frequency);
4186
+ this.element = $(element);
4187
+ this.lastValue = this.getValue();
4188
+ },
4189
+
4190
+ execute: function() {
4191
+ var value = this.getValue();
4192
+ if (Object.isString(this.lastValue) && Object.isString(value) ?
4193
+ this.lastValue != value : String(this.lastValue) != String(value)) {
4194
+ this.callback(this.element, value);
4195
+ this.lastValue = value;
4196
+ }
4197
+ }
4198
+ });
4199
+
4200
+ Form.Element.Observer = Class.create(Abstract.TimedObserver, {
4201
+ getValue: function() {
4202
+ return Form.Element.getValue(this.element);
4203
+ }
4204
+ });
4205
+
4206
+ Form.Observer = Class.create(Abstract.TimedObserver, {
4207
+ getValue: function() {
4208
+ return Form.serialize(this.element);
4209
+ }
4210
+ });
4211
+
4212
+ /*--------------------------------------------------------------------------*/
4213
+
4214
+ Abstract.EventObserver = Class.create({
4215
+ initialize: function(element, callback) {
4216
+ this.element = $(element);
4217
+ this.callback = callback;
4218
+
4219
+ this.lastValue = this.getValue();
4220
+ if (this.element.tagName.toLowerCase() == 'form')
4221
+ this.registerFormCallbacks();
4222
+ else
4223
+ this.registerCallback(this.element);
4224
+ },
4225
+
4226
+ onElementEvent: function() {
4227
+ var value = this.getValue();
4228
+ if (this.lastValue != value) {
4229
+ this.callback(this.element, value);
4230
+ this.lastValue = value;
4231
+ }
4232
+ },
4233
+
4234
+ registerFormCallbacks: function() {
4235
+ Form.getElements(this.element).each(this.registerCallback, this);
4236
+ },
4237
+
4238
+ registerCallback: function(element) {
4239
+ if (element.type) {
4240
+ switch (element.type.toLowerCase()) {
4241
+ case 'checkbox':
4242
+ case 'radio':
4243
+ Event.observe(element, 'click', this.onElementEvent.bind(this));
4244
+ break;
4245
+ default:
4246
+ Event.observe(element, 'change', this.onElementEvent.bind(this));
4247
+ break;
4248
+ }
4249
+ }
4250
+ }
4251
+ });
4252
+
4253
+ Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
4254
+ getValue: function() {
4255
+ return Form.Element.getValue(this.element);
4256
+ }
4257
+ });
4258
+
4259
+ Form.EventObserver = Class.create(Abstract.EventObserver, {
4260
+ getValue: function() {
4261
+ return Form.serialize(this.element);
4262
+ }
4263
+ });
4264
+ (function() {
4265
+
4266
+ var Event = {
4267
+ KEY_BACKSPACE: 8,
4268
+ KEY_TAB: 9,
4269
+ KEY_RETURN: 13,
4270
+ KEY_ESC: 27,
4271
+ KEY_LEFT: 37,
4272
+ KEY_UP: 38,
4273
+ KEY_RIGHT: 39,
4274
+ KEY_DOWN: 40,
4275
+ KEY_DELETE: 46,
4276
+ KEY_HOME: 36,
4277
+ KEY_END: 35,
4278
+ KEY_PAGEUP: 33,
4279
+ KEY_PAGEDOWN: 34,
4280
+ KEY_INSERT: 45,
4281
+
4282
+ cache: {}
4283
+ };
4284
+
4285
+ var docEl = document.documentElement;
4286
+ var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
4287
+ && 'onmouseleave' in docEl;
4288
+
4289
+ var _isButton;
4290
+ if (Prototype.Browser.IE) {
4291
+ var buttonMap = { 0: 1, 1: 4, 2: 2 };
4292
+ _isButton = function(event, code) {
4293
+ return event.button === buttonMap[code];
4294
+ };
4295
+ } else if (Prototype.Browser.WebKit) {
4296
+ _isButton = function(event, code) {
4297
+ switch (code) {
4298
+ case 0: return event.which == 1 && !event.metaKey;
4299
+ case 1: return event.which == 1 && event.metaKey;
4300
+ default: return false;
4301
+ }
4302
+ };
4303
+ } else {
4304
+ _isButton = function(event, code) {
4305
+ return event.which ? (event.which === code + 1) : (event.button === code);
4306
+ };
4307
+ }
4308
+
4309
+ function isLeftClick(event) { return _isButton(event, 0) }
4310
+
4311
+ function isMiddleClick(event) { return _isButton(event, 1) }
4312
+
4313
+ function isRightClick(event) { return _isButton(event, 2) }
4314
+
4315
+ function element(event) {
4316
+ event = Event.extend(event);
4317
+
4318
+ var node = event.target, type = event.type,
4319
+ currentTarget = event.currentTarget;
4320
+
4321
+ if (currentTarget && currentTarget.tagName) {
4322
+ if (type === 'load' || type === 'error' ||
4323
+ (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
4324
+ && currentTarget.type === 'radio'))
4325
+ node = currentTarget;
4326
+ }
4327
+
4328
+ if (node.nodeType == Node.TEXT_NODE)
4329
+ node = node.parentNode;
4330
+
4331
+ return Element.extend(node);
4332
+ }
4333
+
4334
+ function findElement(event, expression) {
4335
+ var element = Event.element(event);
4336
+ if (!expression) return element;
4337
+ var elements = [element].concat(element.ancestors());
4338
+ return Selector.findElement(elements, expression, 0);
4339
+ }
4340
+
4341
+ function pointer(event) {
4342
+ return { x: pointerX(event), y: pointerY(event) };
4343
+ }
4344
+
4345
+ function pointerX(event) {
4346
+ var docElement = document.documentElement,
4347
+ body = document.body || { scrollLeft: 0 };
4348
+
4349
+ return event.pageX || (event.clientX +
4350
+ (docElement.scrollLeft || body.scrollLeft) -
4351
+ (docElement.clientLeft || 0));
4352
+ }
4353
+
4354
+ function pointerY(event) {
4355
+ var docElement = document.documentElement,
4356
+ body = document.body || { scrollTop: 0 };
4357
+
4358
+ return event.pageY || (event.clientY +
4359
+ (docElement.scrollTop || body.scrollTop) -
4360
+ (docElement.clientTop || 0));
4361
+ }
4362
+
4363
+
4364
+ function stop(event) {
4365
+ Event.extend(event);
4366
+ event.preventDefault();
4367
+ event.stopPropagation();
4368
+
4369
+ event.stopped = true;
4370
+ }
4371
+
4372
+ Event.Methods = {
4373
+ isLeftClick: isLeftClick,
4374
+ isMiddleClick: isMiddleClick,
4375
+ isRightClick: isRightClick,
4376
+
4377
+ element: element,
4378
+ findElement: findElement,
4379
+
4380
+ pointer: pointer,
4381
+ pointerX: pointerX,
4382
+ pointerY: pointerY,
4383
+
4384
+ stop: stop
4385
+ };
4386
+
4387
+
4388
+ var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
4389
+ m[name] = Event.Methods[name].methodize();
4390
+ return m;
4391
+ });
4392
+
4393
+ if (Prototype.Browser.IE) {
4394
+ function _relatedTarget(event) {
4395
+ var element;
4396
+ switch (event.type) {
4397
+ case 'mouseover': element = event.fromElement; break;
4398
+ case 'mouseout': element = event.toElement; break;
4399
+ default: return null;
4400
+ }
4401
+ return Element.extend(element);
4402
+ }
4403
+
4404
+ Object.extend(methods, {
4405
+ stopPropagation: function() { this.cancelBubble = true },
4406
+ preventDefault: function() { this.returnValue = false },
4407
+ inspect: function() { return '[object Event]' }
4408
+ });
4409
+
4410
+ Event.extend = function(event, element) {
4411
+ if (!event) return false;
4412
+ if (event._extendedByPrototype) return event;
4413
+
4414
+ event._extendedByPrototype = Prototype.emptyFunction;
4415
+ var pointer = Event.pointer(event);
4416
+
4417
+ Object.extend(event, {
4418
+ target: event.srcElement || element,
4419
+ relatedTarget: _relatedTarget(event),
4420
+ pageX: pointer.x,
4421
+ pageY: pointer.y
4422
+ });
4423
+
4424
+ return Object.extend(event, methods);
4425
+ };
4426
+ } else {
4427
+ Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
4428
+ Object.extend(Event.prototype, methods);
4429
+ Event.extend = Prototype.K;
4430
+ }
4431
+
4432
+ function _createResponder(element, eventName, handler) {
4433
+ var registry = Element.retrieve(element, 'prototype_event_registry');
4434
+
4435
+ if (Object.isUndefined(registry)) {
4436
+ CACHE.push(element);
4437
+ registry = Element.retrieve(element, 'prototype_event_registry', $H());
4438
+ }
4439
+
4440
+ var respondersForEvent = registry.get(eventName);
4441
+ if (Object.isUndefined(respondersForEvent)) {
4442
+ respondersForEvent = [];
4443
+ registry.set(eventName, respondersForEvent);
4444
+ }
4445
+
4446
+ if (respondersForEvent.pluck('handler').include(handler)) return false;
4447
+
4448
+ var responder;
4449
+ if (eventName.include(":")) {
4450
+ responder = function(event) {
4451
+ if (Object.isUndefined(event.eventName))
4452
+ return false;
4453
+
4454
+ if (event.eventName !== eventName)
4455
+ return false;
4456
+
4457
+ Event.extend(event, element);
4458
+ handler.call(element, event);
4459
+ };
4460
+ } else {
4461
+ if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
4462
+ (eventName === "mouseenter" || eventName === "mouseleave")) {
4463
+ if (eventName === "mouseenter" || eventName === "mouseleave") {
4464
+ responder = function(event) {
4465
+ Event.extend(event, element);
4466
+
4467
+ var parent = event.relatedTarget;
4468
+ while (parent && parent !== element) {
4469
+ try { parent = parent.parentNode; }
4470
+ catch(e) { parent = element; }
4471
+ }
4472
+
4473
+ if (parent === element) return;
4474
+
4475
+ handler.call(element, event);
4476
+ };
4477
+ }
4478
+ } else {
4479
+ responder = function(event) {
4480
+ Event.extend(event, element);
4481
+ handler.call(element, event);
4482
+ };
4483
+ }
4484
+ }
4485
+
4486
+ responder.handler = handler;
4487
+ respondersForEvent.push(responder);
4488
+ return responder;
4489
+ }
4490
+
4491
+ function _destroyCache() {
4492
+ for (var i = 0, length = CACHE.length; i < length; i++) {
4493
+ Event.stopObserving(CACHE[i]);
4494
+ CACHE[i] = null;
4495
+ }
4496
+ }
4497
+
4498
+ var CACHE = [];
4499
+
4500
+ if (Prototype.Browser.IE)
4501
+ window.attachEvent('onunload', _destroyCache);
4502
+
4503
+ if (Prototype.Browser.WebKit)
4504
+ window.addEventListener('unload', Prototype.emptyFunction, false);
4505
+
4506
+
4507
+ var _getDOMEventName = Prototype.K;
4508
+
4509
+ if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
4510
+ _getDOMEventName = function(eventName) {
4511
+ var translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
4512
+ return eventName in translations ? translations[eventName] : eventName;
4513
+ };
4514
+ }
4515
+
4516
+ function observe(element, eventName, handler) {
4517
+ element = $(element);
4518
+
4519
+ var responder = _createResponder(element, eventName, handler);
4520
+
4521
+ if (!responder) return element;
4522
+
4523
+ if (eventName.include(':')) {
4524
+ if (element.addEventListener)
4525
+ element.addEventListener("dataavailable", responder, false);
4526
+ else {
4527
+ element.attachEvent("ondataavailable", responder);
4528
+ element.attachEvent("onfilterchange", responder);
4529
+ }
4530
+ } else {
4531
+ var actualEventName = _getDOMEventName(eventName);
4532
+
4533
+ if (element.addEventListener)
4534
+ element.addEventListener(actualEventName, responder, false);
4535
+ else
4536
+ element.attachEvent("on" + actualEventName, responder);
4537
+ }
4538
+
4539
+ return element;
4540
+ }
4541
+
4542
+ function stopObserving(element, eventName, handler) {
4543
+ element = $(element);
4544
+
4545
+ var registry = Element.retrieve(element, 'prototype_event_registry');
4546
+
4547
+ if (Object.isUndefined(registry)) return element;
4548
+
4549
+ if (eventName && !handler) {
4550
+ var responders = registry.get(eventName);
4551
+
4552
+ if (Object.isUndefined(responders)) return element;
4553
+
4554
+ responders.each( function(r) {
4555
+ Element.stopObserving(element, eventName, r.handler);
4556
+ });
4557
+ return element;
4558
+ } else if (!eventName) {
4559
+ registry.each( function(pair) {
4560
+ var eventName = pair.key, responders = pair.value;
4561
+
4562
+ responders.each( function(r) {
4563
+ Element.stopObserving(element, eventName, r.handler);
4564
+ });
4565
+ });
4566
+ return element;
4567
+ }
4568
+
4569
+ var responders = registry.get(eventName);
4570
+
4571
+ if (!responders) return;
4572
+
4573
+ var responder = responders.find( function(r) { return r.handler === handler; });
4574
+ if (!responder) return element;
4575
+
4576
+ var actualEventName = _getDOMEventName(eventName);
4577
+
4578
+ if (eventName.include(':')) {
4579
+ if (element.removeEventListener)
4580
+ element.removeEventListener("dataavailable", responder, false);
4581
+ else {
4582
+ element.detachEvent("ondataavailable", responder);
4583
+ element.detachEvent("onfilterchange", responder);
4584
+ }
4585
+ } else {
4586
+ if (element.removeEventListener)
4587
+ element.removeEventListener(actualEventName, responder, false);
4588
+ else
4589
+ element.detachEvent('on' + actualEventName, responder);
4590
+ }
4591
+
4592
+ registry.set(eventName, responders.without(responder));
4593
+
4594
+ return element;
4595
+ }
4596
+
4597
+ function fire(element, eventName, memo, bubble) {
4598
+ element = $(element);
4599
+
4600
+ if (Object.isUndefined(bubble))
4601
+ bubble = true;
4602
+
4603
+ if (element == document && document.createEvent && !element.dispatchEvent)
4604
+ element = document.documentElement;
4605
+
4606
+ var event;
4607
+ if (document.createEvent) {
4608
+ event = document.createEvent('HTMLEvents');
4609
+ event.initEvent('dataavailable', true, true);
4610
+ } else {
4611
+ event = document.createEventObject();
4612
+ event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
4613
+ }
4614
+
4615
+ event.eventName = eventName;
4616
+ event.memo = memo || { };
4617
+
4618
+ if (document.createEvent)
4619
+ element.dispatchEvent(event);
4620
+ else
4621
+ element.fireEvent(event.eventType, event);
4622
+
4623
+ return Event.extend(event);
4624
+ }
4625
+
4626
+
4627
+ Object.extend(Event, Event.Methods);
4628
+
4629
+ Object.extend(Event, {
4630
+ fire: fire,
4631
+ observe: observe,
4632
+ stopObserving: stopObserving
4633
+ });
4634
+
4635
+ Element.addMethods({
4636
+ fire: fire,
4637
+
4638
+ observe: observe,
4639
+
4640
+ stopObserving: stopObserving
4641
+ });
4642
+
4643
+ Object.extend(document, {
4644
+ fire: fire.methodize(),
4645
+
4646
+ observe: observe.methodize(),
4647
+
4648
+ stopObserving: stopObserving.methodize(),
4649
+
4650
+ loaded: false
4651
+ });
4652
+
4653
+ if (window.Event) Object.extend(window.Event, Event);
4654
+ else window.Event = Event;
4655
+ })();
4656
+
4657
+ (function() {
4658
+ /* Support for the DOMContentLoaded event is based on work by Dan Webb,
4659
+ Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
4660
+
4661
+ var timer;
4662
+
4663
+ function fireContentLoadedEvent() {
4664
+ if (document.loaded) return;
4665
+ if (timer) window.clearTimeout(timer);
4666
+ document.loaded = true;
4667
+ document.fire('dom:loaded');
4668
+ }
4669
+
4670
+ function checkReadyState() {
4671
+ if (document.readyState === 'complete') {
4672
+ document.stopObserving('readystatechange', checkReadyState);
4673
+ fireContentLoadedEvent();
4674
+ }
4675
+ }
4676
+
4677
+ function pollDoScroll() {
4678
+ try { document.documentElement.doScroll('left'); }
4679
+ catch(e) {
4680
+ timer = pollDoScroll.defer();
4681
+ return;
4682
+ }
4683
+ fireContentLoadedEvent();
4684
+ }
4685
+
4686
+ if (document.addEventListener) {
4687
+ document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
4688
+ } else {
4689
+ document.observe('readystatechange', checkReadyState);
4690
+ if (window == top)
4691
+ timer = pollDoScroll.defer();
4692
+ }
4693
+
4694
+ Event.observe(window, 'load', fireContentLoadedEvent);
4695
+ })();
4696
+
4697
+ Element.addMethods();
4698
+
4699
+ /*------------------------------- DEPRECATED -------------------------------*/
4700
+
4701
+ Hash.toQueryString = Object.toQueryString;
4702
+
4703
+ var Toggle = { display: Element.toggle };
4704
+
4705
+ Element.Methods.childOf = Element.Methods.descendantOf;
4706
+
4707
+ var Insertion = {
4708
+ Before: function(element, content) {
4709
+ return Element.insert(element, {before:content});
4710
+ },
4711
+
4712
+ Top: function(element, content) {
4713
+ return Element.insert(element, {top:content});
4714
+ },
4715
+
4716
+ Bottom: function(element, content) {
4717
+ return Element.insert(element, {bottom:content});
4718
+ },
4719
+
4720
+ After: function(element, content) {
4721
+ return Element.insert(element, {after:content});
4722
+ }
4723
+ };
4724
+
4725
+ var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
4726
+
4727
+ var Position = {
4728
+ includeScrollOffsets: false,
4729
+
4730
+ prepare: function() {
4731
+ this.deltaX = window.pageXOffset
4732
+ || document.documentElement.scrollLeft
4733
+ || document.body.scrollLeft
4734
+ || 0;
4735
+ this.deltaY = window.pageYOffset
4736
+ || document.documentElement.scrollTop
4737
+ || document.body.scrollTop
4738
+ || 0;
4739
+ },
4740
+
4741
+ within: function(element, x, y) {
4742
+ if (this.includeScrollOffsets)
4743
+ return this.withinIncludingScrolloffsets(element, x, y);
4744
+ this.xcomp = x;
4745
+ this.ycomp = y;
4746
+ this.offset = Element.cumulativeOffset(element);
4747
+
4748
+ return (y >= this.offset[1] &&
4749
+ y < this.offset[1] + element.offsetHeight &&
4750
+ x >= this.offset[0] &&
4751
+ x < this.offset[0] + element.offsetWidth);
4752
+ },
4753
+
4754
+ withinIncludingScrolloffsets: function(element, x, y) {
4755
+ var offsetcache = Element.cumulativeScrollOffset(element);
4756
+
4757
+ this.xcomp = x + offsetcache[0] - this.deltaX;
4758
+ this.ycomp = y + offsetcache[1] - this.deltaY;
4759
+ this.offset = Element.cumulativeOffset(element);
4760
+
4761
+ return (this.ycomp >= this.offset[1] &&
4762
+ this.ycomp < this.offset[1] + element.offsetHeight &&
4763
+ this.xcomp >= this.offset[0] &&
4764
+ this.xcomp < this.offset[0] + element.offsetWidth);
4765
+ },
4766
+
4767
+ overlap: function(mode, element) {
4768
+ if (!mode) return 0;
4769
+ if (mode == 'vertical')
4770
+ return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
4771
+ element.offsetHeight;
4772
+ if (mode == 'horizontal')
4773
+ return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
4774
+ element.offsetWidth;
4775
+ },
4776
+
4777
+
4778
+ cumulativeOffset: Element.Methods.cumulativeOffset,
4779
+
4780
+ positionedOffset: Element.Methods.positionedOffset,
4781
+
4782
+ absolutize: function(element) {
4783
+ Position.prepare();
4784
+ return Element.absolutize(element);
4785
+ },
4786
+
4787
+ relativize: function(element) {
4788
+ Position.prepare();
4789
+ return Element.relativize(element);
4790
+ },
4791
+
4792
+ realOffset: Element.Methods.cumulativeScrollOffset,
4793
+
4794
+ offsetParent: Element.Methods.getOffsetParent,
4795
+
4796
+ page: Element.Methods.viewportOffset,
4797
+
4798
+ clone: function(source, target, options) {
4799
+ options = options || { };
4800
+ return Element.clonePosition(target, source, options);
4801
+ }
4802
+ };
4803
+
4804
+ /*--------------------------------------------------------------------------*/
4805
+
4806
+ if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
4807
+ function iter(name) {
4808
+ return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
4809
+ }
4810
+
4811
+ instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
4812
+ function(element, className) {
4813
+ className = className.toString().strip();
4814
+ var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
4815
+ return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
4816
+ } : function(element, className) {
4817
+ className = className.toString().strip();
4818
+ var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
4819
+ if (!classNames && !className) return elements;
4820
+
4821
+ var nodes = $(element).getElementsByTagName('*');
4822
+ className = ' ' + className + ' ';
4823
+
4824
+ for (var i = 0, child, cn; child = nodes[i]; i++) {
4825
+ if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
4826
+ (classNames && classNames.all(function(name) {
4827
+ return !name.toString().blank() && cn.include(' ' + name + ' ');
4828
+ }))))
4829
+ elements.push(Element.extend(child));
4830
+ }
4831
+ return elements;
4832
+ };
4833
+
4834
+ return function(className, parentElement) {
4835
+ return $(parentElement || document.body).getElementsByClassName(className);
4836
+ };
4837
+ }(Element.Methods);
4838
+
4839
+ /*--------------------------------------------------------------------------*/
4840
+
4841
+ Element.ClassNames = Class.create();
4842
+ Element.ClassNames.prototype = {
4843
+ initialize: function(element) {
4844
+ this.element = $(element);
4845
+ },
4846
+
4847
+ _each: function(iterator) {
4848
+ this.element.className.split(/\s+/).select(function(name) {
4849
+ return name.length > 0;
4850
+ })._each(iterator);
4851
+ },
4852
+
4853
+ set: function(className) {
4854
+ this.element.className = className;
4855
+ },
4856
+
4857
+ add: function(classNameToAdd) {
4858
+ if (this.include(classNameToAdd)) return;
4859
+ this.set($A(this).concat(classNameToAdd).join(' '));
4860
+ },
4861
+
4862
+ remove: function(classNameToRemove) {
4863
+ if (!this.include(classNameToRemove)) return;
4864
+ this.set($A(this).without(classNameToRemove).join(' '));
4865
+ },
4866
+
4867
+ toString: function() {
4868
+ return $A(this).join(' ');
4869
+ }
4870
+ };
4871
+
4872
+ Object.extend(Element.ClassNames.prototype, Enumerable);
4873
+
4874
+ /*--------------------------------------------------------------------------*/
js/bootic/prototype-color-picker/js/prototype_colorpicker.js ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var colorPicker=Class.create({
2
+ initialize:function(){
3
+ var args = $A(arguments);
4
+ this.el = $(args[0]);
5
+ this.options = Object.extend({
6
+ previewElement:false,
7
+ inputElement:false,
8
+ eventName: 'click',
9
+ onLoad:function(){return true;},
10
+ onShow:function(){return true;},
11
+ onBeforeShow:function(){return true;},
12
+ onHide:function(){return true;},
13
+ onChange:function(){return true;},
14
+ onSubmit:function(){return true;},
15
+ color: '000000',
16
+ origColor: false,
17
+ livePreview: true,
18
+ hideOnSubmit:true,
19
+ updateOnChange:true,
20
+ flat: false,
21
+ hasExtraInfo:false,
22
+ extraInfo:function(){return true;}
23
+ },args[1]);
24
+ this.ids = {};
25
+ this.fields = [];
26
+ this.current = {}
27
+ this.inAction = false;
28
+ this.charMin = 65;
29
+ this.visible = false;
30
+ this.time = new Date().getTime();
31
+ this.id = 'colorpicker_' + this.time;
32
+ var cp_tpl = '<div class="colorpicker_color"><div><div></div></div></div><div class="colorpicker_hue"><div></div></div><div class="colorpicker_new_color"></div><div class="colorpicker_current_color"></div><div class="colorpicker_hex"><input type="text" maxlength="6" size="6" /></div><div class="colorpicker_rgb_r colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_g colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_h colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_s colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_submit"></div><div class="colorpicker_extra" style="display:none;"></div><div class="colorpicker_extrafill" style="display:none;"></div>';
33
+ this.cp = $(document.createElement('DIV'));
34
+ this.cp.writeAttribute('id',this.id).addClassName('colorpicker').setStyle({'display':'none'}).insert(cp_tpl);
35
+ if (typeof this.options.color == 'string') {
36
+ this.color = this.HexToHSB(this.options.color);
37
+ } else if (this.color.r != undefined && this.options.color.g != undefined && this.options.color.b != undefined) {
38
+ this.color = this.RGBToHSB(this.options.color);
39
+ } else if (this.options.color.h != undefined && this.options.color.s != undefined && this.options.color.b != undefined) {
40
+ this.color = this.fixHSB(this.options.color);
41
+ } else {
42
+ return this;
43
+ }
44
+ var color_picker = this;
45
+
46
+ this.options.origColor = this.options.color;
47
+
48
+ if (this.options.flat) {
49
+ this.options.hideOnSubmit = false;
50
+ this.cp.setStyle({position: 'relative',display: 'block'});
51
+ this.el.insert({after:this.cp});
52
+ this.cp.show();
53
+ } else {
54
+ document.body.appendChild(this.cp);
55
+ $(this.el).observe(this.options.eventName, this.show.bind(this));
56
+ }
57
+ this.fields = $$('#'+this.id+' input')
58
+ this.fields.each(function(field){
59
+ field.observe('keyup', this.keyUp.bind(this));
60
+ field.observe('change', function(ev){this.change(ev.element());}.bind(this));
61
+ field.observe('blur', this.blur.bind(this));
62
+ field.observe('focus', this.focus.bind(this));
63
+ }.bind(this));
64
+ $$('#'+this.id+' span').each(function(span){span.observe('mousedown', this.downIncrement.bind(this));}.bind(this));
65
+ this.cp.down('div.colorpicker_current_color').observe('click', this.restoreOriginal.bind(this));
66
+ this.selector = this.cp.down('div.colorpicker_color');
67
+ this.selector.observe('mousedown', this.downSelector.bind(this));
68
+ this.selectorIndic = this.selector.down('div').down('div');
69
+ this.hue = this.cp.down('div.colorpicker_hue div');
70
+ this.cp.down('div.colorpicker_hue').observe('mousedown', this.downHue.bind(this));
71
+ this.newColor = this.cp.down('div.colorpicker_new_color');
72
+ this.currentColor = this.cp.down('div.colorpicker_current_color');
73
+ this.submit = this.cp.down('div.colorpicker_submit');
74
+ this.submit.observe('mouseenter', this.enterSubmit.bind(this));
75
+ this.submit.observe('mouseleave', this.leaveSubmit.bind(this));
76
+ this.submit.observe('click', this.clickSubmit.bind(this));
77
+
78
+ this.extra = this.cp.down('div.colorpicker_extra');
79
+ this.extraInfo = this.cp.down('div.colorpicker_extrafill');
80
+ if(this.options.hasExtraInfo == true){
81
+ this.extra.show();
82
+ this.options.extraInfo(this);
83
+ this.extra.observe('mouseenter',function(ev){ev.element().addClassName('colorpicker_focus');});
84
+ this.extra.observe('mouseleave',function(ev){ev.element().removeClassName('colorpicker_focus');});
85
+ this.extra.observe('click', function(ev){
86
+ var el = this.extraInfo;
87
+ if(el.visible()) el.hide();
88
+ else el.show();
89
+ }.bind(this));
90
+ }
91
+ this.fillRGBFields(this.color);
92
+ this.fillHSBFields(this.color);
93
+ this.fillHexFields(this.color);
94
+ this.setHue(this.color);
95
+ this.setSelector(this.color);
96
+ this.setCurrentColor((this.options.origColor?this.options.origColor:this.color));
97
+ this.setNewColor(this.color);
98
+ if($(this.options.previewElement)) $(this.options.previewElement).setStyle({'backgroundColor':'#'+this.HSBToHex(this.color)});
99
+
100
+ //Event.observe(window, "scroll", this.repositionPicker.bindAsEventListener(this));
101
+ this.options.onLoad(this);
102
+ return this;
103
+
104
+ },
105
+ fillRGBFields:function(hsb) {
106
+ var rgb = this.HSBToRGB(hsb);
107
+ this.fields[1].value = rgb.r;
108
+ this.fields[2].value = rgb.g;
109
+ this.fields[3].value = rgb.b;
110
+ },
111
+ fillHSBFields:function(hsb) {
112
+ this.fields[4].value = parseInt(hsb.h);
113
+ this.fields[5].value = parseInt(hsb.s);
114
+ this.fields[6].value = parseInt(hsb.b);
115
+ },
116
+ fillHexFields:function(hsb){
117
+ this.fields[0].value = this.HSBToHex(hsb).toUpperCase();
118
+ },
119
+ setSelector:function(hsb){
120
+ this.selector.setStyle({'backgroundColor':'#' + this.HSBToHex({h: hsb.h, s: 100, b: 100})});
121
+ this.selectorIndic.setStyle({
122
+ left: parseInt(150 * hsb.s/100, 10)+'px',
123
+ top: parseInt(150 * (100-hsb.b)/100, 10)+'px'
124
+ });
125
+ },
126
+ setHue:function(hsb){
127
+ this.hue.setStyle({'top':parseInt(150 - 150 * hsb.h/360, 10)+'px'});
128
+ },
129
+ setCurrentColor:function(hsb){
130
+ this.currentColor.setStyle({'backgroundColor':'#' + this.HSBToHex(hsb)});
131
+ if(!this.options.origColor) this.options.origColor = hsb;
132
+ },
133
+ setNewColor:function(hsb){
134
+ this.newColor.setStyle({'backgroundColor':'#' + this.HSBToHex(hsb)});
135
+ },
136
+ keyUp:function(ev){
137
+ var pressedKey = ev.charCode || ev.keyCode || -1;
138
+ if ((pressedKey > this.charMin && pressedKey <= 90) || pressedKey == 32) {
139
+ return false;
140
+ }
141
+ if (this.options.livePreview === true) {
142
+ this.change(ev.element());
143
+ }
144
+ },
145
+ change:function(el){
146
+ var col;
147
+ if (el.up().className.indexOf('_hex')!=-1) {
148
+ this.color = col = this.HexToHSB(this.fixHex(el.value));
149
+ } else if (el.up().className.indexOf('_rgb')!=-1) {
150
+ this.color = col = this.RGBToHSB(this.fixRGB({
151
+ r: parseInt(this.fields[1].value, 10),
152
+ g: parseInt(this.fields[2].value, 10),
153
+ b: parseInt(this.fields[3].value, 10)
154
+ }));
155
+ } else {
156
+ this.color = col = this.fixHSB({
157
+ h: parseInt(this.fields[4].value, 10),
158
+ s: parseInt(this.fields[5].value, 10),
159
+ b: parseInt(this.fields[6].value, 10)
160
+ });
161
+ }
162
+ this.setSelector(col);
163
+ this.setHue(col);
164
+ this.setNewColor(col);
165
+ this.options.onChange(this);
166
+ if(this.options.updateOnChange){
167
+ if(this.el.nodeName == 'INPUT') this.el.value = this.HSBToHex(col);
168
+ if($(this.options.inputElement)) $(this.options.inputElement).value = this.HSBToHex(col);
169
+ if($(this.options.previewElement)) $(this.options.previewElement).setStyle({'backgroundColor':'#'+this.HSBToHex(col)});
170
+ }
171
+ },
172
+ blur:function(ev){
173
+ ev.element().up().removeClassName('colorpicker_focus');
174
+ },
175
+ focus:function(ev){
176
+ this.charMin = ev.element().hasClassName('_hex') > 0 ? 70 : 65;
177
+ ev.element().addClassName('colorpicker_focus');
178
+ },
179
+ downIncrement:function(ev){
180
+ var parent = ev.element().up();
181
+ var field = parent.down('input');
182
+ field.focus();
183
+
184
+ this.current = {
185
+ el: parent.addClassName('colorpicker_slider'),
186
+ max: (parent.className.indexOf('_hsb_h')!=-1)? 360 : ((parent.className.indexOf('_hsb')!=-1)? 100 : 255),
187
+ y: ev.pointerY(),
188
+ field: field,
189
+ val: parseInt(field.value, 10),
190
+ preview: this.options.livePreview
191
+ };
192
+ this.eventUpIncrement = this.upIncrement.bindAsEventListener(this);
193
+ document.observe("mouseup", this.eventUpIncrement);
194
+ this.eventMoveIncrement = this.moveIncrement.bindAsEventListener(this);
195
+ document.observe("mousemove", this.eventMoveIncrement);
196
+ },
197
+ moveIncrement:function(ev){
198
+ this.current.field.value = Math.max(0, Math.min(this.current.max, parseInt(this.current.val + ev.pointerY() - this.current.y, 10)));
199
+ if(this.current.preview) {
200
+ this.change(this.current.field);
201
+ }
202
+ return false;
203
+ },
204
+ upIncrement:function(ev){
205
+ this.change(ev.element());
206
+ this.current.el.removeClassName('colorpicker_slider');
207
+ this.current.el.down('input').focus();
208
+ if(ev.element().up().className.indexOf('_hsb')!=-1) this.fillRGBFields(this.color);
209
+ else this.fillHSBFields(this.color);
210
+ this.fillHexFields(this.color);
211
+ Event.stopObserving(document, "mouseup", this.eventUpIncrement);
212
+ Event.stopObserving(document, "mousemove", this.eventMoveIncrement);
213
+ return false;
214
+ },
215
+ downHue:function(ev){
216
+ this.current = {
217
+ y: ev.element().cumulativeOffset().top,
218
+ preview: this.options.livePreview
219
+ };
220
+ this.eventUpHue = this.upHue.bindAsEventListener(this);
221
+ document.observe("mouseup", this.eventUpHue);
222
+ this.eventMoveHue = this.moveHue.bindAsEventListener(this);
223
+ document.observe("mousemove", this.eventMoveHue);
224
+ },
225
+ moveHue:function(ev){
226
+ this.fields[4].value = parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pointerY() - this.current.y))))/150, 10)
227
+ this.change(this.fields[4]);
228
+ return false;
229
+ },
230
+ upHue:function(ev){
231
+ this.fillRGBFields(this.color);
232
+ this.fillHexFields(this.color);
233
+ this.fields[4].value = parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pointerY() - this.current.y))))/150, 10)
234
+ this.change(this.fields[4]);
235
+ Event.stopObserving(document, "mouseup", this.eventUpHue);
236
+ Event.stopObserving(document, "mousemove", this.eventMoveHue);
237
+ return false;
238
+ },
239
+ downSelector:function(ev){
240
+ this.current = {
241
+ pos: ev.element().cumulativeOffset(),
242
+ preview:this.options.livePreview
243
+ };
244
+ this.eventUpSelector = this.upSelector.bindAsEventListener(this);
245
+ document.observe("mouseup", this.eventUpSelector);
246
+ this.eventMoveSelector = this.moveSelector.bindAsEventListener(this);
247
+ document.observe("mousemove", this.eventMoveSelector);
248
+ },
249
+ moveSelector:function(ev){
250
+ this.fields[6].value = parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pointerY() - this.current.pos.top))))/150, 10);
251
+ this.fields[5].value = parseInt(100*(Math.max(0,Math.min(150,(ev.pointerX() - this.current.pos.left))))/150, 10);
252
+ this.change(ev.element());
253
+ return false;
254
+ },
255
+ upSelector:function(ev){
256
+ this.moveSelector(ev);
257
+ this.fillRGBFields(this.color);
258
+ this.fillHexFields(this.color);
259
+ Event.stopObserving(document, "mouseup", this.eventUpSelector);
260
+ Event.stopObserving(document, "mousemove", this.eventMoveSelector);
261
+ return false;
262
+ },
263
+ enterSubmit:function(ev){
264
+ ev.element().addClassName('colorpicker_focus');
265
+ },
266
+ leaveSubmit:function(ev){
267
+ ev.element().removeClassName('colorpicker_focus');
268
+ },
269
+ clickSubmit:function(ev){
270
+ var col = this.color;
271
+ this.origColor = col;
272
+ this.setCurrentColor(col);
273
+ if(this.el.nodeName == 'INPUT') this.el.value = this.HSBToHex(col);
274
+ if($(this.options.inputElement)) $(this.options.inputElement).value = this.HSBToHex(col);
275
+ if($(this.options.previewElement)) $(this.options.previewElement).setStyle({'backgroundColor':'#'+this.HSBToHex(col)});
276
+ this.options.onSubmit(this);
277
+ if(this.options.hideOnSubmit) this.hidePicker();
278
+ },
279
+ show:function(ev){
280
+ this.options.onBeforeShow(this);
281
+ this.positionPicker(ev);
282
+ if(this.options.onShow(this)) this.cp.setStyle({display:'block'});
283
+ this.eventHide = this.hide.bindAsEventListener(this);
284
+ document.observe("mousedown", this.eventHide);
285
+ return false;
286
+ },
287
+ hide:function(ev){
288
+ var el = (typeof(ev) == 'object')?ev.element():$(document.body);
289
+ if (!this.isChildOf(this.cp, el)) {
290
+ if(this.options.onHide(this)) this.cp.setStyle({'display':'none'});
291
+ Event.stopObserving(document, "mousedown", this.eventHide);
292
+ }
293
+ },
294
+ isChildOf:function(parentEl, el) {
295
+ if (parentEl == el) {
296
+ return true;
297
+ }
298
+ return $(el).descendantOf(parentEl);
299
+ },
300
+ getViewport:function(){
301
+ return {
302
+ l : document.viewport.getScrollOffsets().left,
303
+ t : document.viewport.getScrollOffsets().top,
304
+ w : document.viewport.getWidth(),
305
+ h : document.viewport.getHeight()
306
+ };
307
+ },
308
+ positionPicker:function(ev){
309
+ var pos = ev.element().cumulativeOffset();
310
+ var viewPort = this.getViewport();
311
+ var top = pos.top + ev.element().getHeight();
312
+ var left = pos.left;
313
+ if (top + 176 > viewPort.t + viewPort.h) {
314
+ top -= ev.element().getHeight() + 176;
315
+ }
316
+ if (left + 356 > viewPort.l + viewPort.w) {
317
+ left -= (356-this.el.getWidth());
318
+ }
319
+ this.cp.setStyle({left: left + 'px', top: top + 'px'});
320
+
321
+ },
322
+ fixHSB:function(hsb){
323
+ return {
324
+ h: Math.min(360, Math.max(0, hsb.h)),
325
+ s: Math.min(100, Math.max(0, hsb.s)),
326
+ b: Math.min(100, Math.max(0, hsb.b))
327
+ };
328
+ },
329
+ fixRGB:function(rgb){
330
+ return {
331
+ r: Math.min(255, Math.max(0, rgb.r)),
332
+ g: Math.min(255, Math.max(0, rgb.g)),
333
+ b: Math.min(255, Math.max(0, rgb.b))
334
+ };
335
+ },
336
+ fixHex:function(hex){
337
+ var len = 6 - hex.length;
338
+ if (len > 0) {
339
+ var o = [];
340
+ for (var i=0; i<len; i++) {
341
+ o.push('0');
342
+ }
343
+ o.push(hex);
344
+ hex = o.join('');
345
+ }
346
+ return hex;
347
+ },
348
+ HexToRGB:function(hex){
349
+ var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
350
+ return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
351
+ },
352
+ HexToHSB:function(hex){
353
+ return this.RGBToHSB(this.HexToRGB(hex));
354
+ },
355
+ RGBToHSB:function(rgb){
356
+ var hsb = {
357
+ h: 0,
358
+ s: 0,
359
+ b: 0
360
+ };
361
+ var min = Math.min(rgb.r, rgb.g, rgb.b);
362
+ var max = Math.max(rgb.r, rgb.g, rgb.b);
363
+ var delta = max - min;
364
+ hsb.b = max;
365
+ if (max != 0) {
366
+
367
+ }
368
+ hsb.s = max != 0 ? 255 * delta / max : 0;
369
+ if (hsb.s != 0) {
370
+ if (rgb.r == max) {
371
+ hsb.h = (rgb.g - rgb.b) / delta;
372
+ } else if (rgb.g == max) {
373
+ hsb.h = 2 + (rgb.b - rgb.r) / delta;
374
+ } else {
375
+ hsb.h = 4 + (rgb.r - rgb.g) / delta;
376
+ }
377
+ } else {
378
+ hsb.h = -1;
379
+ }
380
+ hsb.h *= 60;
381
+ if (hsb.h < 0) {
382
+ hsb.h += 360;
383
+ }
384
+ hsb.s *= 100/255;
385
+ hsb.b *= 100/255;
386
+ return hsb;
387
+ },
388
+ HSBToRGB:function(hsb){
389
+ var rgb = {};
390
+ var h = Math.round(hsb.h);
391
+ var s = Math.round(hsb.s*255/100);
392
+ var v = Math.round(hsb.b*255/100);
393
+ if(s == 0) {
394
+ rgb.r = rgb.g = rgb.b = v;
395
+ } else {
396
+ var t1 = v;
397
+ var t2 = (255-s)*v/255;
398
+ var t3 = (t1-t2)*(h%60)/60;
399
+ if(h==360) h = 0;
400
+ if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3}
401
+ else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3}
402
+ else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3}
403
+ else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3}
404
+ else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3}
405
+ else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3}
406
+ else {rgb.r=0; rgb.g=0; rgb.b=0}
407
+ }
408
+ return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
409
+ },
410
+ RGBToHex:function(rgb){
411
+ var hex = [
412
+ rgb.r.toString(16),
413
+ rgb.g.toString(16),
414
+ rgb.b.toString(16)
415
+ ];
416
+ hex.each(function(val,nr) {
417
+ if(val.length == 1){
418
+ hex[nr] = '0' + val;
419
+ }
420
+ });
421
+ return hex.join('');
422
+ },
423
+ HSBToHex:function(hsb){
424
+ return this.RGBToHex(this.HSBToRGB(hsb));
425
+ },
426
+ restoreOriginal:function(){
427
+ var col = this.options.origColor;
428
+ this.color = col;
429
+ this.fillRGBFields(col);
430
+ this.fillHexFields(col);
431
+ this.fillHSBFields(col);
432
+ this.setSelector(col);
433
+ this.setHue(col);
434
+ this.setNewColor(col);
435
+ },
436
+ showPicker: function() {
437
+ this.cp.show();
438
+ },
439
+ hidePicker: function() {
440
+ this.cp.hide();
441
+ },
442
+ setColor: function(col) {
443
+ if (typeof col == 'string') {
444
+ col = this.HexToHSB(col);
445
+ } else if (col.r != undefined && col.g != undefined && col.b != undefined) {
446
+ col = this.RGBToHSB(col);
447
+ } else if (col.h != undefined && col.s != undefined && col.b != undefined) {
448
+ col = fixHSB(col);
449
+ } else {
450
+ return this;
451
+ }
452
+ this.color = col;
453
+ this.origColor = col;
454
+ this.fillRGBFields(col);
455
+ this.fillHSBFields(col);
456
+ this.fillHexFields(col);
457
+ this.setHue(col);
458
+ this.setSelector(col);
459
+ this.setCurrentColor(col);
460
+ this.setNewColor(col);
461
+ }
462
+ });
lib/Bootic/Api/Client.php ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Api_Client
7
+ {
8
+ /** @var string */
9
+ protected $_defaultUri = 'http://jfuchs.tech.bootic.com/cgi-bin/api';
10
+ // protected $_defaultUri = 'https://secure.bootic.com/cgi-bin/api';
11
+
12
+ /** @var array|Zend_Uri_Http[] */
13
+ protected $_uris = array();
14
+
15
+ /** @var Zend_Http_CookieJar */
16
+ protected $_cookieJar;
17
+
18
+ /** @var Zend_Rest_Client */
19
+ protected $_restClient;
20
+
21
+ const COMMON_LIST_SHIPPING_COMPANY = 'shipping_company';
22
+ const COMMON_LIST_COUNTRY = 'country';
23
+ const COMMON_LIST_REGION = 'region';
24
+
25
+ /**
26
+ * Constructor
27
+ *
28
+ * @param Zend_Rest_Client $restClient
29
+ */
30
+ public function __construct(Zend_Rest_Client $restClient = null)
31
+ {
32
+ if (null !== $restClient) {
33
+ $this->setRestClient($restClient);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Initialize stuff like authentication
39
+ *
40
+ * @return void
41
+ */
42
+ protected function _init()
43
+ {
44
+ // URIs
45
+ if (empty($this->_uris)) {
46
+ $this->_configureUris();
47
+ }
48
+
49
+ $httpClient = $this->getRestClient()->getHttpClient();
50
+ $httpClient->resetParameters();
51
+ $this->getRestClient()->setUri($this->getUri('api_ssl'));
52
+ if (null == $this->_cookieJar) {
53
+ $httpClient->setCookieJar();
54
+ $this->_cookieJar = $httpClient->getCookieJar();
55
+ } else {
56
+ $httpClient->setCookieJar($this->_cookieJar);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Makes a call to the API to retrieve actual URIs to use for
62
+ * next calls.
63
+ */
64
+ protected function _configureUris()
65
+ {
66
+ $result = $this->_doRequest('/common/info');
67
+ if ($result->isSuccess()) {
68
+ $this->addUri('api', $result->getData('api_url'));
69
+ $this->addUri('api_ssl', $result->getData('api_url_ssl'));
70
+ $this->addUri('static', $result->getData('server_url_static'));
71
+ $this->addUri('server_main', $result->getData('server_url_main'));
72
+ $this->addUri('server_ssl', $result->getData('server_url_ssl'));
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Authenticates user using the username and password
78
+ *
79
+ * @param string $username
80
+ * @param string $password
81
+ * @return Bootic_Api_Result
82
+ */
83
+ public function authenticateByEmailAndPassword($email, $password)
84
+ {
85
+ return $this->_request('/account/login', array(
86
+ 'email' => $email,
87
+ 'password' => $password,
88
+ ));
89
+ }
90
+
91
+ /**
92
+ * Authenticates user using the API Key
93
+ *
94
+ * @param $apiKey
95
+ * @return Bootic_Api_Result
96
+ */
97
+ public function authenticateByApiKey($apiKey)
98
+ {
99
+ return $this->_request('/account/login', array(
100
+ 'api_key' => $apiKey
101
+ ));
102
+ }
103
+
104
+ /**
105
+ * @return Bootic_Api_Result
106
+ */
107
+ public function getInfo()
108
+ {
109
+ $result = $this->_request('/common/info');
110
+
111
+ return $result;
112
+ }
113
+
114
+ /**
115
+ * get common list
116
+ * @param str $type
117
+ */
118
+ public function getCommonList($type)
119
+ {
120
+ return $this->_request('/common/list', array('name' => $type));
121
+ }
122
+ /**
123
+ * Creates an account
124
+ *
125
+ * @param array $credentials
126
+ * @return Bootic_Api_Result
127
+ */
128
+ public function createAccount($email, $password)
129
+ {
130
+ $result = $this->_request('account/create', array(
131
+ 'email' => $email,
132
+ 'password' => $password
133
+ ));
134
+
135
+ return $result;
136
+ }
137
+
138
+ /**
139
+ * Generates a unique API Key that can be used as an alternate way to connect
140
+ * to the API
141
+ *
142
+ * @param $apiKeyName
143
+ * @param $apiKeyDescription
144
+ * @return Bootic_Api_Result
145
+ */
146
+ public function createKey($apiKeyName, $apiKeyDescription)
147
+ {
148
+ $result = $this->_request('/account/create_key', array(
149
+ 'api_key_name' => $apiKeyName,
150
+ 'api_key_description' => $apiKeyDescription
151
+ ));
152
+
153
+ return $result;
154
+ }
155
+
156
+ /**
157
+ * Get Profile
158
+ *
159
+ * @return Bootic_Api_Result
160
+ */
161
+ public function getProfile()
162
+ {
163
+ $result = $this->_request('/account/get_profile');
164
+
165
+ return $result;
166
+ }
167
+
168
+ /**
169
+ * Edit Profile
170
+ *
171
+ * @param array $profile The profile data to post
172
+ * @return Bootic_Api_Result
173
+ */
174
+ public function editProfile(array $profile)
175
+ {
176
+ $result = $this->_request('/account/edit_profile', $profile);
177
+
178
+ return $result;
179
+ }
180
+
181
+ /**
182
+ * Edit Profile Picture
183
+ *
184
+ * @param array $data
185
+ * @return Bootic_Api_Result
186
+ */
187
+ public function editProfilePicture(array $data)
188
+ {
189
+ $result = $this->_request('/account/edit_profile_image', $data);
190
+
191
+ return $result;
192
+ }
193
+
194
+ /**
195
+ * Get order list
196
+ *
197
+ * @param array $params
198
+ * @return Bootic_Api_Result
199
+ */
200
+ public function getOrderList(array $params = array())
201
+ {
202
+ $result = $this->_request('/transaction/list_orders', $params);
203
+ if ($result->getData('record_count') > 0) {
204
+ //load additional data
205
+ $transactions = array();
206
+ foreach ($result->getData('transactions') as $k => $transaction) {
207
+ $p = array('id' => $transaction['transaction']);
208
+ $transactionResult = $this->_request('/transaction/get_order_details', $p);
209
+ if (is_array($transactionResult->getData())) {
210
+ $transactions[] = $transaction + $transactionResult->getData();
211
+ }
212
+ }
213
+ $result->setData('transactions', $transactions);
214
+ }
215
+
216
+ return $result;
217
+ }
218
+
219
+ /**
220
+ * Update transaction status
221
+ *
222
+ * @param int $id
223
+ * @param array $data
224
+ */
225
+ public function updateTransactionStatus($id, array $data)
226
+ {
227
+ $data['id'] = $id;
228
+ $result = $this->_request('/transaction/update_status', $data);
229
+ return $result;
230
+ }
231
+
232
+ /**
233
+ * get sales list
234
+ * @param array $params
235
+ */
236
+ public function getSalesList(array $params = array())
237
+ {
238
+ $result = $this->_request('/transaction/list_sales', $params);
239
+ return $result;
240
+ }
241
+
242
+ /**
243
+ * Gets list of storefronts - in our use case, should only be one
244
+ *
245
+ * @return Bootic_Api_Result
246
+ */
247
+ public function getStoreFrontList()
248
+ {
249
+ $result = $this->_request('/storefront/list');
250
+
251
+ return $result;
252
+ }
253
+
254
+ /**
255
+ * Create a storefront
256
+ *
257
+ * @param array $data
258
+ * @return Bootic_Api_Result
259
+ */
260
+ public function createStorefront(array $data)
261
+ {
262
+ $result = $this->_request('/storefront/create', $data);
263
+
264
+ return $result;
265
+ }
266
+
267
+ /**
268
+ * Updates a particular storefront
269
+ *
270
+ * @param array $data
271
+ * @return Bootic_Api_Result
272
+ */
273
+ public function updateStorefront(array $data)
274
+ {
275
+ $result = $this->_request('/storefront/update', $data);
276
+
277
+ return $result;
278
+ }
279
+
280
+ /**
281
+ * Gets all options for a particular storefront, like banners, templates, etc...
282
+ *
283
+ * @param $shop_id
284
+ * @return Bootic_Api_Result
285
+ */
286
+ public function getAvailableOptionsForStorefront($shop_id)
287
+ {
288
+ $result = $this->_request('/storefront/get_available_options', array(
289
+ 'shop_id' => $shop_id
290
+ ));
291
+
292
+ return $result;
293
+ }
294
+
295
+ /**
296
+ * Adds a banner to the list of available banners
297
+ *
298
+ * @param array $data
299
+ * @return Bootic_Api_Result
300
+ */
301
+ public function addStorefrontBanner(array $data)
302
+ {
303
+ $result = $this->_request('/storefront/add_banner', $data);
304
+
305
+ return $result;
306
+ }
307
+
308
+ /**
309
+ * Adds a product
310
+ *
311
+ * @param array $data
312
+ * @return Bootic_Api_Result
313
+ */
314
+ public function addProduct(array $data)
315
+ {
316
+ $result = $this->_request('product/add_product', $data);
317
+
318
+ return $result;
319
+ }
320
+
321
+ /**
322
+ * Edits a product
323
+ *
324
+ * @param array $data
325
+ * @return Bootic_Api_Result
326
+ */
327
+ public function editProduct(array $data)
328
+ {
329
+ $result = $this->_request('product/edit_product', $data);
330
+
331
+ return $result;
332
+ }
333
+
334
+ /**
335
+ * Updates the stock level for a particular product
336
+ *
337
+ * @param array $data
338
+ * @return Bootic_Api_Result
339
+ */
340
+ public function updateProductStock(array $data)
341
+ {
342
+ $result = $this->_request('product/update_product_stock', $data);
343
+
344
+ return $result;
345
+ }
346
+
347
+ /**
348
+ * Gets the stock level for a particular product
349
+ *
350
+ * @param array $data
351
+ * @return Bootic_Api_Result
352
+ */
353
+ public function getProductStock(array $data)
354
+ {
355
+ $result = $this->_request('product/get_product_stock', $data);
356
+
357
+ return $result;
358
+ }
359
+
360
+ /**
361
+ * Gets the product detailed info
362
+ *
363
+ * @param array $data
364
+ * @return Bootic_Api_Result
365
+ */
366
+ public function getProductInfo(array $data)
367
+ {
368
+ $result = $this->_request('/product/get_product_info', $data);
369
+
370
+ return $result;
371
+ }
372
+
373
+ /**
374
+ * Creates a new attribute for product
375
+ *
376
+ * @param array $data
377
+ * @return Bootic_Api_Result
378
+ */
379
+ public function createProductAttribute(array $data)
380
+ {
381
+ $result = $this->_request('product/create_attribute', $data);
382
+
383
+ return $result;
384
+ }
385
+
386
+ /**
387
+ * Lists all available attributes
388
+ *
389
+ * @return Bootic_Api_Result
390
+ */
391
+ public function listProductAvailableAttributes()
392
+ {
393
+ $result = $this->_request('product/list_available_attributes');
394
+
395
+ return $result;
396
+ }
397
+
398
+ /**
399
+ * Lists all available categories
400
+ *
401
+ * @return Bootic_Api_Result
402
+ */
403
+ public function listCategories()
404
+ {
405
+ $result = $this->_request('category/list');
406
+
407
+ return $result;
408
+ }
409
+
410
+ /**
411
+ * Gets all messages for an account
412
+ *
413
+ * @return Bootic_Api_Result
414
+ */
415
+ public function getAccountMessages()
416
+ {
417
+ $result = $this->_request('account/message');
418
+
419
+ return $result;
420
+ }
421
+
422
+ /**
423
+ * Marks a specific message as read
424
+ *
425
+ * @param array $data
426
+ * @return Bootic_Api_Result
427
+ */
428
+ public function markMessageAsRead(array $data)
429
+ {
430
+ $result = $this->_request('message/mark_as_read', $data);
431
+
432
+ return $result;
433
+ }
434
+
435
+
436
+ public function getOrderDetails($orderId)
437
+ {
438
+ $result = $this->_request('/transaction/get_order_details', array('id' => $orderId));
439
+
440
+ return $result;
441
+ }
442
+
443
+ /**
444
+ * Issues the request, always with POST method
445
+ *
446
+ * @param string $path
447
+ * @param array $params
448
+ * @return Bootic_Api_Result
449
+ */
450
+ protected function _request($path, array $params = array())
451
+ {
452
+ $this->_init();
453
+
454
+ return $this->_doRequest($path, $params);
455
+ }
456
+
457
+ protected function _doRequest($path, array $params = array())
458
+ {
459
+ $client = $this->getRestClient();
460
+ $path = '/' . ltrim($path, '/');
461
+ $realPath = rtrim($client->getUri()->getPath(), '/') . $path;
462
+
463
+ try {
464
+ $response = $this->getRestClient()->restPost($realPath, $params);
465
+
466
+ // If network or server failed, we throw an exception
467
+ if ($response->isError()) {
468
+ throw new \Exception($response->getMessage());
469
+ }
470
+
471
+ return new Bootic_Api_Result($response->getBody());
472
+
473
+ } catch(\Exception $e) {
474
+ // If anything weird happens, we throw a Bootic_Api_Exception
475
+ throw new Bootic_Api_Exception($e->getMessage());
476
+ }
477
+ }
478
+
479
+ /**
480
+ * @param Zend_Rest_Client $restClient
481
+ * @return Bootic_Api_Client Fluent interface
482
+ */
483
+ public function setRestClient($restClient)
484
+ {
485
+ $this->_restClient = $restClient;
486
+ $this->_restClient->setUri(Zend_Uri_Http::fromString($this->_defaultUri));
487
+
488
+ return $this;
489
+ }
490
+
491
+ /**
492
+ * @return Zend_Rest_Client
493
+ */
494
+ public function getRestClient()
495
+ {
496
+ if (null == $this->_restClient) {
497
+ $restClient = new Zend_Rest_Client();
498
+ $this->setRestClient($restClient);
499
+ }
500
+
501
+ return $this->_restClient;
502
+ }
503
+
504
+ /**
505
+ * @param Zend_Http_CookieJar $cookieJar
506
+ * @return Bootic_Api_Client Fluent interface
507
+ */
508
+ public function setCookieJar($cookieJar)
509
+ {
510
+ $this->_cookieJar = $cookieJar;
511
+
512
+ return $this;
513
+ }
514
+
515
+ /**
516
+ * @return Zend_Http_CookieJar
517
+ */
518
+ public function getCookieJar()
519
+ {
520
+ return $this->_cookieJar;
521
+ }
522
+
523
+ /**
524
+ * @param string $key
525
+ * @param string $value
526
+ */
527
+ public function addUri($key, $value)
528
+ {
529
+ $this->_uris[$key] = $value;
530
+ }
531
+
532
+ /**
533
+ * @param string $key
534
+ * @return bool
535
+ */
536
+ public function hasUri($key)
537
+ {
538
+ return array_key_exists($key, $this->_uris);
539
+ }
540
+
541
+ /**
542
+ * @param string $key
543
+ * @param bool $asString
544
+ * @return Zend_Uri_Http
545
+ *
546
+ * @throws \InvalidArgumentException
547
+ */
548
+ public function getUri($key, $asString = false)
549
+ {
550
+ if ($this->hasUri($key)) {
551
+ if ($asString) {
552
+ return (string) $this->_uris[$key];
553
+ }
554
+
555
+ return (!$this->_uris[$key] instanceof Zend_Uri_Http)
556
+ ? Zend_Uri_Http::fromString($this->_uris[$key])
557
+ : $this->_uris[$key]
558
+ ;
559
+ }
560
+
561
+ throw new \InvalidArgumentException(sprintf('No URI found for %s', $key));
562
+ }
563
+ }
lib/Bootic/Api/Exception.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Api_Exception extends Mage_Core_Exception
7
+ {
8
+ }
lib/Bootic/Api/Exception/UnsupportedDataType.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Api_Exception_UnsupportedDataType extends Bootic_Api_Exception
7
+ {
8
+ }
lib/Bootic/Api/Result.php ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_Api_Result
7
+ {
8
+ /** @var boolean */
9
+ protected $_success;
10
+
11
+ /** @var string */
12
+ protected $_errorMessage;
13
+
14
+ /** @var string */
15
+ protected $_hasWarning;
16
+
17
+ /** @var array */
18
+ protected $_warningMessages = array();
19
+
20
+ /** @var array|mixed */
21
+ protected $_data = array();
22
+
23
+ /** @var string */
24
+ protected $_rawData;
25
+
26
+ /**
27
+ * Constructor
28
+ *
29
+ * @param string $rawData
30
+ * @param string $dataType
31
+ */
32
+ public function __construct($rawData, $dataType = 'json')
33
+ {
34
+ $this->_rawData = $rawData;
35
+ $this->parseData($dataType);
36
+ }
37
+
38
+ /**
39
+ * Parses the raw data, extracts status and messages,
40
+ * and returns an array representation of the actual data
41
+ *
42
+ * @param string $dataType
43
+ *
44
+ * @throws Bootic_Api_Exception_UnsupportedDataType
45
+ */
46
+ public function parseData($dataType)
47
+ {
48
+ if (empty($this->_rawData)) {
49
+ return;
50
+ }
51
+
52
+ if (!$this->_supportDataType($dataType)) {
53
+ throw new Bootic_Api_Exception_UnsupportedDataType(
54
+ sprintf('"%s" data type is not supported'), $dataType
55
+ );
56
+ }
57
+
58
+ $data = Zend_Serializer::unserialize($this->_rawData, array('adapter' => $dataType));
59
+
60
+ $status = true;
61
+ if (isset($data['status'])) {
62
+ $status = (bool) $data['status'];
63
+ unset($data['status']);
64
+ } elseif (empty($data)) {
65
+ $status = false;
66
+ }
67
+ $this->setSuccess($status);
68
+
69
+ if (!$this->isSuccess()) {
70
+ if (isset($data['error'])) {
71
+ $this->setErrorMessage($data['error']);
72
+ unset($data['error']);
73
+ }
74
+ }
75
+
76
+ $hasWarning = false;
77
+ if (isset($data['warnings'])) {
78
+ if (count($data['warnings']) > 0) {
79
+ $hasWarning = true;
80
+ }
81
+
82
+ $this->setWarningMessages($data['warnings']);
83
+ unset($data['warnings']);
84
+ }
85
+ $this->setHasWarning($hasWarning);
86
+
87
+ $this->setData(null, $data['data']);
88
+ }
89
+
90
+ /**
91
+ * Checks if the input data type is supported
92
+ *
93
+ * @param string $dataType
94
+ * @return bool
95
+ */
96
+ protected function _supportDataType($dataType)
97
+ {
98
+ return in_array(strtolower($dataType), array('json'));
99
+ }
100
+
101
+ /**
102
+ * @param string $name
103
+ * @param array|mixed $data
104
+ * @return Bootic_Api_Result Fluent interface
105
+ */
106
+ public function setData($name = null, $data)
107
+ {
108
+ if (null !== $name) {
109
+ $this->_data[$name] = $data;
110
+ } else {
111
+ $this->_data = $data;
112
+ }
113
+
114
+ return $this;
115
+ }
116
+
117
+ /**
118
+ * @param string $name
119
+ * @param mixed $default
120
+ * @return array|mixed
121
+ */
122
+ public function getData($name = null, $default = null)
123
+ {
124
+ if (null !== $name) {
125
+ return $this->hasData($name) ? $this->_data[$name] : $default;
126
+ }
127
+
128
+ return $this->_data;
129
+ }
130
+
131
+ /**
132
+ * @param $warningMessages
133
+ */
134
+ public function setWarningMessages($warningMessages)
135
+ {
136
+ $this->_warningMessages = (array) $warningMessages;
137
+ }
138
+
139
+ /**
140
+ * @return array
141
+ */
142
+ public function getWarningMessages()
143
+ {
144
+ return $this->_warningMessages;
145
+ }
146
+
147
+ /**
148
+ * @param boolean $hasWarning
149
+ */
150
+ public function setHasWarning($hasWarning)
151
+ {
152
+ $this->_hasWarning = (bool) $hasWarning;
153
+ }
154
+
155
+ /**
156
+ * @return boolean
157
+ */
158
+ public function hasWarning()
159
+ {
160
+ return $this->_hasWarning;
161
+ }
162
+
163
+ /**
164
+ * @param string $errorMessage
165
+ */
166
+ public function setErrorMessage($errorMessage)
167
+ {
168
+ $this->_errorMessage = $errorMessage;
169
+ }
170
+
171
+ /**
172
+ * @return string
173
+ */
174
+ public function getErrorMessage()
175
+ {
176
+ return $this->_errorMessage;
177
+ }
178
+
179
+ /**
180
+ * @param boolean $success
181
+ */
182
+ public function setSuccess($success)
183
+ {
184
+ $this->_success = (bool) $success;
185
+ }
186
+
187
+ /**
188
+ * @return boolean
189
+ */
190
+ public function getSuccess()
191
+ {
192
+ return $this->_success;
193
+ }
194
+
195
+ /**
196
+ * @return bool
197
+ */
198
+ public function isSuccess()
199
+ {
200
+ return $this->getSuccess();
201
+ }
202
+
203
+ /**
204
+ * @param string $name
205
+ * @return bool
206
+ */
207
+ public function hasData($name)
208
+ {
209
+ return array_key_exists($name, $this->_data);
210
+ }
211
+ }
lib/Bootic/File/Uploader.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @copyright Copyright (c) 2012 by Bootic.
4
+ */
5
+
6
+ class Bootic_File_Uploader extends Varien_File_Uploader
7
+ {
8
+ /**
9
+ * {@inheritDoc}
10
+ */
11
+ public function __construct($fileId)
12
+ {
13
+ try {
14
+ parent::__construct($fileId);
15
+ } catch(Exception $e) {
16
+ $this->_fileExists = false;
17
+ }
18
+ }
19
+
20
+
21
+ /**
22
+ * Returns the original file name
23
+ *
24
+ * @return string
25
+ */
26
+ public function getIncomingFileName()
27
+ {
28
+ return $this->_file['name'];
29
+ }
30
+
31
+ /**
32
+ * Returns the original file extension
33
+ *
34
+ * @return string
35
+ */
36
+ public function getIncomingFileExtension()
37
+ {
38
+ preg_match('/[^?]*/', $this->_file['name'], $matches);
39
+ $string = $matches[0];
40
+
41
+ $pattern = preg_split('/\./', $string, -1, PREG_SPLIT_OFFSET_CAPTURE);
42
+
43
+ if(count($pattern) > 1)
44
+ {
45
+ $filenamepart = $pattern[count($pattern)-1][0];
46
+ preg_match('/[^?]*/', $filenamepart, $matches);
47
+ return $matches[0];
48
+ }
49
+
50
+ return '';
51
+ }
52
+
53
+ /**
54
+ * Returns the tmp file binary content
55
+ *
56
+ * @return null|string
57
+ */
58
+ public function getTempFileBinaryContent()
59
+ {
60
+ return $this->_fileExists ? file_get_contents($this->_file['tmp_name']) : null;
61
+ }
62
+
63
+ /**
64
+ * Returns the tmp file name (full path)
65
+ *
66
+ * @return string|null
67
+ */
68
+ public function getTempFileName()
69
+ {
70
+ return $this->_fileExists ? $this->_file['tmp_name'] : null;
71
+ }
72
+ }
lib/Bootic/Tests/Fixtures/account_create.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ {
2
+ "new_user_id" : 857,
3
+ "shop_id" : 948,
4
+ "status" : 1
5
+ }
lib/Bootic/Tests/Fixtures/order_list.json ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sort": "date",
3
+ "record_count": "3",
4
+ "sort_order": "desc",
5
+ "status": 1,
6
+ "transactions": [
7
+ {
8
+ "pis_deleted": 1,
9
+ "producer_currency_name": "$",
10
+ "basket": 64294,
11
+ "product_in_shop_absolute_uri": "http://dev.bootic.com/francisco-s-store/_shop/apple-mac-pro",
12
+ "status": "Ordered",
13
+ "shop_online": 1,
14
+ "shipping_company": null,
15
+ "shop_url": "/francisco-s-store",
16
+ "marketer": 439,
17
+ "shipping_tracking_number": null,
18
+ "pis_active": 1,
19
+ "transaction_date": "2012-05-07 22:00:57.329531",
20
+ "product_in_shop_name": "Apple Mac Pro",
21
+ "buyer_email": "name@domain.com",
22
+ "producer": 108,
23
+ "transaction": "1606",
24
+ "product_approved": 1,
25
+ "buyer": 439,
26
+ "shop_banner": "3089",
27
+ "quantity": 1,
28
+ "wage_producer_total": "9.00",
29
+ "shop_name": "Francisco\"s Store__(deleted-337)",
30
+ "shop": 337,
31
+ "shop_deleted": 1,
32
+ "pictures_id": [
33
+ "61",
34
+ "62",
35
+ "63",
36
+ "64"
37
+ ],
38
+ "buyer_name": "Francisco\"s Store",
39
+ "product_in_shop": 22121,
40
+ "survey_feedback_id": null,
41
+ "pictures": [
42
+ "400x400/apple-mac-pro_61.jpg",
43
+ "800x456/apple-mac-pro_62.jpg",
44
+ "400x308/apple-mac-pro_63.jpg",
45
+ "672x734/apple-mac-pro_64.jpg"
46
+ ],
47
+ "product": 16,
48
+ "product_in_shop_url": "apple-mac-pro",
49
+ "product_name": "Apple Mac Pro"
50
+ },
51
+ {
52
+ "pis_deleted": 0,
53
+ "producer_currency_name": "$",
54
+ "basket": 63946,
55
+ "product_in_shop_absolute_uri": "http://dev.bootic.com/xinminglai/_shop/apple-mac-pro-21635",
56
+ "status": "Ordered",
57
+ "shop_online": 0,
58
+ "shipping_company": null,
59
+ "shop_url": "/xinminglai",
60
+ "marketer": 325,
61
+ "shipping_tracking_number": null,
62
+ "pis_active": 1,
63
+ "transaction_date": "2012-05-02 08:57:11.594546",
64
+ "product_in_shop_name": "Apple Mac Pro",
65
+ "buyer_email": "name@domain.com",
66
+ "producer": 108,
67
+ "transaction": "1403",
68
+ "product_approved": 1,
69
+ "buyer": 97,
70
+ "shop_banner": "3087",
71
+ "quantity": 1,
72
+ "wage_producer_total": "9.00",
73
+ "shop_name": "xinminglai",
74
+ "shop": 260,
75
+ "shop_deleted": 0,
76
+ "pictures_id": [
77
+ "61",
78
+ "62",
79
+ "63",
80
+ "64"
81
+ ],
82
+ "buyer_name": "Alex Hartan",
83
+ "product_in_shop": 21635,
84
+ "survey_feedback_id": null,
85
+ "pictures": [
86
+ "400x400/apple-mac-pro_61.jpg",
87
+ "800x456/apple-mac-pro_62.jpg",
88
+ "400x308/apple-mac-pro_63.jpg",
89
+ "672x734/apple-mac-pro_64.jpg"
90
+ ],
91
+ "product": 17,
92
+ "product_in_shop_url": "apple-mac-pro-21635",
93
+ "product_name": "Apple Mac Pro"
94
+ },
95
+ {
96
+ "pis_deleted": 0,
97
+ "producer_currency_name": "$",
98
+ "basket": 64018,
99
+ "product_in_shop_absolute_uri": "http://dev.bootic.com/alexb-test/_shop/apple-mac-pro_21312",
100
+ "status": "In delivery / Sent",
101
+ "shop_online": 1,
102
+ "shipping_company": 3,
103
+ "shop_url": "/alexb-test",
104
+ "marketer": 108,
105
+ "shipping_tracking_number": "065861615015110",
106
+ "pis_active": 1,
107
+ "transaction_date": "2012-03-28 03:13:55.8465",
108
+ "product_in_shop_name": "Apple Mac Pro",
109
+ "buyer_email": "name@domain.com",
110
+ "producer": 108,
111
+ "transaction": "1306",
112
+ "product_approved": 1,
113
+ "buyer": 366,
114
+ "shop_banner": "23474",
115
+ "quantity": 1,
116
+ "wage_producer_total": "9.00",
117
+ "shop_name": "Alex\"s Test Store",
118
+ "shop": 80,
119
+ "shop_deleted": 0,
120
+ "pictures_id": [
121
+ "61",
122
+ "62",
123
+ "63",
124
+ "64"
125
+ ],
126
+ "buyer_name": "Pierre VIGIER",
127
+ "product_in_shop": 21312,
128
+ "survey_feedback_id": null,
129
+ "pictures": [
130
+ "400x400/apple-mac-pro_61.jpg",
131
+ "800x456/apple-mac-pro_62.jpg",
132
+ "400x308/apple-mac-pro_63.jpg",
133
+ "672x734/apple-mac-pro_64.jpg"
134
+ ],
135
+ "product": 18,
136
+ "product_in_shop_url": "apple-mac-pro_21312",
137
+ "product_name": "Apple Mac Pro"
138
+ }
139
+ ],
140
+ "offset": 0
141
+ }
package.xml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>Bootic</name>
4
+ <version>1.0.0</version>
5
+ <stability>stable</stability>
6
+ <license>Berkeley Software Distribution License (BSDL)</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>Synchronize Magento with Bootic: products, stock levels, sales and shipments</summary>
10
+ <description>Bootic for Magento enables you to publish your products to the Bootic.com marketplace with great ease. &#xD;
11
+ &#xD;
12
+ The extension lets you:&#xD;
13
+ &#xD;
14
+ Create a Bootic account and storefront directly from your Magento store in minutes.&#xD;
15
+ Automatically push your catalog to Bootic.com from your Magento store.&#xD;
16
+ Automatically synchronize orders placed on Bootic.com into your Magento store. Automatically update stock levels between Bootic and your Magento store.&#xD;
17
+ Automatically update any change to your product information from your Magento store to your Bootic storefront.&#xD;
18
+ &#xD;
19
+ With Bootic for Magento, create a new channel for selling your products without adding any overhead to your store management.</description>
20
+ <notes>First release</notes>
21
+ <authors><author><name>Stephane Tougard</name><user>bootic</user><email>magento@bootic.com</email></author></authors>
22
+ <date>2012-12-26</date>
23
+ <time>15:36:46</time>
24
+ <contents><target name="magecommunity"><dir name="Bootic"><dir name="Bootic"><dir name="Block"><dir name="Adminhtml"><dir name="Catalog"><file name="Category.php" hash="61e515ee3ab1114b42e0469a621ebfb5"/><dir name="Tab"><file name="General.php" hash="659bd4392cad0c83b914c3a56a3caba2"/></dir><file name="Tabs.php" hash="ad9b5ae88bcad6c62c5218305e287592"/></dir><file name="Catalog.php" hash="75a07745c1c30434ea317aa818925864"/><dir name="Connect"><dir name="Profile"><file name="Form.php" hash="13fabf5e01c3bca00de40dcd5d539ac6"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><file name="Profile.php" hash="53c9d21a74afcda2b3e01b248fcf46ce"/></dir><dir name="Log"><file name="Error.php" hash="2e6dadac4c46fa269a11974c63a205e7"/><file name="Grid.php" hash="ca0f774aa0c303f5c1ff5ea2a4ce2c20"/></dir><file name="Log.php" hash="4e5e1775d3d67c3d2d4a72abd76240e3"/><file name="Product.php" hash="b6846a5fe9f6b3f7073838039a501854"/><dir name="Storefront"><file name="Banner.php" hash="e12622feb541bcecb07343299f2f2edf"/><dir name="Create"><file name="Form.php" hash="aa8296efce9f909b58d03e70b60852d0"/></dir><file name="Create.php" hash="5ba25ea32fd4353ae6a7ac6686aade7b"/><dir name="Edit"><file name="Form.php" hash="a3016d71e63337bdf6a2101376c09fe6"/><dir name="Tab"><dir name="Design"><file name="Preview.php" hash="b9edb2c70a2266959721065872274f3a"/></dir><file name="Design.php" hash="c666ff3a2987ebe7fa933ef8a40c0668"/><file name="General.php" hash="9f2667b3f6304077526a2d2b9498ca92"/><file name="Settings.php" hash="6dfcebbbde9dc81aaf9679ab428eaa28"/><file name="Social.php" hash="e08a86710f2c92ba4ab47c98e67e4244"/></dir><file name="Tabs.php" hash="3b2b2b8d9a3c6e8acb2b0b40560cde78"/></dir><file name="Edit.php" hash="7f3830d12da3cac1fff52f760bf23526"/><file name="Preview.php" hash="887ef63c7fb5694dd63960fc197a91d3"/></dir><dir name="System"><dir name="Config"><dir name="Account"><dir name="Create"><file name="Form.php" hash="d4d6bbb2b6834400187b5dbf9f7d5424"/></dir><file name="Create.php" hash="c0f3159d6d5c3da1b4c7c6574b24bfed"/></dir><dir name="Button"><file name="Cron.php" hash="599c7cbf533a2809cb0e8b4c330d068b"/></dir><file name="Createaccount.php" hash="3192790d6089cbdf2c5b542ae49fd48e"/><file name="Tabs.php" hash="a5cb3c71b707f8ed002a1d33b7e1f9b2"/><file name="Testapiconnection.php" hash="b1cd2c239ed264355ddbf724661d54e8"/></dir></dir><dir name="Widget"><dir name="Grid"><dir name="Column"><dir name="Renderer"><dir name="Log"><file name="Message.php" hash="ce36a8329355d7dfeaaa10e401bb7aba"/></dir></dir></dir></dir></dir><file name=".DS_Store" hash="86b5d0dc5126cf670280b5f5be2b8986"/></dir><dir name="Payment"><file name="Form.php" hash="bb94bee836626fa1636997f308a59f3f"/><file name="Info.php" hash="e878b75b994f9a6c97826e6acca70ca2"/></dir><file name=".DS_Store" hash="0047fe0bc161a58c6088a4e959de43a4"/></dir><file name="Exception.php" hash="85fa2711bf13af12eab9162e927f4bd1"/><dir name="Helper"><file name="Abstract.php" hash="d48714ad954c69a1f13f8d835dbcbdad"/><file name="Account.php" hash="dab88db126c74e21039d2765fba289d2"/><file name="Api.php" hash="cce30bd36caee36bab0d18510a0ddc16"/><file name="Category.php" hash="0aac5e04199fa1fc06adcd55a4d1e03f"/><file name="Connect.php" hash="0e33dccb82732343518f6bea1ea83135"/><file name="Data.php" hash="a44a46a29fad09ce994c61b183c5730a"/><file name="Lists.php" hash="cedbf515e0ebe42b1408d8aa73d5e0ba"/><file name="Log.php" hash="4bf257fb8d16c7f25497331469022697"/><file name="Message.php" hash="dc9fb0d515d896972d0039dc736d9154"/><file name="Orders.php" hash="3c7c90992f46479d27140c15072ad695"/><dir name="Product"><file name="Data.php" hash="57a58c08a8e097077d66f4cbd20bf7cd"/><file name="Inventory.php" hash="9e94729f86617dd8b413d2ecfeb99448"/><dir name="Type"><file name="Configurable.php" hash="bc76e8e796578163416b32308dec5407"/></dir></dir><file name="Product.php" hash="08de3b6229ee848eaa343f27d6bdcf95"/><file name="Storefront.php" hash="902b7ce9f6bc72c52c921fb0b2197377"/></dir><dir name="Model"><dir name="Category"><file name="Mapping.php" hash="d90be7d27649c5388566d3bddca38c57"/></dir><file name="Category.php" hash="0308ca6b03e2340693077407cc0fc122"/><file name="Cron.php" hash="11758c445d08649c600a9f57d92cc207"/><file name="Log.php" hash="77f49a02f8c9cf71aa8ec84a1acf55a9"/><dir name="Message"><file name="Observer.php" hash="1f543cba2b2771915d225fdad36699d4"/></dir><file name="Message.php" hash="5eb2da8d19c607222c3d6297a7dbb23d"/><dir name="Mysql4"><dir name="Category"><file name="Collection.php" hash="a2afe60249de587c18e2d2f4ac2d7a78"/><dir name="Mapping"><file name="Collection.php" hash="614dda5dadab20f3b7725e2d5b556e6a"/></dir><file name="Mapping.php" hash="887d7cf4576ffd2c4b40dcf80e6c7021"/></dir><file name="Category.php" hash="9184a297db32337680b6ca54b4e73b74"/><dir name="Log"><file name="Collection.php" hash="d43c896a4c6de8e2280938373739e6e7"/></dir><file name="Log.php" hash="aa05f1803941172a8df3bcf5b4310016"/><file name="Message.php" hash="3f300ab7f6e5133487622562f9db55be"/><dir name="Order"><file name="Data.php" hash="c4e49baba9627bbbdb1b8d69012ac07f"/></dir><dir name="Product"><dir name="Data"><file name="Collection.php" hash="9176b66372bbbe51bb597d4ee2b86f35"/></dir><file name="Data.php" hash="a1365dedd73e0cc3d32d17ee3e0643ff"/></dir></dir><dir name="Order"><file name="Data.php" hash="1a5a72b9ef87f1d5323369535d8e0933"/><file name="Observer.php" hash="73e779f2b27d5ee623bab6bbd55a43ba"/></dir><file name="Payment.php" hash="d021f0560f01b89a37ba2c4d4de26804"/><dir name="Product"><file name="Data.php" hash="c92c28a67e616dc63c4aaccb5bdb5231"/><file name="Observer.php" hash="72b57ae0b34bc195c9b26e0427a9c632"/></dir><file name="Profile.php" hash="4a2067406d93a10aba308f52fc012ca3"/><dir name="Session"><file name="Observer.php" hash="f46c8ac3ce3c987c5ec79f4b9a90d159"/></dir><dir name="Shipping"><file name="Flatrate.php" hash="c383f093244cd04e0ca27583fd0527d1"/></dir><dir name="Stock"><file name="Observer.php" hash="5ff1fe7c76ffbdc5d07173cea2dc03e2"/></dir><file name="Storefront.php" hash="10b6a555b86331d03d4836887065a4ae"/><dir name="System"><dir name="Config"><file name="Commission.php" hash="ee9df0d2091afb9efac78c5eeb6c2c8d"/><file name="Connection.php" hash="7b1fdab2f86c4bd158e7f587b0332e42"/><file name="Observer.php" hash="5669f88c72bb8ff10cb84fd43133b268"/><dir name="Source"><file name="Attribute.php" hash="0d02faa3c926ef506c147b64e494313b"/><file name="Image.php" hash="90c2bb6ff6b860a9abc9a2b181fa8ec0"/><file name="Store.php" hash="15e70a19b7acd1890d45fed5d837cb02"/></dir></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="AbstractController.php" hash="b89a3229d94b02889a4a5cfa9b37d141"/><dir name="Catalog"><file name="CategoryController.php" hash="f31b36b88b88f020e90b4c43ff74a94b"/><file name="ProductController.php" hash="16527d9cd2c76e6f34249d66e7efd131"/></dir><file name="CatalogController.php" hash="7e50ea47a0c0afcba1598c3ef0fb5d02"/><file name="ConnectController.php" hash="e001299d152d1cd5fdec3650de8eeec9"/><file name="StorefrontController.php" hash="7a1e4ae9485cce71670bfcdcab5e7cb5"/><dir name="System"><dir name="Config"><file name="AccountController.php" hash="7296e50848f908ee4f4ce37e5c2552fe"/><file name="TestapiconnectionController.php" hash="1e712c1c029d289e269a74d10bd9c74e"/></dir><file name="CronController.php" hash="a6b58f29920488dca0062d71a4058b0f"/></dir></dir><file name="IndexController.php" hash="eeabb3471a6e5b8327f32165002800ed"/></dir><dir name="etc"><file name="adminhtml.xml" hash="d2f0649aae5e258fbd195b6a61871359"/><file name="config.xml" hash="7bc0a12688fb8575d23b90dc7e8a92bb"/><file name="system.xml" hash="5d1f1f14ba871a69c1d1f5597c4a41f9"/></dir><dir name="sql"><dir name="bootic_setup"><file name="mysql4-install-1.0.0.php" hash="88f4a6dbf3bf7144992ef9d24ed7deac"/></dir><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><file name=".DS_Store" hash="176ff25ca3c96ca91ea2ceb80dd7304b"/></dir><file name=".DS_Store" hash="665e7b0adb116db6663e012e46df97a0"/></dir></target><target name="magelib"><dir name="Bootic"><dir name="Api"><file name="Client.php" hash="32ebdf1b2fc68f4301cc84db59bdff7d"/><dir name="Exception"><file name="UnsupportedDataType.php" hash="aaa6d6d08d34f10ca99766135b7a8942"/></dir><file name="Exception.php" hash="7482fb7536873fbd95a87dd5c34fcdfb"/><file name="Result.php" hash="acbda91e9d44f6a3e6145f02ff0db7c4"/></dir><dir name="File"><file name="Uploader.php" hash="51d7167bd4f68b143d0a1349acbf4c0b"/></dir><dir name="Tests"><dir name="Fixtures"><file name="account_create.json" hash="a66b8d81d7d837fcd5e6663477207113"/><file name="order_list.json" hash="73676f4c34d8d1fe0dccd887bc17d457"/></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Bootic_Bootic.xml" hash="1bf446ba0b56654285cdda9e3eb9caa5"/></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="bootic.xml" hash="894aef183d5db57b26451ffdda0a7b63"/></dir><dir name="template"><dir name="bootic"><dir name="adminhtml"><dir name="system"><dir name="config"><dir name="button"><file name="cron.phtml" hash="8bc3c44ee69082c8bc16f964546246f9"/></dir><file name="createaccount.phtml" hash="89cbf36935071304deb3046f780c132f"/><file name="testapiconnection.phtml" hash="61be68e23ebdc238818a3e14a87aa0f0"/></dir></dir></dir><file name="bootic.phtml" hash="482f4ca6e0d96f0266a63e697ccd7857"/><dir name="catalog"><file name="category.phtml" hash="a51881b295fd898c4404c9a8506a6a59"/><dir name="tab"><file name="general.phtml" hash="3dd8fef0fd39457f70bfda2a94edecba"/></dir></dir><dir name="log"><file name="error.phtml" hash="76097855fd05302da368d17966db3942"/></dir><file name="products.phtml" hash="9af8e0740e281d494e402c413380cb1e"/><dir name="storefront"><file name="banners.phtml" hash="996f628a818a718cdc8b2d8bfcfe9810"/><file name="preview.phtml" hash="7d115f25724734f12ffa212328f7e6f3"/></dir></dir></dir></dir></dir></dir></target><target name="mageweb"><dir name="js"><dir name="bootic"><dir name="prototype-color-picker"><dir name="css"><file name="prototype_colorpicker.css" hash="1e2474f995501728e7cb3a5f74690523"/></dir><dir name="images"><file name="blank.gif" hash="56398e76be6355ad5999b262208a17c9"/><file name="colorpicker_background.png" hash="a79f1a2a81bfe3ed1c2ca4c41b8e1fbf"/><file name="colorpicker_extra.png" hash="920c87d67408ee59529aa1789836a0eb"/><file name="colorpicker_extra_background.png" hash="ae834a141fb00963c5056f015c99db71"/><file name="colorpicker_hex.png" hash="16d6870c36e379c06fb26ebd2e16bf44"/><file name="colorpicker_hsb_b.png" hash="2be4e81b4a5c98674abe6fc60b447e9a"/><file name="colorpicker_hsb_h.png" hash="d47409a203bedc76b26dc60b71a69f6b"/><file name="colorpicker_hsb_s.png" hash="5ff5e43ab6b7b41b6123bfab692a9b19"/><file name="colorpicker_indic.gif" hash="f485d07540a89502e36dc1a55cec05d0"/><file name="colorpicker_overlay.png" hash="c7a33805ffda0d32bd2a9904c8b02750"/><file name="colorpicker_rgb_b.png" hash="2be4e81b4a5c98674abe6fc60b447e9a"/><file name="colorpicker_rgb_g.png" hash="dc17f953a6febbe174e92b54690586c3"/><file name="colorpicker_rgb_r.png" hash="87eeb205d093b713b68a341771f4ee27"/><file name="colorpicker_select.gif" hash="cec464162af0cce10348e7bb7701ef86"/><file name="colorpicker_submit.png" hash="e887e023931707d8b63e390bfad1f39b"/><file name="custom_background.png" hash="06c91f7ae1561ccd8f1c79119529600d"/><file name="custom_hex.png" hash="e27aaa92b15d9392d909f8b94e797524"/><file name="custom_hsb_b.png" hash="b45588ca2fe9ba46673862300fcd3083"/><file name="custom_hsb_h.png" hash="4a1afa5636421aae4c44471d2273496d"/><file name="custom_hsb_s.png" hash="83aa97407fb76615a7db5ed721681148"/><file name="custom_indic.gif" hash="04660eb352eb259581a36a0fe8da2d4d"/><file name="custom_rgb_b.png" hash="f6e50cd567bd3059742d45ab224a413b"/><file name="custom_rgb_g.png" hash="b1e286e06692b3640862a3d07f636a80"/><file name="custom_rgb_r.png" hash="8ae4b3ef109ee66ec1022632470ddd0f"/><file name="custom_submit.png" hash="35423f0f2538e507dec193b52f8d0327"/><file name="select.png" hash="7b086953a8c62d2685a65644979d5d04"/><file name="select2.png" hash="c8d194d92dbad98615b2a9140b34ab48"/><file name="slider.png" hash="3a50b8078dea50b9811603a85ecef836"/></dir><file name="index.html" hash="c9c853215e6be66882221be357c84c16"/><dir name="js"><file name="prototype.1.6.js" hash="965fe52b851d8ff3c2b915ada9fb273f"/><file name="prototype_colorpicker.js" hash="90a7fd9030d010fac223f95953dc7b59"/></dir><file name=".DS_Store" hash="cf29419b5633cc71cfcb916356c6da81"/></dir></dir></dir></target></contents>
25
+ <compatible/>
26
+ <dependencies><required><php><min>5.2.0</min><max>5.4.10</max></php></required></dependencies>
27
+ </package>