Aoe_Scheduler - Version 0.3.2

Version Notes

GIT Revision: 8332e45e88bc9a391906ed0b93920c7ae628a983, Build Date: 2013-03-04 07:51:53

Download this release

Release Info

Developer Fabrizio Branca
Extension Aoe_Scheduler
Version 0.3.2
Comparing to
See all releases


Code changes from version 0.3.1 to 0.3.2

Files changed (24) hide show
  1. app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron.php +57 -0
  2. app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron/Grid.php +138 -0
  3. app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler.php +57 -0
  4. app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler/Grid.php +170 -0
  5. app/code/community/Aoe/Scheduler/Block/Adminhtml/Timeline.php +211 -0
  6. app/code/community/Aoe/Scheduler/Block/Adminhtml/TimelineDetail.php +44 -0
  7. app/code/community/Aoe/Scheduler/Helper/Data.php +161 -0
  8. app/code/community/Aoe/Scheduler/Model/Api.php +49 -0
  9. app/code/community/Aoe/Scheduler/Model/Collection/Crons.php +62 -0
  10. app/code/community/Aoe/Scheduler/Model/Configuration.php +129 -0
  11. app/code/community/Aoe/Scheduler/Model/HeartbeatTask.php +9 -0
  12. app/code/community/Aoe/Scheduler/Model/Observer.php +235 -0
  13. app/code/community/Aoe/Scheduler/Model/Schedule.php +161 -0
  14. app/code/community/Aoe/Scheduler/Model/TestTask.php +12 -0
  15. app/code/community/Aoe/Scheduler/controllers/Adminhtml/AbstractController.php +66 -0
  16. app/code/community/Aoe/Scheduler/controllers/Adminhtml/CronController.php +112 -0
  17. app/code/community/Aoe/Scheduler/controllers/Adminhtml/SchedulerController.php +28 -0
  18. app/code/community/Aoe/Scheduler/controllers/Adminhtml/TimelineController.php +12 -0
  19. app/code/community/Aoe/Scheduler/etc/adminhtml.xml +61 -0
  20. app/code/community/Aoe/Scheduler/etc/api.xml +42 -0
  21. app/code/community/Aoe/Scheduler/etc/config.xml +105 -0
  22. app/code/community/Aoe/Scheduler/etc/system.xml +67 -0
  23. app/locale/en_US/template/email/aoe_scheduler/cron_error.html +7 -0
  24. package.xml +5 -5
app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Cron block
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Cron extends Mage_Adminhtml_Block_Widget_Grid_Container {
9
+
10
+
11
+
12
+ /**
13
+ * Constructor for Cron Adminhtml Block
14
+ */
15
+ public function __construct() {
16
+ $this->_blockGroup = 'aoe_scheduler';
17
+ $this->_controller = 'adminhtml_cron';
18
+ $this->_headerText = Mage::helper('aoe_scheduler')->__('Available tasks');
19
+ parent::__construct();
20
+ }
21
+
22
+
23
+
24
+ /**
25
+ * Prepare layout
26
+ *
27
+ * @return Aoe_Scheduler_Block_Adminhtml_Cron
28
+ */
29
+ protected function _prepareLayout() {
30
+ $this->removeButton('add');
31
+ $this->_addButton('add_new', array(
32
+ 'label' => Mage::helper('aoe_scheduler')->__('Generate Schedule'),
33
+ 'onclick' => "setLocation('{$this->getUrl('*/*/generateSchedule')}')",
34
+ ));
35
+ $this->_addButton('configure', array(
36
+ 'label' => Mage::helper('aoe_scheduler')->__('Cron Configuration'),
37
+ 'onclick' => "setLocation('{$this->getUrl('adminhtml/system_config/edit', array('section' => 'system'))}#system_cron')",
38
+ ));
39
+ return parent::_prepareLayout();
40
+ }
41
+
42
+
43
+
44
+ /**
45
+ * Returns the CSS class for the header
46
+ *
47
+ * Usually 'icon-head' and a more precise class is returned. We return
48
+ * only an empty string to avoid spacing on the left of the header as we
49
+ * don't have an icon.
50
+ *
51
+ * @return string
52
+ */
53
+ public function getHeaderCssClass() {
54
+ return '';
55
+ }
56
+
57
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron/Grid.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Block: Cron grid
5
+ *
6
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Cron_Grid extends Mage_Adminhtml_Block_Widget_Grid {
9
+
10
+
11
+
12
+ /**
13
+ * Constructor
14
+ */
15
+ public function __construct() {
16
+ parent::__construct();
17
+ $this->setId('cron_grid');
18
+ $this->_filterVisibility = false;
19
+ $this->_pagerVisibility = false;
20
+ }
21
+
22
+
23
+
24
+ /**
25
+ * Preparation of the data that is displayed by the grid.
26
+ *
27
+ * @return Aoe_Scheduler_Block_Adminhtml_Cron_Grid Self
28
+ */
29
+ protected function _prepareCollection() {
30
+ $collection = Mage::getModel('aoe_scheduler/collection_crons');
31
+ $this->setCollection($collection);
32
+ return parent::_prepareCollection();
33
+ }
34
+
35
+
36
+
37
+ /**
38
+ * Add mass-actions to grid
39
+ *
40
+ * @return Aoe_Scheduler_Block_Adminhtml_Cron_Grid
41
+ */
42
+ protected function _prepareMassaction() {
43
+ $this->setMassactionIdField('id');
44
+ $this->getMassactionBlock()->setFormFieldName('codes');
45
+ $this->getMassactionBlock()->addItem('schedule', array(
46
+ 'label' => Mage::helper('aoe_scheduler')->__('Schedule now'),
47
+ 'url' => $this->getUrl('*/*/scheduleNow'),
48
+ ));
49
+ $this->getMassactionBlock()->addItem('run', array(
50
+ 'label' => Mage::helper('aoe_scheduler')->__('Run now'),
51
+ 'url' => $this->getUrl('*/*/runNow'),
52
+ ));
53
+ $this->getMassactionBlock()->addItem('disable', array(
54
+ 'label' => Mage::helper('aoe_scheduler')->__('Disable'),
55
+ 'url' => $this->getUrl('*/*/disable'),
56
+ ));
57
+ $this->getMassactionBlock()->addItem('enable', array(
58
+ 'label' => Mage::helper('aoe_scheduler')->__('Enable'),
59
+ 'url' => $this->getUrl('*/*/enable'),
60
+ ));
61
+ return $this;
62
+ }
63
+
64
+
65
+
66
+ /**
67
+ * Preparation of the requested columns of the grid
68
+ *
69
+ * @return Aoe_Scheduler_Block_Adminhtml_Cron_Grid Self
70
+ */
71
+ protected function _prepareColumns() {
72
+ $this->addColumn('id', array (
73
+ 'header' => Mage::helper('aoe_scheduler')->__('Code'),
74
+ 'index' => 'id',
75
+ 'sortable' => false,
76
+ ));
77
+ $this->addColumn('cron_expr', array (
78
+ 'header' => Mage::helper('aoe_scheduler')->__('Cron Expression'),
79
+ 'index' => 'cron_expr',
80
+ 'sortable' => false,
81
+ ));
82
+ $this->addColumn('model', array (
83
+ 'header' => Mage::helper('aoe_scheduler')->__('Model'),
84
+ 'index' => 'model',
85
+ 'sortable' => false,
86
+ ));
87
+ $this->addColumn('status', array (
88
+ 'header' => Mage::helper('aoe_scheduler')->__('Status'),
89
+ 'index' => 'status',
90
+ 'sortable' => false,
91
+ 'frame_callback' => array($this, 'decorateStatus'),
92
+ ));
93
+ return parent::_prepareColumns();
94
+ }
95
+
96
+
97
+
98
+ /**
99
+ * Decorate status column values
100
+ *
101
+ * @return string
102
+ */
103
+ public function decorateStatus($value) {
104
+ $cell = sprintf('<span class="grid-severity-%s"><span>%s</span></span>',
105
+ ($value == Aoe_Scheduler_Model_Configuration::STATUS_DISABLED) ? 'critical' : 'notice',
106
+ Mage::helper('aoe_scheduler')->__($value)
107
+ );
108
+ return $cell;
109
+ }
110
+
111
+
112
+
113
+ /**
114
+ * Helper function to add store filter condition
115
+ *
116
+ * @param Mage_Core_Model_Mysql4_Collection_Abstract $collection Data collection
117
+ * @param Mage_Adminhtml_Block_Widget_Grid_Column $column Column information to be filtered
118
+ * @return void
119
+ */
120
+ protected function _filterStoreCondition($collection, $column) {
121
+ if (!$value = $column->getFilter()->getValue()) {
122
+ return;
123
+ }
124
+ $this->getCollection()->addStoreFilter($value);
125
+ }
126
+
127
+
128
+
129
+ /**
130
+ * Helper function to receive grid functionality urls for current grid
131
+ *
132
+ * @return string Requested URL
133
+ */
134
+ public function getGridUrl() {
135
+ return $this->getUrl('adminhtml/scheduler/cron', array('_current' => true));
136
+ }
137
+
138
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Scheduler Block
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Scheduler extends Mage_Adminhtml_Block_Widget_Grid_Container {
9
+
10
+
11
+
12
+ /**
13
+ * Constructor for Scheduler Adminhtml Block
14
+ */
15
+ public function __construct() {
16
+ $this->_blockGroup = 'aoe_scheduler';
17
+ $this->_controller = 'adminhtml_scheduler';
18
+ $this->_headerText = Mage::helper('aoe_scheduler')->__('Scheduled tasks');
19
+ parent::__construct();
20
+ }
21
+
22
+
23
+
24
+ /**
25
+ * Prepare layout
26
+ *
27
+ * @return Aoe_Scheduler_Block_Adminhtml_Cron
28
+ */
29
+ protected function _prepareLayout() {
30
+ $this->removeButton('add');
31
+ $this->_addButton('add_new', array(
32
+ 'label' => Mage::helper('aoe_scheduler')->__('Generate Schedule'),
33
+ 'onclick' => "setLocation('{$this->getUrl('*/*/generateSchedule')}')",
34
+ ));
35
+ $this->_addButton('configure', array(
36
+ 'label' => Mage::helper('aoe_scheduler')->__('Cron Configuration'),
37
+ 'onclick' => "setLocation('{$this->getUrl('adminhtml/system_config/edit', array('section' => 'system'))}#system_cron')",
38
+ ));
39
+ return parent::_prepareLayout();
40
+ }
41
+
42
+
43
+
44
+ /**
45
+ * Returns the CSS class for the header
46
+ *
47
+ * Usually 'icon-head' and a more precise class is returned. We return
48
+ * only an empty string to avoid spacing on the left of the header as we
49
+ * don't have an icon.
50
+ *
51
+ * @return string
52
+ */
53
+ public function getHeaderCssClass() {
54
+ return '';
55
+ }
56
+
57
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler/Grid.php ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Block: Scheduler Grid
5
+ *
6
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Scheduler_Grid extends Mage_Adminhtml_Block_Widget_Grid {
9
+
10
+
11
+
12
+ /**
13
+ * Constructor. Set basic parameters
14
+ */
15
+ public function __construct() {
16
+ parent::__construct();
17
+ $this->setId('scheduler_grid');
18
+ $this->setUseAjax(false);
19
+ $this->setDefaultSort('scheduled_at');
20
+ $this->setDefaultDir('DESC');
21
+ $this->setSaveParametersInSession(true);
22
+ }
23
+
24
+
25
+
26
+ /**
27
+ * Preparation of the data that is displayed by the grid.
28
+ *
29
+ * @return Aoe_SourceContact_Block_Admin_Grid Self
30
+ */
31
+ protected function _prepareCollection() {
32
+ $collection = Mage::getModel('cron/schedule')->getCollection();
33
+ $this->setCollection($collection);
34
+ return parent::_prepareCollection();
35
+ }
36
+
37
+
38
+
39
+ /**
40
+ * Add mass-actions to grid
41
+ *
42
+ * @return Aoe_Scheduler_Block_Adminhtml_Cron_Grid
43
+ */
44
+ protected function _prepareMassaction() {
45
+ $this->setMassactionIdField('schedule_id');
46
+ $this->getMassactionBlock()->setFormFieldName('schedule_ids');
47
+ $this->getMassactionBlock()->addItem('delete', array(
48
+ 'label' => Mage::helper('aoe_scheduler')->__('Delete'),
49
+ 'url' => $this->getUrl('*/*/delete'),
50
+ ));
51
+ return $this;
52
+ }
53
+
54
+
55
+
56
+ /**
57
+ * Preparation of the requested columns of the grid
58
+ *
59
+ * @return Aoe_SourceContact_Block_Admin_Grid Self
60
+ */
61
+ protected function _prepareColumns() {
62
+
63
+ $viewHelper = $this->helper('aoe_scheduler/data');
64
+
65
+ $this->addColumn('job_code', array (
66
+ 'header' => Mage::helper('aoe_scheduler')->__('Code'),
67
+ 'index' => 'job_code',
68
+ 'type' => 'options',
69
+ 'options' => Mage::getModel('aoe_scheduler/collection_crons')->toOptionHash()
70
+ ));
71
+ $this->addColumn('created_at', array (
72
+ 'header' => Mage::helper('aoe_scheduler')->__('Created'),
73
+ 'index' => 'created_at',
74
+ 'frame_callback' => array($viewHelper, 'decorateTimeFrameCallBack')
75
+ ));
76
+ $this->addColumn('scheduled_at', array (
77
+ 'header' => Mage::helper('aoe_scheduler')->__('Scheduled'),
78
+ 'index' => 'scheduled_at',
79
+ 'frame_callback' => array($viewHelper, 'decorateTimeFrameCallBack')
80
+ ));
81
+ $this->addColumn('executed_at', array (
82
+ 'header' => Mage::helper('aoe_scheduler')->__('Executed'),
83
+ 'index' => 'executed_at',
84
+ 'frame_callback' => array($viewHelper, 'decorateTimeFrameCallBack')
85
+ ));
86
+ $this->addColumn('finished_at', array (
87
+ 'header' => Mage::helper('aoe_scheduler')->__('Finished'),
88
+ 'index' => 'finished_at',
89
+ 'frame_callback' => array($viewHelper, 'decorateTimeFrameCallBack')
90
+ ));
91
+ $this->addColumn('messages', array (
92
+ 'header' => Mage::helper('aoe_scheduler')->__('Messages'),
93
+ 'index' => 'messages',
94
+ 'frame_callback' => array($this, 'decorateMessages')
95
+ ));
96
+ $this->addColumn('status', array (
97
+ 'header' => Mage::helper('aoe_scheduler')->__('Status'),
98
+ 'index' => 'status',
99
+ 'frame_callback' => array($viewHelper, 'decorateStatus'),
100
+ 'type' => 'options',
101
+ 'options' => array(
102
+ Mage_Cron_Model_Schedule::STATUS_PENDING => Mage_Cron_Model_Schedule::STATUS_PENDING,
103
+ Mage_Cron_Model_Schedule::STATUS_SUCCESS => Mage_Cron_Model_Schedule::STATUS_SUCCESS,
104
+ Mage_Cron_Model_Schedule::STATUS_ERROR => Mage_Cron_Model_Schedule::STATUS_ERROR,
105
+ Mage_Cron_Model_Schedule::STATUS_MISSED => Mage_Cron_Model_Schedule::STATUS_MISSED,
106
+ Mage_Cron_Model_Schedule::STATUS_RUNNING => Mage_Cron_Model_Schedule::STATUS_RUNNING,
107
+ )
108
+ ));
109
+
110
+ return parent::_prepareColumns();
111
+ }
112
+
113
+
114
+
115
+ /**
116
+ * Decorate message
117
+ *
118
+ * @param string $value
119
+ * @param Aoe_Scheduler_Model_Schedule $row
120
+ * @return string
121
+ */
122
+ public function decorateMessages($value, Aoe_Scheduler_Model_Schedule $row) {
123
+ $return = '';
124
+ if (!empty($value)) {
125
+ $return .= '<a href="#" onclick="$(\'messages_'.$row->getScheduleId().'\').toggle(); return false;">'.Mage::helper('aoe_scheduler')->__('Message').'</a>';
126
+ $return .= '<div class="schedule-message" id="messages_'.$row->getScheduleId().'" style="display: none; width: 300px; overflow: auto; font-size: small;"><pre>'.$value.'</pre></div>';
127
+ }
128
+ return $return;
129
+ }
130
+
131
+
132
+
133
+ /**
134
+ * Helper function to do after load modifications
135
+ *
136
+ * @return void
137
+ */
138
+ protected function _afterLoadCollection() {
139
+ $this->getCollection()->walk('afterLoad');
140
+ parent::_afterLoadCollection();
141
+ }
142
+
143
+
144
+
145
+ /**
146
+ * Helper function to add store filter condition
147
+ *
148
+ * @param Mage_Core_Model_Mysql4_Collection_Abstract $collection Data collection
149
+ * @param Mage_Adminhtml_Block_Widget_Grid_Column $column Column information to be filtered
150
+ * @return void
151
+ */
152
+ protected function _filterStoreCondition($collection, $column) {
153
+ if (!$value = $column->getFilter()->getValue()) {
154
+ return;
155
+ }
156
+ $this->getCollection()->addStoreFilter($value);
157
+ }
158
+
159
+
160
+
161
+ /**
162
+ * Helper function to receive grid functionality urls for current grid
163
+ *
164
+ * @return string Requested URL
165
+ */
166
+ public function getGridUrl() {
167
+ return $this->getUrl('adminhtml/scheduler/index', array('_current' => true));
168
+ }
169
+
170
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Timeline.php ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Timeline block
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Timeline extends Mage_Adminhtml_Block_Widget_Container {
9
+
10
+ /**
11
+ * @var int amount of seconds per pixel
12
+ */
13
+ protected $zoom = 15;
14
+
15
+ /**
16
+ * @var int starttime
17
+ */
18
+ protected $starttime;
19
+
20
+ /**
21
+ * @var int endtime
22
+ */
23
+ protected $endtime;
24
+
25
+ /**
26
+ * @var array schedules
27
+ */
28
+ protected $schedules = array();
29
+
30
+
31
+
32
+ /**
33
+ * Constructor
34
+ *
35
+ * @return void
36
+ */
37
+ protected function _construct() {
38
+ $this->_headerText = Mage::helper('aoe_scheduler')->__('Scheduler Timeline');
39
+ $this->loadSchedules();
40
+ parent::_construct();
41
+ }
42
+
43
+
44
+
45
+ /**
46
+ * Prepare layout
47
+ *
48
+ * @return Aoe_Scheduler_Block_Adminhtml_Cron
49
+ */
50
+ protected function _prepareLayout() {
51
+ $this->removeButton('add');
52
+ $this->_addButton('add_new', array(
53
+ 'label' => Mage::helper('aoe_scheduler')->__('Generate Schedule'),
54
+ 'onclick' => "setLocation('{$this->getUrl('*/*/generateSchedule')}')",
55
+ ));
56
+ $this->_addButton('configure', array(
57
+ 'label' => Mage::helper('aoe_scheduler')->__('Cron Configuration'),
58
+ 'onclick' => "setLocation('{$this->getUrl('adminhtml/system_config/edit', array('section' => 'system'))}#system_cron')",
59
+ ));
60
+ return parent::_prepareLayout();
61
+ }
62
+
63
+
64
+
65
+ /**
66
+ * Return the last full houd
67
+ *
68
+ * @param int $timestamp
69
+ * @return int
70
+ */
71
+ protected function hourFloor($timestamp) {
72
+ return mktime(date('H', $timestamp), 0, 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
73
+ }
74
+
75
+
76
+
77
+ /**
78
+ * Returns the next full hour
79
+ *
80
+ * @param int $timestamp
81
+ * @return int
82
+ */
83
+ protected function hourCeil($timestamp) {
84
+ return mktime(date('H', $timestamp)+1, 0, 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
85
+ }
86
+
87
+
88
+
89
+ /**
90
+ * Load schedules
91
+ *
92
+ * @return void
93
+ */
94
+ protected function loadSchedules() {
95
+ $collection = Mage::getModel('cron/schedule')->getCollection(); /* @var $collection Mage_Cron_Model_Mysql4_Schedule_Collection */
96
+
97
+ $minDate = null; $maxDate = null;
98
+
99
+ foreach ($collection as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
100
+ $startTime = $schedule->getStarttime();
101
+ $minDate = is_null($minDate) ? $startTime : min($minDate, $startTime);
102
+ $maxDate = is_null($maxDate) ? $startTime : max($maxDate, $startTime);
103
+ $this->schedules[$schedule->getJobCode()][] = $schedule;
104
+ }
105
+
106
+ $this->starttime = $this->hourFloor(strtotime($minDate));
107
+ $this->endtime = $this->hourCeil(strtotime($maxDate));
108
+ }
109
+
110
+
111
+
112
+ /**
113
+ * Get timeline panel width
114
+ *
115
+ * @return int
116
+ */
117
+ public function getTimelinePanelWidth() {
118
+ return ($this->endtime - $this->starttime) / $this->zoom;
119
+ }
120
+
121
+
122
+
123
+ /**
124
+ * Get "now" line
125
+ *
126
+ * @return float
127
+ */
128
+ public function getNowline() {
129
+ return (time() - $this->starttime) / $this->zoom;
130
+ }
131
+
132
+
133
+
134
+ /**
135
+ * Get all available job codes
136
+ *
137
+ * @return array
138
+ */
139
+ public function getAvailableJobCodes() {
140
+ return array_keys($this->schedules);
141
+ }
142
+
143
+
144
+
145
+ /**
146
+ * Get schedules for given code
147
+ *
148
+ * @param string $code
149
+ * @return array
150
+ */
151
+ public function getSchedulesForCode($code) {
152
+ return $this->schedules[$code];
153
+ }
154
+
155
+
156
+
157
+ /**
158
+ * Get starttime
159
+ *
160
+ * @return int
161
+ */
162
+ public function getStarttime() {
163
+ return $this->starttime;
164
+ }
165
+
166
+
167
+
168
+ /**
169
+ * Get endtime
170
+ *
171
+ * @return int
172
+ */
173
+ public function getEndtime() {
174
+ return $this->endtime;
175
+ }
176
+
177
+
178
+
179
+ /**
180
+ * Get attributes for div representing a gantt element
181
+ *
182
+ * @param Aoe_Scheduler_Model_Schedule $schedule
183
+ * @return string
184
+ */
185
+ public function getGanttDivAttributes(Aoe_Scheduler_Model_Schedule $schedule) {
186
+
187
+ if ($schedule->getStatus() == Mage_Cron_Model_Schedule::STATUS_RUNNING) {
188
+ $duration = time() - strtotime($schedule->getExecutedAt());
189
+ } else {
190
+ $duration = $schedule->getDuration() ? $schedule->getDuration() : 0;
191
+ }
192
+ $duration = $duration / $this->zoom;
193
+ $duration = ceil($duration / 4) * 4 - 1; // round to numbers dividable by 4, then remove 1 px border
194
+ $duration = max($duration, 3);
195
+
196
+ $offset = (strtotime($schedule->getStarttime()) - $this->starttime) / $this->zoom;
197
+
198
+ if ($offset < 0) { // cut bar
199
+ $duration += $offset;
200
+ $offset = 0;
201
+ }
202
+
203
+ return sprintf('class="task %s" id="id_%s" style="width: %spx; left: %spx;"',
204
+ $schedule->getStatus(),
205
+ $schedule->getScheduleId(),
206
+ $duration,
207
+ $offset
208
+ );
209
+ }
210
+
211
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/TimelineDetail.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * TimelineDetail block
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_TimelineDetail extends Mage_Adminhtml_Block_Template {
9
+
10
+ /**
11
+ * @var string path to default template
12
+ */
13
+ protected $_template = 'aoe_scheduler/timeline_detail.phtml';
14
+
15
+ /**
16
+ * @var Aoe_Scheduler_Model_Schedule
17
+ */
18
+ protected $schedule;
19
+
20
+
21
+
22
+ /**
23
+ * Set schedule
24
+ *
25
+ * @param Aoe_Scheduler_Model_Schedule $schedule
26
+ * @return Aoe_Scheduler_Block_Adminhtml_TimelineDetail
27
+ */
28
+ public function setSchedule(Aoe_Scheduler_Model_Schedule $schedule) {
29
+ $this->schedule = $schedule;
30
+ return $this;
31
+ }
32
+
33
+
34
+
35
+ /**
36
+ * Get schedule
37
+ *
38
+ * @return Aoe_Scheduler_Block_Adminhtml_TimelineDetail
39
+ */
40
+ public function getSchedule() {
41
+ return $this->schedule;
42
+ }
43
+
44
+ }
app/code/community/Aoe/Scheduler/Helper/Data.php ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Helper
5
+ *
6
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
7
+ */
8
+ class Aoe_Scheduler_Helper_Data extends Mage_Core_Helper_Abstract {
9
+
10
+
11
+ /**
12
+ * Explodes a string and trims all values for whitespace in the ends.
13
+ * If $onlyNonEmptyValues is set, then all blank ('') values are removed.
14
+ *
15
+ * @see t3lib_div::trimExplode() in TYPO3
16
+ * @param $delim
17
+ * @param string $string
18
+ * @param bool $removeEmptyValues If set, all empty values will be removed in output
19
+ * @return array Exploded values
20
+ */
21
+ public function trimExplode($delim, $string, $removeEmptyValues=false) {
22
+ $explodedValues = explode($delim, $string);
23
+
24
+ $result = array_map('trim', $explodedValues);
25
+
26
+ if ($removeEmptyValues) {
27
+ $temp = array();
28
+ foreach ($result as $value) {
29
+ if ($value !== '') {
30
+ $temp[] = $value;
31
+ }
32
+ }
33
+ $result = $temp;
34
+ }
35
+
36
+ return $result;
37
+ }
38
+
39
+
40
+
41
+ /**
42
+ * Decorate status values
43
+ *
44
+ * @param $status
45
+ * @return string
46
+ */
47
+ public function decorateStatus($status) {
48
+ switch ($status) {
49
+ case Mage_Cron_Model_Schedule::STATUS_SUCCESS:
50
+ $result = '<span class="bar-green"><span>'.$status.'</span></span>';
51
+ break;
52
+ case Mage_Cron_Model_Schedule::STATUS_PENDING:
53
+ $result = '<span class="bar-lightgray"><span>'.$status.'</span></span>';
54
+ break;
55
+ case Mage_Cron_Model_Schedule::STATUS_RUNNING:
56
+ $result = '<span class="bar-yellow"><span>'.$status.'</span></span>';
57
+ break;
58
+ case Mage_Cron_Model_Schedule::STATUS_MISSED:
59
+ $result = '<span class="bar-orange"><span>'.$status.'</span></span>';
60
+ break;
61
+ case Mage_Cron_Model_Schedule::STATUS_ERROR:
62
+ $result = '<span class="bar-red"><span>'.$status.'</span></span>';
63
+ break;
64
+ default:
65
+ $result = $status;
66
+ break;
67
+ }
68
+ return $result;
69
+ }
70
+
71
+
72
+
73
+ /**
74
+ * Wrapepr for decorateTime to be used a frame_callback to avoid that additional parameters
75
+ * conflict with the method's optional ones
76
+ *
77
+ * @param string $value
78
+ * @return string
79
+ */
80
+ public function decorateTimeFrameCallBack($value) {
81
+ return $this->decorateTime($value, false, NULL);
82
+ }
83
+
84
+
85
+
86
+ /**
87
+ * Decorate time values
88
+ *
89
+ * @param string $value
90
+ * @param bool $echoToday if true "Today" will be added
91
+ * @param string $dateFormat make sure Y-m-d is in it, if you want to have it replaced
92
+ * @return string
93
+ */
94
+ public function decorateTime($value, $echoToday=false, $dateFormat=NULL) {
95
+ if (empty($value) || $value == '0000-00-00 00:00:00') {
96
+ $value = '';
97
+ } else {
98
+ $value = Mage::getModel('core/date')->date($dateFormat, $value);
99
+ $replace = array(
100
+ Mage::getModel('core/date')->date('Y-m-d ', time()) => $echoToday ? Mage::helper('aoe_scheduler')->__('Today') . ', ' : '', // today
101
+ Mage::getModel('core/date')->date('Y-m-d ', strtotime('+1 day')) => Mage::helper('aoe_scheduler')->__('Tomorrow') . ', ',
102
+ Mage::getModel('core/date')->date('Y-m-d ', strtotime('-1 day')) => Mage::helper('aoe_scheduler')->__('Yesterday') . ', ',
103
+ );
104
+ $value = str_replace(array_keys($replace), array_values($replace), $value);
105
+ }
106
+ return $value;
107
+ }
108
+
109
+
110
+ /**
111
+ * Get last heartbeat
112
+ */
113
+ public function getLastHeartbeat() {
114
+ if ($this->isDisabled('aoescheduler_heartbeat')) {
115
+ return false;
116
+ }
117
+ $schedules = Mage::getModel('cron/schedule')->getCollection(); /* @var $schedules Mage_Cron_Model_Mysql4_Schedule_Collection */
118
+ $schedules->getSelect()->limit(1)->order('executed_at DESC');
119
+ $schedules->addFieldToFilter('status', Mage_Cron_Model_Schedule::STATUS_SUCCESS);
120
+ $schedules->addFieldToFilter('job_code', 'aoescheduler_heartbeat');
121
+ $schedules->load();
122
+ if (count($schedules) == 0) {
123
+ return false;
124
+ }
125
+ $executedAt = $schedules->getFirstItem()->getExecutedAt();
126
+ $value = Mage::getModel('core/date')->date(NULL, $executedAt);
127
+ return $value;
128
+ }
129
+
130
+
131
+ /**
132
+ * Diff between to times;
133
+ *
134
+ * @param $time1
135
+ * @param $time2
136
+ * @return int
137
+ */
138
+ public function dateDiff($time1, $time2=NULL) {
139
+ if (is_null($time2)) {
140
+ $time2 = Mage::getModel('core/date')->date();
141
+ }
142
+ $time1 = strtotime($time1);
143
+ $time2 = strtotime($time2);
144
+ return $time2 - $time1;
145
+ }
146
+
147
+
148
+
149
+ /**
150
+ * Check if job code is disabled in configuration
151
+ *
152
+ * @param $jobCode
153
+ * @return bool
154
+ */
155
+ public function isDisabled($jobCode) {
156
+ $disabledJobs = Mage::getStoreConfig('system/cron/disabled_crons');
157
+ $disabledJobs = $this->trimExplode(',', $disabledJobs);
158
+ return in_array($jobCode, $disabledJobs);
159
+ }
160
+
161
+ }
app/code/community/Aoe/Scheduler/Model/Api.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Scheduler API
4
+ *
5
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
6
+ */
7
+ class Aoe_Scheduler_Model_Api extends Mage_Api_Model_Resource_Abstract {
8
+
9
+ /**
10
+ * Run task
11
+ *
12
+ * @param $code
13
+ * @return array
14
+ */
15
+ public function runNow($code) {
16
+ $schedule = Mage::getModel('cron/schedule') /* @var $schedule Aoe_Scheduler_Model_Schedule */
17
+ ->setJobCode($code)
18
+ ->runNow(false) // without trying to lock the job
19
+ ->save();
20
+ return $schedule->getData();
21
+ }
22
+
23
+ /**
24
+ * Schedule task
25
+ *
26
+ * @param $code
27
+ * @param null $time
28
+ * @return array
29
+ */
30
+ public function schedule($code, $time=NULL) {
31
+ $schedule = Mage::getModel('cron/schedule') /* @var $schedule Aoe_Scheduler_Model_Schedule */
32
+ ->setJobCode($code)
33
+ ->schedule($time)
34
+ ->save();
35
+ return $schedule->getData();
36
+ }
37
+
38
+ /**
39
+ * Get info
40
+ *
41
+ * @param $id
42
+ * @return string
43
+ */
44
+ public function info($id) {
45
+ $schedule = Mage::getModel('cron/schedule')->load($id); /* @var $schedule Aoe_Scheduler_Model_Schedule */
46
+ return $schedule->getData();
47
+ }
48
+
49
+ }
app/code/community/Aoe/Scheduler/Model/Collection/Crons.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Collection of available tasks (crons)
5
+ *
6
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
7
+ */
8
+ class Aoe_Scheduler_Model_Collection_Crons extends Varien_Data_Collection {
9
+
10
+ protected $_dataLoaded = false;
11
+
12
+ /**
13
+ * Load data
14
+ *
15
+ * @param bool $printQuery
16
+ * @param bool $logQuery
17
+ * @return Aoe_Scheduler_Model_Collection_Crons
18
+ */
19
+ public function loadData($printQuery = false, $logQuery = false) {
20
+ if ($this->_dataLoaded) {
21
+ return $this;
22
+ }
23
+
24
+ foreach ($this->getAllCodes() as $code) {
25
+ $configuration = Mage::getModel('aoe_scheduler/configuration')->loadByCode($code);
26
+ $this->addItem($configuration);
27
+ }
28
+
29
+ $this->_dataLoaded = true;
30
+ return $this;
31
+ }
32
+
33
+
34
+
35
+ /**
36
+ * Get all available codes
37
+ *
38
+ * @return array
39
+ */
40
+ protected function getAllCodes() {
41
+ $codes = array();
42
+ $config = Mage::getConfig()->getNode('crontab/jobs'); /* @var $config Mage_Core_Model_Config_Element */
43
+ if ($config instanceof Mage_Core_Model_Config_Element) {
44
+ foreach ($config->children() as $key => $tmp) {
45
+ if (!in_array($key, $codes)) {
46
+ $codes[] = $key;
47
+ }
48
+ }
49
+ }
50
+ $config = Mage::getConfig()->getNode('default/crontab/jobs'); /* @var $config Mage_Core_Model_Config_Element */
51
+ if ($config instanceof Mage_Core_Model_Config_Element) {
52
+ foreach ($config->children() as $key => $tmp) {
53
+ if (!in_array($key, $codes)) {
54
+ $codes[] = $key;
55
+ }
56
+ }
57
+ }
58
+ sort($codes);
59
+ return $codes;
60
+ }
61
+
62
+ }
app/code/community/Aoe/Scheduler/Model/Configuration.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ *
5
+ * Enter description here ...
6
+ *
7
+ * @author Fabrizio
8
+ *
9
+ * @method string getModel()
10
+ * @method string getStatus()
11
+ * @method string getCronExpr()
12
+ */
13
+ class Aoe_Scheduler_Model_Configuration extends Mage_Core_Model_Abstract {
14
+
15
+ const STATUS_DISABLED = 'disabled';
16
+ const STATUS_ENABLED = 'enabled';
17
+
18
+
19
+
20
+ /**
21
+ * Override method.
22
+ *
23
+ * @return false
24
+ */
25
+ protected function _getResource() {
26
+ return false;
27
+ }
28
+
29
+
30
+
31
+ /**
32
+ * Get id field name
33
+ *
34
+ * @return string
35
+ */
36
+ public function getIdFieldName() {
37
+ return 'id';
38
+ }
39
+
40
+
41
+
42
+ /**
43
+ * Load configuration object by code
44
+ *
45
+ * @param string $code
46
+ */
47
+ public function loadByCode($code) {
48
+ $this->setId($code);
49
+ $this->setName($code);
50
+
51
+ $global = $this->getGlobalCrontabJobXmlConfig();
52
+ $cronExpr = null;
53
+ if ($global && $global->schedule && $global->schedule->config_path) {
54
+ $cronExpr = Mage::getStoreConfig((string)$global->schedule->config_path);
55
+ }
56
+ if (empty($cronExpr) && $global && $global->schedule && $global->schedule->cron_expr) {
57
+ $cronExpr = (string)$global->schedule->cron_expr;
58
+ }
59
+ if ($cronExpr) {
60
+ $this->setCronExpr($cronExpr);
61
+ }
62
+ if ($global && $global->run && $global->run->model) {
63
+ $this->setModel((string)$global->run->model);
64
+ }
65
+
66
+ $configurable = $this->getConfigurableCrontabJobXmlConfig();
67
+ if ($configurable) {
68
+ if (is_object($configurable->schedule)) {
69
+ if ($configurable && $configurable->schedule && $configurable->schedule->cron_expr) {
70
+ $this->setCronExpr((string)$configurable->schedule->cron_expr);
71
+ }
72
+ }
73
+ if (is_object($configurable->run)) {
74
+ if ($configurable && $configurable->run && $configurable->run->model) {
75
+ $this->setModel((string)$configurable->run->model);
76
+ }
77
+ }
78
+ }
79
+
80
+ if (!$this->getModel()) {
81
+ Mage::throwException(sprintf('No configuration found for code "%s"', $code));
82
+ }
83
+
84
+ $disabledCrons = Mage::helper('aoe_scheduler')->trimExplode(',', Mage::getStoreConfig('system/cron/disabled_crons'), true);
85
+ $this->setStatus(in_array($this->getId(), $disabledCrons) ? self::STATUS_DISABLED : self::STATUS_ENABLED);
86
+
87
+ return $this;
88
+ }
89
+
90
+
91
+
92
+ /**
93
+ * Get global crontab job xml configuration
94
+ *
95
+ * @return Mage_Core_Model_Config_Element|false
96
+ */
97
+ protected function getGlobalCrontabJobXmlConfig() {
98
+ return $this->getJobXmlConfig('crontab/jobs');
99
+ }
100
+
101
+
102
+
103
+ /**
104
+ * Get configurable crontab job xml configuration
105
+ *
106
+ * @return Mage_Core_Model_Config_Element|false
107
+ */
108
+ protected function getConfigurableCrontabJobXmlConfig() {
109
+ return $this->getJobXmlConfig('default/crontab/jobs');
110
+ }
111
+
112
+
113
+
114
+ /**
115
+ * Get job xml configuration
116
+ *
117
+ * @param string $path path to configuration
118
+ * @return Mage_Core_Model_Config_Element|false
119
+ */
120
+ protected function getJobXmlConfig($path) {
121
+ $xmlConfig = false;
122
+ $config = Mage::getConfig()->getNode($path);
123
+ if ($config instanceof Mage_Core_Model_Config_Element) {
124
+ $xmlConfig = $config->{$this->getId()};
125
+ }
126
+ return $xmlConfig;
127
+ }
128
+
129
+ }
app/code/community/Aoe/Scheduler/Model/HeartbeatTask.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Model_HeartbeatTask {
4
+
5
+ public function run() {
6
+ return true;
7
+ }
8
+
9
+ }
app/code/community/Aoe/Scheduler/Model/Observer.php ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Crontab observer.
5
+ *
6
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
7
+ */
8
+ class Aoe_Scheduler_Model_Observer extends Mage_Cron_Model_Observer {
9
+
10
+ const XML_PATH_MAX_RUNNING_TIME = 'system/cron/max_running_time';
11
+ const XML_PATH_EMAIL_TEMPLATE = 'system/cron/error_email_template';
12
+ const XML_PATH_EMAIL_IDENTITY = 'system/cron/error_email_identity';
13
+ const XML_PATH_EMAIL_RECIPIENT = 'system/cron/error_email';
14
+
15
+ /**
16
+ * Process cron queue
17
+ * Generate tasks schedule
18
+ * Cleanup tasks schedule
19
+ *
20
+ * @param Varien_Event_Observer $observer
21
+ */
22
+ public function dispatch($observer)
23
+ {
24
+ $schedules = $this->getPendingSchedules();
25
+ $scheduleLifetime = Mage::getStoreConfig(self::XML_PATH_SCHEDULE_LIFETIME) * 60;
26
+ $now = time();
27
+ $jobsRoot = Mage::getConfig()->getNode('crontab/jobs');
28
+
29
+ foreach ($schedules->getIterator() as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
30
+ try {
31
+ $errorStatus = Mage_Cron_Model_Schedule::STATUS_ERROR;
32
+ $errorMessage = Mage::helper('cron')->__('Unknown error.');
33
+
34
+ $jobConfig = $jobsRoot->{$schedule->getJobCode()};
35
+ if (!$jobConfig || !$jobConfig->run) {
36
+ Mage::throwException(Mage::helper('cron')->__('No valid configuration found.'));
37
+ }
38
+
39
+ $runConfig = $jobConfig->run;
40
+ $time = strtotime($schedule->getScheduledAt());
41
+ if ($time > $now) {
42
+ continue;
43
+ }
44
+
45
+ if ($time < $now - $scheduleLifetime) {
46
+ $errorStatus = Mage_Cron_Model_Schedule::STATUS_MISSED;
47
+ Mage::throwException(Mage::helper('cron')->__('Too late for the schedule.'));
48
+ }
49
+
50
+ if ($runConfig->model) {
51
+ if (!preg_match(self::REGEX_RUN_MODEL, (string)$runConfig->model, $run)) {
52
+ Mage::throwException(Mage::helper('cron')->__('Invalid model/method definition, expecting "model/class::method".'));
53
+ }
54
+ if (!($model = Mage::getModel($run[1])) || !method_exists($model, $run[2])) {
55
+ Mage::throwException(Mage::helper('cron')->__('Invalid callback: %s::%s does not exist', $run[1], $run[2]));
56
+ }
57
+ $callback = array($model, $run[2]);
58
+ $arguments = array($schedule);
59
+ }
60
+ if (empty($callback)) {
61
+ Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
62
+ }
63
+
64
+ if (!$schedule->tryLockJob()) {
65
+ // another cron started this job intermittently, so skip it
66
+ continue;
67
+ }
68
+ /**
69
+ though running status is set in tryLockJob we must set it here because the object
70
+ was loaded with a pending status and will set it back to pending if we don't set it here
71
+ */
72
+ $schedule
73
+ ->setStatus(Mage_Cron_Model_Schedule::STATUS_RUNNING)
74
+ ->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
75
+ ->save();
76
+
77
+ Mage::dispatchEvent('cron_' . $schedule->getJobCode() . '_before', array('schedule' => $schedule));
78
+
79
+ $messages = call_user_func_array($callback, $arguments);
80
+
81
+ // added by Fabrizio to also save messages when no exception was thrown
82
+ if (!empty($messages)) {
83
+ if (is_object($messages)) {
84
+ $messages = get_class($messages);
85
+ } elseif (!is_scalar($messages)) {
86
+ $messages = var_export($messages, 1);
87
+ }
88
+ $schedule->setMessages($messages);
89
+ }
90
+
91
+ // schedules can report an error state by returning a string that starts with "ERROR:"
92
+ if (is_string($messages) && strtoupper(substr($messages, 0, 6)) == 'ERROR:') {
93
+ $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_ERROR);
94
+ $this->sendErrorMail($schedule, $messages);
95
+ Mage::dispatchEvent('cron_' . $schedule->getJobCode() . '_after_error', array('schedule' => $schedule));
96
+ } else {
97
+ $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS);
98
+ Mage::dispatchEvent('cron_' . $schedule->getJobCode() . '_after_success', array('schedule' => $schedule));
99
+ }
100
+
101
+ $schedule->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()));
102
+ Mage::dispatchEvent('cron_' . $schedule->getJobCode() . '_after', array('schedule' => $schedule));
103
+
104
+ } catch (Exception $e) {
105
+ $schedule->setStatus($errorStatus)
106
+ ->setMessages($e->__toString());
107
+ Mage::dispatchEvent('cron_' . $schedule->getJobCode() . '_exception', array('schedule' => $schedule, 'exception' => $e));
108
+
109
+ $this->sendErrorMail($schedule, $e->__toString());
110
+
111
+ }
112
+ $schedule->save();
113
+ }
114
+
115
+ $this->generate();
116
+ $this->checkRunningJobs();
117
+ $this->cleanup();
118
+ }
119
+
120
+
121
+
122
+ /**
123
+ * Check running jobs
124
+ *
125
+ * @return void
126
+ */
127
+ public function checkRunningJobs() {
128
+
129
+ $maxAge = time() - Mage::getStoreConfig(self::XML_PATH_MAX_RUNNING_TIME) * 60;
130
+
131
+ $schedules = Mage::getModel('cron/schedule')->getCollection()
132
+ ->addFieldToFilter('status', Mage_Cron_Model_Schedule::STATUS_RUNNING)
133
+ ->addFieldToFilter('executed_at', array('lt' => strftime('%Y-%m-%d %H:%M:00', $maxAge)))
134
+ ->load();
135
+
136
+ foreach ($schedules->getIterator() as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
137
+ $schedule
138
+ ->setStatus(Mage_Cron_Model_Schedule::STATUS_ERROR)
139
+ ->setMessages('Job was running longer than the configured max_running_time')
140
+ ->save();
141
+ }
142
+ }
143
+
144
+
145
+
146
+ /**
147
+ * Generate jobs for config information
148
+ * Rewrites the original method to filter deactivated jobs
149
+ *
150
+ * @param $jobs
151
+ * @param array $exists
152
+ * @return Mage_Cron_Model_Observer
153
+ */
154
+ protected function _generateJobs($jobs, $exists) {
155
+
156
+ $conf = Mage::getStoreConfig('system/cron/disabled_crons');
157
+ $conf = explode(',', $conf);
158
+ foreach ($conf as &$c) { $c = trim($c); }
159
+
160
+ $newJobs = array();
161
+ foreach ($jobs as $code => $config) {
162
+ if (!in_array($code, $conf)) {
163
+ $newJobs[$code] = $config;
164
+ }
165
+ }
166
+
167
+ return parent::_generateJobs($newJobs, $exists);
168
+ }
169
+
170
+
171
+
172
+ /**
173
+ * Generate cron schedule.
174
+ * Rewrites the original method to remove duplicates afterwards (that exists because of a bug)
175
+ *
176
+ * @return Mage_Cron_Model_Observer
177
+ */
178
+ public function generate() {
179
+ $result = parent::generate();
180
+
181
+ $cron_schedule = Mage::getSingleton('core/resource')->getTableName('cron_schedule');
182
+ $conn = Mage::getSingleton('core/resource')->getConnection('core_read');
183
+ $results = $conn->fetchAll("
184
+ SELECT
185
+ GROUP_CONCAT(schedule_id) AS ids,
186
+ CONCAT(job_code, scheduled_at) AS jobkey,
187
+ count(*) AS qty
188
+ FROM {$cron_schedule}
189
+ WHERE status = 'pending'
190
+ GROUP BY jobkey
191
+ HAVING qty > 1;
192
+ ");
193
+ foreach($results as $row) {
194
+ $ids = explode(',', $row['ids']);
195
+ $removeIds = array_slice($ids, 1);
196
+ foreach ($removeIds as $id) {
197
+ Mage::getModel('cron/schedule')->load($id)->delete();
198
+ }
199
+ }
200
+
201
+ return $result;
202
+ }
203
+
204
+
205
+ /**
206
+ * Send error mail
207
+ *
208
+ * @param Aoe_Scheduler_Model_Schedule $schedule
209
+ * @param $error
210
+ * @return Aoe_Scheduler_Model_Observer
211
+ */
212
+ protected function sendErrorMail(Aoe_Scheduler_Model_Schedule $schedule, $error) {
213
+ if (!Mage::getStoreConfig(self::XML_PATH_EMAIL_RECIPIENT)) {
214
+ return $this;
215
+ }
216
+
217
+ $translate = Mage::getSingleton('core/translate'); /* @var $translate Mage_Core_Model_Translate */
218
+ $translate->setTranslateInline(false);
219
+
220
+ $emailTemplate = Mage::getModel('core/email_template'); /* @var $emailTemplate Mage_Core_Model_Email_Template */
221
+ $emailTemplate->setDesignConfig(array('area' => 'backend'));
222
+ $emailTemplate->sendTransactional(
223
+ Mage::getStoreConfig(self::XML_PATH_EMAIL_TEMPLATE),
224
+ Mage::getStoreConfig(self::XML_PATH_EMAIL_IDENTITY),
225
+ Mage::getStoreConfig(self::XML_PATH_EMAIL_RECIPIENT),
226
+ null,
227
+ array('error' => $error, 'schedule' => $schedule)
228
+ );
229
+
230
+ $translate->setTranslateInline(true);
231
+
232
+ return $this;
233
+ }
234
+
235
+ }
app/code/community/Aoe/Scheduler/Model/Schedule.php ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Schedule
5
+ *
6
+ * @method string getExecutedAt()
7
+ * @method string getFinishedAt()
8
+ * @method string getStatus()
9
+ * @method string getMessages()
10
+ * @method string getCreatedAt()
11
+ * @method string getScheduledAt()
12
+ * @method string getJobCode()
13
+ * @method string setMessages()
14
+ * @method string setExecutedAt()
15
+ * @method string setCreatedAt()
16
+ * @method string setScheduledAt()
17
+ * @method string setStatus()
18
+ * @method string setFinishedAt()
19
+ */
20
+ class Aoe_Scheduler_Model_Schedule extends Mage_Cron_Model_Schedule {
21
+
22
+ /**
23
+ * @var Aoe_Scheduler_Model_Configuration
24
+ */
25
+ protected $_jobConfiguration;
26
+
27
+
28
+ /**
29
+ * Run this task now
30
+ *
31
+ * @param bool $tryLockJob
32
+ * @return Aoe_Scheduler_Model_Schedule
33
+ */
34
+ public function runNow($tryLockJob=true) {
35
+ $modelCallback = $this->getJobConfiguration()->getModel();
36
+
37
+ if (!$this->getCreatedAt()) {
38
+ $this->schedule();
39
+ }
40
+
41
+ if (!preg_match(Mage_Cron_Model_Observer::REGEX_RUN_MODEL, $modelCallback, $run)) {
42
+ Mage::throwException(Mage::helper('cron')->__('Invalid model/method definition, expecting "model/class::method".'));
43
+ }
44
+ if (!($model = Mage::getModel($run[1])) || !method_exists($model, $run[2])) {
45
+ Mage::throwException(Mage::helper('cron')->__('Invalid callback: %s::%s does not exist', $run[1], $run[2]));
46
+ }
47
+ $callback = array($model, $run[2]);
48
+
49
+ if (empty($callback)) {
50
+ Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
51
+ }
52
+
53
+ // lock job requires the record to be saved and having status Mage_Cron_Model_Schedule::STATUS_PENDING
54
+ // workaround could be to do this: $this->setStatus(Mage_Cron_Model_Schedule::STATUS_PENDING)->save();
55
+ if ($tryLockJob && !$this->tryLockJob()) {
56
+ // another cron started this job intermittently, so skip it
57
+ return $this;
58
+ }
59
+ $this->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', time()));
60
+
61
+ $messages = call_user_func_array($callback, array($this));
62
+
63
+ // added by Fabrizio to also save messages when no exception was thrown
64
+ if (!empty($messages)) {
65
+ if (is_object($messages)) {
66
+ $messages = get_class($messages);
67
+ } elseif (!is_scalar($messages)) {
68
+ $messages = var_export($messages, 1);
69
+ }
70
+ $this->setMessages($messages);
71
+ }
72
+
73
+ if (strtoupper(substr($messages, 0, 6)) != 'ERROR:') {
74
+ $this->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS);
75
+ } else {
76
+ $this->setStatus(Mage_Cron_Model_Schedule::STATUS_ERROR);
77
+ }
78
+
79
+ $this->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()));
80
+
81
+ return $this;
82
+ }
83
+
84
+
85
+
86
+ /**
87
+ * Schedule this task to be executed as soon as possible
88
+ *
89
+ * @deprecated use Aoe_Scheduler_Model_Schedule::schedule() instead
90
+ * @return Aoe_Scheduler_Model_Schedule
91
+ */
92
+ public function scheduleNow() {
93
+ return $this->schedule();
94
+ }
95
+
96
+
97
+
98
+ /**
99
+ * Schedule this task to be executed at a given time
100
+ *
101
+ * @param int $time
102
+ * @return Aoe_Scheduler_Model_Schedule
103
+ */
104
+ public function schedule($time=NULL) {
105
+ if (is_null($time)) {
106
+ $time = time();
107
+ }
108
+ $this->setStatus(Mage_Cron_Model_Schedule::STATUS_PENDING)
109
+ ->setCreatedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
110
+ ->setScheduledAt(strftime('%Y-%m-%d %H:%M:%S', $time))
111
+ ->save();
112
+ return $this;
113
+ }
114
+
115
+
116
+
117
+ /**
118
+ * Get job configuration
119
+ *
120
+ * @return Aoe_Scheduler_Model_Configuration
121
+ */
122
+ public function getJobConfiguration() {
123
+ if (is_null($this->_jobConfiguration)) {
124
+ $this->_jobConfiguration = Mage::getModel('aoe_scheduler/configuration')->loadByCode($this->getJobCode());
125
+ }
126
+ return $this->_jobConfiguration;
127
+ }
128
+
129
+
130
+
131
+ /**
132
+ * Get start time (planned or actual)
133
+ *
134
+ * @return string
135
+ */
136
+ public function getStarttime() {
137
+ $starttime = $this->getExecutedAt();
138
+ if (empty($starttime) || $starttime == '0000-00-00 00:00:00') {
139
+ $starttime = $this->getScheduledAt();
140
+ }
141
+ return $starttime;
142
+ }
143
+
144
+
145
+
146
+ /**
147
+ * Get job duration
148
+ *
149
+ * @return bool|int time in seconds, or false
150
+ */
151
+ public function getDuration() {
152
+ $duration = false;
153
+ if ($this->getExecutedAt() && ($this->getExecutedAt() != '0000-00-00 00:00:00')
154
+ && $this->getFinishedAt() && ($this->getFinishedAt() != '0000-00-00 00:00:00')) {
155
+ $duration = strtotime($this->getFinishedAt()) - strtotime($this->getExecutedAt());
156
+ }
157
+ return $duration;
158
+ }
159
+
160
+
161
+ }
app/code/community/Aoe/Scheduler/Model/TestTask.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Model_TestTask {
4
+
5
+ public function run() {
6
+ sleep(rand(60,180));
7
+ if (rand(0, 1) == 0) {
8
+ throw new Exception('This is a dummy exception');
9
+ }
10
+ }
11
+
12
+ }
app/code/community/Aoe/Scheduler/controllers/Adminhtml/AbstractController.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Abstract controller
5
+ *
6
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
7
+ */
8
+ abstract class Aoe_Scheduler_Adminhtml_AbstractController extends Mage_Adminhtml_Controller_Action {
9
+
10
+ /**
11
+ * Index action (display grid)
12
+ *
13
+ * @return void
14
+ */
15
+ public function indexAction() {
16
+ $this->checkHeartbeat();
17
+
18
+ $this->loadLayout();
19
+
20
+ $this->_setActiveMenu('system');
21
+ $this->renderLayout();
22
+ }
23
+
24
+
25
+
26
+ /**
27
+ * Check heartbeat
28
+ */
29
+ protected function checkHeartbeat() {
30
+ if (!Mage::helper('aoe_scheduler')->isDisabled('aoescheduler_heartbeat')) {
31
+ $lastHeartbeat = Mage::helper('aoe_scheduler')->getLastHeartbeat();
32
+ if ($lastHeartbeat === false) {
33
+ // no heartbeat task found
34
+ $this->_getSession()->addError('No heartbeat task found. Check if cron is configured correctly.');
35
+ } else {
36
+ $timespan = Mage::helper('aoe_scheduler')->dateDiff($lastHeartbeat);
37
+ if ($timespan <= 5 * 60) {
38
+ $this->_getSession()->addSuccess(sprintf('Scheduler is working. (Last execution: %s minute(s) ago)', round($timespan/60)));
39
+ } elseif ($timespan > 5 * 60 && $timespan <= 60 * 60 ) {
40
+ // heartbeat wasn't executed in the last 5 minutes. Heartbeat schedule could be modified to not run every five minutes!
41
+ $this->_getSession()->addNotice(sprintf('Last heartbeat is older than %s minutes.', round($timespan/60)));
42
+ } else {
43
+ // everything ok
44
+ $this->_getSession()->addError('Last heartbeat is older than one hour. Please check your settings and your configuration!');
45
+ }
46
+ }
47
+
48
+ }
49
+ }
50
+
51
+
52
+
53
+ /**
54
+ * Generate schedule now
55
+ *
56
+ * @return void
57
+ */
58
+ public function generateScheduleAction() {
59
+ $observer = Mage::getModel('cron/observer'); /* @var $observer Mage_Cron_Model_Observer */
60
+ $observer->generate();
61
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Generated schedule'));
62
+ $this->_redirect('*/*/index');
63
+ }
64
+
65
+ }
66
+
app/code/community/Aoe/Scheduler/controllers/Adminhtml/CronController.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once Mage::getModuleDir('controllers', 'Aoe_Scheduler').'/Adminhtml/AbstractController.php';
4
+
5
+ /**
6
+ * Cron controller
7
+ *
8
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
9
+ */
10
+ class Aoe_Scheduler_Adminhtml_CronController extends Aoe_Scheduler_Adminhtml_AbstractController {
11
+
12
+
13
+
14
+ /**
15
+ * Mass action: disable
16
+ *
17
+ * @return void
18
+ */
19
+ public function disableAction() {
20
+ $codes = $this->getRequest()->getParam('codes');
21
+ $disabledCrons = Mage::helper('aoe_scheduler')->trimExplode(',', Mage::getStoreConfig('system/cron/disabled_crons'), true);
22
+ foreach ($codes as $code) {
23
+ if (!in_array($code, $disabledCrons)) {
24
+ $disabledCrons[] = $code;
25
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Disabled "%s"', $code));
26
+ }
27
+ }
28
+ Mage::getModel('core/config')->saveConfig('system/cron/disabled_crons/', implode(',', $disabledCrons));
29
+ Mage::app()->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array(Mage_Core_Model_Config::CACHE_TAG));
30
+ $this->_redirect('*/*/index');
31
+ }
32
+
33
+
34
+
35
+ /**
36
+ * Mass action: enable
37
+ *
38
+ * @return void
39
+ */
40
+ public function enableAction() {
41
+ $codes = $this->getRequest()->getParam('codes');
42
+ $disabledCrons = Mage::helper('aoe_scheduler')->trimExplode(',', Mage::getStoreConfig('system/cron/disabled_crons'), true);
43
+ foreach ($codes as $key => $code) {
44
+ if (in_array($code, $disabledCrons)) {
45
+ unset($disabledCrons[array_search($code, $disabledCrons)]);
46
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Enabled "%s"', $code));
47
+ }
48
+ }
49
+ Mage::getModel('core/config')->saveConfig('system/cron/disabled_crons/', implode(',', $disabledCrons));
50
+ Mage::app()->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array(Mage_Core_Model_Config::CACHE_TAG));
51
+ $this->_redirect('*/*/index');
52
+ }
53
+
54
+
55
+
56
+
57
+ /**
58
+ * Mass action: schedule now
59
+ *
60
+ * @return void
61
+ */
62
+ public function scheduleNowAction() {
63
+ $codes = $this->getRequest()->getParam('codes');
64
+ if (is_array($codes)) {
65
+ foreach ($codes as $key) {
66
+ Mage::getModel('cron/schedule') /* @var Aoe_Scheduler_Model_Schedule */
67
+ ->setJobCode($key)
68
+ ->schedule()
69
+ ->save();
70
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Scheduled "%s"', $key));
71
+ }
72
+ }
73
+ $this->_redirect('*/*/index');
74
+ }
75
+
76
+
77
+
78
+ /**
79
+ * Mass action: run now
80
+ *
81
+ * @return void
82
+ */
83
+ public function runNowAction() {
84
+ $codes = $this->getRequest()->getParam('codes');
85
+ if (is_array($codes)) {
86
+ foreach ($codes as $key) {
87
+ $schedule = Mage::getModel('cron/schedule') /* @var $schedule Aoe_Scheduler_Model_Schedule */
88
+ ->setJobCode($key)
89
+ ->runNow(false) // without trying to lock the job
90
+ ->save();
91
+
92
+ $messages = $schedule->getMessages();
93
+
94
+ if ($schedule->getStatus() == Mage_Cron_Model_Schedule::STATUS_SUCCESS) {
95
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Ran "%s" (Duration: %s sec)', $key, intval($schedule->getDuration())));
96
+ if ($messages) {
97
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('"%s" messages:<pre>%s</pre>', $key, $messages));
98
+ }
99
+ } else {
100
+ Mage::getSingleton('adminhtml/session')->addError($this->__('Error while running "%s"', $key));
101
+ if ($messages) {
102
+ Mage::getSingleton('adminhtml/session')->addError($this->__('"%s" messages:<pre>%s</pre>', $key, $messages));
103
+ }
104
+ }
105
+
106
+ }
107
+ }
108
+ $this->_redirect('*/*/index');
109
+ }
110
+
111
+ }
112
+
app/code/community/Aoe/Scheduler/controllers/Adminhtml/SchedulerController.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once Mage::getModuleDir('controllers', 'Aoe_Scheduler').'/Adminhtml/AbstractController.php';
4
+
5
+ /**
6
+ * Scheduler controller
7
+ *
8
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
9
+ */
10
+ class Aoe_Scheduler_Adminhtml_SchedulerController extends Aoe_Scheduler_Adminhtml_AbstractController {
11
+
12
+
13
+
14
+ /**
15
+ * Mass action: delete
16
+ *
17
+ * @return void
18
+ */
19
+ public function deleteAction() {
20
+ $ids = $this->getRequest()->getParam('schedule_ids');
21
+ foreach ($ids as $id) {
22
+ $schedule = Mage::getModel('cron/schedule')->load($id)->delete(); /* @var $schedule Mage_Cron_Model_Schedule */
23
+ }
24
+ Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Deleted task(s) "%s"', implode(', ', $ids)));
25
+ $this->_redirect('*/*/index');
26
+ }
27
+
28
+ }
app/code/community/Aoe/Scheduler/controllers/Adminhtml/TimelineController.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once Mage::getModuleDir('controllers', 'Aoe_Scheduler').'/Adminhtml/AbstractController.php';
4
+
5
+ /**
6
+ * Timeline controller
7
+ *
8
+ * @author Fabrizio Branca <fabrizio.branca@aoemedia.de>
9
+ */
10
+ class Aoe_Scheduler_Adminhtml_TimelineController extends Aoe_Scheduler_Adminhtml_AbstractController {
11
+
12
+ }
app/code/community/Aoe/Scheduler/etc/adminhtml.xml ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <menu>
4
+ <system>
5
+ <children>
6
+ <aoe_scheduler translate="title">
7
+ <title>Scheduler</title>
8
+ <sort_order>1</sort_order>
9
+ <children>
10
+ <aoe_scheduler_cron translate="title">
11
+ <title>Schedule Configuration</title>
12
+ <sort_order>10</sort_order>
13
+ <action>adminhtml/cron/index</action>
14
+ </aoe_scheduler_cron>
15
+ <aoe_scheduler_scheduler translate="title">
16
+ <title>List View</title>
17
+ <sort_order>20</sort_order>
18
+ <action>adminhtml/scheduler/index</action>
19
+ </aoe_scheduler_scheduler>
20
+ <aoe_scheduler_timeline translate="title">
21
+ <title>Timeline View</title>
22
+ <sort_order>30</sort_order>
23
+ <action>adminhtml/timeline/index</action>
24
+ </aoe_scheduler_timeline>
25
+ </children>
26
+ </aoe_scheduler>
27
+ </children>
28
+ </system>
29
+ </menu>
30
+
31
+ <acl>
32
+ <resources>
33
+ <admin>
34
+ <children>
35
+ <system>
36
+ <children>
37
+ <aoe_scheduler translate="title" module="aoe_scheduler">
38
+ <title>AOE Scheduler</title>
39
+ <sort_order>1</sort_order>
40
+ <children>
41
+ <aoe_scheduler_cron translate="title">
42
+ <title>Schedule Configuration</title>
43
+ <sort_order>10</sort_order>
44
+ </aoe_scheduler_cron>
45
+ <aoe_scheduler_scheduler translate="title">
46
+ <title>List View</title>
47
+ <sort_order>20</sort_order>
48
+ </aoe_scheduler_scheduler>
49
+ <aoe_scheduler_timeline translate="title">
50
+ <title>Timeline View</title>
51
+ <sort_order>30</sort_order>
52
+ </aoe_scheduler_timeline>
53
+ </children>
54
+ </aoe_scheduler>
55
+ </children>
56
+ </system>
57
+ </children>
58
+ </admin>
59
+ </resources>
60
+ </acl>
61
+ </config>
app/code/community/Aoe/Scheduler/etc/api.xml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <api>
4
+ <resources>
5
+ <aoe_scheduler translate="title" module="aoe_scheduler">
6
+ <model>aoe_scheduler/api</model>
7
+ <title>Scheduler API</title>
8
+ <acl>aoe_scheduler/aoe_scheduler</acl>
9
+ <methods>
10
+ <runNow translate="title" module="aoe_scheduler">
11
+ <title>Run scheduler task</title>
12
+ <acl>aoe_scheduler/aoe_scheduler/runNow</acl>
13
+ </runNow>
14
+ <schedule translate="title" module="aoe_scheduler">
15
+ <title>Schedule task</title>
16
+ <acl>aoe_scheduler/aoe_scheduler/schedule</acl>
17
+ </schedule>
18
+ <info translate="title" module="aoe_scheduler">
19
+ <title>Get task information</title>
20
+ <acl>aoe_scheduler/aoe_scheduler/info</acl>
21
+ </info>
22
+ </methods>
23
+ </aoe_scheduler>
24
+ </resources>
25
+ <acl>
26
+ <resources>
27
+ <aoe_scheduler translate="title" module="aoe_scheduler">
28
+ <title>AOE Scheduler</title>
29
+ <runNow translate="title" module="aoe_scheduler">
30
+ <title>Run scheduler task</title>
31
+ </runNow>
32
+ <schedule translate="title" module="aoe_scheduler">
33
+ <title>Schedule task</title>
34
+ </schedule>
35
+ <info translate="title" module="aoe_scheduler">
36
+ <title>Get task information</title>
37
+ </info>
38
+ </aoe_scheduler>
39
+ </resources>
40
+ </acl>
41
+ </api>
42
+ </config>
app/code/community/Aoe/Scheduler/etc/config.xml ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" ?>
2
+ <config>
3
+ <modules>
4
+ <Aoe_Scheduler>
5
+ <version>0.3.2</version>
6
+ </Aoe_Scheduler>
7
+ </modules>
8
+
9
+ <global>
10
+ <blocks>
11
+ <aoe_scheduler>
12
+ <class>Aoe_Scheduler_Block</class>
13
+ </aoe_scheduler>
14
+ </blocks>
15
+
16
+ <helpers>
17
+ <aoe_scheduler>
18
+ <class>Aoe_Scheduler_Helper</class>
19
+ </aoe_scheduler>
20
+ </helpers>
21
+
22
+ <models>
23
+ <aoe_scheduler>
24
+ <class>Aoe_Scheduler_Model</class>
25
+ </aoe_scheduler>
26
+
27
+ <cron>
28
+ <rewrite>
29
+ <observer>Aoe_Scheduler_Model_Observer</observer>
30
+ <schedule>Aoe_Scheduler_Model_Schedule</schedule>
31
+ </rewrite>
32
+ </cron>
33
+ </models>
34
+
35
+
36
+ <template>
37
+ <email>
38
+ <system_cron_error_email_template translate="label" module="aoe_scheduler">
39
+ <label>Cron error</label>
40
+ <file>aoe_scheduler/cron_error.html</file>
41
+ <type>text</type>
42
+ </system_cron_error_email_template>
43
+ </email>
44
+ </template>
45
+
46
+ </global>
47
+
48
+ <admin>
49
+ <routers>
50
+ <adminhtml>
51
+ <args>
52
+ <modules>
53
+ <Aoe_Scheduler before="Mage_Adminhtml">Aoe_Scheduler_Adminhtml</Aoe_Scheduler>
54
+ </modules>
55
+ </args>
56
+ </adminhtml>
57
+ </routers>
58
+ </admin>
59
+
60
+ <adminhtml>
61
+ <translate>
62
+ <modules>
63
+ <aoe_scheduler>
64
+ <files>
65
+ <default>Aoe_Scheduler.csv</default>
66
+ </files>
67
+ </aoe_scheduler>
68
+ </modules>
69
+ </translate>
70
+ <layout>
71
+ <updates>
72
+ <aoe_scheduler>
73
+ <file>aoe_scheduler/aoe_scheduler.xml</file>
74
+ </aoe_scheduler>
75
+ </updates>
76
+ </layout>
77
+ </adminhtml>
78
+
79
+ <crontab>
80
+ <jobs>
81
+ <!-- <aoescheduler_testtask>
82
+ <schedule><cron_expr>*/5 * * * *</cron_expr></schedule>
83
+ <run><model>aoe_scheduler/testTask::run</model></run>
84
+ </aoescheduler_testtask> -->
85
+
86
+ <aoescheduler_heartbeat>
87
+ <schedule><config_path>system/cron/scheduler_cron_expr_heartbeat</config_path></schedule>
88
+ <run><model>aoe_scheduler/heartbeatTask::run</model></run>
89
+ </aoescheduler_heartbeat>
90
+ </jobs>
91
+ </crontab>
92
+
93
+ <default>
94
+ <system>
95
+ <cron>
96
+ <max_running_time>120</max_running_time>
97
+ <scheduler_cron_expr_heartbeat>*/5 * * * *</scheduler_cron_expr_heartbeat>
98
+
99
+ <error_email_identity>general</error_email_identity>
100
+ <error_email_template>system_cron_error_email_template</error_email_template>
101
+ </cron>
102
+ </system>
103
+ </default>
104
+
105
+ </config>
app/code/community/Aoe/Scheduler/etc/system.xml ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <sections>
4
+ <system>
5
+ <groups>
6
+ <cron translate="label comment" module="cron">
7
+ <fields>
8
+ <disabled_crons translate="label">
9
+ <label>Disabled cron tasks</label>
10
+ <frontend_type>text</frontend_type>
11
+ <comment><![CDATA[Do not edit this field manually unless you know what you do. This field is populated by the "disable mass action" of Aoe_Scheduler.]]></comment>
12
+ <sort_order>100</sort_order>
13
+ <show_in_default>1</show_in_default>
14
+ <show_in_website>0</show_in_website>
15
+ <show_in_store>0</show_in_store>
16
+ </disabled_crons>
17
+ <max_running_time translate="label">
18
+ <label>Mark jobs as failed after</label>
19
+ <comment><![CDATA[Maximum time in minutes. The will only marked as failed. It won't be killed (if it should actually be still running).]]></comment>
20
+ <frontend_type>text</frontend_type>
21
+ <sort_order>110</sort_order>
22
+ <show_in_default>1</show_in_default>
23
+ <show_in_website>0</show_in_website>
24
+ <show_in_store>0</show_in_store>
25
+ </max_running_time>
26
+ <scheduler_cron_expr_heartbeat>
27
+ <label>Heartbeat task schedule (cron syntax)</label>
28
+ <frontend_type>text</frontend_type>
29
+ <sort_order>120</sort_order>
30
+ <show_in_default>1</show_in_default>
31
+ <show_in_website>0</show_in_website>
32
+ <show_in_store>0</show_in_store>
33
+ </scheduler_cron_expr_heartbeat>
34
+
35
+ <error_email translate="label">
36
+ <label>Error Email Recipient</label>
37
+ <frontend_type>text</frontend_type>
38
+ <sort_order>130</sort_order>
39
+ <show_in_default>1</show_in_default>
40
+ <show_in_website>0</show_in_website>
41
+ <show_in_store>0</show_in_store>
42
+ </error_email>
43
+ <error_email_identity translate="label">
44
+ <label>Error Email Sender</label>
45
+ <frontend_type>select</frontend_type>
46
+ <source_model>adminhtml/system_config_source_email_identity</source_model>
47
+ <sort_order>140</sort_order>
48
+ <show_in_default>1</show_in_default>
49
+ <show_in_website>0</show_in_website>
50
+ <show_in_store>0</show_in_store>
51
+ </error_email_identity>
52
+ <error_email_template translate="label">
53
+ <label>Error Email Template</label>
54
+ <frontend_type>select</frontend_type>
55
+ <source_model>adminhtml/system_config_source_email_template</source_model>
56
+ <sort_order>150</sort_order>
57
+ <show_in_default>1</show_in_default>
58
+ <show_in_website>0</show_in_website>
59
+ <show_in_store>0</show_in_store>
60
+ </error_email_template>
61
+
62
+ </fields>
63
+ </cron>
64
+ </groups>
65
+ </system>
66
+ </sections>
67
+ </config>
app/locale/en_US/template/email/aoe_scheduler/cron_error.html ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <!--@subject Cron Error @-->
2
+ <!--@vars
3
+ {"var error":"Error"}
4
+ @-->
5
+ Cron error while executing {{var schedule.job_code}}:
6
+
7
+ {{var error}}
package.xml CHANGED
@@ -1,18 +1,18 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Aoe_Scheduler</name>
4
- <version>0.3.1</version>
5
  <stability>stable</stability>
6
  <license uri="http://www.opensource.org/licenses/osl-3.0.php">OSL v3.0</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Scheduler / Cron Management</summary>
10
  <description>This extension allows you to manage your scheduler task. Schedules can be disabled. From version 0.1.0 this extension also comes with a graphical timeline view.</description>
11
- <notes>GIT Revision: 5831049ed3c4f99a32351649ed5599c15d3a5192, Build Date: 2013-02-28 22:29:19</notes>
12
  <authors><author><name>Fabrizio Branca</name><user>fbrnc</user><email>magento@fabrizio-branca.de</email></author></authors>
13
- <date>2013-02-28</date>
14
- <time>22:29:19</time>
15
- <contents><target name="mageetc"><dir><dir name="modules"><file name="Aoe_Scheduler.xml" hash="d347eefeddc87aea31e3243f6108fa70"/></dir></dir></target><target name="mage"><dir><dir name="shell"><file name="scheduler.php" hash="126f850a96a163db7fa8e82c16998507"/></dir></dir></target><target name="magelocale"><dir><dir name="de_DE"><file name="Aoe_Scheduler.csv" hash="f06c379c1170bc67f978b05943197601"/></dir></dir></target><target name="magedesign"><dir><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="aoe_scheduler"><file name="timeline.phtml" hash="9b8bf5864319542e8aa6ecc19efa5108"/><file name="timeline_detail.phtml" hash="d353689ad539f2adc0df97716ff7eb88"/></dir></dir><dir name="layout"><dir name="aoe_scheduler"><file name="aoe_scheduler.xml" hash="95be2d020b3281f3400a2edb809fce79"/></dir></dir></dir></dir></dir></dir></target><target name="mageskin"><dir><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="aoe_scheduler"><dir><dir name="Images"><file name="bg_notifications.gif" hash="df73b8aa7e48bb56e0a644245aa3683f"/><file name="gradient.png" hash="17f6c6ef64b1682a06060950933561c9"/><file name="hour.gif" hash="91a63b82b2b41a046ca938aea3238a41"/></dir><dir name="JavaScript"><file name="common.js" hash="af6dadb7e3a18a2d4a882d9249f3f7a0"/><file name="jquery-1.6.2.min.js" hash="a1a8cb16a060f6280a767187fd22e037"/><file name="tooltip.dynamic.js" hash="c6737dd54890ca0cac816f3fecaf33d9"/><file name="tooltip.js" hash="d731eecbe152f86d3c702b55b4767fb1"/></dir><dir name="StyleSheet"><file name="bars.css" hash="c98df4a3611f628dba01b8cd0142f3d2"/><file name="timeline.css" hash="41510bd533aaeab5df46fa49210cfc9f"/></dir></dir></dir></dir></dir></dir></dir></target></contents>
16
  <compatible/>
17
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
18
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Aoe_Scheduler</name>
4
+ <version>0.3.2</version>
5
  <stability>stable</stability>
6
  <license uri="http://www.opensource.org/licenses/osl-3.0.php">OSL v3.0</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Scheduler / Cron Management</summary>
10
  <description>This extension allows you to manage your scheduler task. Schedules can be disabled. From version 0.1.0 this extension also comes with a graphical timeline view.</description>
11
+ <notes>GIT Revision: 8332e45e88bc9a391906ed0b93920c7ae628a983, Build Date: 2013-03-04 07:51:53</notes>
12
  <authors><author><name>Fabrizio Branca</name><user>fbrnc</user><email>magento@fabrizio-branca.de</email></author></authors>
13
+ <date>2013-03-04</date>
14
+ <time>07:51:53</time>
15
+ <contents><target name="magecommunity"><dir><dir name="Aoe"><dir name="Scheduler"><dir><dir name="Block"><dir name="Adminhtml"><dir name="Cron"><file name="Grid.php" hash="794200ea4a6885a19b86c972d06b3693"/></dir><file name="Cron.php" hash="f3fe20d102c7ddff6c1a4f72c14ab95f"/><dir name="Scheduler"><file name="Grid.php" hash="73eb69714d3ec1a6b3e5ceb566067ad1"/></dir><file name="Scheduler.php" hash="a9d0f50f2a66532f76746cc311ad1b09"/><file name="Timeline.php" hash="4febff14b58b081b3df00871a3ce0d8e"/><file name="TimelineDetail.php" hash="0cdc46705c070dee76c6938175586c6a"/></dir></dir><dir name="Helper"><file name="Data.php" hash="80d5b208e3921b18a50a9557748b340e"/></dir><dir name="Model"><file name="Api.php" hash="2f674c989ec27b19f2f5530fcf698c0c"/><dir name="Collection"><file name="Crons.php" hash="9ec07bc98bf6ce69ba830490d224ea81"/></dir><file name="Configuration.php" hash="1c21f0c4fa50cd4980fe1bdb7e4956f3"/><file name="HeartbeatTask.php" hash="0c8e9cb7c28cb412b20062ee1d6647b5"/><file name="Observer.php" hash="e61b0412a15f674333bc626a97c5e7b9"/><file name="Schedule.php" hash="465d3d4354376435df01e713976a81b2"/><file name="TestTask.php" hash="96ac1c00c24cf959cbef452dd57900e4"/></dir><dir name="controllers"><dir name="Adminhtml"><file name="AbstractController.php" hash="9aa27b90cc34f36b3ad3b7025012f26e"/><file name="CronController.php" hash="fdb5947cb2a45ba4e616cb78127a217a"/><file name="SchedulerController.php" hash="95ffe62214e872e566332de6a2141cab"/><file name="TimelineController.php" hash="c0ccb4163c8495c9b8b07a869e27563b"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="40a0190b9c9e79f69c05fb6308b1310e"/><file name="api.xml" hash="cc6cc05874a1277ba53e07c3450541c5"/><file name="config.xml" hash="98c3636585d2f31b8583bfcc2f4d5f33"/><file name="system.xml" hash="07c4e474eafbf88fa0feb6b7b173c43e"/></dir></dir></dir></dir></dir></target><target name="mageetc"><dir><dir name="modules"><file name="Aoe_Scheduler.xml" hash="d347eefeddc87aea31e3243f6108fa70"/></dir></dir></target><target name="mageweb"><dir><dir name="shell"><file name="scheduler.php" hash="126f850a96a163db7fa8e82c16998507"/></dir></dir></target><target name="magelocale"><dir><dir name="de_DE"><file name="Aoe_Scheduler.csv" hash="f06c379c1170bc67f978b05943197601"/></dir><dir name="en_US"><dir name="template"><dir name="email"><dir name="aoe_scheduler"><file name="cron_error.html" hash="0dbbecfef932b0326f86deedc4012c4e"/></dir></dir></dir></dir></dir></target><target name="magedesign"><dir><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="aoe_scheduler"><file name="timeline.phtml" hash="9b8bf5864319542e8aa6ecc19efa5108"/><file name="timeline_detail.phtml" hash="d353689ad539f2adc0df97716ff7eb88"/></dir></dir><dir name="layout"><dir name="aoe_scheduler"><file name="aoe_scheduler.xml" hash="95be2d020b3281f3400a2edb809fce79"/></dir></dir></dir></dir></dir></dir></target><target name="mageskin"><dir><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="aoe_scheduler"><dir><dir name="Images"><file name="bg_notifications.gif" hash="df73b8aa7e48bb56e0a644245aa3683f"/><file name="gradient.png" hash="17f6c6ef64b1682a06060950933561c9"/><file name="hour.gif" hash="91a63b82b2b41a046ca938aea3238a41"/></dir><dir name="JavaScript"><file name="common.js" hash="af6dadb7e3a18a2d4a882d9249f3f7a0"/><file name="jquery-1.6.2.min.js" hash="a1a8cb16a060f6280a767187fd22e037"/><file name="tooltip.dynamic.js" hash="c6737dd54890ca0cac816f3fecaf33d9"/><file name="tooltip.js" hash="d731eecbe152f86d3c702b55b4767fb1"/></dir><dir name="StyleSheet"><file name="bars.css" hash="c98df4a3611f628dba01b8cd0142f3d2"/><file name="timeline.css" hash="41510bd533aaeab5df46fa49210cfc9f"/></dir></dir></dir></dir></dir></dir></dir></target></contents>
16
  <compatible/>
17
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
18
  </package>