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
- app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron.php +57 -0
- app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron/Grid.php +138 -0
- app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler.php +57 -0
- app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler/Grid.php +170 -0
- app/code/community/Aoe/Scheduler/Block/Adminhtml/Timeline.php +211 -0
- app/code/community/Aoe/Scheduler/Block/Adminhtml/TimelineDetail.php +44 -0
- app/code/community/Aoe/Scheduler/Helper/Data.php +161 -0
- app/code/community/Aoe/Scheduler/Model/Api.php +49 -0
- app/code/community/Aoe/Scheduler/Model/Collection/Crons.php +62 -0
- app/code/community/Aoe/Scheduler/Model/Configuration.php +129 -0
- app/code/community/Aoe/Scheduler/Model/HeartbeatTask.php +9 -0
- app/code/community/Aoe/Scheduler/Model/Observer.php +235 -0
- app/code/community/Aoe/Scheduler/Model/Schedule.php +161 -0
- app/code/community/Aoe/Scheduler/Model/TestTask.php +12 -0
- app/code/community/Aoe/Scheduler/controllers/Adminhtml/AbstractController.php +66 -0
- app/code/community/Aoe/Scheduler/controllers/Adminhtml/CronController.php +112 -0
- app/code/community/Aoe/Scheduler/controllers/Adminhtml/SchedulerController.php +28 -0
- app/code/community/Aoe/Scheduler/controllers/Adminhtml/TimelineController.php +12 -0
- app/code/community/Aoe/Scheduler/etc/adminhtml.xml +61 -0
- app/code/community/Aoe/Scheduler/etc/api.xml +42 -0
- app/code/community/Aoe/Scheduler/etc/config.xml +105 -0
- app/code/community/Aoe/Scheduler/etc/system.xml +67 -0
- app/locale/en_US/template/email/aoe_scheduler/cron_error.html +7 -0
- 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.
|
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:
|
12 |
<authors><author><name>Fabrizio Branca</name><user>fbrnc</user><email>magento@fabrizio-branca.de</email></author></authors>
|
13 |
-
<date>2013-
|
14 |
-
<time>
|
15 |
-
<contents><target name="mageetc"><dir><dir name="modules"><file name="Aoe_Scheduler.xml" hash="d347eefeddc87aea31e3243f6108fa70"/></dir></dir></target><target name="
|
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>
|