Wisepricer_Syncer2 - Version 2.0.0.0

Version Notes

First stable release.

Download this release

Release Info

Developer Pavel Zhytomirsky
Extension Wisepricer_Syncer2
Version 2.0.0.0
Comparing to
See all releases


Version 2.0.0.0

Files changed (31) hide show
  1. app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Job/View.php +95 -0
  2. app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue.php +38 -0
  3. app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue/Grid.php +201 -0
  4. app/code/community/Wisepricer/JobQueue/Helper/Data.php +28 -0
  5. app/code/community/Wisepricer/JobQueue/Model/Job.php +46 -0
  6. app/code/community/Wisepricer/JobQueue/Model/Job/Abstract.php +84 -0
  7. app/code/community/Wisepricer/JobQueue/Model/Resource/Job.php +32 -0
  8. app/code/community/Wisepricer/JobQueue/Model/Resource/Job/Collection.php +32 -0
  9. app/code/community/Wisepricer/JobQueue/Model/Resource/Setup.php +28 -0
  10. app/code/community/Wisepricer/JobQueue/Model/Worker.php +125 -0
  11. app/code/community/Wisepricer/JobQueue/controllers/Adminhtml/QueueController.php +235 -0
  12. app/code/community/Wisepricer/JobQueue/etc/config.xml +98 -0
  13. app/code/community/Wisepricer/JobQueue/sql/wisepricer_jobqueue_setup/mysql4-install-1.0.0.0.php +47 -0
  14. app/code/community/Wisepricer/Syncer2/Exception.php +6 -0
  15. app/code/community/Wisepricer/Syncer2/Helper/Data.php +82 -0
  16. app/code/community/Wisepricer/Syncer2/Model/Abstract.php +129 -0
  17. app/code/community/Wisepricer/Syncer2/Model/Exporter.php +141 -0
  18. app/code/community/Wisepricer/Syncer2/Model/Importer.php +246 -0
  19. app/code/community/Wisepricer/Syncer2/controllers/ApiController.php +348 -0
  20. app/code/community/Wisepricer/Syncer2/etc/adminhtml.xml +22 -0
  21. app/code/community/Wisepricer/Syncer2/etc/config.xml +75 -0
  22. app/code/community/Wisepricer/Syncer2/etc/system.xml +55 -0
  23. app/etc/modules/Wisepricer_Syncer2.xml +16 -0
  24. lib/DJJob/DJJob.php +525 -0
  25. lib/DJJob/examples/HelloWorldJob.php +13 -0
  26. lib/DJJob/jobs.sql +12 -0
  27. lib/DJJob/test/custom_table_name.php +82 -0
  28. lib/DJJob/test/database.php +84 -0
  29. lib/DJJob/test/original_database_configure.php +79 -0
  30. package.xml +18 -0
  31. skin/adminhtml/default/default/images/wisepricer_syncer2/wiser_logo.png +0 -0
app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Job/View.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Block_Adminhtml_Job_View extends Mage_Adminhtml_Block_Widget_Container
27
+ {
28
+
29
+ protected $_job;
30
+
31
+ public function __construct()
32
+ {
33
+ $this->_job = Mage::registry('wisepricer_jobqueue_job');
34
+
35
+ $this->_blockGroup = 'jobqueue';
36
+ $this->_controller = 'adminhtml_job';
37
+
38
+ parent::__construct();
39
+
40
+ $confirmMsg = $this->__('Are you sure you want to do this?');
41
+ $resubmitUrl = $this->getUrl('*/*/resubmit', array('id' => $this->_job->getId()));
42
+ $this->_addButton('resubmit', array(
43
+ 'label' => $this->__('Resubmit'),
44
+ 'onclick' => "confirmSetLocation('{$confirmMsg}', '{$resubmitUrl}')",
45
+ ), 0, -10);
46
+
47
+ if(!$this->_job->getFailedAt()) {
48
+ $cancelUrl = $this->getUrl('*/*/cancel', array('id' => $this->_job->getId()));
49
+ $this->_addButton('cancel', array(
50
+ 'label' => $this->__('Cancel'),
51
+ 'onclick' => "confirmSetLocation('{$confirmMsg}', '{$cancelUrl}')",
52
+ ), 0, -5);
53
+ }
54
+ }
55
+
56
+ public function getHeaderText()
57
+ {
58
+ return $this->__("Job: \"%s\"", $this->_job->getName());
59
+ }
60
+
61
+ protected function _toHtml()
62
+ {
63
+ $this->setJobIdHtml($this->escapeHtml($this->_job->getId()));
64
+ $this->setJobNameHtml($this->escapeHtml($this->_job->getName()));
65
+ $this->setJobNameHtml($this->escapeHtml($this->_job->getName()));
66
+
67
+ $storeId = $this->_job->getStoreId();
68
+ $store = Mage::app()->getStore($storeId);
69
+ $this->setStoreNameHtml($this->escapeHtml($store->getName()));
70
+
71
+ $this->setJobQueueHtml($this->escapeHtml($this->_job->getQueue()));
72
+ $this->setAttemptsHtml($this->escapeHtml($this->_job->getAttempts()));
73
+
74
+ $runAt = (strtotime($this->_job->getRunAt()))
75
+ ? $this->formatDate($this->_job->getRunAt(), Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM, true)
76
+ : $this->__('N/A');
77
+ $this->setRunAtHtml($this->escapeHtml($runAt));
78
+
79
+ $status = $this->__("Pending");
80
+ if( $this->_job->getFailedAt()) {
81
+ $status = $this->__('Failed');
82
+ } else if($this->_job->getLockedAt()) {
83
+ $status = $this->__('In Process');
84
+ }
85
+ $this->setStatusHtml($this->escapeHtml($status));
86
+
87
+ $this->setErrorHtml($this->escapeHtml($this->_job->getError()));
88
+
89
+ $createdAt = (strtotime($this->_job->getCreatedAt()))
90
+ ? $this->formatDate($this->_job->getCreatedAt(), Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM, true)
91
+ : $this->__('N/A');
92
+ $this->setCreatedAtHtml($this->escapeHtml($createdAt));
93
+ return parent::_toHtml();
94
+ }
95
+ }
app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Block_Adminhtml_Queue extends Mage_Adminhtml_Block_Widget_Grid_Container
27
+ {
28
+ public function __construct()
29
+ {
30
+ $this->_blockGroup = 'jobqueue';
31
+ $this->_controller = 'adminhtml_queue';
32
+ $this->_headerText = $this->__('JobQueue');
33
+
34
+ parent::__construct();
35
+
36
+ $this->removeButton('add');
37
+ }
38
+ }
app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue/Grid.php ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Block_Adminhtml_Queue_Grid extends Mage_Adminhtml_Block_Widget_Grid
27
+ {
28
+ public function __construct()
29
+ {
30
+ parent::__construct();
31
+
32
+ $this->setDefaultSort('created_at');
33
+ $this->setId('wisepricer_jobqueue_grid');
34
+ $this->setDefaultDir('desc');
35
+ $this->setSaveParametersInSession(true);
36
+ }
37
+
38
+ protected function _getCollectionClass()
39
+ {
40
+ return 'jobqueue/job_collection';
41
+ }
42
+
43
+ protected function _prepareCollection()
44
+ {
45
+ $collection = Mage::getModel('jobqueue/job')->getCollection();
46
+ //$collection->getSelect()->columns('(`main_table`.`failed_at` is null) as status');
47
+ $collection->getSelect()->columns("(case when main_table.locked_at is not null then 2 when main_table.failed_at is null then 1 else 0 end) as status");
48
+ $this->setCollection($collection);
49
+
50
+ return parent::_prepareCollection();
51
+ }
52
+
53
+ protected function _addColumnFilterToCollection($column)
54
+ {
55
+ if ($column->getId() == 'status') {
56
+ $value = $column->getFilter()->getValue();
57
+ if($value == '2') {
58
+ $this->getCollection()->addFieldToFilter('locked_at', array('notnull'=> true));
59
+ } else {
60
+ $condition = $value == '1' ? 'null' : 'notnull';
61
+ $this->getCollection()->addFieldToFilter('failed_at', array($condition => true));
62
+ $this->getCollection()->addFieldToFilter('locked_at', array('null'=> true));
63
+ }
64
+ } else {
65
+ parent::_addColumnFilterToCollection($column);
66
+ }
67
+ return $this;
68
+ }
69
+
70
+
71
+ protected function _prepareColumns()
72
+ {
73
+ $this->addColumn('id',
74
+ array(
75
+ 'header'=> $this->__('ID'),
76
+ 'align' => 'right',
77
+ 'type' => 'number',
78
+ 'width' => '50px',
79
+ 'index' => 'id'
80
+ )
81
+ );
82
+
83
+ if (!Mage::app()->isSingleStoreMode()) {
84
+ $this->addColumn('store_id', array(
85
+ 'header' => $this->__('Store'),
86
+ 'index' => 'store_id',
87
+ 'type' => 'store',
88
+ 'store_view'=> true,
89
+ 'width' => '200px',
90
+ ));
91
+ }
92
+
93
+ $this->addColumn('name',
94
+ array(
95
+ 'header'=> $this->__('Name'),
96
+ 'index' => 'name'
97
+ )
98
+ );
99
+
100
+ $this->addColumn('queue',
101
+ array(
102
+ 'header'=> $this->__('Queue'),
103
+ 'index' => 'queue',
104
+ 'align' => 'center',
105
+ 'width' => '80px',
106
+ )
107
+ );
108
+
109
+ $this->addColumn('created_at',
110
+ array(
111
+ 'header'=> $this->__('Created At'),
112
+ 'index' => 'created_at',
113
+ 'type' => 'datetime',
114
+ 'width' => '175px',
115
+ 'align' => 'center',
116
+ )
117
+ );
118
+
119
+ $this->addColumn('run_at',
120
+ array(
121
+ 'header'=> $this->__('Run At'),
122
+ 'index' => 'run_at',
123
+ 'type' => 'datetime',
124
+ 'align' => 'center',
125
+ )
126
+ );
127
+
128
+ $this->addColumn('attempts',
129
+ array(
130
+ 'header'=> $this->__('Attempts'),
131
+ 'index' => 'attempts',
132
+ 'type' => 'number',
133
+ 'align' => 'center',
134
+ 'width' => '100px',
135
+ )
136
+ );
137
+
138
+ $this->addColumn('status',
139
+ array(
140
+ 'header'=> $this->__('Status'),
141
+ 'index' => 'status',
142
+ 'type' => 'options',
143
+ 'options' => array('1'=>'Pending', '2'=>'In Process', '0'=>'Failed'),
144
+ 'align' => 'center',
145
+ 'width' => '80px',
146
+ )
147
+ );
148
+
149
+ $this->addColumn('action',
150
+ array(
151
+ 'header' => $this->__('Action'),
152
+ 'width' => '50px',
153
+ 'type' => 'action',
154
+ 'getter' => 'getId',
155
+ 'actions' => array(
156
+ array(
157
+ 'caption' => $this->__('View'),
158
+ 'url' => array('base'=>'*/*/view'),
159
+ 'field' => 'id'
160
+ )
161
+ ),
162
+ 'filter' => false,
163
+ 'sortable' => false,
164
+ 'align' => 'center',
165
+ )
166
+ );
167
+
168
+ return parent::_prepareColumns();
169
+ }
170
+
171
+ protected function _prepareMassaction()
172
+ {
173
+ $this->setMassactionIdField('id');
174
+ $this->getMassactionBlock()->setFormFieldName('job_id');
175
+
176
+ $this->getMassactionBlock()->addItem('resubmit_job', array(
177
+ 'label' => $this->__('Resubmit Job'),
178
+ 'url' => $this->getUrl('*/*/massResubmitJob'),
179
+ 'confirm' => $this->__('Are you sure?')
180
+ ));
181
+
182
+ $this->getMassactionBlock()->addItem('cancel_job', array(
183
+ 'label' => $this->__('Cancel Job'),
184
+ 'url' => $this->getUrl('*/*/massCancelJob'),
185
+ 'confirm' => $this->__('Are you sure?')
186
+ ));
187
+
188
+ $this->getMassactionBlock()->addItem('delete_job', array(
189
+ 'label' => $this->__('Delete Job'),
190
+ 'url' => $this->getUrl('*/*/massDeleteJob'),
191
+ 'confirm' => $this->__('Are you sure?')
192
+ ));
193
+
194
+ return $this;
195
+ }
196
+
197
+ public function getRowUrl($row)
198
+ {
199
+ return $this->getUrl('*/*/view', array('id' => $row->getId()));
200
+ }
201
+ }
app/code/community/Wisepricer/JobQueue/Helper/Data.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Helper_Data extends Mage_Core_Helper_Abstract
27
+ {
28
+ }
app/code/community/Wisepricer/JobQueue/Model/Job.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Model_Job extends Mage_Core_Model_Abstract
27
+ {
28
+ protected function _construct()
29
+ {
30
+ $this->_init('jobqueue/job');
31
+ }
32
+
33
+ public function resubmit() {
34
+ $this->setFailedAt(null);
35
+ $this->setRunAt(null);
36
+ $this->setAttempts(0);
37
+ $this->setError(null);
38
+ $this->save();
39
+ }
40
+
41
+ public function cancel() {
42
+ $this->setFailedAt(Mage::getModel('core/date')->timestamp(time()));
43
+ $this->setError(Mage::helper('jobqueue')->__("Job canceled"));
44
+ $this->save();
45
+ }
46
+ }
app/code/community/Wisepricer/JobQueue/Model/Job/Abstract.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ abstract class Wisepricer_JobQueue_Model_Job_Abstract extends Mage_Core_Model_Abstract
27
+ {
28
+ private $name;
29
+ private $storeId;
30
+
31
+ public function __construct($name=null) {
32
+ $this->name = $name ? $name : $this->getType();
33
+ $this->setStoreId(Mage::app()->getStore()->getStoreId());
34
+ }
35
+
36
+ public abstract function perform();
37
+
38
+ public function performImmediate($retryQueue="default") {
39
+ try {
40
+ $this->perform();
41
+ } catch(Exception $e) {
42
+ $this->enqueue($retryQueue);
43
+ Mage::logException($e);
44
+ }
45
+ }
46
+
47
+ public function enqueue($queue="default", $run_at=null) {
48
+ $job = Mage::getModel('jobqueue/job');
49
+ $job->setStoreId($this->getStoreId());
50
+ $job->setName($this->getName());
51
+ $job->setHandler(serialize($this));
52
+ $job->setQueue($queue);
53
+ $job->setRunAt($run_at);
54
+ $job->setCreatedAt(now());
55
+ $job->save();
56
+ }
57
+
58
+ public function setName($name)
59
+ {
60
+ $this->name = $name;
61
+ return $this;
62
+ }
63
+
64
+ public function getName()
65
+ {
66
+ return $this->name;
67
+ }
68
+
69
+ public function setStoreId($storeId)
70
+ {
71
+ $this->storeId = $storeId;
72
+ return $this;
73
+ }
74
+
75
+ public function getStoreId()
76
+ {
77
+ return $this->storeId;
78
+ }
79
+
80
+ public function getType()
81
+ {
82
+ return get_class($this);
83
+ }
84
+ }
app/code/community/Wisepricer/JobQueue/Model/Resource/Job.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Model_Resource_Job extends Mage_Core_Model_Resource_Db_Abstract
27
+ {
28
+ protected function _construct()
29
+ {
30
+ $this->_init('jobqueue/job', 'id');
31
+ }
32
+ }
app/code/community/Wisepricer/JobQueue/Model/Resource/Job/Collection.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Model_Resource_Job_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
27
+ {
28
+ protected function _construct()
29
+ {
30
+ $this->_init('jobqueue/job');
31
+ }
32
+ }
app/code/community/Wisepricer/JobQueue/Model/Resource/Setup.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup
27
+ {
28
+ }
app/code/community/Wisepricer/JobQueue/Model/Worker.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ set_include_path(get_include_path().PS.Mage::getBaseDir('lib').DS.'DJJob');
27
+
28
+ require_once('DJJob.php');
29
+
30
+ class Wisepricer_JobQueue_Model_Worker extends Mage_Core_Model_Abstract
31
+ {
32
+ const DEFAULT_QUEUE = 'default';
33
+
34
+ private $workerName;
35
+ private $queue;
36
+
37
+ public function __construct() {
38
+ list($hostname, $pid) = array(trim(`hostname`), getmypid());
39
+ $this->workerName = "host::$hostname pid::$pid";
40
+ $this->queue = Mage::getStoreConfig('jobqueue/config/queue');
41
+ if(empty($this->queue)) {
42
+ $this->queue = self::DEFAULT_QUEUE;
43
+ }
44
+ }
45
+
46
+ public function getQueue() {
47
+ return $this->queue;
48
+ }
49
+
50
+ public function setQueue($queue) {
51
+ $this->queue = $queue;
52
+ }
53
+
54
+ public function getWorkerName() {
55
+ return $this->workerName;
56
+ }
57
+
58
+ public function executeJobs($schedule=null) {
59
+ if(!Mage::getStoreConfig('jobqueue/config/enabled')) {
60
+ return;
61
+ }
62
+
63
+ if($schedule) {
64
+ $jobsRoot = Mage::getConfig()->getNode('crontab/jobs');
65
+ $jobConfig = $jobsRoot->{$schedule->getJobCode()};
66
+ $queue = $jobConfig->queue;
67
+ if($queue) {
68
+ $this->setQueue($queue);
69
+ }
70
+ }
71
+
72
+ $this->setupDJJob();
73
+
74
+ try {
75
+ $collection = Mage::getModel('jobqueue/job')->getCollection();
76
+ $collection->addFieldToFilter('queue', array('eq' => $this->getQueue()))
77
+ ->addFieldToFilter('run_at', array(
78
+ array('null' => true),
79
+ array('lteq' => now())
80
+ ))
81
+ ->addFieldToFilter(array('locked_at', 'locked_by'), array(
82
+ array('locked_at', 'null' => true),
83
+ array('locked_by', 'eq' => $this->workerName)
84
+ ))
85
+ ->addFieldToFilter('failed_at', array('null' => true))
86
+ ->addFieldToFilter('attempts', array('lt' => (int)Mage::getStoreConfig('jobqueue/config/max_attempts')));
87
+
88
+ // randomly order to prevent lock contention among workers
89
+ $collection->getSelect()->order(new Zend_Db_Expr('RAND()'));
90
+ $collection->load();
91
+
92
+ foreach($collection as $row) {
93
+ $job = new DJJob($this->workerName, $row->getId(), array(
94
+ "max_attempts" => Mage::getStoreConfig('jobqueue/config/max_attempts')
95
+ ));
96
+ if ($job->acquireLock()) {
97
+ $job->run();
98
+ }
99
+ }
100
+ } catch (Exception $e) {
101
+ Mage::logException($e);
102
+ }
103
+ }
104
+
105
+ protected function setupDJJob() {
106
+ $config = Mage::getConfig()->getResourceConnectionConfig("default_setup");
107
+
108
+ $dsn = "";
109
+ if (strpos($config->host, '/') !== false) {
110
+ $dsn = "mysql:unix_socket=" . $config->host . ";dbname=" . $config->dbname;
111
+ } else {
112
+ $dsn = "mysql:host=" . $config->host . ";dbname=" . $config->dbname . ";port=" . $config->port;
113
+ }
114
+
115
+ DJJob::configure(
116
+ $dsn,
117
+ array('mysql_user' => $config->username, 'mysql_pass' => $config->password),
118
+ Mage::getSingleton('core/resource')->getTableName('jobqueue/job')
119
+ );
120
+
121
+ if(!empty($config->initStatements)) {
122
+ DJJob::runQuery($config->initStatements);
123
+ }
124
+ }
125
+ }
app/code/community/Wisepricer/JobQueue/controllers/Adminhtml/QueueController.php ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ class Wisepricer_JobQueue_Adminhtml_QueueController extends Mage_Adminhtml_Controller_Action
27
+ {
28
+ public function indexAction()
29
+ {
30
+ $this->_init()
31
+ ->renderLayout();
32
+ }
33
+
34
+ protected function _init()
35
+ {
36
+ $this->loadLayout()
37
+ ->_setActiveMenu('system/wisepricer_jobqueue_queue')
38
+ ->_title($this->__('System'))->_title($this->__('JobQueue'))
39
+ ->_addBreadcrumb($this->__('System'), $this->__('System'))
40
+ ->_addBreadcrumb($this->__('JobQueue'), $this->__('JobQueue'));
41
+
42
+ return $this;
43
+ }
44
+
45
+ public function viewAction()
46
+ {
47
+ $id = $this->getRequest()->getParam('id');
48
+ $job = Mage::getModel('jobqueue/job');
49
+
50
+ if ($id) {
51
+ $job->load($id);
52
+
53
+ if (!$job->getId()) {
54
+ Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
55
+ $this->_redirect('*/*/index');
56
+ return;
57
+ }
58
+ }
59
+
60
+ $this->_title($job->getId() ? $job->getName() : "Job Details");
61
+
62
+ $data = Mage::getSingleton('adminhtml/session')->getJobData(true);
63
+ if (!empty($data)) {
64
+ $job->setData($data);
65
+ }
66
+
67
+ Mage::register('wisepricer_jobqueue_job', $job);
68
+
69
+ $this->_init()
70
+ ->renderLayout();
71
+ }
72
+
73
+ public function resubmitAction()
74
+ {
75
+ $id = $this->getRequest()->getParam('id');
76
+ $job = Mage::getModel('jobqueue/job');
77
+
78
+ if ($id) {
79
+ $job->load($id);
80
+
81
+ if (!$job->getId()) {
82
+ Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
83
+ $this->_redirect('*/*/index');
84
+ return;
85
+ }
86
+
87
+ try {
88
+ $job->resubmit();
89
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been resubmitted', $job->getName()));
90
+ } catch (Exception $e) {
91
+ Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be resubmitted', $job->getName()));
92
+ }
93
+ }
94
+ $this->_redirect('*/*/index');
95
+ }
96
+
97
+ public function cancelAction()
98
+ {
99
+ $id = $this->getRequest()->getParam('id');
100
+ $job = Mage::getModel('jobqueue/job');
101
+
102
+ if ($id) {
103
+ $job->load($id);
104
+
105
+ if (!$job->getId()) {
106
+ Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
107
+ $this->_redirect('*/*/index');
108
+ return;
109
+ }
110
+
111
+ try {
112
+ $job->cancel();
113
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been canceled', $job->getName()));
114
+ } catch (Exception $e) {
115
+ Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be canceled', $job->getName()));
116
+ }
117
+ }
118
+ $this->_redirect('*/*/index');
119
+ }
120
+
121
+ public function deleteAction()
122
+ {
123
+ $id = $this->getRequest()->getParam('id');
124
+ $job = Mage::getModel('jobqueue/job');
125
+
126
+ if ($id) {
127
+ $job->load($id);
128
+
129
+ if (!$job->getId()) {
130
+ Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
131
+ $this->_redirect('*/*/index');
132
+ return;
133
+ }
134
+
135
+ try {
136
+ $job->delete();
137
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been deleted', $job->getName()));
138
+ } catch (Exception $e) {
139
+ Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be deleted', $job->getName()));
140
+ }
141
+ }
142
+ $this->_redirect('*/*/index');
143
+ }
144
+
145
+ public function massResubmitJobAction()
146
+ {
147
+ $jobIds = $this->getRequest()->getParam('job_id');
148
+ $success = 0;
149
+ $error = 0;
150
+
151
+ foreach($jobIds as $jobId) {
152
+ $job = Mage::getModel('jobqueue/job')->load($jobId);
153
+ try {
154
+ $job->resubmit();
155
+ $success++;
156
+ } catch (Exception $e) {
157
+ Mage::logException($e);
158
+ $error++;
159
+ }
160
+ }
161
+
162
+
163
+ if($error) {
164
+ Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be resubmitted', $error));
165
+ }
166
+
167
+ if($success) {
168
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) resubmitted', $success));
169
+ }
170
+
171
+ $this->_redirect('*/*/index');
172
+ }
173
+
174
+ public function massCancelJobAction()
175
+ {
176
+ $jobIds = $this->getRequest()->getParam('job_id');
177
+ $success = 0;
178
+ $error = 0;
179
+
180
+ foreach($jobIds as $jobId) {
181
+ $job = Mage::getModel('jobqueue/job')->load($jobId);
182
+ try {
183
+ if($job->getFailedAt()) {
184
+ $error++;
185
+ } else {
186
+ $job->cancel();
187
+ $success++;
188
+ }
189
+ } catch (Exception $e) {
190
+ Mage::logException($e);
191
+ $error++;
192
+ }
193
+ }
194
+
195
+
196
+ if($error) {
197
+ Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be canceled', $error));
198
+ }
199
+
200
+ if($success) {
201
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) canceled', $success));
202
+ }
203
+
204
+ $this->_redirect('*/*/index');
205
+ }
206
+
207
+ public function massDeleteJobAction()
208
+ {
209
+ $jobIds = $this->getRequest()->getParam('job_id');
210
+ $success = 0;
211
+ $error = 0;
212
+
213
+ foreach($jobIds as $jobId) {
214
+ $job = Mage::getModel('jobqueue/job')->load($jobId);
215
+ try {
216
+ $job->delete();
217
+ $success++;
218
+ } catch (Exception $e) {
219
+ Mage::logException($e);
220
+ $error++;
221
+ }
222
+ }
223
+
224
+
225
+ if($error) {
226
+ Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be deleted', $error));
227
+ }
228
+
229
+ if($success) {
230
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) deleted', $success));
231
+ }
232
+
233
+ $this->_redirect('*/*/index');
234
+ }
235
+ }
app/code/community/Wisepricer/JobQueue/etc/config.xml ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <!--
3
+ The MIT License (MIT)
4
+
5
+ Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
24
+ -->
25
+ <config>
26
+ <modules>
27
+ <Wisepricer_JobQueue>
28
+ <version>1.0.0.0</version>
29
+ </Wisepricer_JobQueue>
30
+ </modules>
31
+ <global>
32
+ <models>
33
+ <jobqueue>
34
+ <class>Wisepricer_JobQueue_Model</class>
35
+ <resourceModel>jobqueue_resource</resourceModel>
36
+ </jobqueue>
37
+ <jobqueue_resource>
38
+ <class>Wisepricer_JobQueue_Model_Resource</class>
39
+ <entities>
40
+ <job>
41
+ <table>ws2_jobs</table>
42
+ </job>
43
+ </entities>
44
+ </jobqueue_resource>
45
+ </models>
46
+ <blocks>
47
+ <jobqueue>
48
+ <class>Wisepricer_JobQueue_Block</class>
49
+ </jobqueue>
50
+ </blocks>
51
+ <helpers>
52
+ <jobqueue>
53
+ <class>Wisepricer_JobQueue_Helper</class>
54
+ </jobqueue>
55
+ </helpers>
56
+ <resources>
57
+ <wisepricer_jobqueue_setup>
58
+ <setup>
59
+ <module>Wisepricer_JobQueue</module>
60
+ <class>Wisepricer_JobQueue_Model_Resource_Setup</class>
61
+ </setup>
62
+ </wisepricer_jobqueue_setup>
63
+ </resources>
64
+ </global>
65
+ <frontend>
66
+ <routers>
67
+ <wisepricer_jobqueue>
68
+ <use>standard</use>
69
+ <args>
70
+ <module>Wisepricer_JobQueue</module>
71
+ <frontName>jobqueue</frontName>
72
+ </args>
73
+ </wisepricer_jobqueue>
74
+ </routers>
75
+ </frontend>
76
+ <crontab>
77
+ <jobs>
78
+ <jobqueue_default>
79
+ <schedule>
80
+ <config_path>jobqueue/config/cron_expr</config_path>
81
+ </schedule>
82
+ <run>
83
+ <model>jobqueue/worker::executeJobs</model>
84
+ </run>
85
+ </jobqueue_default>
86
+ </jobs>
87
+ </crontab>
88
+ <default>
89
+ <jobqueue>
90
+ <config>
91
+ <enabled>1</enabled>
92
+ <cron_expr>* * * * *</cron_expr>
93
+ <max_attempts>3</max_attempts>
94
+ <queue>default</queue>
95
+ </config>
96
+ </jobqueue>
97
+ </default>
98
+ </config>
app/code/community/Wisepricer/JobQueue/sql/wisepricer_jobqueue_setup/mysql4-install-1.0.0.0.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+
26
+ $installer = $this;
27
+
28
+ $installer->startSetup();
29
+
30
+ $installer->run(
31
+ "CREATE TABLE " . $installer->getTable('jobqueue/job')." (
32
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
33
+ `store_id` INT UNSIGNED NOT NULL DEFAULT 0,
34
+ `name` VARCHAR(255),
35
+ `handler` TEXT NOT NULL,
36
+ `queue` VARCHAR(255) NOT NULL DEFAULT 'default',
37
+ `attempts` INT UNSIGNED NOT NULL DEFAULT 0,
38
+ `run_at` DATETIME NULL,
39
+ `locked_at` DATETIME NULL,
40
+ `locked_by` VARCHAR(255) NULL,
41
+ `failed_at` DATETIME NULL,
42
+ `error` TEXT NULL,
43
+ `created_at` DATETIME NOT NULL
44
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
45
+ );
46
+
47
+ $installer->endSetup();
app/code/community/Wisepricer/Syncer2/Exception.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Wisepricer_Syncer2_Exception extends Mage_Core_Exception
4
+ {
5
+
6
+ }
app/code/community/Wisepricer/Syncer2/Helper/Data.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Wisepricer_Syncer2_Helper_Data extends Mage_Core_Helper_Abstract
4
+ {
5
+ const LOG_FILENAME = 'wisepricer_syncer2.log';
6
+
7
+ const PATH_VAR_WS2 = 'ws2';
8
+ const PATH_EXPORT = 'export';
9
+ const PATH_IMPORT = 'import';
10
+
11
+ const CONFIG_TOKEN = 'wisepricer_syncer2/authentication/token';
12
+
13
+ protected $_allAttributesCollection = null;
14
+
15
+ public function checkAttributeExists($attributeCode, $throwException = true)
16
+ {
17
+ if ($attributeCode) {
18
+
19
+ /**
20
+ * @var $_attribute Mage_Catalog_Model_Resource_Eav_Attribute
21
+ */
22
+ foreach ($this->getAllAttributesCollection()->getItems() as $_attribute) {
23
+ if ($_attribute->getAttributeCode() === $attributeCode) {
24
+ return true;
25
+ }
26
+ }
27
+
28
+ }
29
+
30
+ if ($throwException) {
31
+ $this->exception(sprintf('Attribute with code "%s" does not exists', $attributeCode));
32
+ }
33
+
34
+ return false;
35
+ }
36
+
37
+ public function getAllAttributesCollection()
38
+ {
39
+ if( is_null($this->_allAttributesCollection) ) {
40
+ $this->_allAttributesCollection = Mage::getResourceModel('catalog/product_attribute_collection');
41
+ }
42
+
43
+ return $this->_allAttributesCollection;
44
+ }
45
+
46
+ public function getExportPath($baseFile = null)
47
+ {
48
+ return implode(DS, array(Mage::getBaseDir('var'), self::PATH_VAR_WS2, self::PATH_EXPORT, $baseFile));
49
+ }
50
+
51
+ public function getImportPath($baseFile = null)
52
+ {
53
+ return implode(DS, array(Mage::getBaseDir('var'), self::PATH_VAR_WS2, self::PATH_IMPORT, $baseFile));
54
+ }
55
+
56
+ public function getToken()
57
+ {
58
+ return Mage::getStoreConfig(self::CONFIG_TOKEN);
59
+ }
60
+
61
+ /**
62
+ * @param mixed $message
63
+ * @param int $level
64
+ */
65
+ public function log($message, $level = Zend_log::INFO)
66
+ {
67
+ $message = is_string($message) ? $message : print_r($message, true);
68
+
69
+ Mage::log($message, $level, self::LOG_FILENAME);
70
+ }
71
+
72
+ /**
73
+ * @param $message
74
+ * @param int $code
75
+ * @throws Mage_Core_Exception
76
+ */
77
+ public function exception($message, $code = 0)
78
+ {
79
+ $this->log('Exception thrown: ' . $message, Zend_Log::CRIT);
80
+ throw Mage::exception('Wisepricer_Syncer2', $message, $code);
81
+ }
82
+ }
app/code/community/Wisepricer/Syncer2/Model/Abstract.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ abstract class Wisepricer_Syncer2_Model_Abstract extends Wisepricer_JobQueue_Model_Job_Abstract
4
+ {
5
+ const REMOTE_SUCCESS_CODE = 1;
6
+
7
+ const REMOTE_FAILURE_CODE = 500;
8
+
9
+ public function perform()
10
+ {
11
+ Mage::app()->setCurrentStore($this->getStoreId());
12
+ }
13
+
14
+ /**
15
+ * Enqueue job
16
+ *
17
+ * @param string $queue
18
+ * @param null $run_at
19
+ */
20
+ public function enqueue($queue = "default", $run_at = null)
21
+ {
22
+ $this->validateNotRunning($queue);
23
+
24
+ $this->getHelper()->log(sprintf(
25
+ "Job '%s' (ID: '%s') added to queue. Data:\n%s",
26
+ get_class($this),
27
+ $this->getName(),
28
+ print_r($this->getData(), true)
29
+ ));
30
+
31
+ parent::enqueue($queue, $run_at);
32
+ }
33
+
34
+ /**
35
+ * After max attempts running this method.
36
+ *
37
+ * @param $error
38
+ */
39
+ public function _onDjjobRetryError($error)
40
+ {
41
+ $this->callbackRequest(self::REMOTE_FAILURE_CODE, $error);
42
+ }
43
+
44
+ /**
45
+ * Validate that job not running now.
46
+ *
47
+ * @param $queue
48
+ * @throws Mage_Core_Exception
49
+ */
50
+ protected function validateNotRunning($queue)
51
+ {
52
+ $collection = Mage::getModel('jobqueue/job')->getCollection();
53
+ $collection->addFieldToFilter('queue', array('eq' => $queue))
54
+ ->addFieldToFilter('store_id', array('eq' => $this->getStoreId()))
55
+ ->addFieldToFilter('failed_at', array('null' => true))
56
+ ->addFieldToFilter('attempts', array('lt' => (int)Mage::getStoreConfig('jobqueue/config/max_attempts')));
57
+
58
+ if (count($collection->getItems()) > 0) {
59
+ $this->getHelper()->exception('Can\'t enqueue job, job running.');
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @param int $code
65
+ * @param null $message
66
+ * @param array $data
67
+ * @return Zend_Http_Response
68
+ * @throws Exception
69
+ */
70
+ protected function callbackRequest($code = self::REMOTE_SUCCESS_CODE, $message = null, $data = array())
71
+ {
72
+ $token = $this->getHelper()->getToken();
73
+ $defaults = array('token' => $token, 'job_id' => $this->getName(), 'magento_store_id' => $this->getStoreId());
74
+ $params = array('code' => $code, 'error_message' => $message) + $defaults + $data;
75
+
76
+ $this->getHelper()->log(sprintf(
77
+ "Making request to %s with params\n%s",
78
+ $this->getCallbackUrl(),
79
+ print_r($params, true)
80
+ ));
81
+
82
+ try {
83
+
84
+ $httpClient = new Varien_Http_Client($this->getCallbackUrl());
85
+ $httpClient->setMethod(Varien_Http_Client::POST);
86
+ $httpClient->setParameterGet('token', $token);
87
+ $httpClient->setRawData(json_encode($params), 'application/json');
88
+ $httpClient->setHeaders(Varien_Http_Client::CONTENT_TYPE, 'application/json; charset=utf-8');
89
+ $httpClient->setHeaders(array('Accept' => 'application/json'));
90
+
91
+ $response = $httpClient->request();
92
+
93
+ } catch (Exception $e) {
94
+ $this->getHelper()->exception("Failed make callback request: " . $e->getMessage());
95
+ }
96
+
97
+ if ($response->isError()) {
98
+ $this->getHelper()->exception(
99
+ sprintf("Got error status '%s' and body: %s", $response->getStatus(), $response->getBody())
100
+ );
101
+ }
102
+
103
+ if ($jsonResponse = json_decode($response->getBody(), true)) {
104
+
105
+ if (empty($jsonResponse['code']) || $jsonResponse['code'] != 200) {
106
+ $this->getHelper()->exception(sprintf(
107
+ "Got error in response from callback server '%s'",
108
+ isset($jsonResponse['message']) ? $jsonResponse['message'] : 'EMPTY MESSAGE'
109
+ ));
110
+ }
111
+
112
+ } else {
113
+ $this->getHelper()->exception(sprintf(
114
+ "Error on json decode. Error code: %s\nResponse: '%s'",
115
+ json_last_error(),
116
+ $response->getBody()
117
+ ));
118
+ }
119
+
120
+ }
121
+
122
+ /**
123
+ * @return Wisepricer_Syncer2_Helper_Data
124
+ */
125
+ protected function getHelper()
126
+ {
127
+ return Mage::helper('wisepricer_syncer2');
128
+ }
129
+ }
app/code/community/Wisepricer/Syncer2/Model/Exporter.php ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once(Mage::getModuleDir('', 'Wisepricer_Syncer2') . DS . 'Model' . DS . 'Abstract.php');
4
+
5
+ class Wisepricer_Syncer2_Model_Exporter extends Wisepricer_Syncer2_Model_Abstract
6
+ {
7
+
8
+ public function perform()
9
+ {
10
+ parent::perform();
11
+
12
+ try {
13
+
14
+ $fileName = $this->getName() . '.csv';
15
+ $csvFilePath = $this->getHelper()->getExportPath($fileName);
16
+
17
+ if (!file_exists($csvFilePath)) {
18
+ $this->prepareCsvFile($csvFilePath);
19
+ }
20
+
21
+ $this->callbackRequest(self::REMOTE_SUCCESS_CODE, null, array(
22
+ 'file_url' => Mage::getUrl('wisesyncer2/api/export', array(
23
+ 'token' => $this->getHelper()->getToken(),
24
+ 'file' => $fileName,
25
+ ))
26
+ ));
27
+
28
+ } catch (Exception $e) {
29
+ $this->getHelper()->log("Failed on export: " . $e->getMessage());
30
+ throw $e;
31
+ }
32
+ }
33
+
34
+ protected function prepareCsvFile($filePath)
35
+ {
36
+ $this->getHelper()->log("Creating CSV file: $filePath");
37
+
38
+ $io = new Varien_Io_File();
39
+ $attributes = $this->getData('attributes');
40
+ $collection = $this->getFilteredCollection();
41
+ $temporaryFile = pathinfo($filePath, PATHINFO_FILENAME) . '.tmp';
42
+
43
+ $io->setAllowCreateFolders(true);
44
+ $io->open(array('path' => pathinfo($filePath, PATHINFO_DIRNAME)));
45
+ $io->streamOpen($temporaryFile, 'w+');
46
+ $io->streamLock(true);
47
+
48
+ if (!$io->streamWriteCsv($attributes)) {
49
+ $this->getHelper()->exception("Failed on writing headers to CSV file.");
50
+ }
51
+
52
+ //Add a page size to the result set.
53
+ $collection->setPageSize(100);
54
+
55
+ //discover how many page the result will be.
56
+ $pages = $collection->getLastPageNumber();
57
+
58
+ $currentPage = 1;
59
+
60
+ do {
61
+
62
+ $collection->setCurPage($currentPage);
63
+ $collection->load();
64
+
65
+ foreach ($collection as $item) {
66
+ $data = array();
67
+
68
+ foreach ($attributes as $attributeName) {
69
+ $data[] = $item->getData($attributeName);
70
+ }
71
+
72
+ if (!$io->streamWriteCsv($data)) {
73
+ $this->getHelper()->exception("Failed on writing data to CSV file.");
74
+ }
75
+ }
76
+
77
+ $currentPage++;
78
+
79
+ //make the collection unload the data in memory so it will pick up the next page when load() is called.
80
+ $collection->clear();
81
+
82
+ } while ($currentPage <= $pages);
83
+
84
+ $io->streamUnlock();
85
+ $io->streamClose();
86
+
87
+ if (!$io->mv($temporaryFile, $filePath)) {
88
+ $this->getHelper()->exception("Failed on moving $temporaryFile to $filePath");
89
+ }
90
+
91
+ $this->getHelper()->log("File writing finished.");
92
+ }
93
+
94
+ protected function getFilteredCollection()
95
+ {
96
+ $attributes = $this->getData('attributes');
97
+ $enabledOnly = $this->getData('enabled_only');
98
+ $productTypeFilter = $this->getData('product_type_filter');
99
+ $excludeAttribute = $this->getData('exclude_attribute');
100
+
101
+ /**
102
+ * @var $collection Mage_Catalog_Model_Resource_Product_Collection
103
+ */
104
+ $collection = Mage::getModel('catalog/product')->getCollection()->addStoreFilter($this->getStoreId());
105
+
106
+ /**
107
+ * Adding attributes to select, but validating each attribute. If the attribute is specified, but
108
+ * does not exists, we will get FATAL ERROR, the job will be interrupted, with no clue what happened.
109
+ */
110
+ if (is_array($attributes) && !empty($attributes)) {
111
+ foreach ($attributes as $attributeCode) {
112
+ if ($this->getHelper()->checkAttributeExists($attributeCode)) {
113
+ $collection->addAttributeToSelect($attributeCode);
114
+ }
115
+ }
116
+ }
117
+
118
+ if ($enabledOnly) {
119
+ $collection->addFieldToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
120
+ }
121
+
122
+ if (is_array($productTypeFilter) && !empty($productTypeFilter)) {
123
+ $collection->addAttributeToFilter('type_id', array('in' => $productTypeFilter));
124
+ }
125
+
126
+ if ($excludeAttribute && $this->getHelper()->checkAttributeExists($excludeAttribute)) {
127
+
128
+ $collection->addAttributeToFilter(
129
+ array(
130
+ array('attribute' => $excludeAttribute, 'neq' => 1),
131
+ array('attribute' => $excludeAttribute, 'null' => true),
132
+ ), '', 'left'
133
+ );
134
+ }
135
+
136
+ // die(print_r($collection->getSelect()->__toString(), true));
137
+
138
+ return $collection;
139
+ }
140
+
141
+ }
app/code/community/Wisepricer/Syncer2/Model/Importer.php ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once(Mage::getModuleDir('', 'Wisepricer_Syncer2') . DS . 'Model' . DS . 'Abstract.php');
4
+
5
+ class Wisepricer_Syncer2_Model_Importer extends Wisepricer_Syncer2_Model_Abstract
6
+ {
7
+
8
+ /**
9
+ * Downloads CSV file then running on it and repricing products.
10
+ *
11
+ * @throws Exception
12
+ */
13
+ public function perform()
14
+ {
15
+ parent::perform();
16
+
17
+ try {
18
+
19
+ $data = array();
20
+ $fileName = $this->getName() . '.csv';
21
+ $localFile = $this->getHelper()->getImportPath($fileName);
22
+
23
+ if (!file_exists($localFile)) {
24
+ $this->downloadFile($this->getFileUrl(), $localFile);
25
+ }
26
+
27
+ $failedProducts = $this->performRepriceFromCSV($localFile);
28
+
29
+ if ($this->getReindexRequired()) {
30
+ Mage::getModel('index/indexer')->getProcessByCode('catalog_product_price')->reindexAll();
31
+ Mage::getModel('index/indexer')->getProcessByCode('catalog_product_attribute')->reindexAll();
32
+ }
33
+
34
+ if (!empty($failedProducts)) {
35
+ $data['failed_products'] = array();
36
+
37
+ foreach ($failedProducts as $sku => $product) {
38
+ $data['failed_products'][] = array(
39
+ 'sku' => $sku,
40
+ 'error_code' => 404,
41
+ 'error_message' => 'Product not found.',
42
+ );
43
+ }
44
+ }
45
+
46
+ $this->callbackRequest(static::REMOTE_SUCCESS_CODE, null, $data);
47
+
48
+ } catch (Exception $e) {
49
+ $this->getHelper()->log("Failed on import: " . $e->getMessage());
50
+ throw $e;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Download file from remote to local server.
56
+ *
57
+ * @param $remoteFile
58
+ * @param $localFile
59
+ * @throws Mage_Core_Exception
60
+ */
61
+ protected function downloadFile($remoteFile, $localFile)
62
+ {
63
+ $this->getHelper()->log("Downloading file: $remoteFile");
64
+
65
+ $tmpFile = $localFile . '.tmp';
66
+ $tmpPath = pathinfo($tmpFile, PATHINFO_DIRNAME);
67
+ $remoteUrl = $remoteFile . '?' . http_build_query(array(
68
+ 'token' => $this->getHelper()->getToken(),
69
+ 'job_id' => $this->getName(),
70
+ 'magento_store_id' => $this->getStoreId()
71
+ ));
72
+
73
+ if (!file_exists($tmpPath) && !mkdir($tmpPath, 0777, true)) {
74
+ $this->getHelper()->exception("Can't create directory: $tmpPath. " . error_get_last());
75
+ }
76
+
77
+ if (false === ($fh = fopen($remoteUrl, 'r'))) {
78
+ $this->getHelper()->exception("Can't open stream: $remoteUrl. " . error_get_last());
79
+ }
80
+
81
+ if (!file_put_contents($tmpFile, $fh)) {
82
+ $this->getHelper()->exception("Can't write to file: $tmpFile. " . error_get_last());
83
+ }
84
+
85
+ if (!rename($tmpFile, $localFile)) {
86
+ $this->getHelper()->exception("Can't rename file $tmpFile to $localFile. " . error_get_last());
87
+ }
88
+
89
+ $this->getHelper()->log("File saved at: $localFile");
90
+ }
91
+
92
+ /**
93
+ * Reprice products accodring to CSV
94
+ *
95
+ * @param $csvFile
96
+ * @throws Exception
97
+ */
98
+ protected function performRepriceFromCSV($csvFile)
99
+ {
100
+ $this->getHelper()->log("Starting reprice.");
101
+ Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
102
+
103
+ $productsData = $this->prepareProductsForReprice($csvFile);
104
+ $failedProducts = $productsData;
105
+
106
+ /**
107
+ * @var $product Mage_Catalog_Model_Product
108
+ */
109
+ $product = Mage::getModel('catalog/product');
110
+
111
+ $connection = $product->getResource()->getWriteConnection();
112
+ $connection->beginTransaction();
113
+ try {
114
+
115
+ /**
116
+ * @var $collection Mage_Catalog_Model_Resource_Product_Collection
117
+ */
118
+ $collection = $product->getCollection()
119
+ ->setStoreId($this->getStoreId())
120
+ ->addAttributeToFilter( 'sku', array( 'in' => array_keys($productsData)));
121
+
122
+ //Add a page size to the result set.
123
+ $collection->setPageSize(100);
124
+
125
+ //discover how many page the result will be.
126
+ $pages = $collection->getLastPageNumber();
127
+
128
+ $currentPage = 1;
129
+
130
+ do {
131
+
132
+ $collection->setCurPage($currentPage);
133
+ $collection->load();
134
+
135
+ /**
136
+ * @var $item Mage_Catalog_Model_Product
137
+ */
138
+ foreach ($collection as $item) {
139
+ $sku = $item->getSku();
140
+
141
+ if (isset($productsData[$sku])) {
142
+ foreach ($productsData[$sku] as $attributeCode => $attributeValue) {
143
+ $item->setStoreId($this->getStoreId())
144
+ ->setData($attributeCode, $attributeValue)
145
+ ->getResource()
146
+ ->saveAttribute($item, $attributeCode);
147
+ }
148
+ }
149
+
150
+ unset($failedProducts[$sku]);
151
+ }
152
+
153
+ $currentPage++;
154
+
155
+ //make the collection unload the data in memory so it will pick up the next page when load() is called.
156
+ $collection->clear();
157
+
158
+ } while ($currentPage <= $pages);
159
+
160
+ $connection->commit();
161
+ } catch (Exception $e) {
162
+ $connection->rollBack();
163
+ throw $e;
164
+ }
165
+
166
+ Mage::app()->setCurrentStore($this->getStoreId());
167
+ $this->getHelper()->log("Finished reprice.");
168
+
169
+ return $failedProducts;
170
+ }
171
+
172
+ /**
173
+ * Read csv file and prepares array for future reprice.
174
+ *
175
+ * @param $csvFile
176
+ * @return array
177
+ * @throws Exception
178
+ * @throws Mage_Core_Exception
179
+ */
180
+ protected function prepareProductsForReprice($csvFile)
181
+ {
182
+ $this->getHelper()->log("Reading downloaded CSV file: $csvFile");
183
+ $products = array();
184
+
185
+ $io = new Varien_Io_File();
186
+ $io->streamOpen($csvFile, 'r');
187
+
188
+ $line = 1;
189
+ $attributes = $this->getAttributesFromCsvFile($io);
190
+ while ($csvData = $io->streamReadCsv()) {
191
+ $sku = $csvData[$attributes['sku']];
192
+
193
+ if (isset($products[$sku])) {
194
+ $io->streamClose();
195
+ $this->getHelper()->exception("Dublicate sku '$sku' found in ".basename($csvFile).":$line");
196
+ }
197
+
198
+ $data = array();
199
+ foreach ($attributes as $attributeCode => $index) {
200
+ if ($attributeCode === 'sku') {
201
+ continue;
202
+ }
203
+ $data[$attributeCode] = $csvData[$index];
204
+ }
205
+
206
+ $products[$sku] = $data;
207
+
208
+ $line++;
209
+ }
210
+
211
+ $io->streamClose();
212
+ $this->getHelper()->log("Finished reading downloaded CSV file: $csvFile");
213
+
214
+ return $products;
215
+ }
216
+
217
+ /**
218
+ * @param Varien_Io_File $io
219
+ * @return array
220
+ * @throws Wisepricer_Syncer2_Exception
221
+ */
222
+ protected function getAttributesFromCsvFile(Varien_Io_File $io)
223
+ {
224
+ $attributes = array();
225
+
226
+ if (($headers = $io->streamReadCsv()) && !empty($headers)) {
227
+ foreach ($headers as $index => $attribute) {
228
+ $this->getHelper()->checkAttributeExists($attribute);
229
+ $attributes[$attribute] = $index;
230
+ //$attributes[$index] = $attribute;
231
+ }
232
+ } else {
233
+ $io->streamClose();
234
+ $this->getHelper()->exception('Can\'t read CSV headers from file.');
235
+ }
236
+
237
+ // if (array_search('sku', $attributes)) {
238
+ if (!isset($attributes['sku'])) {
239
+ $io->streamClose();
240
+ $this->getHelper()->exception('Missing "sku" in CSV headers.');
241
+ }
242
+
243
+ return $attributes;
244
+ }
245
+
246
+ }
app/code/community/Wisepricer/Syncer2/controllers/ApiController.php ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Wisepricer_Syncer2_ApiController extends Mage_Core_Controller_Front_Action
4
+ {
5
+ CONST RESPONSE_MESSAGE_SUCCESS = null;
6
+
7
+ CONST RESPONSE_MESSAGE_FAILURE = 'Failure';
8
+
9
+ CONST RESPONSE_MESSAGE_BADREQUEST = 'Wrong Request';
10
+
11
+ CONST RESPONSE_MESSAGE_TOKEN_MISS = 'Missing authorization token.';
12
+
13
+ CONST RESPONSE_MESSAGE_FORBIDDEN = 'Wrong authorization token.';
14
+
15
+ CONST RESPONSE_CODE_SUCCESS = 1;
16
+
17
+ CONST RESPONSE_CODE_FAILURE = 500;
18
+
19
+ CONST RESPONSE_CODE_BADREQUEST = 400;
20
+
21
+ CONST RESPONSE_CODE_TOKEN_MISS = 401;
22
+
23
+ CONST RESPONSE_CODE_FORBIDDEN = 403;
24
+
25
+ protected $_processInstance = null;
26
+
27
+ protected $jsonParams = null;
28
+
29
+ /**
30
+ * Token authorization
31
+ */
32
+ public function preDispatch()
33
+ {
34
+ if ($token = $this->getParam('token')) {
35
+
36
+ if ($token !== $this->getHelper()->getToken()) {
37
+ $this->response(null, self::RESPONSE_CODE_FORBIDDEN, self::RESPONSE_MESSAGE_FORBIDDEN);
38
+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
39
+ }
40
+
41
+ } else {
42
+ $this->response(null, self::RESPONSE_CODE_TOKEN_MISS, self::RESPONSE_MESSAGE_TOKEN_MISS);
43
+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
44
+ }
45
+
46
+ parent::preDispatch();
47
+ return $this;
48
+ }
49
+
50
+ /**
51
+ * Retrieves the current website data, helps to check if the website is available
52
+ */
53
+ public function pingAction()
54
+ {
55
+ try {
56
+
57
+ $website = Mage::app()->getWebsite();
58
+
59
+ $this->response(array(
60
+ 'website_id' => $website->getCode(),
61
+ 'website_name' => $website->getName(),
62
+ 'website_url' => $website->getDefaultStore()->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB),
63
+ ));
64
+
65
+ } catch (Exception $e) {
66
+ $this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Retrieves the list of available websites data.
72
+ */
73
+ public function getWebsitesAction()
74
+ {
75
+ $websites = array();
76
+
77
+ try {
78
+
79
+ /**
80
+ * @var $website Mage_Core_Model_Website
81
+ */
82
+ foreach (Mage::app()->getWebsites() as $website) {
83
+
84
+ $stores = array();
85
+
86
+ /**
87
+ * @var $store Mage_Core_Model_Store
88
+ */
89
+ foreach ($website->getStores() as $store) {
90
+ $stores[] = array(
91
+ 'store_name' => $store->getGroup()->getName() . ' ' . $store->getName(),
92
+ 'store_code' => $store->getCode(),
93
+ 'store_id' => $store->getId(),
94
+ 'store_url' => $store->getBaseUrl(),
95
+ );
96
+ }
97
+
98
+ $websites[] = array(
99
+ 'website_name' => $website->getName(),
100
+ 'website_code' => $website->getCode(),
101
+ 'website_id' => $website->getId(),
102
+ 'default_store' => $website->getDefaultStore()->getId(),
103
+ 'stores' => $stores,
104
+ );
105
+
106
+ }
107
+
108
+ $this->response($websites);
109
+
110
+ } catch (Exception $e) {
111
+ $this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Retrieves the list of product attributes available in the system
117
+ */
118
+ public function getAttributesAction()
119
+ {
120
+ try {
121
+
122
+ $attributesCollection = Mage::getResourceModel('catalog/product_attribute_collection')
123
+ ->setOrder('main_table.frontend_label', 'ASC')
124
+ ->addFieldToFilter('main_table.frontend_label', array('neq' => 'NULL' ))
125
+ ->getItems();
126
+
127
+ $data = array();
128
+ foreach ($attributesCollection as $attribute) {
129
+ $data[] = array(
130
+ 'attribute_code' => $attribute->getAttributeCode(),
131
+ 'attribute_label' => $attribute->getFrontendLabel(),
132
+ 'attribute_type' => $attribute->getFrontendInput(),
133
+ 'attribute_default_value' => $attribute->getDefaultValue(),
134
+ );
135
+ }
136
+
137
+ $this->response($data);
138
+
139
+ } catch (Exception $e) {
140
+ $this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
141
+ }
142
+ }
143
+
144
+ /**
145
+ * When called, processes the File Export scheduler or returns currents Status
146
+ * and current Progress in response;
147
+ */
148
+ public function exportFileAction()
149
+ {
150
+ $errors = array();
151
+ $params = $this->getJsonParams();
152
+
153
+ if (!$this->getRequest()->isPost()) {
154
+ $errors[] = 'Request method should be POST.';
155
+ }
156
+
157
+ if (empty($params['store_id'])) {
158
+ $errors[] = "Param 'store_id' required and should be valid website ID.";
159
+ }
160
+
161
+ if (empty($params['attributes']) || !is_array($params['attributes'])) {
162
+ $errors[] = "Param 'attributes' required and should be not empty array.";
163
+ } else {
164
+ foreach ($params['attributes'] as $attribute) {
165
+ if (!$this->getHelper()->checkAttributeExists($attribute, false)) {
166
+ $errors[] = sprintf('Attribute with code "%s" does not exists', $attribute);
167
+ }
168
+ }
169
+ }
170
+
171
+ if (empty($params['callback_url'])) {
172
+ $errors[] = "Param 'callback_url' required.";
173
+ }
174
+
175
+ if (!empty($params['exclude_attribute']) && !$this->getHelper()->checkAttributeExists($params['exclude_attribute'], false)) {
176
+ $errors[] = sprintf('Exclude attribute with code "%s" does not exists', $params['exclude_attribute']);
177
+ }
178
+
179
+ if (!empty($params['product_type_filter'])) {
180
+ $productTypes = Mage_Catalog_Model_Product_Type::getTypes();
181
+
182
+ $productTypeFilter = explode(',', $params['product_type_filter']);
183
+ foreach ($productTypeFilter as $productType) {
184
+ if (!array_key_exists($productType, array_keys($productTypes))) {
185
+ $errors[] = sprintf("Product type '%s' not exists.", $productType);
186
+ }
187
+ }
188
+ }
189
+
190
+ if (empty($errors)) {
191
+
192
+ try {
193
+
194
+ $store = Mage::app()->getStore($params['store_id']);
195
+ $jobId = uniqid($store->getCode(), true) . '_export';
196
+
197
+ Mage::getModel('wisepricer_syncer2/exporter')
198
+ ->setStoreId($store->getId())
199
+ ->setName($jobId)
200
+ ->addData(array(
201
+ 'attributes' => $params['attributes'],
202
+ 'callback_url' => $params['callback_url'],
203
+ 'enabled_only' => !empty($params['enabled_only']) ? (bool) $params['enabled_only'] : null,
204
+ 'product_type_filter' => !empty($params['product_type_filter']) ? explode(',', $params['product_type_filter']) : null,
205
+ 'exclude_attribute' => !empty($params['exclude_attribute']) ? $params['exclude_attribute'] : null,
206
+ ))
207
+ ->enqueue();
208
+
209
+ $this->response(array('job_id' => $jobId));
210
+
211
+ } catch (Exception $e) {
212
+ $this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
213
+ }
214
+
215
+ } else {
216
+ $this->response(null, self::RESPONSE_CODE_BADREQUEST, implode(' ', $errors));
217
+ }
218
+ }
219
+
220
+ /**
221
+ * @throws Exception
222
+ * @throws Zend_Validate_Exception
223
+ */
224
+ public function importFileAction()
225
+ {
226
+ $errors = array();
227
+ $params = $this->getJsonParams();
228
+
229
+ if (!$this->getRequest()->isPost()) {
230
+ $errors[] = 'Request method should be POST.';
231
+ }
232
+
233
+ if (empty($params['store_id'])) {
234
+ $errors[] = "Param 'store_id' required and should be valid website ID.";
235
+ }
236
+
237
+ if (empty($params['callback_url'])) {
238
+ $errors[] = "Param 'callback_url' required.";
239
+ }
240
+
241
+ if (empty($params['file_url'])) {
242
+ $errors[] = "Param 'file_url' required.";
243
+ }
244
+
245
+ if (empty($errors)) {
246
+
247
+ try {
248
+
249
+ $store = Mage::app()->getStore($params['store_id']);
250
+ $jobId = uniqid($store->getCode(), true) . '_import';
251
+
252
+ Mage::getModel('wisepricer_syncer2/importer')
253
+ ->setStoreId($store->getId())
254
+ ->setName($jobId)
255
+ ->addData(array(
256
+ 'file_url' => $params['file_url'],
257
+ 'callback_url' => $params['callback_url'],
258
+ 'reindex_required' => isset($params['reindex_required']) ? $params['reindex_required'] : true,
259
+ ))
260
+ ->enqueue();
261
+
262
+ $this->response(array('job_id' => $jobId));
263
+
264
+ } catch (Exception $e) {
265
+ $this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
266
+ }
267
+
268
+ } else {
269
+ $this->response(null, self::RESPONSE_CODE_BADREQUEST, implode(' ', $errors));
270
+ }
271
+
272
+ }
273
+
274
+ /**
275
+ * Exports file for wiser
276
+ */
277
+ public function exportAction()
278
+ {
279
+ if ($file = $this->getParam('file')) {
280
+
281
+ try {
282
+
283
+ $this->_prepareDownloadResponse(basename($file), array(
284
+ 'type' => 'filename',
285
+ 'value' => $this->getHelper()->getExportPath() . DS . basename($file),
286
+ ));
287
+
288
+
289
+ } catch (Exception $e) {
290
+ $this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
291
+ }
292
+
293
+ } else {
294
+ $this->response(null, self::RESPONSE_CODE_BADREQUEST, "Param 'file' can't be empty.");
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Response with JSON encoded message.
300
+ * @param array $data Response data
301
+ * @param int $code Response status code
302
+ * @param string $response Response message
303
+ */
304
+ protected function response($data = null, $code = self::RESPONSE_CODE_SUCCESS, $message = self::RESPONSE_MESSAGE_SUCCESS)
305
+ {
306
+ $this->getResponse()->setHeader('Content-type', 'application/json');
307
+ $this->getResponse()->setBody(json_encode(array(
308
+ 'code' => $code,
309
+ 'message' => $message,
310
+ 'data' => $data,
311
+ )));
312
+ }
313
+
314
+ /**
315
+ * Return array from json request
316
+ * @return mixed|null
317
+ */
318
+ protected function getJsonParams()
319
+ {
320
+ if ($this->jsonParams === null) {
321
+ $this->jsonParams = json_decode($this->getRequest()->getRawBody(), true);
322
+ }
323
+
324
+ return $this->jsonParams;
325
+ }
326
+
327
+ /**
328
+ * Get param from json request or from get request.
329
+ *
330
+ * @param $key
331
+ * @return mixed
332
+ */
333
+ protected function getParam($key)
334
+ {
335
+ $params = $this->getJsonParams();
336
+
337
+ return isset($params[$key]) ? $params[$key] : $this->getRequest()->getParam($key, false);
338
+ }
339
+
340
+ /**
341
+ * @return Wisepricer_Syncer2_Helper_Data
342
+ */
343
+ protected function getHelper()
344
+ {
345
+ return Mage::helper('wisepricer_syncer2');
346
+ }
347
+
348
+ }
app/code/community/Wisepricer/Syncer2/etc/adminhtml.xml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <acl>
4
+ <resources>
5
+ <admin>
6
+ <children>
7
+ <system>
8
+ <children>
9
+ <config>
10
+ <children>
11
+ <wisepricer_syncer2>
12
+ <title>Wisepricer Syncer2 Configuration</title>
13
+ </wisepricer_syncer2>
14
+ </children>
15
+ </config>
16
+ </children>
17
+ </system>
18
+ </children>
19
+ </admin>
20
+ </resources>
21
+ </acl>
22
+ </config>
app/code/community/Wisepricer/Syncer2/etc/config.xml ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <Wisepricer_Syncer2>
5
+ <version>2.0.0.0</version>
6
+ <modulename>Wisepricer Syncer2</modulename>
7
+ </Wisepricer_Syncer2>
8
+ </modules>
9
+ <global>
10
+ <models>
11
+ <wisepricer_syncer2>
12
+ <class>Wisepricer_Syncer2_Model</class>
13
+ <resourceModel>wisepricer_syncer2_mysql4</resourceModel>
14
+ </wisepricer_syncer2>
15
+ <wisepricer_syncer2_mysql4>
16
+ <class>Wisepricer_Syncer2_Model_Mysql4</class>
17
+ <entities>
18
+ <state>
19
+ <table>wisepricer_syncer2_state</table>
20
+ </state>
21
+ </entities>
22
+ </wisepricer_syncer2_mysql4>
23
+ </models>
24
+ <resources>
25
+ <wisepricer_syncer2_setup>
26
+ <setup>
27
+ <module>Wisepricer_Syncer2</module>
28
+ <class>Mage_Catalog_Model_Resource_Eav_Mysql4_Setup</class>
29
+ </setup>
30
+ <connection>
31
+ <use>core_setup</use>
32
+ </connection>
33
+ </wisepricer_syncer2_setup>
34
+ </resources>
35
+ <blocks>
36
+ <wisepricer_syncer2>
37
+ <class>Wisepricer_Syncer2_Block</class>
38
+ </wisepricer_syncer2>
39
+ </blocks>
40
+ <helpers>
41
+ <wisepricer_syncer2>
42
+ <class>Wisepricer_Syncer2_Helper</class>
43
+ </wisepricer_syncer2>
44
+ </helpers>
45
+ <events>
46
+ <wisepricer_syncer2_import_download_complete>
47
+ <observers>
48
+ <wisepricer_syncer2_observer>
49
+ <type>singleton</type>
50
+ <class>Wisepricer_Syncer2_Model_Observer</class>
51
+ <method>importDownloadComplete</method>
52
+ </wisepricer_syncer2_observer>
53
+ </observers>
54
+ </wisepricer_syncer2_import_download_complete>
55
+ </events>
56
+ </global>
57
+ <frontend>
58
+ <routers>
59
+ <wisepricer_syncer2>
60
+ <use>standard</use>
61
+ <args>
62
+ <module>Wisepricer_Syncer2</module>
63
+ <frontName>wisesyncer2</frontName>
64
+ </args>
65
+ </wisepricer_syncer2>
66
+ </routers>
67
+ </frontend>
68
+ <default>
69
+ <wisepricer_syncer2>
70
+ <authentication>
71
+ <token></token>
72
+ </authentication>
73
+ </wisepricer_syncer2>
74
+ </default>
75
+ </config>
app/code/community/Wisepricer/Syncer2/etc/system.xml ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <tabs>
4
+ <wisepricer_syncer2 translate="label" module="wisepricer_syncer2">
5
+ <!--label>Wisepricer Syncer2</label-->
6
+ <!--label><![CDATA[<img id="wisepricer_syncer2_block" src="" style="margin: 4px 0 0 -3px;" alt="" border="0" height="23" />&nbsp;<script>$('wisepricer_syncer2_block').src = SKIN_URL + "../../default/default/images/wisepricer_syncer2/wiser_logo.png";</script>]]></label-->
7
+ <label><![CDATA[<div style="position: relative"><img id="wisepricer_syncer2_block" src="" style="position: absolute; left: -2px;" alt="" border="0" height="18" /></div>&nbsp;<script>$('wisepricer_syncer2_block').src = SKIN_URL + "../../default/default/images/wisepricer_syncer2/wiser_logo.png";</script>]]></label>
8
+ <sort_order>100</sort_order>
9
+ </wisepricer_syncer2>
10
+ </tabs>
11
+ <sections>
12
+ <wisepricer_syncer2 translate="label" module="wisepricer_syncer2">
13
+ <label>Options</label>
14
+ <tab>wisepricer_syncer2</tab>
15
+ <sort_order>1000</sort_order>
16
+ <show_in_default>1</show_in_default>
17
+ <show_in_website>0</show_in_website>
18
+ <show_in_store>0</show_in_store>
19
+
20
+ <groups>
21
+ <authentication translate="label" module="wisepricer_syncer2">
22
+ <label>Authentication Setup</label>
23
+ <comment><![CDATA[
24
+ <p>Hello,</p>
25
+ <p>This document is intended to be a guide to installing Wiser’s Magento Extension on your Magento Installation. This will detail the necessary steps on both the Wiser app as well as on the user’s Magento Installation. This guide assumes you have already downloaded and installed the Magento Extension from Magento Connect and installed it on your Magento Installation. If you are experiencing challenges in doing so please submit a ticket to <a href="mailto:support@wiser.com">support@wiser.com</a>.</p>
26
+ <p>
27
+ <ol type="1">
28
+ <li>1) The first step to integrating Magento to Wiser is to create a store in the Wiser app. Your CS representative can do this for you or if you feel comfortable enough in the app, you can do this on your own. This will generate an Authentication Key on Wiser’s app.</li>
29
+ <li>2) The second step is taking the Authentication Key generated from the Wiser app and pasting it in the Authentication Key field within the Wiser tab of your Magento Installation. The Authentication key field should be the first field you see in the Extension tab.</li>
30
+ <li>3) Once this is done, please notify your CS representative for the next steps in fully integrating your account. Thanks!</li>
31
+ </ol>
32
+ </p>
33
+ ]]>
34
+ </comment>
35
+ <sort_order>10</sort_order>
36
+ <show_in_default>1</show_in_default>
37
+ <show_in_website>1</show_in_website>
38
+ <show_in_store>1</show_in_store>
39
+ <fields>
40
+ <token translate="label">
41
+ <label>Authentication Key: </label>
42
+ <comment><![CDATA[<span class="required">*</span>]]> Required</comment>
43
+ <frontend_type>text</frontend_type>
44
+ <sort_order>20</sort_order>
45
+ <show_in_default>1</show_in_default>
46
+ <show_in_website>1</show_in_website>
47
+ <show_in_store>1</show_in_store>
48
+ </token>
49
+ </fields>
50
+ </authentication>
51
+
52
+ </groups>
53
+ </wisepricer_syncer2>
54
+ </sections>
55
+ </config>
app/etc/modules/Wisepricer_Syncer2.xml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <modules>
4
+ <Wisepricer_Syncer2>
5
+ <active>true</active>
6
+ <codePool>community</codePool>
7
+ <depends>
8
+ <Wisepricer_JobQueue/>
9
+ </depends>
10
+ </Wisepricer_Syncer2>
11
+ <Wisepricer_JobQueue>
12
+ <active>true</active>
13
+ <codePool>community</codePool>
14
+ </Wisepricer_JobQueue>
15
+ </modules>
16
+ </config>
lib/DJJob/DJJob.php ADDED
@@ -0,0 +1,525 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ # This system is mostly a port of delayed_job: http://github.com/tobi/delayed_job
4
+
5
+ class DJException extends Exception { }
6
+
7
+ class DJRetryException extends DJException {
8
+
9
+ private $delay_seconds = 7200;
10
+
11
+ public function setDelay($delay) {
12
+ $this->delay_seconds = $delay;
13
+ }
14
+ public function getDelay() {
15
+ return $this->delay_seconds;
16
+ }
17
+ }
18
+
19
+ class DJBase {
20
+
21
+ // error severity levels
22
+ const CRITICAL = 4;
23
+ const ERROR = 3;
24
+ const WARN = 2;
25
+ const INFO = 1;
26
+ const DEBUG = 0;
27
+
28
+ private static $log_level = self::DEBUG;
29
+
30
+ private static $db = null;
31
+ protected static $jobsTable = "";
32
+
33
+ private static $dsn = "";
34
+ private static $user = "";
35
+ private static $password = "";
36
+ private static $retries = 3; //default retries
37
+
38
+ // use either `configure` or `setConnection`, depending on if
39
+ // you already have a PDO object you can re-use
40
+
41
+ public static function configure(){
42
+ $args = func_get_args();
43
+ $numArgs = func_num_args();
44
+
45
+ switch ($numArgs) {
46
+ case 1:{
47
+ if (is_array($args[0])){
48
+ self::configureWithOptions($args[0]);
49
+ } else {
50
+ self::configureWithDsnAndOptions($args[0]);
51
+ }
52
+ break;
53
+ }
54
+ case 2:{
55
+ if (is_array($args[0])){
56
+ self::configureWithOptions($args[0], $args[1]);
57
+ } else {
58
+ self::configureWithDsnAndOptions($args[0], $args[1]);
59
+ }
60
+ break;
61
+ }
62
+ case 3: {
63
+ self::configureWithDsnAndOptions($args[0], $args[1], $args[2]);
64
+ break;
65
+ }
66
+ }
67
+ }
68
+
69
+ protected static function configureWithDsnAndOptions($dsn, array $options = array(), $jobsTable = 'jobs') {
70
+ if (!isset($options['mysql_user'])){
71
+ throw new DJException("Please provide the database user in configure options array.");
72
+ }
73
+ if (!isset($options['mysql_pass'])){
74
+ throw new DJException("Please provide the database password in configure options array.");
75
+ }
76
+
77
+ self::$dsn = $dsn;
78
+ self::$jobsTable = $jobsTable;
79
+
80
+ self::$user = $options['mysql_user'];
81
+ self::$password = $options['mysql_pass'];
82
+
83
+ // searches for retries
84
+ if (isset($options['retries'])){
85
+ self::$retries = (int) $options['retries'];
86
+ }
87
+ }
88
+
89
+ protected static function configureWithOptions(array $options, $jobsTable = 'jobs') {
90
+
91
+ if (!isset($options['driver'])){
92
+ throw new DJException("Please provide the database driver used in configure options array.");
93
+ }
94
+ if (!isset($options['user'])){
95
+ throw new DJException("Please provide the database user in configure options array.");
96
+ }
97
+ if (!isset($options['password'])){
98
+ throw new DJException("Please provide the database password in configure options array.");
99
+ }
100
+
101
+ self::$user = $options['user'];
102
+ self::$password = $options['password'];
103
+ self::$jobsTable = $jobsTable;
104
+
105
+ self::$dsn = $options['driver'] . ':';
106
+ foreach ($options as $key => $value) {
107
+ // skips options already used
108
+ if ($key == 'driver' || $key == 'user' || $key == 'password') {
109
+ continue;
110
+ }
111
+
112
+ // searches for retries
113
+ if ($key == 'retries'){
114
+ self::$retries = (int) $value;
115
+ continue;
116
+ }
117
+
118
+ self::$dsn .= $key . '=' . $value . ';';
119
+ }
120
+ }
121
+
122
+ public static function setLogLevel($const) {
123
+ self::$log_level = $const;
124
+ }
125
+
126
+ public static function setConnection(PDO $db) {
127
+ self::$db = $db;
128
+ }
129
+
130
+ protected static function getConnection() {
131
+ if (self::$db === null) {
132
+ try {
133
+ self::$db = new PDO(self::$dsn, self::$user, self::$password);
134
+ self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
135
+ } catch (PDOException $e) {
136
+ throw new Exception("DJJob couldn't connect to the database. PDO said [{$e->getMessage()}]");
137
+ }
138
+ }
139
+ return self::$db;
140
+ }
141
+
142
+ public static function runQuery($sql, $params = array()) {
143
+ for ($attempts = 0; $attempts < self::$retries; $attempts++) {
144
+ try {
145
+ $stmt = self::getConnection()->prepare($sql);
146
+ $stmt->execute($params);
147
+
148
+ $ret = array();
149
+ if ($stmt->rowCount()) {
150
+ // calling fetchAll on a result set with no rows throws a
151
+ // "general error" exception
152
+ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) $ret []= $r;
153
+ }
154
+
155
+ $stmt->closeCursor();
156
+ return $ret;
157
+ }
158
+ catch (PDOException $e) {
159
+ // Catch "MySQL server has gone away" error.
160
+ if ($e->errorInfo[1] == 2006) {
161
+ self::$db = null;
162
+ }
163
+ // Throw all other errors as expected.
164
+ else {
165
+ throw $e;
166
+ }
167
+ }
168
+ }
169
+
170
+ throw new DJException("DJJob exhausted retries connecting to database");
171
+ }
172
+
173
+ public static function runUpdate($sql, $params = array()) {
174
+ for ($attempts = 0; $attempts < self::$retries; $attempts++) {
175
+ try {
176
+ $stmt = self::getConnection()->prepare($sql);
177
+ $stmt->execute($params);
178
+ return $stmt->rowCount();
179
+ }
180
+ catch (PDOException $e) {
181
+ // Catch "MySQL server has gone away" error.
182
+ if ($e->errorInfo[1] == 2006) {
183
+ self::$db = null;
184
+ }
185
+ // Throw all other errors as expected.
186
+ else {
187
+ throw $e;
188
+ }
189
+ }
190
+ }
191
+
192
+ throw new DJException("DJJob exhausted retries connecting to database");
193
+ }
194
+
195
+ protected static function log($mesg, $severity=self::CRITICAL) {
196
+ if ($severity >= self::$log_level) {
197
+ printf("[%s] %s\n", date('c'), $mesg);
198
+ }
199
+ }
200
+ }
201
+
202
+ class DJWorker extends DJBase {
203
+ # This is a singleton-ish thing. It wouldn't really make sense to
204
+ # instantiate more than one in a single request (or commandline task)
205
+
206
+ public function __construct($options = array()) {
207
+ $options = array_merge(array(
208
+ "queue" => "default",
209
+ "count" => 0,
210
+ "sleep" => 5,
211
+ "max_attempts" => 5,
212
+ "fail_on_output" => false
213
+ ), $options);
214
+ list($this->queue, $this->count, $this->sleep, $this->max_attempts, $this->fail_on_output) =
215
+ array($options["queue"], $options["count"], $options["sleep"], $options["max_attempts"], $options["fail_on_output"]);
216
+
217
+ list($hostname, $pid) = array(trim(`hostname`), getmypid());
218
+ $this->name = "host::$hostname pid::$pid";
219
+
220
+ if (function_exists("pcntl_signal")) {
221
+ pcntl_signal(SIGTERM, array($this, "handleSignal"));
222
+ pcntl_signal(SIGINT, array($this, "handleSignal"));
223
+ }
224
+ }
225
+
226
+ public function handleSignal($signo) {
227
+ $signals = array(
228
+ SIGTERM => "SIGTERM",
229
+ SIGINT => "SIGINT"
230
+ );
231
+ $signal = $signals[$signo];
232
+
233
+ $this->log("[WORKER] Received received {$signal}... Shutting down", self::INFO);
234
+ $this->releaseLocks();
235
+ die(0);
236
+ }
237
+
238
+ public function releaseLocks() {
239
+ $this->runUpdate("
240
+ UPDATE " . self::$jobsTable . "
241
+ SET locked_at = NULL, locked_by = NULL
242
+ WHERE locked_by = ?",
243
+ array($this->name)
244
+ );
245
+ }
246
+
247
+ /**
248
+ * Returns a new job ordered by most recent first
249
+ * why this?
250
+ * run newest first, some jobs get left behind
251
+ * run oldest first, all jobs get left behind
252
+ * @return DJJob
253
+ */
254
+ public function getNewJob() {
255
+ # we can grab a locked job if we own the lock
256
+ $rs = $this->runQuery("
257
+ SELECT id
258
+ FROM " . self::$jobsTable . "
259
+ WHERE queue = ?
260
+ AND (run_at IS NULL OR NOW() >= run_at)
261
+ AND (locked_at IS NULL OR locked_by = ?)
262
+ AND failed_at IS NULL
263
+ AND attempts < ?
264
+ ORDER BY created_at DESC
265
+ LIMIT 10
266
+ ", array($this->queue, $this->name, $this->max_attempts));
267
+
268
+ // randomly order the 10 to prevent lock contention among workers
269
+ shuffle($rs);
270
+
271
+ foreach ($rs as $r) {
272
+ $job = new DJJob($this->name, $r["id"], array(
273
+ "max_attempts" => $this->max_attempts,
274
+ "fail_on_output" => $this->fail_on_output
275
+ ));
276
+ if ($job->acquireLock()) return $job;
277
+ }
278
+
279
+ return false;
280
+ }
281
+
282
+ public function start() {
283
+ $this->log("[JOB] Starting worker {$this->name} on queue::{$this->queue}", self::INFO);
284
+
285
+ $count = 0;
286
+ $job_count = 0;
287
+ try {
288
+ while ($this->count == 0 || $count < $this->count) {
289
+ if (function_exists("pcntl_signal_dispatch")) pcntl_signal_dispatch();
290
+
291
+ $count += 1;
292
+ $job = $this->getNewJob($this->queue);
293
+
294
+ if (!$job) {
295
+ $this->log("[JOB] Failed to get a job, queue::{$this->queue} may be empty", self::DEBUG);
296
+ sleep($this->sleep);
297
+ continue;
298
+ }
299
+
300
+ $job_count += 1;
301
+ $job->run();
302
+ }
303
+ } catch (Exception $e) {
304
+ $this->log("[JOB] unhandled exception::\"{$e->getMessage()}\"", self::ERROR);
305
+ }
306
+
307
+ $this->log("[JOB] worker shutting down after running {$job_count} jobs, over {$count} polling iterations", self::INFO);
308
+ }
309
+ }
310
+
311
+ class DJJob extends DJBase {
312
+
313
+ public function __construct($worker_name, $job_id, $options = array()) {
314
+ $options = array_merge(array(
315
+ "max_attempts" => 5,
316
+ "fail_on_output" => false
317
+ ), $options);
318
+ $this->worker_name = $worker_name;
319
+ $this->job_id = $job_id;
320
+ $this->max_attempts = $options["max_attempts"];
321
+ $this->fail_on_output = $options["fail_on_output"];
322
+ }
323
+
324
+ public function run() {
325
+ # pull the handler from the db
326
+ $handler = $this->getHandler();
327
+ if (!is_object($handler)) {
328
+ $msg = "[JOB] bad handler for job::{$this->job_id}";
329
+ $this->finishWithError($msg);
330
+ return false;
331
+ }
332
+
333
+ # run the handler
334
+ try {
335
+
336
+ if ($this->fail_on_output) {
337
+ ob_start();
338
+ }
339
+
340
+ $handler->perform();
341
+
342
+ if ($this->fail_on_output) {
343
+ $output = ob_get_contents();
344
+ ob_end_clean();
345
+
346
+ if (!empty($output)) {
347
+ throw new Exception("Job produced unexpected output: $output");
348
+ }
349
+ }
350
+
351
+ # cleanup
352
+ $this->finish();
353
+ return true;
354
+
355
+ } catch (DJRetryException $e) {
356
+ # attempts hasn't been incremented yet.
357
+ $attempts = $this->getAttempts()+1;
358
+
359
+ $msg = "Caught DJRetryException \"{$e->getMessage()}\" on attempt $attempts/{$this->max_attempts}.";
360
+
361
+ if($attempts == $this->max_attempts) {
362
+ $msg = "[JOB] job::{$this->job_id} $msg Giving up.";
363
+ $this->finishWithError($msg, $handler);
364
+ } else {
365
+ $this->log("[JOB] job::{$this->job_id} $msg Try again in {$e->getDelay()} seconds.", self::WARN);
366
+ $this->retryLater($e->getDelay());
367
+ }
368
+ return false;
369
+
370
+ } catch (Exception $e) {
371
+
372
+ $this->finishWithError($e->getMessage(), $handler);
373
+ return false;
374
+
375
+ }
376
+ }
377
+
378
+ public function acquireLock() {
379
+ $this->log("[JOB] attempting to acquire lock for job::{$this->job_id} on {$this->worker_name}", self::INFO);
380
+
381
+ $lock = $this->runUpdate("
382
+ UPDATE " . self::$jobsTable . "
383
+ SET locked_at = NOW(), locked_by = ?
384
+ WHERE id = ? AND (locked_at IS NULL OR locked_by = ?) AND failed_at IS NULL
385
+ ", array($this->worker_name, $this->job_id, $this->worker_name));
386
+
387
+ if (!$lock) {
388
+ $this->log("[JOB] failed to acquire lock for job::{$this->job_id}", self::INFO);
389
+ return false;
390
+ }
391
+
392
+ return true;
393
+ }
394
+
395
+ public function releaseLock() {
396
+ $this->runUpdate("
397
+ UPDATE " . self::$jobsTable . "
398
+ SET locked_at = NULL, locked_by = NULL
399
+ WHERE id = ?",
400
+ array($this->job_id)
401
+ );
402
+ }
403
+
404
+ public function finish() {
405
+ $this->runUpdate(
406
+ "DELETE FROM " . self::$jobsTable . " WHERE id = ?",
407
+ array($this->job_id)
408
+ );
409
+ $this->log("[JOB] completed job::{$this->job_id}", self::INFO);
410
+ }
411
+
412
+ public function finishWithError($error, $handler = null) {
413
+ $this->runUpdate("
414
+ UPDATE " . self::$jobsTable . "
415
+ SET attempts = attempts + 1,
416
+ failed_at = IF(attempts >= ?, NOW(), NULL),
417
+ error = IF(attempts >= ?, ?, NULL)
418
+ WHERE id = ?",
419
+ array(
420
+ $this->max_attempts,
421
+ $this->max_attempts,
422
+ $error,
423
+ $this->job_id
424
+ )
425
+ );
426
+ $this->log($error, self::ERROR);
427
+ $this->log("[JOB] failure in job::{$this->job_id}", self::ERROR);
428
+ $this->releaseLock();
429
+
430
+ if ($handler && ($this->getAttempts() == $this->max_attempts) && method_exists($handler, '_onDjjobRetryError')) {
431
+ $handler->_onDjjobRetryError($error);
432
+ }
433
+ }
434
+
435
+ public function retryLater($delay) {
436
+ $this->runUpdate("
437
+ UPDATE " . self::$jobsTable . "
438
+ SET run_at = DATE_ADD(NOW(), INTERVAL ? SECOND),
439
+ attempts = attempts + 1
440
+ WHERE id = ?",
441
+ array(
442
+ $delay,
443
+ $this->job_id
444
+ )
445
+ );
446
+ $this->releaseLock();
447
+ }
448
+
449
+ public function getHandler() {
450
+ $rs = $this->runQuery(
451
+ "SELECT handler FROM " . self::$jobsTable . " WHERE id = ?",
452
+ array($this->job_id)
453
+ );
454
+ foreach ($rs as $r) return unserialize($r["handler"]);
455
+ return false;
456
+ }
457
+
458
+ public function getAttempts() {
459
+ $rs = $this->runQuery(
460
+ "SELECT attempts FROM " . self::$jobsTable . " WHERE id = ?",
461
+ array($this->job_id)
462
+ );
463
+ foreach ($rs as $r) return $r["attempts"];
464
+ return false;
465
+ }
466
+
467
+ public static function enqueue($handler, $queue = "default", $run_at = null) {
468
+ $affected = self::runUpdate(
469
+ "INSERT INTO " . self::$jobsTable . " (handler, queue, run_at, created_at) VALUES(?, ?, ?, NOW())",
470
+ array(serialize($handler), (string) $queue, $run_at)
471
+ );
472
+
473
+ if ($affected < 1) {
474
+ self::log("[JOB] failed to enqueue new job", self::ERROR);
475
+ return false;
476
+ }
477
+
478
+ return self::getConnection()->lastInsertId(); // return the job ID, for manipulation later
479
+ }
480
+
481
+ public static function bulkEnqueue($handlers, $queue = "default", $run_at = null) {
482
+ $sql = "INSERT INTO " . self::$jobsTable . " (handler, queue, run_at, created_at) VALUES";
483
+ $sql .= implode(",", array_fill(0, count($handlers), "(?, ?, ?, NOW())"));
484
+
485
+ $parameters = array();
486
+ foreach ($handlers as $handler) {
487
+ $parameters []= serialize($handler);
488
+ $parameters []= (string) $queue;
489
+ $parameters []= $run_at;
490
+ }
491
+ $affected = self::runUpdate($sql, $parameters);
492
+
493
+ if ($affected < 1) {
494
+ self::log("[JOB] failed to enqueue new jobs", self::ERROR);
495
+ return false;
496
+ }
497
+
498
+ if ($affected != count($handlers))
499
+ self::log("[JOB] failed to enqueue some new jobs", self::ERROR);
500
+
501
+ return true;
502
+ }
503
+
504
+ public static function status($queue = "default") {
505
+ $rs = self::runQuery("
506
+ SELECT COUNT(*) as total, COUNT(failed_at) as failed, COUNT(locked_at) as locked
507
+ FROM `" . self::$jobsTable . "`
508
+ WHERE queue = ?
509
+ ", array($queue));
510
+ $rs = $rs[0];
511
+
512
+ $failed = $rs["failed"];
513
+ $locked = $rs["locked"];
514
+ $total = $rs["total"];
515
+ $outstanding = $total - $locked - $failed;
516
+
517
+ return array(
518
+ "outstanding" => $outstanding,
519
+ "locked" => $locked,
520
+ "failed" => $failed,
521
+ "total" => $total
522
+ );
523
+ }
524
+
525
+ }
lib/DJJob/examples/HelloWorldJob.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class HelloWorldJob {
4
+
5
+ public function __construct($name) {
6
+ $this->name = $name;
7
+ }
8
+
9
+ public function perform() {
10
+ echo "Hello {$this->name}!\n";
11
+ }
12
+
13
+ }
lib/DJJob/jobs.sql ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CREATE TABLE `jobs` (
2
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
3
+ `handler` TEXT NOT NULL,
4
+ `queue` VARCHAR(255) NOT NULL DEFAULT 'default',
5
+ `attempts` INT UNSIGNED NOT NULL DEFAULT 0,
6
+ `run_at` DATETIME NULL,
7
+ `locked_at` DATETIME NULL,
8
+ `locked_by` VARCHAR(255) NULL,
9
+ `failed_at` DATETIME NULL,
10
+ `error` TEXT NULL,
11
+ `created_at` DATETIME NOT NULL
12
+ ) ENGINE = INNODB;
lib/DJJob/test/custom_table_name.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function assert_handler($file, $line, $code, $desc = null) {
4
+ printf("Assertion failed at %s:%s: %s: %s\n", $file, $line, $code, $desc);
5
+ }
6
+
7
+ assert_options(ASSERT_ACTIVE, 1);
8
+ assert_options(ASSERT_WARNING, 0);
9
+ assert_options(ASSERT_QUIET_EVAL, 1);
10
+ assert_options(ASSERT_CALLBACK, 'assert_handler');
11
+
12
+ date_default_timezone_set('America/New_York');
13
+
14
+ require dirname(__FILE__) . "/../DJJob.php";
15
+
16
+ DJJob::configure([
17
+ 'driver' => 'mysql',
18
+ 'host' => '127.0.0.1',
19
+ 'dbname' => 'djjob',
20
+ 'user' => 'root',
21
+ 'password' => 'root',
22
+ ], 'my_jobs');
23
+
24
+ DJJob::runQuery("
25
+ DROP TABLE IF EXISTS `my_jobs`;
26
+ CREATE TABLE `my_jobs` (
27
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
28
+ `handler` VARCHAR(255) NOT NULL,
29
+ `queue` VARCHAR(255) NOT NULL DEFAULT 'default',
30
+ `attempts` INT UNSIGNED NOT NULL DEFAULT 0,
31
+ `run_at` DATETIME NULL,
32
+ `locked_at` DATETIME NULL,
33
+ `locked_by` VARCHAR(255) NULL,
34
+ `failed_at` DATETIME NULL,
35
+ `error` VARCHAR(255) NULL,
36
+ `created_at` DATETIME NOT NULL
37
+ ) ENGINE = MEMORY;
38
+ ");
39
+
40
+ class HelloWorldJob {
41
+ public function __construct($name) {
42
+ $this->name = $name;
43
+ }
44
+ public function perform() {
45
+ echo "Hello {$this->name}!\n";
46
+ sleep(1);
47
+ }
48
+ }
49
+
50
+ class FailingJob {
51
+ public function perform() {
52
+ sleep(1);
53
+ throw new Exception("Uh oh");
54
+ }
55
+ }
56
+
57
+ $status = DJJob::status();
58
+
59
+ assert('$status["outstanding"] == 0', "Initial outstanding status is incorrect");
60
+ assert('$status["locked"] == 0', "Initial locked status is incorrect");
61
+ assert('$status["failed"] == 0', "Initial failed status is incorrect");
62
+ assert('$status["total"] == 0', "Initial total status is incorrect");
63
+
64
+ printf("=====================\nStarting run of DJJob\n=====================\n\n");
65
+
66
+ DJJob::enqueue(new HelloWorldJob("delayed_job"));
67
+ DJJob::bulkEnqueue(array(
68
+ new HelloWorldJob("shopify"),
69
+ new HelloWorldJob("github"),
70
+ ));
71
+ DJJob::enqueue(new FailingJob());
72
+
73
+ $worker = new DJWorker(array("count" => 5, "max_attempts" => 2, "sleep" => 10));
74
+ $worker->start();
75
+ printf("\n============\nRun complete\n============\n\n");
76
+
77
+ $status = DJJob::status();
78
+
79
+ assert('$status["outstanding"] == 0', "Final outstanding status is incorrect");
80
+ assert('$status["locked"] == 0', "Final locked status is incorrect");
81
+ assert('$status["failed"] == 1', "Final failed status is incorrect");
82
+ assert('$status["total"] == 1', "Final total status is incorrect");
lib/DJJob/test/database.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function assert_handler($file, $line, $code, $desc = null) {
4
+ printf("Assertion failed at %s:%s: %s: %s\n", $file, $line, $code, $desc);
5
+ }
6
+
7
+ assert_options(ASSERT_ACTIVE, 1);
8
+ assert_options(ASSERT_WARNING, 0);
9
+ assert_options(ASSERT_QUIET_EVAL, 1);
10
+ assert_options(ASSERT_CALLBACK, 'assert_handler');
11
+
12
+ date_default_timezone_set('America/New_York');
13
+
14
+ require dirname(__FILE__) . "/../DJJob.php";
15
+
16
+ DJJob::configure([
17
+ 'driver' => 'mysql',
18
+ 'host' => '127.0.0.1',
19
+ 'dbname' => 'djjob',
20
+ 'user' => 'root',
21
+ 'password' => 'root',
22
+ ]);
23
+
24
+ DJJob::runQuery("
25
+ DROP TABLE IF EXISTS `jobs`;
26
+ CREATE TABLE `jobs` (
27
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
28
+ `handler` VARCHAR(255) NOT NULL,
29
+ `queue` VARCHAR(255) NOT NULL DEFAULT 'default',
30
+ `attempts` INT UNSIGNED NOT NULL DEFAULT 0,
31
+ `run_at` DATETIME NULL,
32
+ `locked_at` DATETIME NULL,
33
+ `locked_by` VARCHAR(255) NULL,
34
+ `failed_at` DATETIME NULL,
35
+ `error` VARCHAR(255) NULL,
36
+ `created_at` DATETIME NOT NULL
37
+ ) ENGINE = MEMORY;
38
+ ");
39
+
40
+ class HelloWorldJob {
41
+ public function __construct($name) {
42
+ $this->name = $name;
43
+ }
44
+ public function perform() {
45
+ echo "Hello {$this->name}!\n";
46
+ sleep(1);
47
+ }
48
+ }
49
+
50
+ class FailingJob {
51
+ public function perform() {
52
+ sleep(1);
53
+ throw new Exception("Uh oh");
54
+ }
55
+ }
56
+
57
+ $status = DJJob::status();
58
+
59
+ assert('$status["outstanding"] == 0', "Initial outstanding status is incorrect");
60
+ assert('$status["locked"] == 0', "Initial locked status is incorrect");
61
+ assert('$status["failed"] == 0', "Initial failed status is incorrect");
62
+ assert('$status["total"] == 0', "Initial total status is incorrect");
63
+
64
+ printf("=====================\nStarting run of DJJob\n=====================\n\n");
65
+
66
+ DJJob::enqueue(new HelloWorldJob("delayed_job"));
67
+ DJJob::bulkEnqueue(array(
68
+ new HelloWorldJob("shopify"),
69
+ new HelloWorldJob("github"),
70
+ ));
71
+ DJJob::enqueue(new FailingJob());
72
+ // Test unicode support using the classic, rails snowman: http://www.fileformat.info/info/unicode/char/2603/browsertest.htm
73
+ DJJob::enqueue(new HelloWorldJob(html_entity_decode("&#9731;", ENT_HTML5, "UTF-8")));
74
+
75
+ $worker = new DJWorker(array("count" => 5, "max_attempts" => 2, "sleep" => 10));
76
+ $worker->start();
77
+ printf("\n============\nRun complete\n============\n\n");
78
+
79
+ $status = DJJob::status();
80
+
81
+ assert('$status["outstanding"] == 0', "Final outstanding status is incorrect");
82
+ assert('$status["locked"] == 0', "Final locked status is incorrect");
83
+ assert('$status["failed"] == 1', "Final failed status is incorrect");
84
+ assert('$status["total"] == 1', "Final total status is incorrect");
lib/DJJob/test/original_database_configure.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ function assert_handler($file, $line, $code, $desc = null) {
4
+ printf("Assertion failed at %s:%s: %s: %s\n", $file, $line, $code, $desc);
5
+ }
6
+
7
+ assert_options(ASSERT_ACTIVE, 1);
8
+ assert_options(ASSERT_WARNING, 0);
9
+ assert_options(ASSERT_QUIET_EVAL, 1);
10
+ assert_options(ASSERT_CALLBACK, 'assert_handler');
11
+
12
+ date_default_timezone_set('America/New_York');
13
+
14
+ require dirname(__FILE__) . "/../DJJob.php";
15
+
16
+ DJJob::configure("mysql:host=127.0.0.1;dbname=djjob;", array(
17
+ "mysql_user" => "root",
18
+ "mysql_pass" => "root",
19
+ ));
20
+
21
+ DJJob::runQuery("
22
+ DROP TABLE IF EXISTS `jobs`;
23
+ CREATE TABLE `jobs` (
24
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
25
+ `handler` VARCHAR(255) NOT NULL,
26
+ `queue` VARCHAR(255) NOT NULL DEFAULT 'default',
27
+ `attempts` INT UNSIGNED NOT NULL DEFAULT 0,
28
+ `run_at` DATETIME NULL,
29
+ `locked_at` DATETIME NULL,
30
+ `locked_by` VARCHAR(255) NULL,
31
+ `failed_at` DATETIME NULL,
32
+ `error` VARCHAR(255) NULL,
33
+ `created_at` DATETIME NOT NULL
34
+ ) ENGINE = MEMORY;
35
+ ");
36
+
37
+ class HelloWorldJob {
38
+ public function __construct($name) {
39
+ $this->name = $name;
40
+ }
41
+ public function perform() {
42
+ echo "Hello {$this->name}!\n";
43
+ sleep(1);
44
+ }
45
+ }
46
+
47
+ class FailingJob {
48
+ public function perform() {
49
+ sleep(1);
50
+ throw new Exception("Uh oh");
51
+ }
52
+ }
53
+
54
+ $status = DJJob::status();
55
+
56
+ assert('$status["outstanding"] == 0', "Initial outstanding status is incorrect");
57
+ assert('$status["locked"] == 0', "Initial locked status is incorrect");
58
+ assert('$status["failed"] == 0', "Initial failed status is incorrect");
59
+ assert('$status["total"] == 0', "Initial total status is incorrect");
60
+
61
+ printf("=====================\nStarting run of DJJob\n=====================\n\n");
62
+
63
+ DJJob::enqueue(new HelloWorldJob("delayed_job"));
64
+ DJJob::bulkEnqueue(array(
65
+ new HelloWorldJob("shopify"),
66
+ new HelloWorldJob("github"),
67
+ ));
68
+ DJJob::enqueue(new FailingJob());
69
+
70
+ $worker = new DJWorker(array("count" => 5, "max_attempts" => 2, "sleep" => 10));
71
+ $worker->start();
72
+ printf("\n============\nRun complete\n============\n\n");
73
+
74
+ $status = DJJob::status();
75
+
76
+ assert('$status["outstanding"] == 0', "Final outstanding status is incorrect");
77
+ assert('$status["locked"] == 0', "Final locked status is incorrect");
78
+ assert('$status["failed"] == 1', "Final failed status is incorrect");
79
+ assert('$status["total"] == 1', "Final total status is incorrect");
package.xml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>Wisepricer_Syncer2</name>
4
+ <version>2.0.0.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://www.opensource.org/licenses/osl-3.0.php">OSL</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>WisePricer - Beat your competition</summary>
10
+ <description>WisePricer is a new tool that allows you to Track &amp; monitor successful online retailers and update your store prices in real-time. With WisePricer you'll never get left behind the competition.</description>
11
+ <notes>First stable release.</notes>
12
+ <authors><author><name>Pavel Zhytomirsky</name><user>r3volut1oner</user><email>r3volut1oner@gmail.com</email></author></authors>
13
+ <date>2015-09-02</date>
14
+ <time>10:13:46</time>
15
+ <contents><target name="magecommunity"><dir name="Wisepricer"><dir name="JobQueue"><dir name="Block"><dir name="Adminhtml"><dir name="Job"><file name="View.php" hash="aae12860e5c8c4b58f6d22372905ee7a"/></dir><dir name="Queue"><file name="Grid.php" hash="901021bad6652b9d4df6477fe893154a"/></dir><file name="Queue.php" hash="6f91a2d61d08d50fdf8c45c8857469be"/></dir></dir><dir name="Helper"><file name="Data.php" hash="055ae3239bd12f249a04ff46c27715d4"/></dir><dir name="Model"><dir name="Job"><file name="Abstract.php" hash="424f8974635068988567d637ab336d37"/></dir><file name="Job.php" hash="70aa35a24b5a972385187906cd01cf82"/><dir name="Resource"><dir name="Job"><file name="Collection.php" hash="69378351d91089d7c3e5f21c44c85ef2"/></dir><file name="Job.php" hash="9aa3bd32786cdf3d8a999d8c2fe1790e"/><file name="Setup.php" hash="c44ec3d6eed166951904d5ca7b654e40"/></dir><file name="Worker.php" hash="6b1e043cdc802851eecca990feb84f8f"/></dir><dir name="controllers"><dir name="Adminhtml"><file name="QueueController.php" hash="13e4a6e275dd92d8f6bfa4aef2860124"/></dir></dir><dir name="etc"><file name="config.xml" hash="cf1ed321c554546028181d31abf374e4"/></dir><dir name="sql"><dir name="wisepricer_jobqueue_setup"><file name="mysql4-install-1.0.0.0.php" hash="a6a1b8cd8254993835ddb6d047079dbf"/></dir></dir></dir><dir name="Syncer2"><file name="Exception.php" hash="02c7cdc73eaf99357a064cde11a92118"/><dir name="Helper"><file name="Data.php" hash="9a703e543cdd3e6e8b0647e61e86cc61"/></dir><dir name="Model"><file name="Abstract.php" hash="6bf02b02568f9693e26db9fc71c1fd00"/><file name="Exporter.php" hash="8dee6359099c961c238ae01cf4dbc29a"/><file name="Importer.php" hash="8819178eef966f3646c2c10015ffc95f"/></dir><dir name="controllers"><file name="ApiController.php" hash="b344fea43fc81815551e2ec172c6d98f"/></dir><dir name="etc"><file name="adminhtml.xml" hash="b40b1354d89057ce7ab80132b396cbf7"/><file name="config.xml" hash="8abb6452b90d7c7b3d3feef03d0feb48"/><file name="system.xml" hash="6e432cee618a82b158d4b2809cfa1f5f"/></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Wisepricer_Syncer2.xml" hash="42013aa3e5049f0ad3bd6932a4ad15be"/></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="images"><dir name="wisepricer_syncer2"><file name="wiser_logo.png" hash="324530e55dada5d853c811ab31457db6"/></dir></dir></dir></dir></dir></target><target name="magelib"><dir name="DJJob"><file name="DJJob.php" hash="b692e8d7a53a8a7096ea60d2cfd237f0"/><dir name="examples"><file name="HelloWorldJob.php" hash="3b7a9e4b1f912fb48acf5399f5fe33b9"/></dir><file name="jobs.sql" hash="d73a8213feedadf9dc9eb719fe33b935"/><dir name="test"><file name="custom_table_name.php" hash="3fbebd54f7814c9797107014c9cf0228"/><file name="database.php" hash="82e80af8b072a6276cd46dc4d09f937b"/><file name="original_database_configure.php" hash="e3acccdc25c6f876467c35729505626f"/></dir></dir></target></contents>
16
+ <compatible/>
17
+ <dependencies><required><php><min>5.3.0</min><max>6.0.0</max></php></required></dependencies>
18
+ </package>
skin/adminhtml/default/default/images/wisepricer_syncer2/wiser_logo.png ADDED
Binary file