cloudiq - Version 1.0.0

Version Notes

You can manage the look and feel of your button. You can schedule call backs to suit your business hours.

Download this release

Release Info

Developer cloud.IQ
Extension cloudiq
Version 1.0.0
Comparing to
See all releases


Version 1.0.0

Files changed (69) hide show
  1. app/code/community/Cloudiq/Callme/Block/Adminhtml/Config/Edit/Tab/Callme.php +340 -0
  2. app/code/community/Cloudiq/Callme/Block/Button.php +151 -0
  3. app/code/community/Cloudiq/Callme/Helper/Data.php +63 -0
  4. app/code/community/Cloudiq/Callme/Helper/Options.php +78 -0
  5. app/code/community/Cloudiq/Callme/Model/Api/Callme.php +155 -0
  6. app/code/community/Cloudiq/Callme/Model/Config.php +168 -0
  7. app/code/community/Cloudiq/Callme/Model/Observer.php +97 -0
  8. app/code/community/Cloudiq/Callme/Model/Resource/Config.php +8 -0
  9. app/code/community/Cloudiq/Callme/Model/Resource/Config/Collection.php +7 -0
  10. app/code/community/Cloudiq/Callme/Test/Config/Base.php +26 -0
  11. app/code/community/Cloudiq/Callme/Test/Controller/Block.php +46 -0
  12. app/code/community/Cloudiq/Callme/Test/Controller/Block/fixtures/testCallmeDisabled.yaml +43 -0
  13. app/code/community/Cloudiq/Callme/Test/Controller/Block/fixtures/testCoreDisabled.yaml +43 -0
  14. app/code/community/Cloudiq/Callme/Test/Controller/Block/fixtures/testDisplayOnAllPages.yaml +41 -0
  15. app/code/community/Cloudiq/Callme/Test/Controller/Block/providers/testDisplayOnAllPages.yaml +3 -0
  16. app/code/community/Cloudiq/Callme/Test/Helper/Options.php +61 -0
  17. app/code/community/Cloudiq/Callme/Test/Model/Config.php +97 -0
  18. app/code/community/Cloudiq/Callme/Test/Model/Config/fixtures/validConfigObject.yaml +38 -0
  19. app/code/community/Cloudiq/Callme/Test/Model/Config/providers/testOptionalField.yaml +3 -0
  20. app/code/community/Cloudiq/Callme/Test/Model/Config/providers/testRequiredField.yaml +34 -0
  21. app/code/community/Cloudiq/Callme/Varien/Data/Form/Element/Colourpicker.php +19 -0
  22. app/code/community/Cloudiq/Callme/Varien/Data/Form/Element/Openinghours.php +42 -0
  23. app/code/community/Cloudiq/Callme/controllers/Adminhtml/CallmeController.php +35 -0
  24. app/code/community/Cloudiq/Callme/controllers/CallmeController.php +36 -0
  25. app/code/community/Cloudiq/Callme/etc/config.xml +115 -0
  26. app/code/community/Cloudiq/Callme/sql/cloudiq_callme_setup/mysql4-install-1.0.0.php +53 -0
  27. app/code/community/Cloudiq/Core/.gitkeep +0 -0
  28. app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit.php +15 -0
  29. app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Form.php +12 -0
  30. app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Tab/Frame.php +39 -0
  31. app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Tab/Global.php +87 -0
  32. app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Tabs.php +20 -0
  33. app/code/community/Cloudiq/Core/Helper/Config.php +58 -0
  34. app/code/community/Cloudiq/Core/Helper/Data.php +5 -0
  35. app/code/community/Cloudiq/Core/Model/Api/Abstract.php +10 -0
  36. app/code/community/Cloudiq/Core/Model/Api/Core.php +67 -0
  37. app/code/community/Cloudiq/Core/Model/Api/Request.php +109 -0
  38. app/code/community/Cloudiq/Core/Model/Api/Response.php +170 -0
  39. app/code/community/Cloudiq/Core/Test/Config/Base.php +26 -0
  40. app/code/community/Cloudiq/Core/Test/Model/Api/Response.php +55 -0
  41. app/code/community/Cloudiq/Core/Test/Model/Api/TestCase.php +47 -0
  42. app/code/community/Cloudiq/Core/Test/Model/Api/data/Response/testWasSuccessfulBadResponseAttribute.txt +15 -0
  43. app/code/community/Cloudiq/Core/Test/Model/Api/data/Response/testWasSuccessfulStatusCodeAndResponseAttribute.txt +16 -0
  44. app/code/community/Cloudiq/Core/controllers/Adminhtml/CloudiqController.php +93 -0
  45. app/code/community/Cloudiq/Core/etc/adminhtml.xml +31 -0
  46. app/code/community/Cloudiq/Core/etc/config.xml +70 -0
  47. app/design/adminhtml/default/default/layout/cloudiq/callme.xml +24 -0
  48. app/design/adminhtml/default/default/layout/cloudiq/core.xml +47 -0
  49. app/design/adminhtml/default/default/template/cloudiq/core/tab/frame/iframe.phtml +1 -0
  50. app/design/adminhtml/default/default/template/cloudiq/core/tab/frame/submitter.phtml +11 -0
  51. app/design/adminhtml/default/default/template/cloudiq/core/tabs.phtml +39 -0
  52. app/design/adminhtml/default/default/template/cloudiq/global/tab.phtml +1 -0
  53. app/design/adminhtml/default/default/template/cloudiq/global/widget/form.phtml +15 -0
  54. app/design/frontend/base/default/template/cloudiq/callme/button.phtml +36 -0
  55. app/etc/modules/Cloudiq_Callme.xml +12 -0
  56. app/etc/modules/Cloudiq_Core.xml +9 -0
  57. app/locale/en_GB/Cloudiq_Callme.csv +1 -0
  58. js/cloudiq/callme-button.js +58 -0
  59. js/cloudiq/callme-jscolor/arrow.gif +0 -0
  60. js/cloudiq/callme-jscolor/cross.gif +0 -0
  61. js/cloudiq/callme-jscolor/demo.html +12 -0
  62. js/cloudiq/callme-jscolor/hs.png +0 -0
  63. js/cloudiq/callme-jscolor/hv.png +0 -0
  64. js/cloudiq/callme-jscolor/jscolor.js +840 -0
  65. package.xml +22 -0
  66. skin/adminhtml/default/default/cloudiq/callme.css +17 -0
  67. skin/adminhtml/default/default/cloudiq/core.css +41 -0
  68. skin/adminhtml/default/default/cloudiq/js/callme.js +55 -0
  69. skin/frontend/base/default/cloudiq/css/callme-button.css +198 -0
app/code/community/Cloudiq/Callme/Block/Adminhtml/Config/Edit/Tab/Callme.php ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Callme_Block_Adminhtml_Config_Edit_Tab_Callme extends Mage_Adminhtml_Block_Widget_Form implements Mage_Adminhtml_Block_Widget_Tab_Interface {
3
+ /** @var $_helper Cloudiq_Callme_Helper_Data */
4
+ protected $_helper;
5
+ /** @var $_config_helper Cloudiq_Core_Helper_Config */
6
+ protected $_config_helper;
7
+ /** @var $_options_helper Cloudiq_Callme_Helper_Options */
8
+ protected $_options_helper;
9
+ /** @var $_config_model Cloudiq_Callme_Model_Config */
10
+ protected $_config_model;
11
+ /** @var $_input_data Varien_Object */
12
+ protected $_input_data;
13
+
14
+ public function __construct() {
15
+ parent::__construct();
16
+
17
+ $this->_helper = Mage::helper('cloudiq_core');
18
+ $this->_config_helper = Mage::helper('cloudiq_core/config');
19
+
20
+ $this->_options_helper = Mage::helper('cloudiq_callme/options');
21
+
22
+ $this->_config_model = Mage::getModel('cloudiq_callme/config');
23
+
24
+ $_input_data = Mage::getSingleton("adminhtml/session")->getData("callme_form_data", true);
25
+ if ($_input_data && count($_input_data->getData()) > 0) {
26
+ $this->_input_data = $_input_data;
27
+ }
28
+ }
29
+
30
+ protected function _prepareForm() {
31
+ $form = new Varien_Data_Form();
32
+
33
+ $this->_addServiceFieldset($form);
34
+ $this->_addButtonFieldset($form);
35
+ $this->_addPopupFieldset($form);
36
+ $this->_addCallbackFieldset($form);
37
+ $this->_addPublishedFieldset($form);
38
+ }
39
+
40
+ protected function _addPublishedFieldset($form) {
41
+ $fieldset = $form->addFieldset('published', array('legend' => $this->_helper->__('Published')));
42
+
43
+ $fieldset->addField('callme[published][all]', 'checkbox', array(
44
+ 'label' => $this->_helper->__('Display on all pages'),
45
+ 'title' => $this->_helper->__('Display on all pages'),
46
+ 'name' => 'callme[published][all]',
47
+ 'value' => true,
48
+ 'checked' => ($this->_input_data) ? $this->_input_data->getData("published/all") : $this->_config_model->getDisplayOnAll()
49
+ ));
50
+
51
+ $published_pages_value = $this->_config_model->getDisplayOnPages();
52
+ if ($this->_input_data) {
53
+ $published_pages_value = ($this->_input_data->getData("published/pages")) ? implode(",", $this->_input_data->getData("published/pages")) : "";
54
+ }
55
+
56
+ $fieldset->addField('callme[published][pages]', 'multiselect', array(
57
+ 'label' => $this->_helper->__('Display on selected pages'),
58
+ 'title' => $this->_helper->__('Display on selected pages'),
59
+ 'name' => 'callme[published][pages]',
60
+ 'note' => 'To select multiple pages hold the CTRL button (on Windows) or CMD button (on Mac) down on your keyboard.',
61
+ 'value' => $published_pages_value,
62
+ 'values' => $this->_options_helper->getPagesOptions(),
63
+ 'disabled' => ($this->_input_data) ? $this->_input_data->getData("published/all") : $this->_config_model->getDisplayOnAll()
64
+ ));
65
+ }
66
+
67
+ protected function _addCallbackFieldset($form) {
68
+ $fieldset = $form->addFieldset('callback', array('legend' => $this->_helper->__('Callback settings')));
69
+
70
+ $fieldset->addField('callme[callback][number]', 'text', array(
71
+ 'label' => $this->_helper->__('Your number'),
72
+ 'title' => $this->_helper->__('Your number'),
73
+ 'name' => 'callme[callback][number]',
74
+ 'note' => 'This is the number called when the consumer makes a request. It should be a landline number. Once answered you will be connected to the consumer.',
75
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("callback/number") : $this->_config_model->getCallbackCallcentreNumber(),
76
+ 'required' => true,
77
+ ));
78
+
79
+ $fieldset->addField('callme[callback][dtmf]', 'text', array(
80
+ 'label' => $this->_helper->__('IVR Menu'),
81
+ 'title' => $this->_helper->__('IVR Menu'),
82
+ 'name' => 'callme[callback][dtmf]',
83
+ 'note' => 'Indicate which options should be selected to route the call to the right person if you have an IVR menu in place.',
84
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("callback/dtmf") : $this->_config_model->getCallbackDtmfTunnel(),
85
+ ));
86
+
87
+ $fieldset->addField('callme[callback][time_zone]', 'select', array(
88
+ 'label' => $this->_helper->__('Call centre time zone'),
89
+ 'title' => $this->_helper->__('Call centre time zone'),
90
+ 'name' => 'callme[callback][time_zone]',
91
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("callback/time_zone") : $this->_config_model->getCallCentreTimeZone(),
92
+ 'values' => Mage::getSingleton("adminhtml/system_config_source_locale_timezone")->toOptionArray()
93
+ ));
94
+
95
+ $fieldset->addField('callme[callback][open_hours_label]', 'label', array(
96
+ 'label' => $this->_helper->__('Open Hours'),
97
+ 'title' => $this->_helper->__('Open Hours'),
98
+ 'name' => 'callme[callback][open_hours_label]',
99
+ ));
100
+
101
+ $fieldset->addType('openinghours', 'Cloudiq_Callme_Varien_Data_Form_Element_Openinghours');
102
+
103
+ $fieldset->addField('callme[callback][monday]', 'openinghours', array(
104
+ 'label' => $this->_helper->__('Monday'),
105
+ 'title' => $this->_helper->__('Monday'),
106
+ 'name' => 'callme[callback][monday]',
107
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/monday")) : array(
108
+ $this->_config_model->getMondayFrom(),
109
+ $this->_config_model->getMondayTo(),
110
+ ),
111
+ 'required' => true,
112
+ ));
113
+
114
+ $fieldset->addField('callme[callback][tuesday]', 'openinghours', array(
115
+ 'label' => $this->_helper->__('Tuesday'),
116
+ 'title' => $this->_helper->__('Tuesday'),
117
+ 'name' => 'callme[callback][tuesday]',
118
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/tuesday")) : array(
119
+ $this->_config_model->getTuesdayFrom(),
120
+ $this->_config_model->getTuesdayTo(),
121
+ ),
122
+ 'required' => true,
123
+ ));
124
+
125
+ $fieldset->addField('callme[callback][wednesday]', 'openinghours', array(
126
+ 'label' => $this->_helper->__('Wednesday'),
127
+ 'title' => $this->_helper->__('Wednesday'),
128
+ 'name' => 'callme[callback][wednesday]',
129
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/wednesday")) : array(
130
+ $this->_config_model->getWednesdayFrom(),
131
+ $this->_config_model->getWednesdayTo(),
132
+ ),
133
+ 'required' => true,
134
+ ));
135
+
136
+ $fieldset->addField('callme[callback][thursday]', 'openinghours', array(
137
+ 'label' => $this->_helper->__('Thursday'),
138
+ 'title' => $this->_helper->__('Thursday'),
139
+ 'name' => 'callme[callback][thursday]',
140
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/thursday")) : array(
141
+ $this->_config_model->getThursdayFrom(),
142
+ $this->_config_model->getThursdayTo(),
143
+ ),
144
+ 'required' => true,
145
+ ));
146
+
147
+ $fieldset->addField('callme[callback][friday]', 'openinghours', array(
148
+ 'label' => $this->_helper->__('Friday'),
149
+ 'title' => $this->_helper->__('Friday'),
150
+ 'name' => 'callme[callback][friday]',
151
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/friday")) : array(
152
+ $this->_config_model->getFridayFrom(),
153
+ $this->_config_model->getFridayTo(),
154
+ ),
155
+ 'required' => true,
156
+ ));
157
+
158
+ $fieldset->addField('callme[callback][saturday]', 'openinghours', array(
159
+ 'label' => $this->_helper->__('Saturday'),
160
+ 'title' => $this->_helper->__('Saturday'),
161
+ 'name' => 'callme[callback][saturday]',
162
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/saturday")) : array(
163
+ $this->_config_model->getSaturdayFrom(),
164
+ $this->_config_model->getSaturdayTo(),
165
+ ),
166
+ 'required' => true,
167
+ ));
168
+
169
+ $fieldset->addField('callme[callback][sunday]', 'openinghours', array(
170
+ 'label' => $this->_helper->__('Sunday'),
171
+ 'title' => $this->_helper->__('Sunday'),
172
+ 'name' => 'callme[callback][sunday]',
173
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/sunday")) : array(
174
+ $this->_config_model->getSundayFrom(),
175
+ $this->_config_model->getSundayTo(),
176
+ ),
177
+ 'required' => true,
178
+ ));
179
+
180
+ $fieldset->addField('callme[callback][bank_holiday]', 'openinghours', array(
181
+ 'label' => $this->_helper->__('Bank Holiday'),
182
+ 'title' => $this->_helper->__('Bank Holiday'),
183
+ 'name' => 'callme[callback][bank_holiday]',
184
+ 'value' => ($this->_input_data) ? array_values($this->_input_data->getData("callback/bank_holiday")) : array(
185
+ $this->_config_model->getBankHolidayFrom(),
186
+ $this->_config_model->getBankHolidayTo(),
187
+ ),
188
+ 'required' => true,
189
+ ));
190
+ }
191
+
192
+ protected function _addPopupFieldset($form) {
193
+ $fieldset = $form->addFieldset('popup', array('legend' => $this->_helper->__('Popup Settings (Click the preview button to view changes)')));
194
+
195
+ $fieldset->addField('callme[popup][logo]', 'select', array(
196
+ 'label' => $this->_helper->__('Show company logo?'),
197
+ 'title' => $this->_helper->__('Show company logo?'),
198
+ 'name' => 'callme[popup][logo]',
199
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("popup/logo") : $this->_config_model->getPopUpLogo(),
200
+ 'values' => Mage::getSingleton('adminhtml/system_config_source_yesno')->toOptionArray(),
201
+ 'required' => true,
202
+ ));
203
+
204
+ $fieldset->addField('callme[popup][title]', 'text', array(
205
+ 'label' => $this->_helper->__('Title'),
206
+ 'title' => $this->_helper->__('Title'),
207
+ 'name' => 'callme[popup][title]',
208
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("popup/title") : $this->_config_model->getPopUpTitle(),
209
+ 'required' => true,
210
+ ));
211
+
212
+ $fieldset->addField('callme[popup][strap_line]', 'text', array(
213
+ 'label' => $this->_helper->__('Strap line'),
214
+ 'title' => $this->_helper->__('Strap line'),
215
+ 'name' => 'callme[popup][strap_line]',
216
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("popup/strap_line") : $this->_config_model->getPopUpStrapline(),
217
+ 'required' => true,
218
+ ));
219
+
220
+ $fieldset->addField('callme[popup][confirmation_open]', 'text', array(
221
+ 'label' => $this->_helper->__('Confirmation - Open'),
222
+ 'title' => $this->_helper->__('Confirmation - Open'),
223
+ 'name' => 'callme[popup][confirmation_open]',
224
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("popup/confirmation_open") : $this->_config_model->getPopUpConfOpen(),
225
+ 'required' => true,
226
+ ));
227
+
228
+ $fieldset->addField('callme[popup][confirmation_closed]', 'text', array(
229
+ 'label' => $this->_helper->__('Confirmation - Closed'),
230
+ 'title' => $this->_helper->__('Confirmation - Closed'),
231
+ 'name' => 'callme[popup][confirmation_closed]',
232
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("popup/confirmation_closed") : $this->_config_model->getPopUpConfClosed(),
233
+ 'required' => true,
234
+ ));
235
+
236
+ $this->setForm($form);
237
+ }
238
+
239
+ protected function _addButtonFieldset($form) {
240
+ $fieldset = $form->addFieldset('button', array('legend' => $this->_helper->__('Button Settings (Please see the preview shown on this page)')));
241
+
242
+ $fieldset->addField('callme[button][position]', 'select', array(
243
+ 'label' => $this->_helper->__('Position'),
244
+ 'title' => $this->_helper->__('Position'),
245
+ 'name' => 'callme[button][position]',
246
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("button/position") : $this->_config_model->getButtonPosition(),
247
+ 'values' => $this->_options_helper->getButtonPositionOptions(),
248
+ 'required' => true,
249
+ ));
250
+
251
+ $fieldset->addField('callme[button][template]', 'select', array(
252
+ 'label' => $this->_helper->__('Template'),
253
+ 'title' => $this->_helper->__('Template'),
254
+ 'name' => 'callme[button][template]',
255
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("button/template") : $this->_config_model->getButtonTemplate(),
256
+ 'values' => $this->_options_helper->getButtonTemplateOptions(),
257
+ 'required' => true,
258
+ ));
259
+
260
+ $fieldset->addType('colourpicker','Cloudiq_Callme_Varien_Data_Form_Element_Colourpicker');
261
+ $fieldset->addField('callme[button][colour]', 'colourpicker', array(
262
+ 'label' => $this->_helper->__('Colour'),
263
+ 'title' => $this->_helper->__('Colour'),
264
+ 'name' => 'callme[button][colour]',
265
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("button/colour") : $this->_config_model->getButtonColour(),
266
+ 'required' => true,
267
+ ));
268
+
269
+ $fieldset->addField('callme[button][heading]', 'text', array(
270
+ 'label' => $this->_helper->__('Button heading'),
271
+ 'title' => $this->_helper->__('Button heading'),
272
+ 'name' => 'callme[button][heading]',
273
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("button/heading") : $this->_config_model->getButtonHeading(),
274
+ 'required' => true,
275
+ ));
276
+
277
+ $fieldset->addField('callme[button][message]', 'text', array(
278
+ 'label' => $this->_helper->__('Button message'),
279
+ 'title' => $this->_helper->__('Button message'),
280
+ 'name' => 'callme[button][message]',
281
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("button/message") : $this->_config_model->getButtonMessage(),
282
+ 'required' => true,
283
+ ));
284
+
285
+ $fieldset->addField('callme[button][preview_url]', 'hidden', array(
286
+ 'label' => $this->_helper->__('Preview'),
287
+ 'title' => $this->_helper->__('Preview'),
288
+ 'name' => 'callme[button][preview]',
289
+ 'value' => Mage::helper("adminhtml")->getUrl('*/callme/preview'),
290
+ ));
291
+ }
292
+
293
+ protected function _addServiceFieldset($form) {
294
+ $fieldset = $form->addFieldset('service', array('legend' => $this->_helper->__('Service')));
295
+ $fieldset->addField('callme[enabled]', 'select', array(
296
+ 'label' => $this->_helper->__('Enable?'),
297
+ 'title' => $this->_helper->__('Enable?'),
298
+ 'name' => 'callme[enabled]',
299
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("enabled") : $this->_config_model->getEnabled(),
300
+ 'values' => Mage::getSingleton('adminhtml/system_config_source_yesno')->toOptionArray()
301
+ ));
302
+
303
+ $fieldset->addField('callme[service][average_sale_value]', 'text', array(
304
+ 'label' => $this->_helper->__('Average sale value'),
305
+ 'title' => $this->_helper->__('Average sale value'),
306
+ 'name' => 'callme[service][average_sale_value]',
307
+ 'note' => 'Please estimate the average value of a sale on your site. This is used for reporting.',
308
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("service/average_sale_value") : $this->_config_model->getAverageSaleValue(),
309
+ 'required' => true,
310
+ ));
311
+
312
+ $fieldset->addField('callme[service][average_sales_call]', 'select', array(
313
+ 'label' => $this->_helper->__('Average sales call duration'),
314
+ 'title' => $this->_helper->__('Average sales call duration'),
315
+ 'name' => 'callme[service][average_sales_call]',
316
+ 'note' => 'Please estimate how long a sales call would usually take. This is used for reporting.',
317
+ 'value' => ($this->_input_data) ? $this->_input_data->getData("service/average_sales_call") : $this->_config_model->getAverageSalesCall(),
318
+ 'values' => $this->_options_helper->getSalesCallOptions(),
319
+ 'required' => true,
320
+ ));
321
+ }
322
+
323
+ public function getTabLabel() {
324
+ return $this->__('callMe Settings');
325
+ }
326
+
327
+ public function getTabTitle() {
328
+ return $this->__('callMe Settings');
329
+ }
330
+
331
+ public function canShowTab() {
332
+ // Show this if we have an API token
333
+ return $this->_config_helper->hasBeenSetUp();
334
+ }
335
+
336
+ public function isHidden() {
337
+ // Hide this if we don't have an API token
338
+ return !$this->_config_helper->hasBeenSetUp();
339
+ }
340
+ }
app/code/community/Cloudiq/Callme/Block/Button.php ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Block_Button extends Mage_Core_Block_Template {
4
+
5
+ const GRADIENT_COLOUR_STEP = 50;
6
+
7
+ public function _construct() {
8
+ parent::_construct();
9
+
10
+ $this->setArea("frontend");
11
+ $this->setTemplate("cloudiq/callme/button.phtml");
12
+ }
13
+
14
+ /**
15
+ * Return the config model.
16
+ *
17
+ * @return Varien_Object
18
+ */
19
+ public function getConfig() {
20
+ if (is_null($this->getData("config"))) {
21
+ $this->setData("config", Mage::getModel("cloudiq_callme/config"));
22
+ }
23
+
24
+ return $this->getData("config");
25
+ }
26
+
27
+ /**
28
+ * Fetch callMe button properties from the configuration.
29
+ *
30
+ * @param $key string The property to fetch.
31
+ *
32
+ * @return mixed
33
+ */
34
+ protected function _getFromConfig($key) {
35
+ return $this->getConfig()->getData($key);
36
+ }
37
+
38
+ /**
39
+ * Return the css class representing the selected callMe template.
40
+ *
41
+ * @return string
42
+ */
43
+ protected function _getTemplateClass() {
44
+ return "cloudiq-callme-template-" . strtolower($this->_getFromConfig("button_template"));
45
+ }
46
+
47
+ /**
48
+ * Return the class string for the callMe button based on the config.
49
+ *
50
+ * @return string
51
+ */
52
+ protected function _getButtonClass() {
53
+ $classes = array();
54
+ // Apply the button template
55
+ $classes[] = $this->_getTemplateClass();
56
+ // Position the button
57
+ $classes[] = "cloudiq-callme-position-" . strtolower($this->_getFromConfig("button_position"));
58
+ return implode(" ", $classes);
59
+ }
60
+
61
+ /**
62
+ * Return the class string for the callMe pop up based on the config.
63
+ *
64
+ * @return string
65
+ */
66
+ protected function _getPopupClass() {
67
+ $classes = array();
68
+ // Apply the template
69
+ $classes[] = $this->_getTemplateClass();
70
+
71
+ return implode(" ", $classes);
72
+ }
73
+
74
+ /**
75
+ * Return user selected colour for the button in hex form.
76
+ *
77
+ * @return string
78
+ */
79
+ protected function _getColour() {
80
+ return $this->_getFromConfig("button_colour");
81
+ }
82
+
83
+ /**
84
+ * Return the starting colour for a gradient based on user selected button colour.
85
+ *
86
+ * @return string
87
+ */
88
+ protected function _getGradientStartColour() {
89
+ $base_colour = $this->_getColour();
90
+ $colour = "";
91
+ foreach (array(0, 2, 4) as $pos) {
92
+ $rgb_val = hexdec(substr($base_colour, $pos, 2));
93
+ $rgb_val = max(0, min(255, $rgb_val + (self::GRADIENT_COLOUR_STEP / 2)));
94
+ $colour .= str_pad(dechex($rgb_val), 2, "0", STR_PAD_LEFT);
95
+ }
96
+ return $colour;
97
+ }
98
+
99
+ /**
100
+ * Return the ending colour for a gradient based on user selected button colour.
101
+ *
102
+ * @return string
103
+ */
104
+ protected function _getGradientEndColour() {
105
+ $base_colour = $this->_getColour();
106
+ $colour = "";
107
+ foreach (array(0, 2, 4) as $pos) {
108
+ $rgb_val = hexdec(substr($base_colour, $pos, 2));
109
+ $rgb_val = max(0, min(255, $rgb_val - (self::GRADIENT_COLOUR_STEP / 2)));
110
+ $colour .= str_pad(dechex($rgb_val), 2, "0", STR_PAD_LEFT);
111
+ }
112
+ return $colour;
113
+ }
114
+
115
+ /**
116
+ * Return black or white to contrast the given colour.
117
+ *
118
+ * @param $colour string Colour to contrast in hex form.
119
+ *
120
+ * @return string
121
+ */protected function _getContrastColourBW($colour) {
122
+ // Count the perceptive luminance (human eye favours green colour)
123
+ // Source: http://stackoverflow.com/questions/1855884/determine-font-color-based-on-background-color
124
+ $alpha = 1 - (0.299 * hexdec(substr($colour, 0, 2)) + 0.587 * hexdec(substr($colour, 2, 2)) + 0.114 * hexdec(substr($colour, 4, 2))) / 255;
125
+
126
+ if ($alpha < 0.5) {
127
+ // Bright colour, return black to contrast
128
+ return "000000";
129
+ } else {
130
+ return "FFFFFF";
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Return the submit URL for the callMe callback request form.
136
+ *
137
+ * @return string
138
+ */
139
+ protected function _getFormUrl() {
140
+ return $this->getUrl('cloudiq/callme/request');
141
+ }
142
+
143
+ /**
144
+ * Return the logo to be displayed on the popup if the logo has been enabled.
145
+ *
146
+ * @return string Full URL to the logo or an empty string.
147
+ */
148
+ protected function _getLogoUrl() {
149
+ return ($this->_getFromConfig('pop_up_logo')) ? $this->getSkinUrl(Mage::getStoreConfig('design/header/logo_src'), array("_area" => "frontend")) : "";
150
+ }
151
+ }
app/code/community/Cloudiq/Callme/Helper/Data.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Callme_Helper_Data extends Mage_Core_Helper_Abstract {
3
+
4
+ /**
5
+ * Update the provided config model with the data from the provided
6
+ * form model. Used for easy translation between form field names and
7
+ * config model property names. Updates the config model in place.
8
+ *
9
+ * @param Varien_Object $config Config object to update.
10
+ * @param Varien_Object $form Object containing form data.
11
+ */
12
+ public function updateConfigFromForm(Varien_Object &$config, Varien_Object $form) {
13
+ $config->addData(array(
14
+ "enabled" => $form->getData("enabled"),
15
+
16
+ "average_sale_value" => $form->getData("service/average_sale_value"),
17
+ "average_sales_call" => $form->getData("service/average_sales_call"),
18
+
19
+ "button_position" => $form->getData("button/position"),
20
+ "button_template" => $form->getData("button/template"),
21
+ "button_colour" => $form->getData("button/colour"),
22
+ "button_heading" => $form->getData("button/heading"),
23
+ "button_message" => $form->getData("button/message"),
24
+
25
+ "pop_up_logo" => $form->getData("popup/logo"),
26
+ "pop_up_title" => $form->getData("popup/title"),
27
+ "pop_up_strapline" => $form->getData("popup/strap_line"),
28
+ "pop_up_conf_open" => $form->getData("popup/confirmation_open"),
29
+ "pop_up_conf_closed" => $form->getData("popup/confirmation_closed"),
30
+
31
+ "callback_callcentre_number" => $form->getData("callback/number"),
32
+ "callback_dtmf_tunnel" => $form->getData("callback/dtmf"),
33
+ "call_centre_time_zone" => $form->getData("callback/time_zone"),
34
+
35
+ "monday_from" => $form->getData("callback/monday/from"),
36
+ "monday_to" => $form->getData("callback/monday/to"),
37
+
38
+ "tuesday_from" => $form->getData("callback/tuesday/from"),
39
+ "tuesday_to" => $form->getData("callback/tuesday/to"),
40
+
41
+ "wednesday_from" => $form->getData("callback/wednesday/from"),
42
+ "wednesday_to" => $form->getData("callback/wednesday/to"),
43
+
44
+ "thursday_from" => $form->getData("callback/thursday/from"),
45
+ "thursday_to" => $form->getData("callback/thursday/to"),
46
+
47
+ "friday_from" => $form->getData("callback/friday/from"),
48
+ "friday_to" => $form->getData("callback/friday/to"),
49
+
50
+ "saturday_from" => $form->getData("callback/saturday/from"),
51
+ "saturday_to" => $form->getData("callback/saturday/to"),
52
+
53
+ "sunday_from" => $form->getData("callback/sunday/from"),
54
+ "sunday_to" => $form->getData("callback/sunday/to"),
55
+
56
+ "bank_holiday_from" => $form->getData("callback/bank_holiday/from"),
57
+ "bank_holiday_to" => $form->getData("callback/bank_holiday/to"),
58
+
59
+ "display_on_all" => ($form->getData("published/all")) ? 1 : 0,
60
+ "display_on_pages" => ($form->getData("published/pages")) ? implode(",", $form->getData("published/pages")) : ""
61
+ ));
62
+ }
63
+ }
app/code/community/Cloudiq/Callme/Helper/Options.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Helper_Options extends Mage_Core_Helper_Abstract {
4
+
5
+ const DAY_START_HOUR = 7;
6
+ const DAY_START_MINUTE = 0;
7
+ const DAY_END_HOUR = 23;
8
+ const DAY_END_MINUTE = 30;
9
+ const MINUTE_INCREMENT = 15;
10
+
11
+ public function getButtonPositionOptions() {
12
+ return array(
13
+ array("value" => "leftBottom", "label" => $this->__("Left Bottom")),
14
+ array("value" => "leftMiddle", "label" => $this->__("Left Middle")),
15
+ array("value" => "leftTop", "label" => $this->__("Left Top")),
16
+ array("value" => "topLeft", "label" => $this->__("Top Left")),
17
+ array("value" => "topMiddle", "label" => $this->__("Top Middle")),
18
+ array("value" => "topRight", "label" => $this->__("Top Right")),
19
+ array("value" => "rightTop", "label" => $this->__("Right Top")),
20
+ array("value" => "rightMiddle", "label" => $this->__("Right Middle")),
21
+ array("value" => "rightBottom", "label" => $this->__("Right Bottom")),
22
+ array("value" => "bottomRight", "label" => $this->__("Bottom Right")),
23
+ array("value" => "bottomMiddle", "label" => $this->__("Bottom Middle")),
24
+ array("value" => "bottomLeft", "label" => $this->__("Bottom Left")),
25
+ );
26
+ }
27
+
28
+ public function getButtonTemplateOptions() {
29
+ return array(
30
+ array("value" => "1", "label" => $this->__("Squared corners no fill")),
31
+ array("value" => "2", "label" => $this->__("Squared corners with fill")),
32
+ array("value" => "3", "label" => $this->__("Lozenge with fill")),
33
+ array("value" => "4", "label" => $this->__("Rounded corners no fill")),
34
+ array("value" => "5", "label" => $this->__("Rounded corners with fill")),
35
+ );
36
+ }
37
+
38
+ public function getHoursOptions() {
39
+ $values = array();
40
+
41
+ $values[] = array("value" => "--", "label" => "--");
42
+
43
+ for ($i = self::DAY_START_HOUR; $i <= self::DAY_END_HOUR; $i++) {
44
+ for ($j = 0; $j < 60; $j = $j + self::MINUTE_INCREMENT) {
45
+ if (($i == self::DAY_START_HOUR && $j < self::DAY_START_MINUTE)
46
+ || ($i == self::DAY_END_HOUR && $j > self::DAY_END_MINUTE)) {
47
+ continue;
48
+ }
49
+ $value = (($i < 10) ? "0" . $i : $i) . ":" . (($j < 10) ? "0" . $j : $j);
50
+ $values[] = array("value" => $value, "label" => $this->__($value));
51
+ }
52
+ }
53
+ return $values;
54
+ }
55
+
56
+ public function getPagesOptions() {
57
+ return array(
58
+ array("value" => "cms_index_index", "label" => $this->__("Home page")),
59
+ array("value" => "cms_page", "label" => $this->__("All CMS pages")),
60
+ array("value" => "catalog_category_view", "label" => $this->__("Category pages")),
61
+ array("value" => "catalog_product_view", "label" => $this->__("Product pages")),
62
+ array("value" => "catalogsearch_result_index", "label" => $this->__("Search page")),
63
+ array("value" => "checkout_cart_index", "label" => $this->__("Cart page")),
64
+ array("value" => "checkout_onepage_index", "label" => $this->__("Checkout page")),
65
+ array("value" => "customer_account", "label" => $this->__("Account Area pages")),
66
+ );
67
+ }
68
+
69
+ public function getSalesCallOptions() {
70
+ return array(
71
+ array("value" => "1", "label" => $this->__("1 minutes")),
72
+ array("value" => "2", "label" => $this->__("2 minutes")),
73
+ array("value" => "3", "label" => $this->__("3 minutes")),
74
+ array("value" => "4", "label" => $this->__("4 minutes")),
75
+ array("value" => "5", "label" => $this->__("5 minutes or more")),
76
+ );
77
+ }
78
+ }
app/code/community/Cloudiq/Callme/Model/Api/Callme.php ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Model_Api_Callme extends Cloudiq_Core_Model_Api_Abstract {
4
+
5
+ /**
6
+ * Submit callMe configuration changes to the callMe API. Sends a request
7
+ * to the API with mode="store" and action="callMe". If the request results
8
+ * in a new app being created, the configuration model passed to
9
+ * the method will be updated with a new app ID, but not saved. Returns
10
+ * true if the action was successful or an array of error messages if it
11
+ * failed.
12
+ *
13
+ * @param Cloudiq_Callme_Model_Config &$config Configuration to submit to the API
14
+ * @return bool|array
15
+ */
16
+ public function storeCallme(Cloudiq_Callme_Model_Config &$config) {
17
+ $parameters = $this->_convertConfigToParameters($config);
18
+
19
+ $parameters["mode"] = "store";
20
+ $parameters["action"] = "callme";
21
+
22
+ /** @var Cloudiq_Core_Model_Api_Request $request */
23
+ $request = $this->_getRequestObject()->setParameters($parameters);
24
+
25
+ $errors = array();
26
+
27
+ $response = $request->send(Zend_Http_Client::POST);
28
+
29
+ if ($response) {
30
+ if ($response->wasSuccessful()) {
31
+ $callme_response = $response->getResponse()->callMe;
32
+
33
+ if (!is_null($callme_response)) {
34
+ if ($callme_response['status'] == Cloudiq_Core_Model_Api_Response::STATUS_SUCCESS) {
35
+ // Successful callMe response, check if a new app ID was created
36
+ $app_id = (int) $callme_response->appId;
37
+ if (!is_null($app_id)) {
38
+ $config->setAppId($app_id);
39
+ }
40
+ } else {
41
+ // Unsuccessful callMe response, build an appropriate error message
42
+ $error_message = "Unsuccessful callMe response: " . $response->getApiStatusCodeDescription($callme_response['status']);
43
+ if ($callme_response->description) {
44
+ $error_message .= ": " . $callme_response->description;
45
+ }
46
+ $errors[] = $error_message;
47
+ }
48
+ } else {
49
+ $errors[] = "Unknown callMe response status.";
50
+ }
51
+ } else {
52
+ // Unsuccessful response
53
+ $errors[] = $response->getErrorMessage();
54
+ }
55
+ } else {
56
+ $errors[] = "Failed to connect to cloud.IQ API";
57
+ }
58
+
59
+ if (empty($errors)) {
60
+ return true;
61
+ }
62
+ return $errors;
63
+ }
64
+
65
+ /**
66
+ * Submit a callback request to the API. Returns an array with two elements:
67
+ * status - boolean flag, true if the callback request was successful, false otherwise
68
+ * message - response message or error message if the request was unsuccessful
69
+ *
70
+ * @param $app_id string The id of the app to assign the request to.
71
+ * @param $consumer_number string The phone number for the callback.
72
+ *
73
+ * @return array
74
+ */
75
+ public function requestCallback($app_id, $consumer_number) {
76
+ $parameters = array(
77
+ "mode" => "send",
78
+ "action" => "callback",
79
+ "appId" => $app_id,
80
+ "consumerNumber" => $consumer_number,
81
+ "consumerIp" => Mage::helper("core/http")->getRemoteAddr()
82
+ );
83
+
84
+ $result = array(
85
+ "status" => false,
86
+ "message" => ""
87
+ );
88
+
89
+ $response = null;
90
+ $request = $this->_getRequestObject()->setParameters($parameters);
91
+
92
+ $response = $request->send(Zend_Http_Client::POST);
93
+
94
+ if ($response) {
95
+ if ($response->wasSuccessful()) {
96
+ $callback_response = $response->getResponse()->callback;
97
+
98
+ if (!is_null($callback_response)) {
99
+ if($callback_response['status'] == Cloudiq_Core_Model_Api_Response::STATUS_SUCCESS) {
100
+ // Successfully submitted a callback request, fetch the response message
101
+ $result["status"] = true;
102
+ $result["message"] = sprintf("%s", $callback_response->description);
103
+ } else {
104
+ // Unsuccessful callback response, fetch the error message
105
+ $result["message"] = $response->getApiStatusCodeDescription($callback_response['status']);
106
+ if ($callback_response->description) {
107
+ $result["message"] .= sprintf(": %s", $callback_response->description);
108
+ }
109
+ }
110
+ }
111
+ } else {
112
+ // Unsuccessful response
113
+ $result["message"] = $response->getErrorMessage();
114
+ }
115
+ } else {
116
+ $result["message"] = "Failed to connect to cloud.IQ API";
117
+ }
118
+
119
+ return $result;
120
+ }
121
+
122
+ /**
123
+ * Convert Cloudiq_Callme_Model_Config into an array of parameters used by the API
124
+ *
125
+ * @param Cloudiq_Callme_Model_Config $config
126
+ * @return array
127
+ */
128
+ protected function _convertConfigToParameters(Cloudiq_Callme_Model_Config $config) {
129
+ $parameters = $config->toCamelCaseArray();
130
+
131
+ // Remove parameters not used by the API
132
+ unset($parameters["id"],
133
+ $parameters["enabled"],
134
+ $parameters["displayOnAll"],
135
+ $parameters["displayOnPages"]
136
+ );
137
+
138
+ // Set constant parameters
139
+ // FIXME: Should only send the logo url to the API if it is enabled in the config
140
+ // but at the moment the API request fails if the logo is empty.
141
+ $parameters["popUpLogo"] = Mage::getDesign()->getSkinUrl(Mage::getStoreConfig('design/header/logo_src'), array("_area" => "frontend"));
142
+
143
+ // FIXME: New requirements say there should only be one set of opening hours per day, but the API still uses the period notation
144
+ $parameter_names = array_keys($parameters);
145
+ foreach ($parameter_names as $name) {
146
+ if (preg_match("/^(monday|tuesday|wednesday|thursday|friday|saturday|sunday|bankHoliday)(.*)$/", $name, $matches)) {
147
+ $new_name = $matches[1] . "Period1" . $matches[2];
148
+ $parameters[$new_name] = $parameters[$name];
149
+ unset($parameters[$name]);
150
+ }
151
+ }
152
+
153
+ return $parameters;
154
+ }
155
+ }
app/code/community/Cloudiq/Callme/Model/Config.php ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class Cloudiq_Callme_Model_Config
5
+ *
6
+ * @method bool getEnabled()
7
+ * @method bool getDisplayOnAll()
8
+ * @method string getDisplayOnPages()
9
+ */
10
+
11
+ class Cloudiq_Callme_Model_Config extends Mage_Core_Model_Abstract {
12
+
13
+ protected $_helper;
14
+
15
+ protected function _construct() {
16
+ $this->_init('cloudiq_callme/config');
17
+
18
+ $this->_helper = Mage::helper("cloudiq_callme/options");
19
+
20
+ $this->load(1);
21
+ }
22
+
23
+ /**
24
+ * Validate attribute values, returning true if all fields pass validation or
25
+ * a "field_name => message" array of error messages.
26
+ *
27
+ * @return bool|array
28
+ */
29
+ public function validate() {
30
+ $errors = array();
31
+
32
+ // Enabled 0/1
33
+ if (!Zend_Validate::is($this->getEnabled(), "InArray", array("haystack" => array("0", "1")))) {
34
+ $errors["Enabled"] = "Enabled must be set to 0 or 1.";
35
+ }
36
+
37
+ // Average sales values must be present
38
+ if (!Zend_Validate::is(trim($this->getAverageSaleValue()), "NotEmpty")) {
39
+ $errors["AverageSaleValue"] = "Average sale value can not be empty.";
40
+ }
41
+
42
+ // Average sales call must match a set of values defined in the API
43
+ if (!Zend_Validate::is($this->getAverageSalesCall(), "InArray", array("haystack" => array_map(function ($el) { return $el["value"]; }, $this->_helper->getSalesCallOptions())))) {
44
+ $errors["AverageSalesCall"] = "Invalid average sales call value.";
45
+ }
46
+
47
+ // Button Position must match a set of values defined in the API
48
+ if (!Zend_Validate::is($this->getButtonPosition(), "InArray", array("haystack" => array_map(function ($el) { return $el["value"]; }, $this->_helper->getButtonPositionOptions())))) {
49
+ $errors["ButtonPosition"] = "Invalid button position value.";
50
+ }
51
+
52
+ // Button Template must match a set of values defined in the API
53
+ if (!Zend_Validate::is($this->getButtonTemplate(), "InArray", array("haystack" => array_map(function ($el) { return $el["value"]; }, $this->_helper->getButtonTemplateOptions())))) {
54
+ $errors["ButtonTemplate"] = "Invalid button template value.";
55
+ }
56
+
57
+ // Button Colour must be a hex colour definition
58
+ if (!Zend_Validate::is($this->getButtonColour(), "Regex", array("pattern" => "/^[0-9A-F]{6}$/i"))) {
59
+ $errors["ButtonColour"] = "Button colour must be a hex colour definition.";
60
+ }
61
+
62
+ // Button Heading is required
63
+ if (!Zend_Validate::is(trim($this->getButtonHeading()), "NotEmpty")) {
64
+ $errors["ButtonHeading"] = "Button heading can not be empty.";
65
+ }
66
+
67
+ // Button Message is required
68
+ if (!Zend_Validate::is(trim($this->getButtonMessage()), "NotEmpty")) {
69
+ $errors["ButtonMessage"] = "Button message can not be empty.";
70
+ }
71
+
72
+ // Pop up logo enabled/disabled
73
+ if (!Zend_Validate::is($this->getPopUpLogo(), "InArray", array("haystack" => array("0", "1")))) {
74
+ $errors["PopUpLogo"] = "Pop up logo must be set to 0 or 1.";
75
+ }
76
+
77
+ // Pop up title is required
78
+ if (!Zend_Validate::is(trim($this->getPopUpTitle()), "NotEmpty")) {
79
+ $errors["PopUpTitle"] = "Pop up title can not be empty.";
80
+ }
81
+
82
+ // Pop up strapline is required
83
+ if (!Zend_Validate::is(trim($this->getPopUpStrapline()), "NotEmpty")) {
84
+ $errors["PopUpStrapline"] = "Pop up strap line can not be empty.";
85
+ }
86
+
87
+ // Pop up confirmations are required
88
+ if (!Zend_Validate::is(trim($this->getPopUpConfOpen()), "NotEmpty")) {
89
+ $errors["PopUpConfOpen"] = "Pop up confirmation (open) can not be empty.";
90
+ }
91
+ if (!Zend_Validate::is(trim($this->getPopUpConfClosed()), "NotEmpty")) {
92
+ $errors["PopUpConfClosed"] = "Pop up confirmation (closed) can not be empty.";
93
+ }
94
+
95
+ // Callback phone number is required
96
+ if (!Zend_Validate::is(trim($this->getCallbackCallcentreNumber()), "NotEmpty")) {
97
+ $errors["CallbackCallcentreNumber"] = "Callback telephone number can not be empty.";
98
+ }
99
+
100
+ // DTMF Tunnel is numeric
101
+ if (Zend_Validate::is(trim($this->getCallbackDtmfTunnel()), "NotEmpty") && !Zend_Validate::is(trim($this->getCallbackDtmfTunnel()), "Int")) {
102
+ $errors["CallbackDtmfTunnel"] = "Callback DTMF tunnel can only contain numbers.";
103
+ }
104
+
105
+ // Call centre time zone must match one of the provided options
106
+ if (!Zend_Validate::is(trim($this->getCallCentreTimeZone()), "InArray", array("haystack" => array_map(function ($el) { return $el["value"]; }, Mage::getSingleton("adminhtml/system_config_source_locale_timezone")->toOptionArray())))) {
107
+ $errors["CallCentreTimeZone"] = "Invalid call centre time zone value.";
108
+ }
109
+
110
+ // All values in Opening Hours must be times
111
+ foreach (array("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "bank_holiday") as $day) {
112
+ $openhours_errors = 0;
113
+ foreach (array("from", "to") as $period) {
114
+ $value = $this->getData($day . "_" . $period);
115
+ if ($value !== "--" && !Zend_Validate::is($value, "Regex", array("pattern" => "/^\d{2}:\d{2}$/"))) {
116
+ $openhours_errors++;
117
+ }
118
+ }
119
+ if ($openhours_errors > 0) {
120
+ $errors[$day] = sprintf("%s Open Hours fields must contain a time value.", ucfirst($day));
121
+ }
122
+ }
123
+
124
+ // Display pages values must match a defined set or be empty
125
+ if (trim($this->getDisplayOnPages()) != "") {
126
+ $displaypages_errors = 0;
127
+ foreach (explode(",",$this->getDisplayOnPages()) as $page) {
128
+ if (!Zend_Validate::is($page, "InArray", array("haystack" => array_map(function ($el) { return $el["value"]; }, $this->_helper->getPagesOptions())))) {
129
+ $displaypages_errors++;
130
+ }
131
+ }
132
+ if ($displaypages_errors > 0) {
133
+ $errors["DisplayOnPages"] = '"Display on selected pages" contains invalid values.';
134
+ }
135
+ }
136
+
137
+ if (empty($errors)) {
138
+ return true;
139
+ }
140
+ return $errors;
141
+ }
142
+
143
+ /**
144
+ * Convert object attributes to an array with camelCase names
145
+ *
146
+ * @param array $attributes array of required attributes
147
+ * @return array
148
+ */
149
+ public function toCamelCaseArray(array $attributes = array()) {
150
+ $data = $this->__toArray($attributes);
151
+ $camelData = array();
152
+ foreach ($data as $key => $value) {
153
+ $camelData[lcfirst($this->_camelize($key))] = $value;
154
+ }
155
+ return $camelData;
156
+ }
157
+
158
+ /**
159
+ * Check if the module is enabled and has been configured.
160
+ *
161
+ * @return bool
162
+ */
163
+ public function isConfigured() {
164
+ $core_helper = Mage::helper("cloudiq_core/config");
165
+
166
+ return ($core_helper->isEnabled() && $core_helper->hasBeenSetUp() && $this->getEnabled() && !is_null($this->getAppId()));
167
+ }
168
+ }
app/code/community/Cloudiq/Callme/Model/Observer.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Model_Observer extends Varien_Object {
4
+
5
+ public function observeCloudiqCoreConfigSave($observer) {
6
+ $request = $observer->getRequest();
7
+
8
+
9
+ /** @var $helper Cloudiq_Callme_Helper_Data */
10
+ $helper = Mage::helper("cloudiq_callme");
11
+
12
+ /** @var $config_model Cloudiq_Callme_Model_Config */
13
+ $config_model = Mage::getModel("cloudiq_callme/config");
14
+
15
+ $form_data = new Varien_Object($request->getParam("callme"));
16
+
17
+ if (count($form_data->getData()) == 0) {
18
+ // Don't save the config model if the callMe tab was not present
19
+ return;
20
+ }
21
+
22
+ // Save form data in the admin session to repopulate the form in case of errors while saving
23
+ Mage::getSingleton("adminhtml/session")->setData("callme_form_data", $form_data);
24
+
25
+ $helper->updateConfigFromForm($config_model, $form_data);
26
+
27
+ // Validate the data, show an error for each field that doesn't pass
28
+ $result = $config_model->validate();
29
+ if (is_array($result)) {
30
+ foreach($result as $field => $message) {
31
+ Mage::getSingleton("adminhtml/session")->addError($helper->__("callMe: " . $message));
32
+ }
33
+ return;
34
+ }
35
+
36
+ // Submit the config changes to the API
37
+ $api = Mage::getModel("cloudiq_callme/api_callme");
38
+ $api_result = $api->storeCallme($config_model);
39
+ if (is_array($api_result)) {
40
+ foreach ($api_result as $message) {
41
+ Mage::getSingleton("adminhtml/session")->addError($helper->__("callMe API error: " . $message));
42
+ }
43
+ return;
44
+ }
45
+
46
+ $config_model->save();
47
+ Mage::getSingleton("adminhtml/session")->addSuccess($helper->__("callMe: Configuration saved."));
48
+ }
49
+
50
+ /**
51
+ * Insert our callMe block if we are on the correct layout handle.
52
+ *
53
+ * @param Varien_Event_Observer $observer
54
+ */
55
+ public function observeControllerActionLayoutGenerateBlocksAfter(Varien_Event_Observer $observer) {
56
+ /** @var Mage_Core_Model_Layout $layout */
57
+ $layout = Mage::app()->getLayout();
58
+
59
+ if ($layout->getArea() != Mage_Core_Model_App_Area::AREA_FRONTEND) {
60
+ return;
61
+ }
62
+
63
+ /** @var array $layout_handles */
64
+ $layout_handles = $layout->getUpdate()->getHandles();
65
+
66
+ /** @var Cloudiq_Callme_Model_Config $callme_config */
67
+ $callme_config = Mage::getModel('cloudiq_callme/config');
68
+
69
+ /** @var Cloudiq_Core_Helper_Config $core_helper */
70
+ $core_helper = Mage::helper('cloudiq_core/config');
71
+
72
+ $core_enabled = $core_helper->hasBeenSetUp() && $core_helper->isEnabled();
73
+
74
+ if (!$core_enabled || !$callme_config->isConfigured()) {
75
+ return;
76
+ }
77
+
78
+ $callme_handles = explode(',', $callme_config->getDisplayOnPages());
79
+
80
+ /**
81
+ * Only render the block if we've checked the "Display on all pages" checkbox, or at least one of our current
82
+ * page handles matches the configured handles.
83
+ */
84
+ $should_render_block = (
85
+ $callme_config->getDisplayOnAll() ||
86
+ count(array_intersect($callme_handles, $layout_handles)) > 0
87
+ );
88
+
89
+ if ($should_render_block) {
90
+ // TODO: convert this into a layout update that gets applied through a handle
91
+ $callme_block = $layout->createBlock('cloudiq_callme/button', 'cloudiq_callme.button_js');
92
+ $layout->getBlock('before_body_end')->insert($callme_block);
93
+ $layout->getBlock('head')->addItem("skin_css", "cloudiq/css/callme-button.css");
94
+ $layout->getBlock('head')->addItem("js", "cloudiq/callme-button.js");
95
+ }
96
+ }
97
+ }
app/code/community/Cloudiq/Callme/Model/Resource/Config.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Model_Resource_Config extends Mage_Core_Model_Mysql4_Abstract {
4
+
5
+ protected function _construct() {
6
+ $this->_init('cloudiq_callme/config', 'id');
7
+ }
8
+ }
app/code/community/Cloudiq/Callme/Model/Resource/Config/Collection.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Model_Resource_Config_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
4
+ protected function _construct() {
5
+ $this->_init('cloudiq_callme/config');
6
+ }
7
+ }
app/code/community/Cloudiq/Callme/Test/Config/Base.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Callme_Test_Config_Base extends EcomDev_PHPUnit_Test_Case_Config {
3
+ /**
4
+ * @test
5
+ */
6
+ public function testBasicConfiguration() {
7
+ $this->assertModuleCodePool('community');
8
+ $this->assertModuleVersion('1.0.0');
9
+ }
10
+
11
+ /**
12
+ * @test
13
+ */
14
+ public function testClassAliases() {
15
+ $this->assertHelperAlias('cloudiq_callme', 'Cloudiq_Callme_Helper_Data');
16
+ $this->assertModelAlias('cloudiq_callme/test', 'Cloudiq_Callme_Model_Test');
17
+ $this->assertBlockAlias('cloudiq_callme/test', 'Cloudiq_Callme_Block_Test');
18
+ }
19
+
20
+ /**
21
+ * @test
22
+ */
23
+ public function testDataHelperExists() {
24
+ $this->assertInstanceOf('Cloudiq_Callme_Helper_Data', Mage::helper('cloudiq_callme'));
25
+ }
26
+ }
app/code/community/Cloudiq/Callme/Test/Controller/Block.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Test_Controller_Block extends EcomDev_PHPUnit_Test_Case_Controller {
4
+
5
+ /**
6
+ * @test
7
+ *
8
+ * @loadFixture
9
+ * @dataProvider dataProvider
10
+ */
11
+ public function testDisplayOnAllPages($path, $handle) {
12
+ $this->dispatch($path);
13
+ $this->assertModuleConfigured();
14
+ $this->assertLayoutHandleLoaded($handle);
15
+ $this->assertLayoutBlockRendered('cloudiq_callme.button_js');
16
+ }
17
+
18
+ /**
19
+ * @test
20
+ *
21
+ * @loadFixture
22
+ */
23
+ public function testCallmeDisabled() {
24
+ $this->dispatch('');
25
+ $this->assertModuleConfigured();
26
+ $this->assertLayoutHandleLoaded('cms_index_index');
27
+ $this->assertLayoutBlockNotRendered('cloudiq_callme.button_js');
28
+ }
29
+
30
+ /**
31
+ * @test
32
+ *
33
+ * @loadFixture
34
+ */
35
+ public function testCoreDisabled() {
36
+ $this->dispatch('');
37
+ $this->assertModuleConfigured();
38
+ $this->assertLayoutHandleLoaded('cms_index_index');
39
+ $this->assertLayoutBlockNotRendered('cloudiq_callme.button_js');
40
+ }
41
+
42
+ public function assertModuleConfigured() {
43
+ $this->assertNotNull(Mage::helper('cloudiq_core/config')->isEnabled(), 'Expected core module to be set');
44
+ $this->assertTrue(Mage::helper('cloudiq_core/config')->hasBeenSetUp(), 'Expected core module to be setup correctly');
45
+ }
46
+ }
app/code/community/Cloudiq/Callme/Test/Controller/Block/fixtures/testCallmeDisabled.yaml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ config:
2
+ default/cloudiq_core/global/account_id: 123
3
+ default/cloudiq_core/global/token: 123
4
+ default/cloudiq_core/global/enabled: 1
5
+ tables:
6
+ cloudiq_callme/config:
7
+ -
8
+ id: 1
9
+ app_id: 123
10
+ enabled: 0
11
+ average_sale_value: 1.00
12
+ average_sales_call: 3
13
+ button_position: "leftBottom"
14
+ button_template: "1"
15
+ button_colour: "FFFFFF"
16
+ button_heading: 1
17
+ button_message: 1
18
+ pop_up_logo: "http://test"
19
+ pop_up_title: 1
20
+ pop_up_strapline: 1
21
+ pop_up_conf_open: 1
22
+ pop_up_conf_closed: 1
23
+ callback_callcentre_number: 1
24
+ callback_dtmf_tunnel: 1
25
+ call_centre_time_zone: "Europe/London"
26
+ monday_from: "00:00"
27
+ monday_to: "00:00"
28
+ tuesday_from: "00:00"
29
+ tuesday_to: "00:00"
30
+ wednesday_from: "00:00"
31
+ wednesday_to: "00:00"
32
+ thursday_from: "00:00"
33
+ thursday_to: "00:00"
34
+ friday_from: "00:00"
35
+ friday_to: "00:00"
36
+ saturday_from: "00:00"
37
+ saturday_to: "00:00"
38
+ sunday_from: "00:00"
39
+ sunday_to: "00:00"
40
+ bank_holiday_from: "00:00"
41
+ bank_holiday_to: "00:00"
42
+ display_on_all: 1
43
+ display_on_pages: ""
app/code/community/Cloudiq/Callme/Test/Controller/Block/fixtures/testCoreDisabled.yaml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ config:
2
+ default/cloudiq_core/global/account_id: 123
3
+ default/cloudiq_core/global/token: 123
4
+ default/cloudiq_core/global/enabled: 0
5
+ tables:
6
+ cloudiq_callme/config:
7
+ -
8
+ id: 1
9
+ app_id: 123
10
+ enabled: 1
11
+ average_sale_value: 1.00
12
+ average_sales_call: 3
13
+ button_position: "leftBottom"
14
+ button_template: "1"
15
+ button_colour: "FFFFFF"
16
+ button_heading: 1
17
+ button_message: 1
18
+ pop_up_logo: "http://test"
19
+ pop_up_title: 1
20
+ pop_up_strapline: 1
21
+ pop_up_conf_open: 1
22
+ pop_up_conf_closed: 1
23
+ callback_callcentre_number: 1
24
+ callback_dtmf_tunnel: 1
25
+ call_centre_time_zone: "Europe/London"
26
+ monday_from: "00:00"
27
+ monday_to: "00:00"
28
+ tuesday_from: "00:00"
29
+ tuesday_to: "00:00"
30
+ wednesday_from: "00:00"
31
+ wednesday_to: "00:00"
32
+ thursday_from: "00:00"
33
+ thursday_to: "00:00"
34
+ friday_from: "00:00"
35
+ friday_to: "00:00"
36
+ saturday_from: "00:00"
37
+ saturday_to: "00:00"
38
+ sunday_from: "00:00"
39
+ sunday_to: "00:00"
40
+ bank_holiday_from: "00:00"
41
+ bank_holiday_to: "00:00"
42
+ display_on_all: 1
43
+ display_on_pages: ""
app/code/community/Cloudiq/Callme/Test/Controller/Block/fixtures/testDisplayOnAllPages.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ config:
2
+ default/cloudiq_core/global/account_id: 123
3
+ default/cloudiq_core/global/token: 123
4
+ default/cloudiq_core/global/enabled: 1
5
+ tables:
6
+ cloudiq_callme/config:
7
+ -
8
+ id: 1
9
+ app_id: 123
10
+ enabled: 1
11
+ button_position: "leftBottom"
12
+ button_template: "1"
13
+ button_colour: "FFFFFF"
14
+ button_heading: 1
15
+ button_message: 1
16
+ pop_up_logo: "http://test"
17
+ pop_up_title: 1
18
+ pop_up_strapline: 1
19
+ pop_up_conf_open: 1
20
+ pop_up_conf_closed: 1
21
+ callback_callcentre_number: 1
22
+ callback_dtmf_tunnel: 1
23
+ call_centre_time_zone: "Europe/London"
24
+ monday_from: "00:00"
25
+ monday_to: "00:00"
26
+ tuesday_from: "00:00"
27
+ tuesday_to: "00:00"
28
+ wednesday_from: "00:00"
29
+ wednesday_to: "00:00"
30
+ thursday_from: "00:00"
31
+ thursday_to: "00:00"
32
+ friday_from: "00:00"
33
+ friday_to: "00:00"
34
+ saturday_from: "00:00"
35
+ saturday_to: "00:00"
36
+ sunday_from: "00:00"
37
+ sunday_to: "00:00"
38
+ bank_holiday_from: "00:00"
39
+ bank_holiday_to: "00:00"
40
+ display_on_all: 1
41
+ display_on_pages: ""
app/code/community/Cloudiq/Callme/Test/Controller/Block/providers/testDisplayOnAllPages.yaml ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ -
2
+ path: ''
3
+ handle: cms_index_index
app/code/community/Cloudiq/Callme/Test/Helper/Options.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Test_Helper_Options extends EcomDev_PHPUnit_Test_Case {
4
+ /** @var Cloudiq_Callme_Helper_Options */
5
+ protected $_helper;
6
+
7
+ protected function setUp() {
8
+ parent::setUp();
9
+
10
+ $this->_helper = Mage::helper('cloudiq_callme/options');
11
+ }
12
+
13
+ protected function tearDown() {
14
+ $this->_helper = null;
15
+ }
16
+
17
+ /**
18
+ * @test
19
+ */
20
+ public function testClassConstruction() {
21
+ $this->assertInstanceOf('Cloudiq_Callme_Helper_Options', $this->_helper);
22
+ }
23
+
24
+ /**
25
+ * @test
26
+ */
27
+ public function testGetButtonPositionOptionsValidSourceModel() {
28
+ $this->_assertValidSourceModel($this->_helper->getButtonPositionOptions());
29
+ }
30
+
31
+ /**
32
+ * @test
33
+ */
34
+ public function testGetButtonTemplateOptionsValidSourceModel() {
35
+ $this->_assertValidSourceModel($this->_helper->getButtonTemplateOptions());
36
+ }
37
+
38
+ /**
39
+ * @test
40
+ */
41
+ public function testGetHoursOptionsValidSourceModel() {
42
+ $this->_assertValidSourceModel($this->_helper->getHoursOptions());
43
+ }
44
+
45
+ /**
46
+ * @test
47
+ */
48
+ public function testGetPagesOptionsValidSourceModel() {
49
+ $this->_assertValidSourceModel($this->_helper->getPagesOptions());
50
+ }
51
+
52
+ protected function _assertValidSourceModel($array) {
53
+ $this->assertTrue(is_array($array));
54
+ $this->assertTrue(count($array) > 0);
55
+
56
+ foreach ($array as $child) {
57
+ $this->assertArrayHasKey('label', $child);
58
+ $this->assertArrayHasKey('value', $child);
59
+ }
60
+ }
61
+ }
app/code/community/Cloudiq/Callme/Test/Model/Config.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Test_Model_Config extends EcomDev_PHPUnit_Test_Case {
4
+
5
+ /** @var Cloudiq_Callme_Model_Config */
6
+ protected $_model;
7
+
8
+ protected function setUp() {
9
+ parent::setUp();
10
+
11
+ $this->_model = Mage::getModel('cloudiq_callme/config');
12
+ }
13
+
14
+ protected function tearDown() {
15
+ $this->_model = null;
16
+ }
17
+
18
+ /**
19
+ * @test
20
+ */
21
+ public function testClassConstruction() {
22
+ $this->assertInstanceOf('Cloudiq_Callme_Model_Config', $this->_model);
23
+ }
24
+
25
+ /**
26
+ * @test
27
+ * @loadFixture validConfigObject.yaml
28
+ */
29
+ public function testModelIsLoadedAfterConstruct() {
30
+ /**
31
+ * Test the fixture loaded
32
+ */
33
+ $this->assertGreaterThan(0, $this->_model->getCollection()->count());
34
+
35
+ $this->assertEquals(1, $this->_model->getId());
36
+ }
37
+
38
+ /**
39
+ * @test
40
+ * @depends testModelIsLoadedAfterConstruct
41
+ * @loadFixture validConfigObject.yaml
42
+ */
43
+ public function testFixtureIsValid() {
44
+ $this->assertTrue($this->_model->validate());
45
+ }
46
+
47
+ /**
48
+ * @test
49
+ * @depends testFixtureIsValid
50
+ * @dataProvider dataProviderSingleValue
51
+ * @loadFixture validConfigObject.yaml
52
+ */
53
+ public function testRequiredField($fieldname) {
54
+ $this->_model->unsetData($fieldname);
55
+
56
+ $validation_result = $this->_model->validate();
57
+
58
+ $this->assertThat($validation_result, $this->logicalNot($this->equalTo(true)));
59
+ $this->assertTrue(is_array($validation_result));
60
+ $this->assertEquals(1, count($validation_result));
61
+ }
62
+
63
+ /**
64
+ * @test
65
+ * @depends testFixtureIsValid
66
+ * @dataProvider dataProviderSingleValue
67
+ * @loadFixture validConfigObject.yaml
68
+ */
69
+ public function testOptionalField($fieldname) {
70
+ $this->_model->unsetData($fieldname);
71
+
72
+ $validation_result = $this->_model->validate();
73
+
74
+ $this->assertTrue($validation_result);
75
+ }
76
+
77
+ /**
78
+ * The standard dataProvider requires an array of arrays in the yaml file. This data provider allows us to just
79
+ * use an array and, therefore, assumes that the test method using this dataProviderSingleValue accepts only one
80
+ * argument.
81
+ *
82
+ * @param $testname
83
+ * @return array
84
+ */
85
+ public function dataProviderSingleValue($testname) {
86
+ $raw_data = parent::dataProvider($testname);
87
+ $data = array();
88
+
89
+ foreach ($raw_data as $item) {
90
+ $data[] = array(
91
+ $item
92
+ );
93
+ }
94
+
95
+ return $data;
96
+ }
97
+ }
app/code/community/Cloudiq/Callme/Test/Model/Config/fixtures/validConfigObject.yaml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ tables:
2
+ cloudiq_callme/config:
3
+ -
4
+ id: 1
5
+ enabled: 1
6
+ average_sale_value: 1.00
7
+ average_sales_call: 3
8
+ button_position: "leftBottom"
9
+ button_template: "1"
10
+ button_colour: "FFFFFF"
11
+ button_heading: 1
12
+ button_message: 1
13
+ pop_up_logo: 1
14
+ pop_up_title: 1
15
+ pop_up_strapline: 1
16
+ pop_up_conf_open: 1
17
+ pop_up_conf_closed: 1
18
+ callback_callcentre_number: 1
19
+ callback_dtmf_tunnel: 1
20
+ call_centre_time_zone: "Europe/London"
21
+ monday_from: "00:00"
22
+ monday_to: "00:00"
23
+ tuesday_from: "00:00"
24
+ tuesday_to: "00:00"
25
+ wednesday_from: "00:00"
26
+ wednesday_to: "00:00"
27
+ thursday_from: "00:00"
28
+ thursday_to: "00:00"
29
+ friday_from: "00:00"
30
+ friday_to: "00:00"
31
+ saturday_from: "00:00"
32
+ saturday_to: "00:00"
33
+ sunday_from: "00:00"
34
+ sunday_to: "00:00"
35
+ bank_holiday_from: "00:00"
36
+ bank_holiday_to: "00:00"
37
+ display_on_all: 1
38
+ display_on_pages: "cms_index_index,cms_page"
app/code/community/Cloudiq/Callme/Test/Model/Config/providers/testOptionalField.yaml ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ - callback_dtmf_tunnel
2
+ - display_on_all
3
+ - display_on_pages
app/code/community/Cloudiq/Callme/Test/Model/Config/providers/testRequiredField.yaml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ - enabled
2
+ - average_sale_value
3
+ - average_sales_call
4
+ - button_position
5
+ - button_template
6
+ - button_colour
7
+ - button_heading
8
+ - button_message
9
+ - pop_up_logo
10
+ - pop_up_title
11
+ - pop_up_strapline
12
+ - pop_up_conf_open
13
+ - pop_up_conf_closed
14
+ - callback_callcentre_number
15
+ #- callback_dtmf_tunnel
16
+ - call_centre_time_zone
17
+ - monday_from
18
+ - monday_to
19
+ - tuesday_from
20
+ - tuesday_to
21
+ - wednesday_from
22
+ - wednesday_to
23
+ - thursday_from
24
+ - thursday_to
25
+ - friday_from
26
+ - friday_to
27
+ - saturday_from
28
+ - saturday_to
29
+ - sunday_from
30
+ - sunday_to
31
+ - bank_holiday_from
32
+ - bank_holiday_to
33
+ #- display_on_all
34
+ #- display_on_pages
app/code/community/Cloudiq/Callme/Varien/Data/Form/Element/Colourpicker.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Callme_Varien_Data_Form_Element_Colourpicker extends Varien_Data_Form_Element_Abstract {
3
+
4
+ public function __construct($attributes=array()) {
5
+ parent::__construct($attributes);
6
+ $this->setType('colourpicker');
7
+ }
8
+
9
+ public function getElementHtml() {
10
+ $html = sprintf('<input id="%s" name="%s" class="color input-text" value="%s"/>',
11
+ $this->getHtmlId(),
12
+ $this->getName(),
13
+ $this->getEscapedValue()
14
+ );
15
+ return $html;
16
+ }
17
+
18
+
19
+ }
app/code/community/Cloudiq/Callme/Varien/Data/Form/Element/Openinghours.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_Varien_Data_Form_Element_Openinghours extends Varien_Data_Form_Element_Abstract {
4
+
5
+ protected $_helper;
6
+
7
+ public function __construct($attributes = array()) {
8
+ parent::__construct($attributes);
9
+ $this->setType("openinghours");
10
+
11
+ $this->_helper = Mage::helper("cloudiq_callme/options");
12
+ }
13
+
14
+ public function getElementHtml() {
15
+ $parts = array("from", "to");
16
+ $values = $this->getValue();
17
+
18
+ $this->addClass("opening_hours_select");
19
+ $html = '<div id="' . $this->getHtmlId() . '" ' . $this->serialize($this->getHtmlAttributes()) . '>' . "\n";
20
+
21
+ for ($i = 0; $i < count($parts); $i++) {
22
+ $name = $this->getName() . "[" . $parts[$i] . "]";
23
+ $html .= $this->_helper->__($parts[$i]);
24
+ $html .= $this->getSelectHtml($name, $this->_helper->getHoursOptions(), $values[$i]);
25
+ }
26
+
27
+ $html .= "</div>\n";
28
+ $html .= $this->getAfterElementHtml();
29
+ return $html;
30
+ }
31
+
32
+ protected function getSelectHtml($name, $values, $selected) {
33
+ $html = '<select name="' . $name . '">' . "\n";
34
+ foreach ($values as $value) {
35
+ $html .= '<option value="' . $value["value"] . '"' . (($value["value"] == $selected) ? ' selected="selected"' : '') . '>';
36
+ $html .= $this->_helper->__($value["label"]);
37
+ $html .= "</option>\n";
38
+ }
39
+ $html .= "</select>\n";
40
+ return $html;
41
+ }
42
+ }
app/code/community/Cloudiq/Callme/controllers/Adminhtml/CallmeController.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class Cloudiq_Callme_Adminhtml_CallmeController
4
+ *
5
+ * @package Cloudiq_Callme
6
+ * @author Meanbee <hello@meanbee.com>
7
+ */
8
+ class Cloudiq_Callme_Adminhtml_CallmeController extends Mage_Adminhtml_Controller_Action {
9
+
10
+ /**
11
+ * Generate and display a preview of the callMe button from the submitted data.
12
+ * Returns only the button specific, incomplete html as this action is meant to
13
+ * be requested through AJAX.
14
+ */
15
+ public function previewAction() {
16
+ $helper = Mage::helper("cloudiq_callme");
17
+
18
+ $form = new Varien_Object($this->getRequest()->getParam("callme"));
19
+
20
+ // Only display the button if config data has been submitted
21
+ if (count($form->getData()) == 0) {
22
+ return;
23
+ }
24
+
25
+ $config = new Varien_Object();
26
+ $helper->updateConfigFromForm($config, $form);
27
+
28
+ /** @var $button Cloudiq_Callme_Block_Button */
29
+ $button = Mage::app()->getLayout()->createBlock("cloudiq_callme/button", "cloudiq_callme.button");
30
+ $button->setConfig($config);
31
+ $button->setDisabled(true);
32
+
33
+ $this->getResponse()->setBody($button->toHtml());
34
+ }
35
+ }
app/code/community/Cloudiq/Callme/controllers/CallmeController.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Callme_CallmeController extends Mage_Core_Controller_Front_Action {
4
+ public function requestAction() {
5
+ /** @var Cloudiq_Callme_Model_Config $_config_model */
6
+ $_config_model = Mage::getModel("cloudiq_callme/config");
7
+
8
+ $result = array(
9
+ "status" => false,
10
+ "message" => "Request status unknown"
11
+ );
12
+
13
+ if ($_config_model->isConfigured()) {
14
+ $phone_number = $this->getRequest()->getParam("phone_number");
15
+ if (!empty($phone_number)) {
16
+ $app_id = $_config_model->getAppId();
17
+
18
+ $api_result = Mage::getModel("cloudiq_callme/api_callme")->requestCallback($app_id, $phone_number);
19
+
20
+ if (is_array($api_result) && is_bool($api_result["status"])) {
21
+ $result = $api_result;
22
+ } else {
23
+ $result["message"] = "API call result unknown";
24
+ }
25
+ } else {
26
+ $result["message"] = "Must specify a phone number to call";
27
+ }
28
+ } else {
29
+ $result["message"] = "callMe module is not available";
30
+ }
31
+
32
+ $this->getResponse()
33
+ ->setHeader('Content-type', 'application/json')
34
+ ->setBody(Zend_Json::encode($result));
35
+ }
36
+ }
app/code/community/Cloudiq/Callme/etc/config.xml ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+
4
+ <modules>
5
+ <Cloudiq_Callme>
6
+ <version>1.0.0</version>
7
+ </Cloudiq_Callme>
8
+ </modules>
9
+
10
+ <global>
11
+ <blocks>
12
+ <cloudiq_callme>
13
+ <class>Cloudiq_Callme_Block</class>
14
+ </cloudiq_callme>
15
+ </blocks>
16
+ <models>
17
+ <cloudiq_callme>
18
+ <class>Cloudiq_Callme_Model</class>
19
+ <resourceModel>cloudiq_callme_resource</resourceModel>
20
+ </cloudiq_callme>
21
+ <cloudiq_callme_resource>
22
+ <class>Cloudiq_Callme_Model_Resource</class>
23
+ <entities>
24
+ <config>
25
+ <table>cloudiq_callme_config</table>
26
+ </config>
27
+ </entities>
28
+ </cloudiq_callme_resource>
29
+ </models>
30
+ <helpers>
31
+ <cloudiq_callme>
32
+ <class>Cloudiq_Callme_Helper</class>
33
+ </cloudiq_callme>
34
+ </helpers>
35
+ <resources>
36
+ <cloudiq_callme_setup>
37
+ <setup>
38
+ <module>Cloudiq_Callme</module>
39
+ </setup>
40
+ </cloudiq_callme_setup>
41
+ </resources>
42
+ <events>
43
+ <cloudiq_core_config_save>
44
+ <observers>
45
+ <cloudiq_callme>
46
+ <type>singleton</type>
47
+ <class>cloudiq_callme/observer</class>
48
+ <method>observeCloudiqCoreConfigSave</method>
49
+ </cloudiq_callme>
50
+ </observers>
51
+ </cloudiq_core_config_save>
52
+ <controller_action_layout_generate_blocks_after>
53
+ <observers>
54
+ <cloudiq_callme_insert_block>
55
+ <type>singleton</type>
56
+ <class>cloudiq_callme/observer</class>
57
+ <method>observeControllerActionLayoutGenerateBlocksAfter</method>
58
+ </cloudiq_callme_insert_block>
59
+ </observers>
60
+ </controller_action_layout_generate_blocks_after>
61
+ </events>
62
+ </global>
63
+
64
+ <frontend>
65
+ <routers>
66
+ <cloudiq>
67
+ <args>
68
+ <modules>
69
+ <cloudiq_callme before="Cloudiq_Core">Cloudiq_Callme</cloudiq_callme>
70
+ </modules>
71
+ </args>
72
+ </cloudiq>
73
+ </routers>
74
+ </frontend>
75
+
76
+ <admin>
77
+ <routers>
78
+ <adminhtml>
79
+ <args>
80
+ <modules>
81
+ <cloudiq_callme before="Mage_Adminhtml">Cloudiq_Callme_Adminhtml</cloudiq_callme>
82
+ </modules>
83
+ </args>
84
+ </adminhtml>
85
+ </routers>
86
+ </admin>
87
+
88
+ <adminhtml>
89
+ <layout>
90
+ <updates>
91
+ <cloudiq_callme>
92
+ <file>cloudiq/callme.xml</file>
93
+ </cloudiq_callme>
94
+ </updates>
95
+ </layout>
96
+ <translate>
97
+ <modules>
98
+ <Cloudiq_Callme>
99
+ <files>
100
+ <default>Cloudiq_Callme.csv</default>
101
+ </files>
102
+ </Cloudiq_Callme>
103
+ </modules>
104
+ </translate>
105
+ </adminhtml>
106
+
107
+ <phpunit>
108
+ <suite>
109
+ <modules>
110
+ <Cloudiq_Callme/>
111
+ </modules>
112
+ </suite>
113
+ </phpunit>
114
+
115
+ </config>
app/code/community/Cloudiq/Callme/sql/cloudiq_callme_setup/mysql4-install-1.0.0.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this;
4
+
5
+ $installer->startSetup();
6
+
7
+ $installer->run("
8
+ CREATE TABLE `{$installer->getTable('cloudiq_callme/config')}` (
9
+ `id` INT(11) NOT NULL auto_increment,
10
+ `enabled` BOOLEAN NOT NULL DEFAULT FALSE,
11
+ `app_id` INT(11) DEFAULT NULL,
12
+ `average_sale_value` VARCHAR(65) DEFAULT NULL,
13
+ `average_sales_call` VARCHAR(16) DEFAULT '3',
14
+ `button_position` VARCHAR(64) DEFAULT 'rightMiddle',
15
+ `button_template` VARCHAR(65) DEFAULT NULL,
16
+ `button_colour` VARCHAR(6) NOT NULL DEFAULT 'EB5E00',
17
+ `button_heading` VARCHAR(64) DEFAULT 'Free Call Back',
18
+ `button_message` VARCHAR(128) DEFAULT 'We\'d love to talk',
19
+ `pop_up_logo` BOOLEAN DEFAULT FALSE,
20
+ `pop_up_title` VARCHAR(64) DEFAULT 'Free Call Back',
21
+ `pop_up_strapline` VARCHAR(256) DEFAULT 'Call back is a free service',
22
+ `pop_up_conf_open` VARCHAR(512) DEFAULT 'Thank you. We\'re open and will be in touch soon',
23
+ `pop_up_conf_closed` VARCHAR(512) DEFAULT 'Thank you. We\'re closed right now, but will call you when we open',
24
+ `callback_callcentre_number` VARCHAR(13) DEFAULT NULL,
25
+ `callback_dtmf_tunnel` VARCHAR(16) DEFAULT NULL,
26
+ `call_centre_time_zone` VARCHAR(32) DEFAULT 'Europe/London',
27
+ `monday_from` VARCHAR(5) DEFAULT '09:00',
28
+ `monday_to` VARCHAR(5) DEFAULT '17:00',
29
+ `tuesday_from` VARCHAR(5) DEFAULT '09:00',
30
+ `tuesday_to` VARCHAR(5) DEFAULT '17:00',
31
+ `wednesday_from` VARCHAR(5) DEFAULT '09:00',
32
+ `wednesday_to` VARCHAR(5) DEFAULT '17:00',
33
+ `thursday_from` VARCHAR(5) DEFAULT '09:00',
34
+ `thursday_to` VARCHAR(5) DEFAULT '17:00',
35
+ `friday_from` VARCHAR(5) DEFAULT '09:00',
36
+ `friday_to` VARCHAR(5) DEFAULT '17:00',
37
+ `saturday_from` VARCHAR(5) DEFAULT '09:00',
38
+ `saturday_to` VARCHAR(5) DEFAULT '17:00',
39
+ `sunday_from` VARCHAR(5) DEFAULT '09:00',
40
+ `sunday_to` VARCHAR(5) DEFAULT '17:00',
41
+ `bank_holiday_from` VARCHAR(5) DEFAULT '09:00',
42
+ `bank_holiday_to` VARCHAR(5) DEFAULT '17:00',
43
+ `display_on_all` BOOLEAN NOT NULL DEFAULT TRUE,
44
+ `display_on_pages` TEXT DEFAULT NULL,
45
+ PRIMARY KEY (`id`),
46
+ UNIQUE KEY `app_idx` (`app_id`)
47
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
48
+
49
+ -- Insert the default config values
50
+ INSERT INTO `{$installer->getTable('cloudiq_callme/config')}` (`id`) VALUES (1);
51
+ ");
52
+
53
+ $installer->endSetup();
app/code/community/Cloudiq/Core/.gitkeep ADDED
File without changes
app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Block_Adminhtml_Config_Edit extends Mage_Adminhtml_Block_Widget_Form_Container {
3
+ public function __construct() {
4
+ parent::__construct();
5
+
6
+ $this->_blockGroup = 'cloudiq_core';
7
+ $this->_controller = 'adminhtml_config';
8
+ $this->_mode = 'edit';
9
+
10
+ }
11
+
12
+ public function getHeaderText() {
13
+ return $this->__('cloud.IQ Settings');
14
+ }
15
+ }
app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Form.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Block_Adminhtml_Config_Edit_Form extends Mage_Adminhtml_Block_Widget_Form {
3
+
4
+ public function _prepareForm() {
5
+ $url = Mage::helper('adminhtml')->getUrl('*/*/save');
6
+ $form = new Varien_Data_Form(array('id' => 'edit_form', 'action' => $url, 'method' => 'post'));
7
+ $form->setUseContainer(true);
8
+ $this->setForm($form);
9
+ return parent::_prepareForm();
10
+ }
11
+
12
+ }
app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Tab/Frame.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Block_Adminhtml_Config_Edit_Tab_Frame extends Mage_Adminhtml_Block_Abstract implements Mage_Adminhtml_Block_Widget_Tab_Interface {
3
+
4
+ /** @var $_helper Cloudiq_Core_Helper_Config */
5
+ protected $_helper;
6
+
7
+ public function _construct() {
8
+ $this->_helper = Mage::helper("cloudiq_core/config");
9
+
10
+ $this->setTemplate("cloudiq/core/tab/frame/iframe.phtml");
11
+ }
12
+
13
+ /**
14
+ * Return the URL to query for the iframe's content.
15
+ *
16
+ * @return string
17
+ */
18
+ public function getSubmitterUrl() {
19
+ return $this->getUrl("adminhtml/cloudiq/frame", array(
20
+ "module" => $this->getModule()
21
+ ));
22
+ }
23
+
24
+ public function getTabLabel() {
25
+ return $this->__($this->getLabel());
26
+ }
27
+
28
+ public function getTabTitle() {
29
+ return $this->__($this->getLabel());
30
+ }
31
+
32
+ public function canShowTab() {
33
+ return $this->_helper->hasBeenSetUp();
34
+ }
35
+
36
+ public function isHidden() {
37
+ return !$this->_helper->hasBeenSetUp();
38
+ }
39
+ }
app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Tab/Global.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Block_Adminhtml_Config_Edit_Tab_Global extends Mage_Adminhtml_Block_Widget_Form implements Mage_Adminhtml_Block_Widget_Tab_Interface {
3
+ /** @var $_helper Cloudiq_Core_Helper_Data */
4
+ protected $_helper;
5
+ /** @var $_config_helper Cloudiq_Core_Helper_Config */
6
+ protected $_config_helper;
7
+
8
+ public function __construct() {
9
+ parent::__construct();
10
+
11
+ $this->_helper = Mage::helper('cloudiq_core');
12
+ $this->_config_helper = Mage::helper('cloudiq_core/config');
13
+
14
+ $this->setTemplate('cloudiq/global/widget/form.phtml');
15
+ }
16
+
17
+ protected function _prepareForm() {
18
+ $form = new Varien_Data_Form();
19
+ $fieldset = $form->addFieldset('settings', array('legend'=>$this->_helper->__('Global Settings')));
20
+
21
+ $fieldset->addField('global[enabled]', 'select', array(
22
+ 'label' => $this->_helper->__('Enable Account?'),
23
+ 'title' => $this->_helper->__('Enable Account?'),
24
+ 'name' => 'global[enabled]',
25
+ 'value' => $this->_config_helper->isEnabled(),
26
+ 'values' => Mage::getSingleton('adminhtml/system_config_source_yesno')->toOptionArray()
27
+ ));
28
+
29
+ $fieldset->addField('global[account_id]', 'text', array(
30
+ 'label' => $this->_helper->__('cloud.IQ Account ID'),
31
+ 'title' => $this->_helper->__('cloud.IQ Account ID'),
32
+ 'name' => 'global[account_id]',
33
+ 'note' => "Cut and paste this from the cloud.IQ confirmation message.",
34
+ 'value' => $this->_config_helper->getAccountId(),
35
+ 'required' => true
36
+ ));
37
+
38
+ $fieldset->addField('global[token]', 'text', array(
39
+ 'label' => $this->_helper->__('cloud.IQ Token'),
40
+ 'title' => $this->_helper->__('cloud.IQ Token'),
41
+ 'name' => 'global[token]',
42
+ 'note' => "Cut and paste this from the cloud.IQ confirmation message.",
43
+ 'value' => $this->_config_helper->getToken(),
44
+ 'required' => true
45
+ ));
46
+
47
+ $this->setForm($form);
48
+ }
49
+
50
+ public function getTabLabel() {
51
+ $label = $this->hasBeenSetUp() ? "Global Settings" : "cloud.IQ Sign up";
52
+ return $this->__($label);
53
+ }
54
+
55
+ public function getTabTitle() {
56
+ $label = $this->hasBeenSetUp() ? "Global Settings" : "cloud.IQ Sign up";
57
+ return $this->__($label);
58
+ }
59
+
60
+ public function canShowTab() {
61
+ // Always show the Global settings tab
62
+ return true;
63
+ }
64
+
65
+ public function isHidden() {
66
+ // Always show the Global settings tab
67
+ return false;
68
+ }
69
+
70
+ /**
71
+ * Check if the module has been setup with API access credentials.
72
+ *
73
+ * @return bool
74
+ */
75
+ protected function hasBeenSetUp() {
76
+ return $this->_config_helper->hasBeenSetUp();
77
+ }
78
+
79
+ /**
80
+ * Return the URL used for the Setup iFrame.
81
+ *
82
+ * @return string
83
+ */
84
+ protected function getSetupUrl() {
85
+ return $this->getUrl("adminhtml/cloudiq/frame", array("module" => "signUp", "profile_id" => 2));
86
+ }
87
+ }
app/code/community/Cloudiq/Core/Block/Adminhtml/Config/Edit/Tabs.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Block_Adminhtml_Config_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs {
3
+
4
+ protected $_config_helper;
5
+
6
+ public function __construct() {
7
+ parent::__construct();
8
+ $this->setId('cloudiq_config_edit_tabs');
9
+ $this->setDestElementId('edit_form');
10
+ $this->setTitle(Mage::helper('adminhtml')->__('cloud.IQ Configuration'));
11
+ $this->_config_helper = Mage::helper('cloudiq_core/config');
12
+ $this->setTemplate('cloudiq/core/tabs.phtml');
13
+ }
14
+
15
+ protected function _beforeToHtml() {
16
+ // Open the Global Settings form tab by default
17
+ $this->setActiveTab("global_section");
18
+ return parent::_beforeToHtml();
19
+ }
20
+ }
app/code/community/Cloudiq/Core/Helper/Config.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Helper_Config extends Mage_Core_Helper_Abstract {
3
+
4
+ const CLOUDIQ_BASE_URL = "https://platform.cloud-iq.com/";
5
+
6
+ public function hasBeenSetUp() {
7
+ return $this->getToken() != "";
8
+ }
9
+
10
+ public function getAccountId() {
11
+ return Mage::getStoreConfig('cloudiq_core/global/account_id');
12
+ }
13
+
14
+ public function getToken() {
15
+ return Mage::getStoreConfig('cloudiq_core/global/token');
16
+ }
17
+
18
+ public function isEnabled() {
19
+ return Mage::getStoreConfig('cloudiq_core/global/enabled');
20
+ }
21
+
22
+ /**
23
+ * Return a URL for the cloud.IQ service.
24
+ *
25
+ * @param $module
26
+ *
27
+ * @return string
28
+ */
29
+ public function getCloudiqUrl($module, $profile_id = false) {
30
+ $url = self::CLOUDIQ_BASE_URL;
31
+
32
+ $parameters = array();
33
+
34
+ if ($module != "") {
35
+ $parameters[] = "module=" . $module;
36
+ }
37
+
38
+ if ($profile_id) {
39
+ $parameters[] = "profileId=" . $profile_id;
40
+ }
41
+
42
+ $parameters = implode("&", $parameters);
43
+ if ($parameters != "") {
44
+ $url .= "?" . $parameters;
45
+ }
46
+
47
+ return $url;
48
+ }
49
+
50
+ /**
51
+ * Return the URL for cloud.IQ API calls.
52
+ *
53
+ * @return string
54
+ */
55
+ public function getCloudiqApiUrl() {
56
+ return self::CLOUDIQ_BASE_URL . "gateway";
57
+ }
58
+ }
app/code/community/Cloudiq/Core/Helper/Data.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Core_Helper_Data extends Mage_Core_Helper_Abstract {
4
+
5
+ }
app/code/community/Cloudiq/Core/Model/Api/Abstract.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Model_Api_Abstract extends Varien_Object {
3
+ /**
4
+ * @return Cloudiq_Core_Model_Api_Request
5
+ */
6
+ protected function _getRequestObject() {
7
+ return Mage::getModel('cloudiq_core/api_request');
8
+ }
9
+ }
10
+
app/code/community/Cloudiq/Core/Model/Api/Core.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Core_Model_Api_Core extends Cloudiq_Core_Model_Api_Abstract {
4
+
5
+ /**
6
+ * Request account information from the cloud.IQ API for either the
7
+ * account provided or the account stored in the configuration (if none
8
+ * is provided). Returns an array with two elements:
9
+ * status - boolean flag, true if the request was successful, false otherwise
10
+ * content - the account information XML element, if the request was successful
11
+ * or the error message if it failed
12
+ *
13
+ * @param $account_id Account ID to lookup instead of the configured one.
14
+ * @param $token Account token to use instead of the configured one.
15
+ *
16
+ * @return array
17
+ */
18
+ public function lookupAccount($account_id = NULL, $token = NULL) {
19
+ $parameters = array(
20
+ "mode" => "lookup",
21
+ "action" => "account"
22
+ );
23
+
24
+ if ($account_id != NULL && $token != NULL) {
25
+ // Lookup the account provided instead of the one saved
26
+ $parameters["id"] = $account_id;
27
+ $parameters["token"] = $token;
28
+ }
29
+
30
+ /** @var Cloudiq_Core_Model_Api_Request $request */
31
+ $request = $this->_getRequestObject()->setParameters($parameters);
32
+ $response = $request->send(Zend_Http_Client::GET);
33
+
34
+ $result = array(
35
+ "status" => false,
36
+ "content" => ""
37
+ );
38
+
39
+ if ($response) {
40
+ if ($response->wasSuccessful()) {
41
+ $account_response = $response->getResponse()->account;
42
+
43
+ if (!is_null($account_response)) {
44
+ if ($account_response["status"] == Cloudiq_Core_Model_Api_Response::STATUS_SUCCESS) {
45
+ $result["status"] = true;
46
+ $result["content"] = $account_response;
47
+ } else {
48
+ // Unsuccessful account response
49
+ $result["content"] = $response->getApiStatusCodeDescription($account_id["status"]);
50
+ if ($account_response->description) {
51
+ $result["content"] .= sprintf(": %s", $account_response->description);
52
+ }
53
+ }
54
+ } else {
55
+ $result["content"] = "Unkown API response status";
56
+ }
57
+ } else {
58
+ // Unsuccessful response
59
+ $result["content"] = $response->getErrorMessage();
60
+ }
61
+ } else {
62
+ $result["content"] = "Failed to connect to cloud.IQ API";
63
+ }
64
+
65
+ return $result;
66
+ }
67
+ }
app/code/community/Cloudiq/Core/Model/Api/Request.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @method setParameters(array $parameters)
4
+ * @method getParameters()
5
+ */
6
+ class Cloudiq_Core_Model_Api_Request extends Varien_Object {
7
+
8
+ const LOG_LOCATION = "cloudiq_api.log";
9
+
10
+ /**
11
+ * @param $method HTTP verb to use when making the request, e.g. GET, POST, DELETE, PUT.
12
+ *
13
+ * @throws Zend_Http_Exception
14
+ *
15
+ * @return Cloudiq_Core_Model_Api_Response
16
+ */
17
+ public function send($method) {
18
+ $_config_helper = Mage::helper("cloudiq_core/config");
19
+
20
+ $parameters = $this->getParameters();
21
+ $get_params = array();
22
+ $post_params = array();
23
+
24
+ if (!isset($parameters["token"])) {
25
+ // Authenticate through GET parameters
26
+ $get_params["id"] = $_config_helper->getAccountId();
27
+ $get_params["token"] = $_config_helper->getToken();
28
+ }
29
+
30
+ if ($method == Zend_Http_Client::GET) {
31
+ $get_params = array_merge($get_params, $parameters);
32
+ } else {
33
+ $post_params = array_merge($post_params, $parameters);
34
+ }
35
+
36
+ $client = new Zend_Http_Client();
37
+ $client
38
+ ->setUri($this->getEndpoint())
39
+ ->setParameterGet($get_params)
40
+ ->setParameterPost($post_params)
41
+ ->setMethod($method);
42
+
43
+ try {
44
+ /** @var $request_result Zend_Http_Response */
45
+ $request_result = $client->request();
46
+ } catch (Zend_Http_Client_Exception $e) {
47
+ $this->_log(Zend_Log::ERR, $e->getMessage(), $this->getEndpoint(), $method, $get_params, $post_params);
48
+ return null;
49
+ }
50
+
51
+ /** @var $response Cloudiq_Core_Model_Api_Response */
52
+ $response = Mage::getModel('cloudiq_core/api_response');
53
+ $response->populate($request_result);
54
+
55
+ $this->_log(Zend_Log::DEBUG, "Success", $this->getEndpoint(), $method, $get_params, $post_params, $response);
56
+
57
+ return $response;
58
+ }
59
+
60
+ /**
61
+ * Return the URI to make API calls against.
62
+ *
63
+ * @return string
64
+ */
65
+ public function getEndpoint() {
66
+ return Mage::helper("cloudiq_core/config")->getCloudiqApiUrl();
67
+ }
68
+
69
+ /**
70
+ * Print the API request details to a log file.
71
+ *
72
+ * @param $level
73
+ * @param $status
74
+ * @param $url
75
+ * @param $method
76
+ * @param array $get_params
77
+ * @param array $post_params
78
+ * @param Cloudiq_Core_Model_Api_Response $response
79
+ */
80
+ protected function _log($level, $status, $url, $method, $get_params = array(), $post_params = array(), $response = NULL) {
81
+ $message = sprintf("%s\nMethod: %s\nEndpoint: %s\n", $status, $method, $url);
82
+
83
+ if (!empty($get_params)) {
84
+ array_walk($get_params, function (&$v, $k) { $v = sprintf("%s = %s", $k, $v); });
85
+ $message .= sprintf("GET Parameters:\n\t%s\n", implode("\n\t", $get_params));
86
+ }
87
+
88
+ if (!empty($post_params)) {
89
+ array_walk($post_params, function (&$v, $k) { $v = sprintf("%s = %s", $k, $v); });
90
+ $message .= sprintf("POST Parameters:\n\t%s\n", implode("\n\t", $post_params));
91
+ }
92
+
93
+ if (!is_null($response)) {
94
+ $message .= sprintf("Response status: %s\n", ($response->wasSuccessful() ? "Successful" : "Failed"));
95
+ if (!$response->wasSuccessful()) {
96
+ $message .= sprintf("Response error: %s\n", $response->getErrorMessage());
97
+ }
98
+ $raw_response = $response->getRawResponse();
99
+ if ($raw_response) {
100
+ $message .= sprintf("Raw response:\n\t%s\n\t%s\n",
101
+ $raw_response->getHeadersAsString(true, "\n\t"),
102
+ implode("\n\t", explode("\n", $raw_response->getBody()))
103
+ );
104
+ }
105
+ }
106
+
107
+ Mage::log($message, $level, self::LOG_LOCATION);
108
+ }
109
+ }
app/code/community/Cloudiq/Core/Model/Api/Response.php ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @method setRawResponse(Zend_Http_Response $raw_response)
4
+ * @method Zend_Http_Response getRawResponse()
5
+ * @method setHttpStatusCode(int $status_code)
6
+ * @method int getHttpStatusCode()
7
+ * @method setXmlParseErrors(array $errors)
8
+ * @method array getXmlParseErrors()
9
+ * @method setResponse(SimpleXMLElement $data)
10
+ * @method SimpleXMLElement getResponse()
11
+ * @method bool hasResponse()
12
+ */
13
+ class Cloudiq_Core_Model_Api_Response extends Varien_Object {
14
+
15
+ const STATUS_SUCCESS = 1;
16
+ const STATUS_AUTHENTICATION_FAILURE = 101;
17
+ const STATUS_REQUESTING_IP_NOT_VALID = 102;
18
+ const STATUS_INVALID_FUNCTION_REQUEST = 103;
19
+ const STATUS_REQUEST_MISSING_A_MANDATORY_PARAMETER = 104;
20
+ const STATUS_FORMAT_ERROR_IN_REQUEST_PARAMETERS = 105;
21
+ const STATUS_REQUEST_NOT_VALID_FOR_GIVEN_ACCOUNT = 106;
22
+ const STATUS_INTERNAL_ERROR_WHILE_PROCESSING_YOUR_REQUEST = 107;
23
+ const STATUS_VALIDATION_FAILED = 200;
24
+
25
+ public static $status_code_descriptions = array(
26
+ self::STATUS_SUCCESS => "Success",
27
+ self::STATUS_AUTHENTICATION_FAILURE => "Authentication failure",
28
+ self::STATUS_REQUESTING_IP_NOT_VALID => "Requesting ip not valid",
29
+ self::STATUS_INVALID_FUNCTION_REQUEST => "Invalid function request",
30
+ self::STATUS_REQUEST_MISSING_A_MANDATORY_PARAMETER => "Request missing a mandatory parameter",
31
+ self::STATUS_FORMAT_ERROR_IN_REQUEST_PARAMETERS => "Format error in request parameters",
32
+ self::STATUS_REQUEST_NOT_VALID_FOR_GIVEN_ACCOUNT => "Request not valid for given account",
33
+ self::STATUS_INTERNAL_ERROR_WHILE_PROCESSING_YOUR_REQUEST => "Internal error while processing your request",
34
+ self::STATUS_VALIDATION_FAILED => "Validation failed"
35
+ );
36
+
37
+ /**
38
+ * Populate the response data from the given Zend_Http_Response object.
39
+ *
40
+ * @param Zend_Http_Response $raw_response
41
+ *
42
+ * @return $this
43
+ */
44
+ public function populate(Zend_Http_Response $raw_response) {
45
+ $this->setRawResponse($raw_response);
46
+
47
+ $this->setHttpStatusCode((int) $raw_response->getStatus());
48
+
49
+ // Handle libxml errors internally
50
+ $this->setXmlParseErrors(NULL);
51
+ $xml_errors_setting = libxml_use_internal_errors(true);
52
+
53
+ // The XML API response gets sent with content type text/html,
54
+ // so decode the HTML entities before parsing
55
+ $decoded_data = simplexml_load_string(utf8_encode(html_entity_decode($raw_response->getBody())));
56
+
57
+ if ($decoded_data) {
58
+ $this->setResponse($decoded_data);
59
+ } else {
60
+ $xml_errors = array();
61
+ foreach (libxml_get_errors() as $error) {
62
+ $xml_errors[] = preg_replace("/\n/", " ", $error->message);
63
+ }
64
+ $this->setXmlParseErrors($xml_errors);
65
+ }
66
+
67
+ // Reset libxml error settings
68
+ libxml_clear_errors();
69
+ libxml_use_internal_errors($xml_errors_setting);
70
+
71
+ return $this;
72
+ }
73
+
74
+ public function getResponseXPath($xpath) {
75
+ if ($this->hasResponse()) {
76
+ return $this->getResponse()->xpath($xpath);
77
+ } else {
78
+ return NULL;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get the API response status code. Return false if there
84
+ * is no status code.
85
+ *
86
+ * @return int
87
+ */
88
+ public function getApiStatusCode() {
89
+ $status_attribute_xpath = $this->getResponseXPath('/response/@status[1]');
90
+ if (count($status_attribute_xpath) == 1) {
91
+ $status_attribute = (string) $status_attribute_xpath[0];
92
+ if (is_numeric($status_attribute)) {
93
+ return (int) $status_attribute;
94
+ }
95
+ }
96
+ return false;
97
+ }
98
+
99
+ /**
100
+ * Return the description text for the given API response code. Returns
101
+ * "Unknown" for unknown codes.
102
+ *
103
+ * @param int $code API response code
104
+ *
105
+ * @return string
106
+ */
107
+ public function getApiStatusCodeDescription($code) {
108
+ if (isset(self::$status_code_descriptions[$code])) {
109
+ return self::$status_code_descriptions[$code];
110
+ }
111
+
112
+ return "Unknown";
113
+ }
114
+
115
+ /**
116
+ * Perform a check on the HTTP Status Code returned by the request and evaluate the status attribute of
117
+ * the wrapping <response> element for a valid status code.
118
+ *
119
+ * @return bool
120
+ */
121
+ public function wasSuccessful() {
122
+ if ($this->getResponse()) {
123
+ $http_status_string = (string) $this->getHttpStatusCode();
124
+
125
+ if (substr($http_status_string, 0, 1) == "2") {
126
+ $api_status = $this->getApiStatusCode();
127
+
128
+ if ($api_status !== false) {
129
+ return $api_status == self::STATUS_SUCCESS;
130
+ }
131
+ }
132
+ }
133
+
134
+ return false;
135
+ }
136
+
137
+ /**
138
+ * Choose and return the appropriate text error message for this request.
139
+ * Returns false if the request was successful.
140
+ *
141
+ * @return bool|string
142
+ */
143
+ public function getErrorMessage() {
144
+ $message = false;
145
+
146
+ if (!$this->wasSuccessful()) {
147
+ // Check for API status errors
148
+ $api_status_code = $this->getApiStatusCode();
149
+ if ($api_status_code !== false) {
150
+ $message = $this->getApiStatusCodeDescription($api_status_code);
151
+ } else {
152
+ // Check for response parse errors
153
+ $xml_parse_errors = $this->getXmlParseErrors();
154
+ if (!empty($xml_parse_errors)) {
155
+ $message = "Failed to parse the API response: " . implode(", ", $xml_parse_errors);
156
+ } else {
157
+ // Check for HTTP errors
158
+ $http_status_code = $this->getHttpStatusCode();
159
+ if (!is_null($http_status_code)) {
160
+ $message = "HTTP Error: " . Zend_Http_Response::responseCodeAsText($http_status_code);
161
+ } else {
162
+ $message = "Unknown error";
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ return $message;
169
+ }
170
+ }
app/code/community/Cloudiq/Core/Test/Config/Base.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Test_Config_Base extends EcomDev_PHPUnit_Test_Case_Config {
3
+ /**
4
+ * @test
5
+ */
6
+ public function testBasicConfiguration() {
7
+ $this->assertModuleCodePool('community');
8
+ $this->assertModuleVersion('1.0.0');
9
+ }
10
+
11
+ /**
12
+ * @test
13
+ */
14
+ public function testClassAliases() {
15
+ $this->assertHelperAlias('cloudiq_core', 'Cloudiq_Core_Helper_Data');
16
+ $this->assertModelAlias('cloudiq_core/test', 'Cloudiq_Core_Model_Test');
17
+ $this->assertBlockAlias('cloudiq_core/test', 'Cloudiq_Core_Block_Test');
18
+ }
19
+
20
+ /**
21
+ * @test
22
+ */
23
+ public function testDataHelperExists() {
24
+ $this->assertInstanceOf('Cloudiq_Core_Helper_Data', Mage::helper('cloudiq_core'));
25
+ }
26
+ }
app/code/community/Cloudiq/Core/Test/Model/Api/Response.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cloudiq_Core_Test_Model_Api_Response extends Cloudiq_Core_Test_Model_Api_TestCase {
4
+
5
+ /** @var Cloudiq_Core_Model_Api_Response */
6
+ protected $_model;
7
+
8
+ protected function setUp() {
9
+ parent::setUp();
10
+
11
+ $this->_model = Mage::getModel('cloudiq_core/api_response');
12
+ }
13
+
14
+ protected function tearDown() {
15
+ parent::tearDown();
16
+
17
+ $this->_model = null;
18
+ }
19
+
20
+ /**
21
+ * @test
22
+ */
23
+ public function testClassConstruction() {
24
+ $this->assertInstanceOf('Cloudiq_Core_Model_Api_Response', $this->_model);
25
+ }
26
+
27
+ /**
28
+ * @test
29
+ */
30
+ public function testWasSuccessfulNoRequest() {
31
+ $this->assertFalse($this->_model->wasSuccessful());
32
+ }
33
+
34
+ /**
35
+ * @test
36
+ */
37
+ public function testWasSuccessfulStatusCodeAndResponseAttribute() {
38
+ $successful_response = Zend_Http_Response::fromString($this->_loadFileContents('Response/testWasSuccessfulStatusCodeAndResponseAttribute.txt'));
39
+
40
+ $this->_model->populate($successful_response);
41
+
42
+ $this->assertTrue($this->_model->wasSuccessful());
43
+ }
44
+
45
+ /**
46
+ * @test
47
+ */
48
+ public function testWasSuccessfulBadResponseAttribute() {
49
+ $bad_response = Zend_Http_Response::fromString($this->_loadFileContents('Response/testWasSuccessfulBadResponseAttribute.txt'));
50
+
51
+ $this->_model->populate($bad_response);
52
+
53
+ $this->assertFalse($this->_model->wasSuccessful());
54
+ }
55
+ }
app/code/community/Cloudiq/Core/Test/Model/Api/TestCase.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ abstract class Cloudiq_Core_Test_Model_Api_TestCase extends EcomDev_PHPUnit_Test_Case {
3
+ /**
4
+ * @param string $http_method
5
+ * @param int $status_code
6
+ * @param string $response_filename
7
+ *
8
+ * @return PHPUnit_Framework_MockObject_MockObject
9
+ */
10
+ public function getRequestMock($http_method, $status_code, $response_filename) {
11
+ $file_contents = $this->_loadFileContents($response_filename);
12
+
13
+ $this->assertNotNull($file_contents);
14
+ $this->assertNotEmpty($file_contents);
15
+
16
+ /** @var $response Cloudiq_Core_Model_Api_Response */
17
+ $response = Mage::getModel('cloudiq_core/api_response');
18
+ $response
19
+ ->setStatusCode($status_code)
20
+ ->setRawResponse($file_contents);
21
+
22
+ $request_mock = $this->getModelMock('cloudiq_core/api_request', array('send'));
23
+
24
+ $request_mock
25
+ ->expects($this->once())
26
+ ->method('send')
27
+ ->with($this->equalTo($http_method))
28
+ ->will($this->returnValue($response));
29
+
30
+ return $request_mock;
31
+ }
32
+
33
+ protected function _loadFileContents($file) {
34
+ $directory_tree = array(
35
+ Mage::getModuleDir('', 'Cloudiq_Core'),
36
+ 'Test',
37
+ 'Model',
38
+ 'Api',
39
+ 'data',
40
+ $file
41
+ );
42
+
43
+ $file_path = join(DS, $directory_tree);
44
+
45
+ return file_get_contents($file_path);
46
+ }
47
+ }
app/code/community/Cloudiq/Core/Test/Model/Api/data/Response/testWasSuccessfulBadResponseAttribute.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ HTTP/1.1 403 Forbidden
2
+ Date: Tue, 18 Jun 2013 11:46:25 GMT
3
+ Server: Apache-Coyote/1.1
4
+ X-powered-by: Servlet 2.4; JBoss-4.0.5.GA (build: CVSTag=Branch_4_0 date=200610162339)/Tomcat-5.5
5
+ Content-type: text/xml;charset=ISO-8859-1
6
+ Content-length: 201
7
+ Via: 1.1 nimbus.cloud-iq.com
8
+ Connection: close
9
+
10
+ <?xml version="1.0" encoding="utf-8" ?>
11
+ <response status="101">
12
+ <description>Authentication failure</description>
13
+ <requestId>273025</requestId>
14
+ <dateTime>2013-06-18T11:46:25Z</dateTime>
15
+ </response>
app/code/community/Cloudiq/Core/Test/Model/Api/data/Response/testWasSuccessfulStatusCodeAndResponseAttribute.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ HTTP/1.1 202 Accepted
2
+ Date: Tue, 18 Jun 2013 11:30:50 GMT
3
+ Server: Apache-Coyote/1.1
4
+ X-powered-by: Servlet 2.4; JBoss-4.0.5.GA (build: CVSTag=Branch_4_0 date=200610162339)/Tomcat-5.5
5
+ Content-type: text/html;charset=ISO-8859-1
6
+ Content-length: 914
7
+ Via: 1.1 nimbus.cloud-iq.com
8
+ Connection: close
9
+
10
+ <?xml version="1.0" encoding="utf-8" ?>
11
+ <response status="1">
12
+ <description>Success</description>
13
+ <requestId>273005</requestId>
14
+ <dateTime>2013-06-18T11:30:50Z</dateTime>
15
+ <timeZone>GMT</timeZone>
16
+ </response>
app/code/community/Cloudiq/Core/controllers/Adminhtml/CloudiqController.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cloudiq_Core_Adminhtml_CloudiqController extends Mage_Adminhtml_Controller_Action {
3
+ public function editAction() {
4
+ $this->loadLayout();
5
+ $this->_setActiveMenu('cloudiq');
6
+ $this->renderLayout();
7
+ }
8
+
9
+ public function saveAction() {
10
+ Mage::dispatchEvent("cloudiq_core_config_save", array('request'=>$this->getRequest()));
11
+
12
+ $request = $this->getRequest();
13
+ $form_data = new Varien_Object($request->getParam("global"));
14
+
15
+ if (count($form_data->getData()) > 0) {
16
+ // Save global Cloud.IQ config data to system config
17
+
18
+ $account_id = trim($form_data->getData("account_id"));
19
+ $token = trim($form_data->getData("token"));
20
+ $enabled = ($form_data->getData("enabled")) ? 1 : 0;
21
+
22
+ // Validate the form fields
23
+ $validation_errors = 0;
24
+
25
+ if ($account_id == "") {
26
+ $validation_errors++;
27
+ Mage::getSingleton("adminhtml/session")->addError($this->__("cloud.IQ: Account ID can not be empty."));
28
+ }
29
+
30
+ if ($token == "") {
31
+ $validation_errors++;
32
+ Mage::getSingleton("adminhtml/session")->addError($this->__("cloud.IQ: Token can not be empty."));
33
+ }
34
+
35
+ if ($validation_errors == 0 && $enabled) {
36
+ // Check if the account is valid using the API
37
+ $result = Mage::getModel("cloudiq_core/api_core")->lookupAccount($account_id, $token);
38
+ if ($result["status"] !== true) {
39
+ $validation_errors++;
40
+ Mage::getSingleton("adminhtml/session")->addError(sprintf("%s: %s", $this->__("cloud.IQ: The account credentials provided could not be verified"), $this->__($result["content"])));
41
+ }
42
+ }
43
+
44
+ if ($validation_errors == 0) {
45
+ // Save configuration to system config
46
+ $_system_config = Mage::getConfig();
47
+
48
+ $_system_config->saveConfig("cloudiq_core/global/account_id", $account_id);
49
+ $_system_config->saveConfig("cloudiq_core/global/token", $token);
50
+
51
+ $_system_config->saveConfig("cloudiq_core/global/enabled", $enabled);
52
+
53
+ Mage::getSingleton("adminhtml/session")->addSuccess(Mage::helper("cloudiq_core")->__("Global configuration saved."));
54
+
55
+ // Reload system config, so changes are picked up
56
+ $_system_config->reinit();
57
+ Mage::app()->reinitStores();
58
+ }
59
+ }
60
+
61
+ // Redirect back to the form, preserving any params (such as active tab)
62
+ $this->_redirect("*/*/edit", array("_current" => true));
63
+ }
64
+
65
+ public function frameAction() {
66
+ $_config_helper = Mage::helper("cloudiq_core/config");
67
+
68
+ $module = $this->getRequest()->getParam("module");
69
+ $profile_id = $this->getRequest()->getParam("profile_id");
70
+
71
+ $destination = $_config_helper->getCloudiqUrl($module, ($profile_id != null) ? $profile_id : null);
72
+
73
+ $additional_parameters = array();
74
+
75
+ // Pre-populate the sign up form with current user details
76
+ if ($module == "signUp") {
77
+ /** @var $user Mage_Admin_Model_User */
78
+ $user = Mage::getSingleton('admin/session')->getUser();
79
+ $additional_parameters["username"] = $user->getEmail();
80
+ $additional_parameters["fullName"] = $user->getName();
81
+ }
82
+
83
+ $this->getResponse()->setBody(
84
+ $this->getLayout()->createBlock("core/template", "")
85
+ ->setTemplate("cloudiq/core/tab/frame/submitter.phtml")
86
+ ->setDestination($destination)
87
+ ->setAccountId($_config_helper->getAccountId())
88
+ ->setToken($_config_helper->getToken())
89
+ ->setAdditionalParameters($additional_parameters)
90
+ ->toHtml()
91
+ );
92
+ }
93
+ }
app/code/community/Cloudiq/Core/etc/adminhtml.xml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <menu>
4
+ <cloudiq translate="title" module="cloudiq_core">
5
+ <title>cloud.IQ</title>
6
+ <sort_order>900</sort_order>
7
+ <action>adminhtml/cloudiq/edit</action>
8
+ </cloudiq>
9
+ </menu>
10
+ <acl>
11
+ <resources>
12
+ <admin>
13
+ <children>
14
+ <system>
15
+ <children>
16
+ <config>
17
+ <children>
18
+ <cloudiq_core translate="title">
19
+ <title>cloud.IQ Section</title>
20
+ <sort_order>60</sort_order>
21
+ </cloudiq_core>
22
+ </children>
23
+ </config>
24
+ </children>
25
+ </system>
26
+ </children>
27
+ </admin>
28
+ </resources>
29
+ </acl>
30
+ </config>
31
+
app/code/community/Cloudiq/Core/etc/config.xml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+
4
+ <modules>
5
+ <Cloudiq_Core>
6
+ <version>1.0.0</version>
7
+ </Cloudiq_Core>
8
+ </modules>
9
+
10
+ <global>
11
+ <blocks>
12
+ <cloudiq_core>
13
+ <class>Cloudiq_Core_Block</class>
14
+ </cloudiq_core>
15
+ </blocks>
16
+ <models>
17
+ <cloudiq_core>
18
+ <class>Cloudiq_Core_Model</class>
19
+ </cloudiq_core>
20
+ </models>
21
+ <helpers>
22
+ <cloudiq_core>
23
+ <class>Cloudiq_Core_Helper</class>
24
+ </cloudiq_core>
25
+ </helpers>
26
+ </global>
27
+
28
+ <frontend>
29
+ <routers>
30
+ <cloudiq>
31
+ <use>standard</use>
32
+ <args>
33
+ <frontName>cloudiq</frontName>
34
+ <module>Cloudiq_Core</module>
35
+ </args>
36
+ </cloudiq>
37
+ </routers>
38
+ </frontend>
39
+
40
+ <admin>
41
+ <routers>
42
+ <adminhtml>
43
+ <args>
44
+ <modules>
45
+ <cloudiq_core before="Mage_Adminhtml">Cloudiq_Core_Adminhtml</cloudiq_core>
46
+ </modules>
47
+ </args>
48
+ </adminhtml>
49
+ </routers>
50
+ </admin>
51
+
52
+ <adminhtml>
53
+ <layout>
54
+ <updates>
55
+ <cloudiq_core>
56
+ <file>cloudiq/core.xml</file>
57
+ </cloudiq_core>
58
+ </updates>
59
+ </layout>
60
+ </adminhtml>
61
+
62
+ <phpunit>
63
+ <suite>
64
+ <modules>
65
+ <Cloudiq_Core />
66
+ </modules>
67
+ </suite>
68
+ </phpunit>
69
+
70
+ </config>
app/design/adminhtml/default/default/layout/cloudiq/callme.xml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <layout>
3
+ <adminhtml_cloudiq_edit>
4
+ <reference name="head">
5
+ <action method="addCss"><stylesheet>cloudiq/callme.css</stylesheet></action>
6
+ <action method="addItem"><type>skin_js</type><name>cloudiq/js/callme.js</name></action>
7
+ <action method="addJs"><script>cloudiq/callme-jscolor/jscolor.js</script></action>
8
+ <action method="addJs"><script>cloudiq/callme-button.js</script></action>
9
+ </reference>
10
+ <reference name="left">
11
+ <reference name="cloudiq.config.edit.tabs">
12
+ <block type="cloudiq_callme/adminhtml_config_edit_tab_callme" name="cloudiq.config.edit.tab.callme">
13
+ <action method="setAfter">
14
+ <after>global_section</after>
15
+ </action>
16
+ </block>
17
+ <action method="addTab">
18
+ <name>callme_section</name>
19
+ <block>cloudiq.config.edit.tab.callme</block>
20
+ </action>
21
+ </reference>
22
+ </reference>
23
+ </adminhtml_cloudiq_edit>
24
+ </layout>
app/design/adminhtml/default/default/layout/cloudiq/core.xml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <layout>
3
+ <default>
4
+ <reference name="head">
5
+ <action method="addCss"><stylesheet>cloudiq/core.css</stylesheet></action>
6
+ </reference>
7
+ </default>
8
+ <adminhtml_cloudiq_edit>
9
+ <reference name="content">
10
+ <block type="cloudiq_core/adminhtml_config_edit" name="cloudiq.config.edit"/>
11
+ </reference>
12
+ <reference name="left">
13
+ <block type="cloudiq_core/adminhtml_config_edit_tabs" name="cloudiq.config.edit.tabs">
14
+ <block type="cloudiq_core/adminhtml_config_edit_tab_global" name="cloudiq.config.edit.tab.global"/>
15
+ <action method="addTab">
16
+ <name>global_section</name>
17
+ <block>cloudiq.config.edit.tab.global</block>
18
+ </action>
19
+ <block type="cloudiq_core/adminhtml_config_edit_tab_frame" name="cloudiq.config.edit.tab.dashboard">
20
+ <action method="setLabel"><label>Dashboard</label></action>
21
+ <action method="setModule"><module>dashboard</module></action>
22
+ </block>
23
+ <action method="addTab">
24
+ <name>dashboard_section</name>
25
+ <block>cloudiq.config.edit.tab.dashboard</block>
26
+ </action>
27
+ <block type="cloudiq_core/adminhtml_config_edit_tab_frame" name="cloudiq.config.edit.tab.account">
28
+ <action method="setLabel"><label>Account</label></action>
29
+ <action method="setModule"><module>account</module></action>
30
+ </block>
31
+ <action method="addTab">
32
+ <name>account_section</name>
33
+ <block>cloudiq.config.edit.tab.account</block>
34
+ </action>
35
+ <block type="cloudiq_core/adminhtml_config_edit_tab_frame" name="cloudiq.config.edit.tab.support">
36
+ <action method="setLabel"><label>Support</label></action>
37
+ <action method="setModule"><module>support</module></action>
38
+ </block>
39
+ <action method="addTab">
40
+ <name>support_section</name>
41
+ <block>cloudiq.config.edit.tab.support</block>
42
+ </action>
43
+ </block>
44
+ </reference>
45
+ </adminhtml_cloudiq_edit>
46
+ </layout>
47
+
app/design/adminhtml/default/default/template/cloudiq/core/tab/frame/iframe.phtml ADDED
@@ -0,0 +1 @@
 
1
+ <iframe src="<?php echo $this->getSubmitterUrl(); ?>" frameborder="0" width="100%" height="787px"></iframe>
app/design/adminhtml/default/default/template/cloudiq/core/tab/frame/submitter.phtml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <form action="<?php echo $this->getDestination(); ?>" method="POST">
2
+ <input type="hidden" name="id" value="<?php echo $this->getAccountId(); ?>" />
3
+ <input type="hidden" name="token" value="<?php echo $this->getToken() ?>" />
4
+ <?php $additional_parameters = $this->getAdditionalParameters(); ?>
5
+ <?php foreach ($additional_parameters as $name => $value): ?>
6
+ <input type="hidden" name="<?php echo $name; ?>" value="<?php echo $value; ?>" />
7
+ <?php endforeach; ?>
8
+ </form>
9
+ <script type="text/javascript">
10
+ document.forms[0].submit();
11
+ </script>
app/design/adminhtml/default/default/template/cloudiq/core/tabs.phtml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if($this->getTitle()): ?>
2
+ <h3><?php echo $this->getTitle() ?></h3>
3
+ <?php endif ?>
4
+ <?php if(!empty($tabs)): ?>
5
+ <ul id="<?php echo $this->getId() ?>" class="tabs">
6
+ <?php foreach ($tabs as $_tab): ?>
7
+ <?php if (!$this->canShowTab($_tab)): continue; endif; ?>
8
+ <li <?php if($this->getTabIsHidden($_tab)): ?> style="display:none"<?php endif; ?>>
9
+ <a href="<?php echo $this->getTabUrl($_tab) ?>" id="<?php echo $this->getTabId($_tab) ?>" name="<?php echo $this->getTabId($_tab, false) ?>" title="<?php echo $this->getTabTitle($_tab) ?>" class="tab-item-link <?php echo $this->getTabClass($_tab) ?><?php if (preg_match('/\s?ajax\s?/', $_tab->getClass())) {?> notloaded<?php }?>">
10
+ <span><span class="changed" title="<?php echo $this->__('The information in this tab has been changed.') ?>"></span><span class="error" title="<?php echo $this->__('This tab contains invalid data. Please solve the problem before saving.') ?>"></span><?php echo $this->getTabLabel($_tab); ?></span>
11
+ </a>
12
+ <div id="<?php echo $this->getTabId($_tab) ?>_content" style="display:none;"><?php echo $this->getTabContent($_tab) ?></div>
13
+ </li>
14
+ <?php endforeach; ?>
15
+ </ul>
16
+ <script type="text/javascript">
17
+ <?php echo $this->getJsObjectName() ?> = new varienTabs('<?php echo $this->getId() ?>', '<?php echo $this->getDestElementId() ?>', '<?php echo $this->getActiveTabId() ?>', <?php echo $this->getAllShadowTabs()?>);
18
+ </script>
19
+ <script type="text/javascript">
20
+ document.observe("dom:loaded", function() {
21
+ var varien_tabs = <?php echo $this->getJsObjectName() ?>;
22
+ var original_url = $('edit_form').action;
23
+
24
+ $$('.form-buttons .save').each(function(element) {
25
+ element.onclick = function (e) {
26
+ var new_url = original_url;
27
+ if (original_url.substr(-1,1) != "/") {
28
+ new_url = new_url + "/";
29
+ }
30
+ new_url = new_url + "active_tab/" + varien_tabs.activeTab.name + "/";
31
+
32
+ $('edit_form').action = new_url;
33
+
34
+ editForm.submit();
35
+ }
36
+ });
37
+ });
38
+ </script>
39
+ <?php endif; ?>
app/design/adminhtml/default/default/template/cloudiq/global/tab.phtml ADDED
@@ -0,0 +1 @@
 
1
+ TODO
app/design/adminhtml/default/default/template/cloudiq/global/widget/form.phtml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Form template for the Global Settings tab of the configuration
4
+ * form for Cloudiq_Core module.
5
+ *
6
+ * Based on default/default/template/widget/form.phtml
7
+ */
8
+ ?>
9
+ <div class="entry-edit">
10
+ <?php if (!$this->hasBeenSetUp()) : ?>
11
+ <iframe src="<?php echo $this->getSetupUrl() ?>" frameborder="0" width="100%" height="600"></iframe>
12
+ <?php endif; ?>
13
+ <?php echo $this->getFormHtml();?>
14
+ </div>
15
+ <?php echo $this->getChildHtml('form_after');?>
app/design/frontend/base/default/template/cloudiq/callme/button.phtml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <style type="text/css">
2
+ .<?php echo $this->_getTemplateClass(); ?> {
3
+ background: -moz-linear-gradient(top, #<?php echo $this->_getGradientStartColour(); ?> 0%, #<?php echo $this->_getGradientEndColour(); ?> 100%); /* FF3.6+ */
4
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#<?php echo $this->_getGradientStartColour(); ?>), color-stop(100%,#<?php echo $this->_getGradientEndColour(); ?>)); /* Chrome,Safari4+ */
5
+ background: -webkit-linear-gradient(top, #<?php echo $this->_getGradientStartColour(); ?> 0%,#<?php echo $this->_getGradientEndColour(); ?> 100%); /* Chrome10+,Safari5.1+ */
6
+ background: -o-linear-gradient(top, #<?php echo $this->_getGradientStartColour(); ?> 0%,#<?php echo $this->_getGradientEndColour(); ?> 100%); /* Opera 11.10+ */
7
+ background: -ms-linear-gradient(top, #<?php echo $this->_getGradientStartColour(); ?> 0%,#<?php echo $this->_getGradientEndColour(); ?> 100%); /* IE10+ */
8
+ background: linear-gradient(to bottom, #<?php echo $this->_getGradientStartColour(); ?> 0%,#<?php echo $this->_getGradientEndColour(); ?> 100%); /* W3C */
9
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#<?php echo $this->_getGradientStartColour(); ?>', endColorstr='#<?php echo $this->_getGradientEndColour(); ?>',GradientType=0 ); /* IE6-8 */
10
+ color: #<?php echo $this->_getContrastColourBW($this->_getColour()); ?>;
11
+ border-color: #<?php echo $this->_getColour(); ?>;
12
+ }
13
+ #cloudiq-callme-popup.<?php echo $this->_getTemplateClass(); ?> button {
14
+ background-color: #<?php echo $this->_getColour(); ?>;
15
+ color: #<?php echo $this->_getContrastColourBW($this->_getColour()); ?>;
16
+ }
17
+ </style>
18
+ <div id="cloudiq-callme-button" class="<?php echo $this->_getButtonClass(); ?>">
19
+ <p id="cloudiq-callme-button-title"><?php echo $this->_getFromConfig("button_heading"); ?></p>
20
+ <p id="cloudiq-callme-button-message"><?php echo $this->_getFromConfig("button_message"); ?></p>
21
+ </div>
22
+ <div id="cloudiq-callme-popup" class="<?php echo $this->_getPopupClass(); ?>">
23
+ <div id="cloudiq-callme-popup-close">&times;</div>
24
+ <?php if ($this->_getLogoUrl()) : ?>
25
+ <img id="cloudiq-callme-popup-logo" src="<?php echo $this->_getLogoUrl(); ?>" alt="Logo" />
26
+ <?php endif; ?>
27
+ <p id="cloudiq-callme-popup-title"><?php echo $this->_getFromConfig("pop_up_title"); ?></p>
28
+ <p id="cloudiq-callme-popup-strapline"><?php echo $this->_getFromConfig("pop_up_strapline"); ?></p>
29
+ <form id="cloudiq-callme-popup-form" name="cloudiq-callme-popup-form" method="POST" action="<?php echo $this->_getFormUrl(); ?>">
30
+ <label for="cloudiq-callme-popup-form-number">Mobile number</label>
31
+ <input type="text" name="phone_number" id="cloudiq-callme-popup-form-number" />
32
+ <button type="submit" <?php if ($this->getDisabled()) echo "disabled"; ?>>Submit</button>
33
+ </form>
34
+ <div id="cloudiq-callme-popup-response"></div>
35
+ </div>
36
+ <script>cloudiqCallmeInit()</script>
app/etc/modules/Cloudiq_Callme.xml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <Cloudiq_Callme>
5
+ <active>true</active>
6
+ <codePool>community</codePool>
7
+ <depends>
8
+ <Cloudiq_Core/>
9
+ </depends>
10
+ </Cloudiq_Callme>
11
+ </modules>
12
+ </config>
app/etc/modules/Cloudiq_Core.xml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <Cloudiq_Core>
5
+ <active>true</active>
6
+ <codePool>community</codePool>
7
+ </Cloudiq_Core>
8
+ </modules>
9
+ </config>
app/locale/en_GB/Cloudiq_Callme.csv ADDED
@@ -0,0 +1 @@
 
1
+ "callMe API error: Authentication failure","callMe: Failed to log in. Please check that your account is up to date and the account credentials are correct."
js/cloudiq/callme-button.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function cloudiqCallmeInit() {
2
+ var callme_button = $('cloudiq-callme-button');
3
+ var callme_popup = $('cloudiq-callme-popup');
4
+ var callme_popup_close_button = $('cloudiq-callme-popup-close');
5
+ var callme_popup_form = $('cloudiq-callme-popup-form');
6
+ var callme_popup_response = $('cloudiq-callme-popup-response');
7
+
8
+ // Position the popup in the centre of the screen
9
+ callme_popup.setStyle({
10
+ top: "50%",
11
+ left: "50%",
12
+ marginTop: "-" + Math.ceil(callme_popup.getHeight() / 2) + "px",
13
+ marginLeft: "-" + Math.ceil(callme_popup.getWidth() / 2) + "px"
14
+ });
15
+
16
+ // Toggle the callMe popup when buttons are clicked
17
+ [callme_button, callme_popup_close_button].each(function (element) {
18
+ element.observe("click", function () {
19
+ var display_value = (callme_popup.getStyle("display") == "none") ? "inline-block" : "none";
20
+
21
+ // Reset popup elements before displaying it
22
+ if (display_value != "none") {
23
+ callme_popup_form.setStyle({display: "block"});
24
+ callme_popup_response.update("");
25
+ callme_popup_response.removeClassName("cloudiq-callme-status-success");
26
+ callme_popup_response.removeClassName("cloudiq-callme-status-failure");
27
+ }
28
+
29
+ callme_popup.setStyle({display: display_value});
30
+ });
31
+ });
32
+
33
+ // Submit callMe callback request through Ajax
34
+ callme_popup_form.observe("submit", function (e) {
35
+ callme_popup_form.request({
36
+ onComplete: function (transport) {
37
+ var result;
38
+ if (transport.request.success()) {
39
+ result = transport.responseJSON;
40
+ } else {
41
+ result = {
42
+ status: false,
43
+ message: transport.statusText
44
+ };
45
+ }
46
+
47
+ // Update the response box
48
+ var result_class = (result.status) ? "success" : "failure";
49
+ callme_popup_response.addClassName("cloudiq-callme-status-" + result_class);
50
+ callme_popup_response.update(result.message);
51
+
52
+ // Hide the form
53
+ callme_popup_form.setStyle({display: "none"});
54
+ }
55
+ });
56
+ e.stop();
57
+ });
58
+ }
js/cloudiq/callme-jscolor/arrow.gif ADDED
Binary file
js/cloudiq/callme-jscolor/cross.gif ADDED
Binary file
js/cloudiq/callme-jscolor/demo.html ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+ <head>
3
+ <title>jscolor demo</title>
4
+ </head>
5
+ <body>
6
+
7
+ <script type="text/javascript" src="jscolor.js"></script>
8
+
9
+ Click here: <input class="color" value="66ff00">
10
+
11
+ </body>
12
+ </html>
js/cloudiq/callme-jscolor/hs.png ADDED
Binary file
js/cloudiq/callme-jscolor/hv.png ADDED
Binary file
js/cloudiq/callme-jscolor/jscolor.js ADDED
@@ -0,0 +1,840 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * jscolor, JavaScript Color Picker
3
+ *
4
+ * @version 1.3.1
5
+ * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
6
+ * @author Jan Odvarko, http://odvarko.cz
7
+ * @created 2008-06-15
8
+ * @updated 2010-01-23
9
+ * @link http://jscolor.com
10
+ */
11
+
12
+
13
+ var jscolor = {
14
+
15
+
16
+ dir : '', // location of jscolor directory (leave empty to autodetect)
17
+ bindClass : 'color', // class name
18
+ binding : true, // automatic binding via <input class="...">
19
+ preloading : true, // use image preloading?
20
+
21
+
22
+ install : function() {
23
+ jscolor.addEvent(window, 'load', jscolor.init);
24
+ },
25
+
26
+
27
+ init : function() {
28
+ if(jscolor.binding) {
29
+ jscolor.bind();
30
+ }
31
+ if(jscolor.preloading) {
32
+ jscolor.preload();
33
+ }
34
+ },
35
+
36
+
37
+ getDir : function() {
38
+ if(!jscolor.dir) {
39
+ var detected = jscolor.detectDir();
40
+ jscolor.dir = detected!==false ? detected : 'jscolor/';
41
+ }
42
+ return jscolor.dir;
43
+ },
44
+
45
+
46
+ detectDir : function() {
47
+ var base = location.href;
48
+
49
+ var e = document.getElementsByTagName('base');
50
+ for(var i=0; i<e.length; i+=1) {
51
+ if(e[i].href) { base = e[i].href; }
52
+ }
53
+
54
+ var e = document.getElementsByTagName('script');
55
+ for(var i=0; i<e.length; i+=1) {
56
+ if(e[i].src && /(^|\/)jscolor\.js([?#].*)?$/i.test(e[i].src)) {
57
+ var src = new jscolor.URI(e[i].src);
58
+ var srcAbs = src.toAbsolute(base);
59
+ srcAbs.path = srcAbs.path.replace(/[^\/]+$/, ''); // remove filename
60
+ srcAbs.query = null;
61
+ srcAbs.fragment = null;
62
+ return srcAbs.toString();
63
+ }
64
+ }
65
+ return false;
66
+ },
67
+
68
+
69
+ bind : function() {
70
+ var matchClass = new RegExp('(^|\\s)('+jscolor.bindClass+')\\s*(\\{[^}]*\\})?', 'i');
71
+ var e = document.getElementsByTagName('input');
72
+ for(var i=0; i<e.length; i+=1) {
73
+ var m;
74
+ if(!e[i].color && e[i].className && (m = e[i].className.match(matchClass))) {
75
+ var prop = {};
76
+ if(m[3]) {
77
+ try {
78
+ eval('prop='+m[3]);
79
+ } catch(eInvalidProp) {}
80
+ }
81
+ e[i].color = new jscolor.color(e[i], prop);
82
+ }
83
+ }
84
+ },
85
+
86
+
87
+ preload : function() {
88
+ for(var fn in jscolor.imgRequire) {
89
+ if(jscolor.imgRequire.hasOwnProperty(fn)) {
90
+ jscolor.loadImage(fn);
91
+ }
92
+ }
93
+ },
94
+
95
+
96
+ images : {
97
+ pad : [ 181, 101 ],
98
+ sld : [ 16, 101 ],
99
+ cross : [ 15, 15 ],
100
+ arrow : [ 7, 11 ]
101
+ },
102
+
103
+
104
+ imgRequire : {},
105
+ imgLoaded : {},
106
+
107
+
108
+ requireImage : function(filename) {
109
+ jscolor.imgRequire[filename] = true;
110
+ },
111
+
112
+
113
+ loadImage : function(filename) {
114
+ if(!jscolor.imgLoaded[filename]) {
115
+ jscolor.imgLoaded[filename] = new Image();
116
+ jscolor.imgLoaded[filename].src = jscolor.getDir()+filename;
117
+ }
118
+ },
119
+
120
+
121
+ fetchElement : function(mixed) {
122
+ return typeof mixed === 'string' ? document.getElementById(mixed) : mixed;
123
+ },
124
+
125
+
126
+ addEvent : function(el, evnt, func) {
127
+ if(el.addEventListener) {
128
+ el.addEventListener(evnt, func, false);
129
+ } else if(el.attachEvent) {
130
+ el.attachEvent('on'+evnt, func);
131
+ }
132
+ },
133
+
134
+
135
+ fireEvent : function(el, evnt) {
136
+ if(!el) {
137
+ return;
138
+ }
139
+ if(document.createEventObject) {
140
+ var ev = document.createEventObject();
141
+ el.fireEvent('on'+evnt, ev);
142
+ } else if(document.createEvent) {
143
+ var ev = document.createEvent('HTMLEvents');
144
+ ev.initEvent(evnt, true, true);
145
+ el.dispatchEvent(ev);
146
+ } else if(el['on'+evnt]) { // alternatively use the traditional event model (IE5)
147
+ el['on'+evnt]();
148
+ }
149
+ },
150
+
151
+
152
+ getElementPos : function(e) {
153
+ var e1=e, e2=e;
154
+ var x=0, y=0;
155
+ if(e1.offsetParent) {
156
+ do {
157
+ x += e1.offsetLeft;
158
+ y += e1.offsetTop;
159
+ } while(e1 = e1.offsetParent);
160
+ }
161
+ while((e2 = e2.parentNode) && e2.nodeName.toUpperCase() !== 'BODY') {
162
+ x -= e2.scrollLeft;
163
+ y -= e2.scrollTop;
164
+ }
165
+ return [x, y];
166
+ },
167
+
168
+
169
+ getElementSize : function(e) {
170
+ return [e.offsetWidth, e.offsetHeight];
171
+ },
172
+
173
+
174
+ getMousePos : function(e) {
175
+ if(!e) { e = window.event; }
176
+ if(typeof e.pageX === 'number') {
177
+ return [e.pageX, e.pageY];
178
+ } else if(typeof e.clientX === 'number') {
179
+ return [
180
+ e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
181
+ e.clientY + document.body.scrollTop + document.documentElement.scrollTop
182
+ ];
183
+ }
184
+ },
185
+
186
+
187
+ getViewPos : function() {
188
+ if(typeof window.pageYOffset === 'number') {
189
+ return [window.pageXOffset, window.pageYOffset];
190
+ } else if(document.body && (document.body.scrollLeft || document.body.scrollTop)) {
191
+ return [document.body.scrollLeft, document.body.scrollTop];
192
+ } else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
193
+ return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
194
+ } else {
195
+ return [0, 0];
196
+ }
197
+ },
198
+
199
+
200
+ getViewSize : function() {
201
+ if(typeof window.innerWidth === 'number') {
202
+ return [window.innerWidth, window.innerHeight];
203
+ } else if(document.body && (document.body.clientWidth || document.body.clientHeight)) {
204
+ return [document.body.clientWidth, document.body.clientHeight];
205
+ } else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
206
+ return [document.documentElement.clientWidth, document.documentElement.clientHeight];
207
+ } else {
208
+ return [0, 0];
209
+ }
210
+ },
211
+
212
+
213
+ URI : function(uri) { // See RFC3986
214
+
215
+ this.scheme = null;
216
+ this.authority = null;
217
+ this.path = '';
218
+ this.query = null;
219
+ this.fragment = null;
220
+
221
+ this.parse = function(uri) {
222
+ var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/);
223
+ this.scheme = m[3] ? m[2] : null;
224
+ this.authority = m[5] ? m[6] : null;
225
+ this.path = m[7];
226
+ this.query = m[9] ? m[10] : null;
227
+ this.fragment = m[12] ? m[13] : null;
228
+ return this;
229
+ };
230
+
231
+ this.toString = function() {
232
+ var result = '';
233
+ if(this.scheme !== null) { result = result + this.scheme + ':'; }
234
+ if(this.authority !== null) { result = result + '//' + this.authority; }
235
+ if(this.path !== null) { result = result + this.path; }
236
+ if(this.query !== null) { result = result + '?' + this.query; }
237
+ if(this.fragment !== null) { result = result + '#' + this.fragment; }
238
+ return result;
239
+ };
240
+
241
+ this.toAbsolute = function(base) {
242
+ var base = new jscolor.URI(base);
243
+ var r = this;
244
+ var t = new jscolor.URI;
245
+
246
+ if(base.scheme === null) { return false; }
247
+
248
+ if(r.scheme !== null && r.scheme.toLowerCase() === base.scheme.toLowerCase()) {
249
+ r.scheme = null;
250
+ }
251
+
252
+ if(r.scheme !== null) {
253
+ t.scheme = r.scheme;
254
+ t.authority = r.authority;
255
+ t.path = removeDotSegments(r.path);
256
+ t.query = r.query;
257
+ } else {
258
+ if(r.authority !== null) {
259
+ t.authority = r.authority;
260
+ t.path = removeDotSegments(r.path);
261
+ t.query = r.query;
262
+ } else {
263
+ if(r.path === '') { // TODO: == or === ?
264
+ t.path = base.path;
265
+ if(r.query !== null) {
266
+ t.query = r.query;
267
+ } else {
268
+ t.query = base.query;
269
+ }
270
+ } else {
271
+ if(r.path.substr(0,1) === '/') {
272
+ t.path = removeDotSegments(r.path);
273
+ } else {
274
+ if(base.authority !== null && base.path === '') { // TODO: == or === ?
275
+ t.path = '/'+r.path;
276
+ } else {
277
+ t.path = base.path.replace(/[^\/]+$/,'')+r.path;
278
+ }
279
+ t.path = removeDotSegments(t.path);
280
+ }
281
+ t.query = r.query;
282
+ }
283
+ t.authority = base.authority;
284
+ }
285
+ t.scheme = base.scheme;
286
+ }
287
+ t.fragment = r.fragment;
288
+
289
+ return t;
290
+ };
291
+
292
+ function removeDotSegments(path) {
293
+ var out = '';
294
+ while(path) {
295
+ if(path.substr(0,3)==='../' || path.substr(0,2)==='./') {
296
+ path = path.replace(/^\.+/,'').substr(1);
297
+ } else if(path.substr(0,3)==='/./' || path==='/.') {
298
+ path = '/'+path.substr(3);
299
+ } else if(path.substr(0,4)==='/../' || path==='/..') {
300
+ path = '/'+path.substr(4);
301
+ out = out.replace(/\/?[^\/]*$/, '');
302
+ } else if(path==='.' || path==='..') {
303
+ path = '';
304
+ } else {
305
+ var rm = path.match(/^\/?[^\/]*/)[0];
306
+ path = path.substr(rm.length);
307
+ out = out + rm;
308
+ }
309
+ }
310
+ return out;
311
+ }
312
+
313
+ if(uri) {
314
+ this.parse(uri);
315
+ }
316
+
317
+ },
318
+
319
+
320
+ /*
321
+ * Usage example:
322
+ * var myColor = new jscolor.color(myInputElement)
323
+ */
324
+
325
+ color : function(target, prop) {
326
+
327
+
328
+ this.required = true; // refuse empty values?
329
+ this.adjust = true; // adjust value to uniform notation?
330
+ this.hash = false; // prefix color with # symbol?
331
+ this.caps = true; // uppercase?
332
+ this.valueElement = target; // value holder
333
+ this.styleElement = target; // where to reflect current color
334
+ this.hsv = [0, 0, 1]; // read-only 0-6, 0-1, 0-1
335
+ this.rgb = [1, 1, 1]; // read-only 0-1, 0-1, 0-1
336
+
337
+ this.pickerOnfocus = true; // display picker on focus?
338
+ this.pickerMode = 'HSV'; // HSV | HVS
339
+ this.pickerPosition = 'bottom'; // left | right | top | bottom
340
+ this.pickerFace = 10; // px
341
+ this.pickerFaceColor = 'ThreeDFace'; // CSS color
342
+ this.pickerBorder = 1; // px
343
+ this.pickerBorderColor = 'ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight'; // CSS color
344
+ this.pickerInset = 1; // px
345
+ this.pickerInsetColor = 'ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow'; // CSS color
346
+ this.pickerZIndex = 10000;
347
+
348
+
349
+ for(var p in prop) {
350
+ if(prop.hasOwnProperty(p)) {
351
+ this[p] = prop[p];
352
+ }
353
+ }
354
+
355
+
356
+ this.hidePicker = function() {
357
+ if(isPickerOwner()) {
358
+ removePicker();
359
+ }
360
+ };
361
+
362
+
363
+ this.showPicker = function() {
364
+ if(!isPickerOwner()) {
365
+ var tp = jscolor.getElementPos(target); // target pos
366
+ var ts = jscolor.getElementSize(target); // target size
367
+ var vp = jscolor.getViewPos(); // view pos
368
+ var vs = jscolor.getViewSize(); // view size
369
+ var ps = [ // picker size
370
+ 2*this.pickerBorder + 4*this.pickerInset + 2*this.pickerFace + jscolor.images.pad[0] + 2*jscolor.images.arrow[0] + jscolor.images.sld[0],
371
+ 2*this.pickerBorder + 2*this.pickerInset + 2*this.pickerFace + jscolor.images.pad[1]
372
+ ];
373
+ var a, b, c;
374
+ switch(this.pickerPosition.toLowerCase()) {
375
+ case 'left': a=1; b=0; c=-1; break;
376
+ case 'right':a=1; b=0; c=1; break;
377
+ case 'top': a=0; b=1; c=-1; break;
378
+ default: a=0; b=1; c=1; break;
379
+ }
380
+ var l = (ts[b]+ps[b])/2;
381
+ var pp = [ // picker pos
382
+ -vp[a]+tp[a]+ps[a] > vs[a] ?
383
+ (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
384
+ tp[a],
385
+ -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
386
+ (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
387
+ (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
388
+ ];
389
+ drawPicker(pp[a], pp[b]);
390
+ }
391
+ };
392
+
393
+
394
+ this.importColor = function() {
395
+ if(!valueElement) {
396
+ this.exportColor();
397
+ } else {
398
+ if(!this.adjust) {
399
+ if(!this.fromString(valueElement.value, leaveValue)) {
400
+ styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
401
+ styleElement.style.color = styleElement.jscStyle.color;
402
+ this.exportColor(leaveValue | leaveStyle);
403
+ }
404
+ } else if(!this.required && /^\s*$/.test(valueElement.value)) {
405
+ valueElement.value = '';
406
+ styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
407
+ styleElement.style.color = styleElement.jscStyle.color;
408
+ this.exportColor(leaveValue | leaveStyle);
409
+
410
+ } else if(this.fromString(valueElement.value)) {
411
+ // OK
412
+ } else {
413
+ this.exportColor();
414
+ }
415
+ }
416
+ };
417
+
418
+
419
+ this.exportColor = function(flags) {
420
+ if(!(flags & leaveValue) && valueElement) {
421
+ var value = this.toString();
422
+ if(this.caps) { value = value.toUpperCase(); }
423
+ if(this.hash) { value = '#'+value; }
424
+ valueElement.value = value;
425
+ }
426
+ if(!(flags & leaveStyle) && styleElement) {
427
+ styleElement.style.backgroundColor =
428
+ '#'+this.toString();
429
+ styleElement.style.color =
430
+ 0.213 * this.rgb[0] +
431
+ 0.715 * this.rgb[1] +
432
+ 0.072 * this.rgb[2]
433
+ < 0.5 ? '#FFF' : '#000';
434
+ }
435
+ if(!(flags & leavePad) && isPickerOwner()) {
436
+ redrawPad();
437
+ }
438
+ if(!(flags & leaveSld) && isPickerOwner()) {
439
+ redrawSld();
440
+ }
441
+ };
442
+
443
+
444
+ this.fromHSV = function(h, s, v, flags) { // null = don't change
445
+ h<0 && (h=0) || h>6 && (h=6);
446
+ s<0 && (s=0) || s>1 && (s=1);
447
+ v<0 && (v=0) || v>1 && (v=1);
448
+ this.rgb = HSV_RGB(
449
+ h===null ? this.hsv[0] : (this.hsv[0]=h),
450
+ s===null ? this.hsv[1] : (this.hsv[1]=s),
451
+ v===null ? this.hsv[2] : (this.hsv[2]=v)
452
+ );
453
+ this.exportColor(flags);
454
+ };
455
+
456
+
457
+ this.fromRGB = function(r, g, b, flags) { // null = don't change
458
+ r<0 && (r=0) || r>1 && (r=1);
459
+ g<0 && (g=0) || g>1 && (g=1);
460
+ b<0 && (b=0) || b>1 && (b=1);
461
+ var hsv = RGB_HSV(
462
+ r===null ? this.rgb[0] : (this.rgb[0]=r),
463
+ g===null ? this.rgb[1] : (this.rgb[1]=g),
464
+ b===null ? this.rgb[2] : (this.rgb[2]=b)
465
+ );
466
+ if(hsv[0] !== null) {
467
+ this.hsv[0] = hsv[0];
468
+ }
469
+ if(hsv[2] !== 0) {
470
+ this.hsv[1] = hsv[1];
471
+ }
472
+ this.hsv[2] = hsv[2];
473
+ this.exportColor(flags);
474
+ };
475
+
476
+
477
+ this.fromString = function(hex, flags) {
478
+ var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
479
+ if(!m) {
480
+ return false;
481
+ } else {
482
+ if(m[1].length === 6) { // 6-char notation
483
+ this.fromRGB(
484
+ parseInt(m[1].substr(0,2),16) / 255,
485
+ parseInt(m[1].substr(2,2),16) / 255,
486
+ parseInt(m[1].substr(4,2),16) / 255,
487
+ flags
488
+ );
489
+ } else { // 3-char notation
490
+ this.fromRGB(
491
+ parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255,
492
+ parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255,
493
+ parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255,
494
+ flags
495
+ );
496
+ }
497
+ return true;
498
+ }
499
+ };
500
+
501
+
502
+ this.toString = function() {
503
+ return (
504
+ (0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) +
505
+ (0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) +
506
+ (0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1)
507
+ );
508
+ };
509
+
510
+
511
+ function RGB_HSV(r, g, b) {
512
+ var n = Math.min(Math.min(r,g),b);
513
+ var v = Math.max(Math.max(r,g),b);
514
+ var m = v - n;
515
+ if(m === 0) { return [ null, 0, v ]; }
516
+ var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
517
+ return [ h===6?0:h, m/v, v ];
518
+ }
519
+
520
+
521
+ function HSV_RGB(h, s, v) {
522
+ if(h === null) { return [ v, v, v ]; }
523
+ var i = Math.floor(h);
524
+ var f = i%2 ? h-i : 1-(h-i);
525
+ var m = v * (1 - s);
526
+ var n = v * (1 - s*f);
527
+ switch(i) {
528
+ case 6:
529
+ case 0: return [v,n,m];
530
+ case 1: return [n,v,m];
531
+ case 2: return [m,v,n];
532
+ case 3: return [m,n,v];
533
+ case 4: return [n,m,v];
534
+ case 5: return [v,m,n];
535
+ }
536
+ }
537
+
538
+
539
+ function removePicker() {
540
+ delete jscolor.picker.owner;
541
+ document.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB);
542
+ }
543
+
544
+
545
+ function drawPicker(x, y) {
546
+ if(!jscolor.picker) {
547
+ jscolor.picker = {
548
+ box : document.createElement('div'),
549
+ boxB : document.createElement('div'),
550
+ pad : document.createElement('div'),
551
+ padB : document.createElement('div'),
552
+ padM : document.createElement('div'),
553
+ sld : document.createElement('div'),
554
+ sldB : document.createElement('div'),
555
+ sldM : document.createElement('div')
556
+ };
557
+ for(var i=0,segSize=4; i<jscolor.images.sld[1]; i+=segSize) {
558
+ var seg = document.createElement('div');
559
+ seg.style.height = segSize+'px';
560
+ seg.style.fontSize = '1px';
561
+ seg.style.lineHeight = '0';
562
+ jscolor.picker.sld.appendChild(seg);
563
+ }
564
+ jscolor.picker.sldB.appendChild(jscolor.picker.sld);
565
+ jscolor.picker.box.appendChild(jscolor.picker.sldB);
566
+ jscolor.picker.box.appendChild(jscolor.picker.sldM);
567
+ jscolor.picker.padB.appendChild(jscolor.picker.pad);
568
+ jscolor.picker.box.appendChild(jscolor.picker.padB);
569
+ jscolor.picker.box.appendChild(jscolor.picker.padM);
570
+ jscolor.picker.boxB.appendChild(jscolor.picker.box);
571
+ }
572
+
573
+ var p = jscolor.picker;
574
+
575
+ // recompute controls positions
576
+ posPad = [
577
+ x+THIS.pickerBorder+THIS.pickerFace+THIS.pickerInset,
578
+ y+THIS.pickerBorder+THIS.pickerFace+THIS.pickerInset ];
579
+ posSld = [
580
+ null,
581
+ y+THIS.pickerBorder+THIS.pickerFace+THIS.pickerInset ];
582
+
583
+ // controls interaction
584
+ p.box.onmouseup =
585
+ p.box.onmouseout = function() { target.focus(); };
586
+ p.box.onmousedown = function() { abortBlur=true; };
587
+ p.box.onmousemove = function(e) { holdPad && setPad(e); holdSld && setSld(e); };
588
+ p.padM.onmouseup =
589
+ p.padM.onmouseout = function() { if(holdPad) { holdPad=false; jscolor.fireEvent(valueElement,'change'); } };
590
+ p.padM.onmousedown = function(e) { holdPad=true; setPad(e); };
591
+ p.sldM.onmouseup =
592
+ p.sldM.onmouseout = function() { if(holdSld) { holdSld=false; jscolor.fireEvent(valueElement,'change'); } };
593
+ p.sldM.onmousedown = function(e) { holdSld=true; setSld(e); };
594
+
595
+ // picker
596
+ p.box.style.width = 4*THIS.pickerInset + 2*THIS.pickerFace + jscolor.images.pad[0] + 2*jscolor.images.arrow[0] + jscolor.images.sld[0] + 'px';
597
+ p.box.style.height = 2*THIS.pickerInset + 2*THIS.pickerFace + jscolor.images.pad[1] + 'px';
598
+
599
+ // picker border
600
+ p.boxB.style.position = 'absolute';
601
+ p.boxB.style.clear = 'both';
602
+ p.boxB.style.left = x+'px';
603
+ p.boxB.style.top = y+'px';
604
+ p.boxB.style.zIndex = THIS.pickerZIndex;
605
+ p.boxB.style.border = THIS.pickerBorder+'px solid';
606
+ p.boxB.style.borderColor = THIS.pickerBorderColor;
607
+ p.boxB.style.background = THIS.pickerFaceColor;
608
+
609
+ // pad image
610
+ p.pad.style.width = jscolor.images.pad[0]+'px';
611
+ p.pad.style.height = jscolor.images.pad[1]+'px';
612
+
613
+ // pad border
614
+ p.padB.style.position = 'absolute';
615
+ p.padB.style.left = THIS.pickerFace+'px';
616
+ p.padB.style.top = THIS.pickerFace+'px';
617
+ p.padB.style.border = THIS.pickerInset+'px solid';
618
+ p.padB.style.borderColor = THIS.pickerInsetColor;
619
+
620
+ // pad mouse area
621
+ p.padM.style.position = 'absolute';
622
+ p.padM.style.left = '0';
623
+ p.padM.style.top = '0';
624
+ p.padM.style.width = THIS.pickerFace + 2*THIS.pickerInset + jscolor.images.pad[0] + jscolor.images.arrow[0] + 'px';
625
+ p.padM.style.height = p.box.style.height;
626
+ p.padM.style.cursor = 'crosshair';
627
+
628
+ // slider image
629
+ p.sld.style.overflow = 'hidden';
630
+ p.sld.style.width = jscolor.images.sld[0]+'px';
631
+ p.sld.style.height = jscolor.images.sld[1]+'px';
632
+
633
+ // slider border
634
+ p.sldB.style.position = 'absolute';
635
+ p.sldB.style.right = THIS.pickerFace+'px';
636
+ p.sldB.style.top = THIS.pickerFace+'px';
637
+ p.sldB.style.border = THIS.pickerInset+'px solid';
638
+ p.sldB.style.borderColor = THIS.pickerInsetColor;
639
+
640
+ // slider mouse area
641
+ p.sldM.style.position = 'absolute';
642
+ p.sldM.style.right = '0';
643
+ p.sldM.style.top = '0';
644
+ p.sldM.style.width = jscolor.images.sld[0] + jscolor.images.arrow[0] + THIS.pickerFace + 2*THIS.pickerInset + 'px';
645
+ p.sldM.style.height = p.box.style.height;
646
+ try {
647
+ p.sldM.style.cursor = 'pointer';
648
+ } catch(eOldIE) {
649
+ p.sldM.style.cursor = 'hand';
650
+ }
651
+
652
+ // load images in optimal order
653
+ switch(modeID) {
654
+ case 0: var padImg = 'hs.png'; break;
655
+ case 1: var padImg = 'hv.png'; break;
656
+ }
657
+ p.padM.style.background = "url('"+jscolor.getDir()+"cross.gif') no-repeat";
658
+ p.sldM.style.background = "url('"+jscolor.getDir()+"arrow.gif') no-repeat";
659
+ p.pad.style.background = "url('"+jscolor.getDir()+padImg+"') 0 0 no-repeat";
660
+
661
+ // place pointers
662
+ redrawPad();
663
+ redrawSld();
664
+
665
+ jscolor.picker.owner = THIS;
666
+ document.getElementsByTagName('body')[0].appendChild(p.boxB);
667
+ }
668
+
669
+
670
+ function redrawPad() {
671
+ // redraw the pad pointer
672
+ switch(modeID) {
673
+ case 0: var yComponent = 1; break;
674
+ case 1: var yComponent = 2; break;
675
+ }
676
+ var x = Math.round((THIS.hsv[0]/6) * (jscolor.images.pad[0]-1));
677
+ var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.pad[1]-1));
678
+ jscolor.picker.padM.style.backgroundPosition =
679
+ (THIS.pickerFace+THIS.pickerInset+x - Math.floor(jscolor.images.cross[0]/2)) + 'px ' +
680
+ (THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.cross[1]/2)) + 'px';
681
+
682
+ // redraw the slider image
683
+ var seg = jscolor.picker.sld.childNodes;
684
+
685
+ switch(modeID) {
686
+ case 0:
687
+ var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 1);
688
+ for(var i=0; i<seg.length; i+=1) {
689
+ seg[i].style.backgroundColor = 'rgb('+
690
+ (rgb[0]*(1-i/seg.length)*100)+'%,'+
691
+ (rgb[1]*(1-i/seg.length)*100)+'%,'+
692
+ (rgb[2]*(1-i/seg.length)*100)+'%)';
693
+ }
694
+ break;
695
+ case 1:
696
+ var rgb, s, c = [ THIS.hsv[2], 0, 0 ];
697
+ var i = Math.floor(THIS.hsv[0]);
698
+ var f = i%2 ? THIS.hsv[0]-i : 1-(THIS.hsv[0]-i);
699
+ switch(i) {
700
+ case 6:
701
+ case 0: rgb=[0,1,2]; break;
702
+ case 1: rgb=[1,0,2]; break;
703
+ case 2: rgb=[2,0,1]; break;
704
+ case 3: rgb=[2,1,0]; break;
705
+ case 4: rgb=[1,2,0]; break;
706
+ case 5: rgb=[0,2,1]; break;
707
+ }
708
+ for(var i=0; i<seg.length; i+=1) {
709
+ s = 1 - 1/(seg.length-1)*i;
710
+ c[1] = c[0] * (1 - s*f);
711
+ c[2] = c[0] * (1 - s);
712
+ seg[i].style.backgroundColor = 'rgb('+
713
+ (c[rgb[0]]*100)+'%,'+
714
+ (c[rgb[1]]*100)+'%,'+
715
+ (c[rgb[2]]*100)+'%)';
716
+ }
717
+ break;
718
+ }
719
+ }
720
+
721
+
722
+ function redrawSld() {
723
+ // redraw the slider pointer
724
+ switch(modeID) {
725
+ case 0: var yComponent = 2; break;
726
+ case 1: var yComponent = 1; break;
727
+ }
728
+ var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.sld[1]-1));
729
+ jscolor.picker.sldM.style.backgroundPosition =
730
+ '0 ' + (THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.arrow[1]/2)) + 'px';
731
+ }
732
+
733
+
734
+ function isPickerOwner() {
735
+ return jscolor.picker && jscolor.picker.owner === THIS;
736
+ }
737
+
738
+
739
+ function blurTarget() {
740
+ if(valueElement === target) {
741
+ THIS.importColor();
742
+ }
743
+ if(THIS.pickerOnfocus) {
744
+ THIS.hidePicker();
745
+ }
746
+ }
747
+
748
+
749
+ function blurValue() {
750
+ if(valueElement !== target) {
751
+ THIS.importColor();
752
+ }
753
+ }
754
+
755
+
756
+ function setPad(e) {
757
+ var posM = jscolor.getMousePos(e);
758
+ var x = posM[0]-posPad[0];
759
+ var y = posM[1]-posPad[1];
760
+ switch(modeID) {
761
+ case 0: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), 1 - y/(jscolor.images.pad[1]-1), null, leaveSld); break;
762
+ case 1: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), null, 1 - y/(jscolor.images.pad[1]-1), leaveSld); break;
763
+ }
764
+ }
765
+
766
+
767
+ function setSld(e) {
768
+ var posM = jscolor.getMousePos(e);
769
+ var y = posM[1]-posPad[1];
770
+ switch(modeID) {
771
+ case 0: THIS.fromHSV(null, null, 1 - y/(jscolor.images.sld[1]-1), leavePad); break;
772
+ case 1: THIS.fromHSV(null, 1 - y/(jscolor.images.sld[1]-1), null, leavePad); break;
773
+ }
774
+ }
775
+
776
+
777
+ var THIS = this;
778
+ var modeID = this.pickerMode.toLowerCase()==='hvs' ? 1 : 0;
779
+ var abortBlur = false;
780
+ var
781
+ valueElement = jscolor.fetchElement(this.valueElement),
782
+ styleElement = jscolor.fetchElement(this.styleElement);
783
+ var
784
+ holdPad = false,
785
+ holdSld = false;
786
+ var
787
+ posPad,
788
+ posSld;
789
+ var
790
+ leaveValue = 1<<0,
791
+ leaveStyle = 1<<1,
792
+ leavePad = 1<<2,
793
+ leaveSld = 1<<3;
794
+
795
+ // target
796
+ jscolor.addEvent(target, 'focus', function() {
797
+ if(THIS.pickerOnfocus) { THIS.showPicker(); }
798
+ });
799
+ jscolor.addEvent(target, 'blur', function() {
800
+ if(!abortBlur) {
801
+ window.setTimeout(function(){ abortBlur || blurTarget(); abortBlur=false; }, 0);
802
+ } else {
803
+ abortBlur = false;
804
+ }
805
+ });
806
+
807
+ // valueElement
808
+ if(valueElement) {
809
+ var updateField = function() {
810
+ THIS.fromString(valueElement.value, leaveValue);
811
+ };
812
+ jscolor.addEvent(valueElement, 'keyup', updateField);
813
+ jscolor.addEvent(valueElement, 'input', updateField);
814
+ jscolor.addEvent(valueElement, 'blur', blurValue);
815
+ valueElement.setAttribute('autocomplete', 'off');
816
+ }
817
+
818
+ // styleElement
819
+ if(styleElement) {
820
+ styleElement.jscStyle = {
821
+ backgroundColor : styleElement.style.backgroundColor,
822
+ color : styleElement.style.color
823
+ };
824
+ }
825
+
826
+ // require images
827
+ switch(modeID) {
828
+ case 0: jscolor.requireImage('hs.png'); break;
829
+ case 1: jscolor.requireImage('hv.png'); break;
830
+ }
831
+ jscolor.requireImage('cross.gif');
832
+ jscolor.requireImage('arrow.gif');
833
+
834
+ this.importColor();
835
+ }
836
+
837
+ };
838
+
839
+
840
+ jscolor.install();
package.xml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>cloudiq</name>
4
+ <version>1.0.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://www.cloud-iq.com">Commercial License</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>The callMe extension provides a click-to-call button/service for your website which will automatically connect calls to customers.</summary>
10
+ <description>Never miss a lead. Convert 10% of your traffic to sales.&#xD;
11
+ &#xD;
12
+ The callMe extension installs a callMe button on your Magento store. It&#x2019;s beautifully simple to install and use.&#xD;
13
+ &#xD;
14
+ Your visitors can then conveniently input their telephone number for an immediate call back, the service will immediately call you (the business) then instantly connect the customer when you pick up the phone!</description>
15
+ <notes>You can manage the look and feel of your button. You can schedule call backs to suit your business hours.</notes>
16
+ <authors><author><name>cloud.IQ</name><user>cloud_iq</user><email>sales@cloud-iq.com</email></author></authors>
17
+ <date>2013-07-05</date>
18
+ <time>10:20:44</time>
19
+ <contents><target name="mageetc"><dir name="modules"><file name="Cloudiq_Callme.xml" hash="e41b3685af06770ec59c90fb9dff6760"/><file name="Cloudiq_Core.xml" hash="cad973564b5dda06d3608f7e51d529e6"/></dir></target><target name="magecommunity"><dir name="Cloudiq"><dir name="Callme"><dir name="Block"><dir name="Adminhtml"><dir name="Config"><dir name="Edit"><dir name="Tab"><file name="Callme.php" hash="b6f541dfa99c1cfbc0148844b742b090"/></dir></dir></dir></dir><file name="Button.php" hash="9053737a38428c9458eba6f34012aeb8"/></dir><dir name="Helper"><file name="Data.php" hash="85b0f590edbd071dadd4dd69f47cf768"/><file name="Options.php" hash="44da04907ac960c206b12c84bdc54850"/></dir><dir name="Model"><dir name="Api"><file name="Callme.php" hash="025cb54a73d4c32f0d80781c772e69a5"/></dir><file name="Config.php" hash="123f7f5b8fc0161e9e94e1c97de74b79"/><file name="Observer.php" hash="a059cbdb977c9c6f7ecb63fd055f8ff8"/><dir name="Resource"><dir name="Config"><file name="Collection.php" hash="c8cb34ede9c8c4ea692a560b47b27025"/></dir><file name="Config.php" hash="f8dfebe59e9f34ee306ea29f188e7bf2"/></dir></dir><dir name="Test"><dir name="Config"><file name="Base.php" hash="8b8b866e1321e1e98e7a4f36a59d2183"/></dir><dir name="Controller"><dir name="Block"><dir name="fixtures"><file name="testCallmeDisabled.yaml" hash="64621424e49f70f56b13857b28268200"/><file name="testCoreDisabled.yaml" hash="4731e31d6f73894232475d7ccbcb7758"/><file name="testDisplayOnAllPages.yaml" hash="81dbaa7b53e28e5d7e5875274dad6ef8"/></dir><dir name="providers"><file name="testDisplayOnAllPages.yaml" hash="c5fb386e2508b360bec626b5acad1076"/></dir></dir><file name="Block.php" hash="87b31a5aa9f2eb7f94f82c4affb9fad9"/></dir><dir name="Helper"><file name="Options.php" hash="431d9ab9633efdbdd0df9ec6e22af9fc"/></dir><dir name="Model"><dir name="Config"><dir name="fixtures"><file name="validConfigObject.yaml" hash="ead7a835c645cbba6e4effe3bc00d864"/></dir><dir name="providers"><file name="testOptionalField.yaml" hash="f253cd136bd4ceacc5f3e07dc936f7db"/><file name="testRequiredField.yaml" hash="63ec4c95468c32b0646eb8e8360097de"/></dir></dir><file name="Config.php" hash="a9b14a2e23922d9638fa12959b1384fb"/></dir></dir><dir name="Varien"><dir name="Data"><dir name="Form"><dir name="Element"><file name="Colourpicker.php" hash="b2f796c6b776291d4820dd355bcd1f7c"/><file name="Openinghours.php" hash="2378bfe07e017eb6e0053443562bbea9"/></dir></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="CallmeController.php" hash="9bc2d43037ea553ee980e32813f17c99"/></dir><file name="CallmeController.php" hash="ea90e4efee01afe4eaaed292abff50b0"/></dir><dir name="etc"><file name="config.xml" hash="4990375a55e13e88c16f4ec8f00fb562"/></dir><dir name="sql"><dir name="cloudiq_callme_setup"><file name="mysql4-install-1.0.0.php" hash="565324b929bcd1cce3813bfbd146a392"/></dir></dir></dir><dir name="Core"><dir name="Block"><dir name="Adminhtml"><dir name="Config"><dir name="Edit"><file name="Form.php" hash="adb6a7430c073ee2930b4d853fb4cfa3"/><dir name="Tab"><file name="Frame.php" hash="5b2f562fb67baf1060eee4c05f67d5da"/><file name="Global.php" hash="46e016a82d206ead0190b45a160df00d"/></dir><file name="Tabs.php" hash="953109aea9e43c0dd960eb9f2aa041f6"/></dir><file name="Edit.php" hash="55f27f8954981871bd7e748f48f9e8c0"/></dir></dir></dir><dir name="Helper"><file name="Config.php" hash="37bd1633ce962c654f0e21818991c191"/><file name="Data.php" hash="528460cf7ec9f942f2f29f6dad1c52f1"/></dir><dir name="Model"><dir name="Api"><file name="Abstract.php" hash="a6df29a5d90cba70242cd36bd0689cd3"/><file name="Core.php" hash="a1c7cfb04097677e5565ce496d558e94"/><file name="Request.php" hash="43bd7500c74d97eac224c23518e3824b"/><file name="Response.php" hash="b977b0f632a7bafa40b52d8d9e0078ac"/></dir></dir><dir name="Test"><dir name="Config"><file name="Base.php" hash="f2e2ca2e59d14048bf46b8c208787793"/></dir><dir name="Model"><dir name="Api"><file name="Response.php" hash="e9d5051331fabfcbe487778ae2c47cc3"/><file name="TestCase.php" hash="2d5114ea542aba75f666b6254390a5d6"/><dir name="data"><dir name="Response"><file name="testWasSuccessfulBadResponseAttribute.txt" hash="7edb6a3d909e8bfb7f04a3450b0fc1c8"/><file name="testWasSuccessfulStatusCodeAndResponseAttribute.txt" hash="14b598d1a6abd984d00cb76fa5351b81"/></dir></dir></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="CloudiqController.php" hash="4cdb6fc9daf31260c6e020496f7ba557"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="8ee43c9b6d84378c552987e7e980a50d"/><file name="config.xml" hash="d53122159940a78b5f8352355c9f0f99"/></dir><file name=".gitkeep" hash="d41d8cd98f00b204e9800998ecf8427e"/></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><dir name="cloudiq"><file name="callme.xml" hash="93aae78cddc91367219762316cf50f9c"/><file name="core.xml" hash="47ca2cb375a8b204080f6cba1cd58074"/></dir></dir><dir name="template"><dir name="cloudiq"><dir name="core"><dir name="tab"><dir name="frame"><file name="iframe.phtml" hash="819fb687981c7054952373f5820824a6"/><file name="submitter.phtml" hash="d0c0dcb6206dd9298630dc564016e24f"/></dir></dir><file name="tabs.phtml" hash="a14de21a37b43d13b4e70b9ef96933c7"/></dir><dir name="global"><file name="tab.phtml" hash="9b7105f44b18bc0d5649658e6e111aaa"/><dir name="widget"><file name="form.phtml" hash="d91ea0fc9b1b2d48068732a3f203610c"/></dir></dir></dir></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="template"><dir name="cloudiq"><dir name="callme"><file name="button.phtml" hash="a1497f83bde303bb33cf5ed02328aacb"/></dir></dir></dir></dir></dir></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="cloudiq"><file name="callme.css" hash="9dcffe69220e524ad1a67068313404e0"/><file name="core.css" hash="c22a2fe861358447637a640e75da9076"/><dir name="js"><file name="callme.js" hash="4007f758fde195875608fcaf32f23a7f"/></dir></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="cloudiq"><dir name="css"><file name="callme-button.css" hash="3feb251dbd7025da16a12f1c122f238e"/></dir></dir></dir></dir></dir></target><target name="mageweb"><dir name="js"><dir name="cloudiq"><file name="callme-button.js" hash="a39591a5eb8e6b4e8d26f7ec1a5dce63"/><dir name="callme-jscolor"><file name="arrow.gif" hash="5034704a76cd55c1cbcbc58ea6bf523f"/><file name="cross.gif" hash="ba9a274b9323753cd95bc3b1eb2f4e5f"/><file name="demo.html" hash="edf71251cb2be20322d2efb00aee86a6"/><file name="hs.png" hash="fefa1a03d92ebad25c88dca94a0b63db"/><file name="hv.png" hash="990d71cada17da100653636cf8490884"/><file name="jscolor.js" hash="b65a1505390592ac6c0177d6b84774e0"/></dir></dir></dir></target><target name="magelocale"><dir name="en_GB"><file name="Cloudiq_Callme.csv" hash="a056a4403ab97dc4308b9b84a8bcfcfd"/></dir></target></contents>
20
+ <compatible/>
21
+ <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
22
+ </package>
skin/adminhtml/default/default/cloudiq/callme.css ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Import button styles for preview
3
+ */
4
+ @import url("../../../../frontend/base/default/cloudiq/css/callme-button.css");
5
+
6
+ /* Make the text inputs in the Pop Up section expand to full width */
7
+ #edit_form #popup.fieldset td.value input {
8
+ width: 95%;
9
+ }
10
+ /*
11
+ * Openinghours form field styling
12
+ */
13
+ .opening_hours_select > select {
14
+ display: inline;
15
+ width: auto !important;
16
+ margin: 5px;
17
+ }
skin/adminhtml/default/default/cloudiq/core.css ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .cloudiq-banner {
2
+ padding: 5px 27px 5px 47px;
3
+ background: rgb(255, 249, 233);
4
+ border-bottom: 1px solid rgb(238, 226, 190);
5
+ border-top: 1px solid rgb(238, 226, 190);
6
+ font-size: 11px;
7
+ line-height: 16px;
8
+ margin: 0 0 -3px;
9
+ color: rgb(68, 68, 68);
10
+ position: relative;
11
+ }
12
+
13
+ /*
14
+ * Expand the config form and adjust all elements to expand to the new size.
15
+ */
16
+ #edit_form .form-list {
17
+ width: 590px;
18
+ }
19
+
20
+ #edit_form .form-list td.value {
21
+ width: auto;
22
+ }
23
+
24
+ #edit_form td.value .note {
25
+ width: 90%;
26
+ }
27
+
28
+ #edit_form td.value select {
29
+ width: 95%;
30
+ }
31
+
32
+ /* Text inputs are half-width by default */
33
+ #edit_form td.value input {
34
+ width: 50%;
35
+ }
36
+
37
+ /* Expand the Account ID and Token fields to fit the data */
38
+ #edit_form #settings.fieldset td.value input {
39
+ font-family: monospace;
40
+ width: 64%;
41
+ }
skin/adminhtml/default/default/cloudiq/js/callme.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Disable the "Display on selected pages" selection box when "Display on all pages"
3
+ * gets selected and vice versa when the selection box gets clicked.
4
+ */
5
+ document.observe("dom:loaded", function() {
6
+ var all_pages = $("callme[published][all]");
7
+ var selected_pages = $("callme[published][pages]");
8
+
9
+ all_pages.observe("change", function () {
10
+ selected_pages.disabled = all_pages.checked ? true : false;
11
+ });
12
+ selected_pages.parentElement.observe("click", function() {
13
+ selected_pages.disabled = false;
14
+ all_pages.checked = false;
15
+ });
16
+ });
17
+
18
+ /**
19
+ * Display a preview of the callMe button
20
+ */
21
+ document.observe("dom:loaded", function () {
22
+ var preview_url = $('callme[button][preview_url]').value;
23
+ var config_form = $('edit_form');
24
+ var button_form_section = $('button');
25
+ var popup_form_section = $('popup');
26
+
27
+ var button_preview = new Element("div", {id: "cloudiq_callme_button_preview"});
28
+ button_preview.setStyle({display: "none"});
29
+ $(document.body).insert(button_preview);
30
+
31
+ function updateCloudiqCallmePreview() {
32
+ new Ajax.Updater(button_preview, preview_url, {
33
+ method: "POST",
34
+ parameters: config_form.serialize(true),
35
+ evalScripts: true
36
+ });
37
+ }
38
+
39
+ // Update the preview when button or popup configuration is changed
40
+ button_form_section.select('input, select')
41
+ .concat(popup_form_section.select('input, select'))
42
+ .each(function (element) {
43
+ element.observe("change", updateCloudiqCallmePreview);
44
+ });
45
+
46
+ // Only show the preview when the callMe config form tab is open
47
+ varienGlobalEvents.attachEventHandler("showTab", function (event) {
48
+ if (event.tab.name == "callme_section") {
49
+ button_preview.setStyle({display: "block"});
50
+ updateCloudiqCallmePreview();
51
+ } else {
52
+ button_preview.setStyle({display: "none"});
53
+ }
54
+ });
55
+ });
skin/frontend/base/default/cloudiq/css/callme-button.css ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Button layout & generic appearance
3
+ */
4
+
5
+ #cloudiq-callme-button {
6
+ display: inline-block;
7
+ position: fixed;
8
+ z-index: 10;
9
+ cursor: pointer;
10
+
11
+ padding: 7px 25px;
12
+ text-align: center;
13
+ }
14
+
15
+ #cloudiq-callme-button p {
16
+ margin: 0px;
17
+ }
18
+
19
+ #cloudiq-callme-button-title {
20
+ font-size: 1.2em;
21
+ font-weight: bold;
22
+ }
23
+
24
+ /*
25
+ * Popup layout & generic appearance
26
+ */
27
+
28
+ #cloudiq-callme-popup {
29
+ position: fixed; /* The popup is positioned with js */
30
+ z-index: 15;
31
+
32
+ display: none; /* Hide the popup when loaded, it is toggled by the button */
33
+
34
+ min-width: 400px;
35
+ padding: 15px 20px;
36
+ }
37
+
38
+ #cloudiq-callme-popup-close {
39
+ float: right;
40
+ padding: 5px;
41
+ line-height: 25px;
42
+ font-size: 25px;
43
+ cursor: pointer;
44
+ }
45
+
46
+ #cloudiq-callme-popup-logo {
47
+ float: left;
48
+ max-height: 50px;
49
+ }
50
+
51
+ #cloudiq-callme-popup p {
52
+ margin: 0px;
53
+ text-align: center;
54
+ }
55
+
56
+ #cloudiq-callme-popup-title {
57
+ clear: both;
58
+ padding-top: 5px;
59
+ font-size: 1.3em;
60
+ font-weight: bold;
61
+ }
62
+
63
+ #cloudiq-callme-popup-form {
64
+ margin: 20px 0;
65
+ }
66
+
67
+ #cloudiq-callme-popup-form * {
68
+ display: block;
69
+ margin: 2px auto;
70
+ }
71
+
72
+ #cloudiq-callme-popup-form label {
73
+ text-align: left;
74
+ color: inherit;
75
+ }
76
+
77
+ #cloudiq-callme-popup-form input {
78
+ width: 90%;
79
+ padding: 8px;
80
+ background: #FFFFFF;
81
+ border: 2px inset;
82
+ }
83
+
84
+ #cloudiq-callme-popup-form button {
85
+ margin-top: 15px;
86
+ padding: 8px 18px;
87
+ background: inherit;
88
+ border: 0;
89
+ -webkit-border-radius: 16px;
90
+ -moz-border-radius: 16px;
91
+ border-radius: 16px;
92
+ border-width: 0;
93
+ font-size: 1.2em;
94
+ cursor: pointer;
95
+ }
96
+
97
+ /*
98
+ * Appearance templates
99
+ */
100
+
101
+ /* Button positioning */
102
+
103
+ .cloudiq-callme-position-leftbottom { left: 0; bottom: 0; }
104
+ .cloudiq-callme-position-leftmiddle { left: 0; top: 50%; }
105
+ .cloudiq-callme-position-lefttop { left: 0; top: 0; }
106
+
107
+ .cloudiq-callme-position-topleft { top: 0; left: 0; }
108
+ .cloudiq-callme-position-topmiddle { top: 0; left: 50%; }
109
+ .cloudiq-callme-position-topright { top: 0; right: 0; }
110
+
111
+ .cloudiq-callme-position-righttop { right: 0 ; top: 0; }
112
+ .cloudiq-callme-position-rightmiddle { right: 0 ; top: 50%; }
113
+ .cloudiq-callme-position-rightbottom { right: 0 ; bottom: 0; }
114
+
115
+ .cloudiq-callme-position-bottomright { bottom: 0; right: 0; }
116
+ .cloudiq-callme-position-bottommiddle { bottom: 0; left: 50%; }
117
+ .cloudiq-callme-position-bottomleft { bottom: 0; left: 0; }
118
+
119
+ /* Response message */
120
+
121
+ .cloudiq-callme-status-failure, .cloudiq-callme-status-success {
122
+ margin: 20px 0;
123
+ padding: 10px;
124
+ color: #000000;
125
+ }
126
+
127
+ .cloudiq-callme-status-success { background: #88CC88; }
128
+ .cloudiq-callme-status-failure { background: #FF8888; }
129
+
130
+ /* Button & Popup templates */
131
+
132
+ /* Template 1 */
133
+
134
+ .cloudiq-callme-template-1 {
135
+ background: -moz-linear-gradient(top, #ffffff 0%, #E5E5E5 100%) !important; /* FF3.6+ */
136
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#E5E5E5)) !important; /* Chrome,Safari4+ */
137
+ background: -webkit-linear-gradient(top, #ffffff 0%,#E5E5E5 100%) !important; /* Chrome10+,Safari5.1+ */
138
+ background: -o-linear-gradient(top, #ffffff 0%,#E5E5E5 100%) !important; /* Opera 11.10+ */
139
+ background: -ms-linear-gradient(top, #ffffff 0%,#E5E5E5 100%) !important; /* IE10+ */
140
+ background: linear-gradient(to bottom, #ffffff 0%,#E5E5E5 100%) !important; /* W3C */
141
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#E5E5E5',GradientType=0 ) !important; /* IE6-8 */
142
+ color: #000000 !important;
143
+ border: 4px solid;
144
+ }
145
+
146
+ /* Template 2 */
147
+
148
+ .cloudiq-callme-template-2 {
149
+ border: 4px solid;
150
+ }
151
+
152
+ #cloudiq-callme-popup.cloudiq-callme-template-2 button {
153
+ background-color: #000000 !important;
154
+ color: #FFFFFF !important;
155
+ }
156
+
157
+ /* Template 3 */
158
+
159
+ .cloudiq-callme-template-3 {
160
+ -webkit-border-radius: 30px;
161
+ -moz-border-radius: 30px;
162
+ border-radius: 30px;
163
+ }
164
+
165
+ #cloudiq-callme-popup.cloudiq-callme-template-3 button {
166
+ background-color: #000000 !important;
167
+ color: #FFFFFF !important;
168
+ }
169
+
170
+ /* Template 4 */
171
+
172
+ .cloudiq-callme-template-4 {
173
+ background: -moz-linear-gradient(top, #FFFFFF 0%, #E5E5E5 100%) !important; /* FF3.6+ */
174
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#FFFFFF), color-stop(100%,#E5E5E5)) !important; /* Chrome,Safari4+ */
175
+ background: -webkit-linear-gradient(top, #FFFFFF 0%,#E5E5E5 100%) !important; /* Chrome10+,Safari5.1+ */
176
+ background: -o-linear-gradient(top, #FFFFFF 0%,#E5E5E5 100%) !important; /* Opera 11.10+ */
177
+ background: -ms-linear-gradient(top, #FFFFFF 0%,#E5E5E5 100%) !important; /* IE10+ */
178
+ background: linear-gradient(to bottom, #FFFFFF 0%,#E5E5E5 100%) !important; /* W3C */
179
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#E5E5E5',GradientType=0 ) !important; /* IE6-8 */
180
+ color: #000000 !important;
181
+ border: 4px solid;
182
+ -webkit-border-radius: 8px;
183
+ -moz-border-radius: 8px;
184
+ border-radius: 8px;
185
+ }
186
+
187
+ /* Template 5 */
188
+
189
+ .cloudiq-callme-template-5 {
190
+ -webkit-border-radius: 8px;
191
+ -moz-border-radius: 8px;
192
+ border-radius: 8px;
193
+ }
194
+
195
+ #cloudiq-callme-popup.cloudiq-callme-template-5 button {
196
+ background-color: #000000 !important;
197
+ color: #FFFFFF !important;
198
+ }