Aoe_Scheduler - Version 1.3.0

Version Notes

GIT Revision: 4bb4776a589c7cf707be13bb4f67f4d7e5067146, Build Date: 2015-11-09 21:32:36

Download this release

Release Info

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


Code changes from version 0.3.2 to 1.3.0

Files changed (71) hide show
  1. app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron.php +0 -57
  2. app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron/Grid.php +0 -138
  3. app/code/community/Aoe/Scheduler/Block/Adminhtml/Instructions.php +20 -0
  4. app/code/community/Aoe/Scheduler/Block/Adminhtml/Job.php +68 -0
  5. app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit.php +68 -0
  6. app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit/Form.php +26 -0
  7. app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit/Tab/Form.php +286 -0
  8. app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit/Tabs.php +21 -0
  9. app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Grid.php +248 -0
  10. app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler.php +53 -49
  11. app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler/Grid.php +234 -163
  12. app/code/community/Aoe/Scheduler/Block/Adminhtml/Timeline.php +240 -203
  13. app/code/community/Aoe/Scheduler/Block/Adminhtml/TimelineDetail.php +36 -36
  14. app/code/community/Aoe/Scheduler/Controller/AbstractController.php +107 -0
  15. app/code/community/Aoe/Scheduler/Helper/Compatibility.php +20 -0
  16. app/code/community/Aoe/Scheduler/Helper/Data.php +316 -153
  17. app/code/community/Aoe/Scheduler/Helper/GracefulDead.php +70 -0
  18. app/code/community/Aoe/Scheduler/Model/Adminhtml/System/Config/Source/List/Code/Filtertype.php +24 -0
  19. app/code/community/Aoe/Scheduler/Model/Api.php +49 -39
  20. app/code/community/Aoe/Scheduler/Model/Collection/Crons.php +0 -62
  21. app/code/community/Aoe/Scheduler/Model/Configuration.php +0 -129
  22. app/code/community/Aoe/Scheduler/Model/HeartbeatTask.php +0 -9
  23. app/code/community/Aoe/Scheduler/Model/Job.php +194 -0
  24. app/code/community/Aoe/Scheduler/Model/Observer.php +77 -228
  25. app/code/community/Aoe/Scheduler/Model/ProcessManager.php +147 -0
  26. app/code/community/Aoe/Scheduler/Model/Resource/Job.php +358 -0
  27. app/code/community/Aoe/Scheduler/Model/Resource/Job/Collection.php +368 -0
  28. app/code/community/Aoe/Scheduler/Model/Resource/Schedule/Collection.php +20 -0
  29. app/code/community/Aoe/Scheduler/Model/Schedule.php +832 -148
  30. app/code/community/Aoe/Scheduler/Model/ScheduleManager.php +370 -0
  31. app/code/community/Aoe/Scheduler/Model/Task/Heartbeat.php +15 -0
  32. app/code/community/Aoe/Scheduler/Model/Task/Test.php +73 -0
  33. app/code/community/Aoe/Scheduler/Model/TestTask.php +0 -12
  34. app/code/community/Aoe/Scheduler/Test/Helper/Data.php +18 -0
  35. app/code/community/Aoe/Scheduler/Test/Model/Schedule/Runnow.php +177 -0
  36. app/code/community/Aoe/Scheduler/Test/Model/Schedule/Scheduling.php +102 -0
  37. app/code/community/Aoe/Scheduler/controllers/Adminhtml/AbstractController.php +0 -66
  38. app/code/community/Aoe/Scheduler/controllers/Adminhtml/CronController.php +0 -112
  39. app/code/community/Aoe/Scheduler/controllers/Adminhtml/InstructionsController.php +19 -0
  40. app/code/community/Aoe/Scheduler/controllers/Adminhtml/JobController.php +267 -0
  41. app/code/community/Aoe/Scheduler/controllers/Adminhtml/SchedulerController.php +50 -19
  42. app/code/community/Aoe/Scheduler/controllers/Adminhtml/TimelineController.php +12 -5
  43. app/code/community/Aoe/Scheduler/data/aoescheduler_setup/data-upgrade-0.5.4-0.5.5.php +30 -0
  44. app/code/community/Aoe/Scheduler/etc/adminhtml.xml +66 -58
  45. app/code/community/Aoe/Scheduler/etc/config.xml +175 -101
  46. app/code/community/Aoe/Scheduler/etc/system.xml +149 -62
  47. app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/install-1.0.0.php +78 -0
  48. app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.4.0-0.4.1.php +46 -0
  49. app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.4.1-0.4.2.php +15 -0
  50. app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.4.2-0.4.3.php +15 -0
  51. app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.5.0-0.5.1.php +23 -0
  52. app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.5.3-0.5.4.php +7 -0
  53. app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.5.4-1.1.0.php +17 -0
  54. app/design/adminhtml/default/default/layout/aoe_scheduler/aoe_scheduler.xml +64 -40
  55. app/design/adminhtml/default/default/template/aoe_scheduler/instructions.phtml +48 -0
  56. app/design/adminhtml/default/default/template/aoe_scheduler/timeline.phtml +53 -50
  57. app/design/adminhtml/default/default/template/aoe_scheduler/timeline_detail.phtml +56 -34
  58. app/etc/modules/Aoe_Scheduler.xml +6 -6
  59. app/locale/sv_SE/Aoe_Scheduler.csv +10 -0
  60. package.xml +8 -8
  61. scheduler_cron.sh +179 -0
  62. shell/scheduler.php +398 -172
  63. skin/adminhtml/default/default/aoe_scheduler/Images/animation.gif +0 -0
  64. skin/adminhtml/default/default/aoe_scheduler/Images/animation2.gif +0 -0
  65. skin/adminhtml/default/default/aoe_scheduler/Images/red.png +0 -0
  66. skin/adminhtml/default/default/aoe_scheduler/JavaScript/common.js +58 -55
  67. skin/adminhtml/default/default/aoe_scheduler/JavaScript/instructions.js +24 -0
  68. skin/adminhtml/default/default/aoe_scheduler/JavaScript/tooltip.js +161 -161
  69. skin/adminhtml/default/default/aoe_scheduler/StyleSheet/bars.css +13 -1
  70. skin/adminhtml/default/default/aoe_scheduler/StyleSheet/instructions.css +17 -0
  71. skin/adminhtml/default/default/aoe_scheduler/StyleSheet/timeline.css +84 -60
app/code/community/Aoe/Scheduler/Block/Adminhtml/Cron.php DELETED
@@ -1,57 +0,0 @@
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 DELETED
@@ -1,138 +0,0 @@
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/Instructions.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Job block
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Instructions extends Mage_Adminhtml_Block_Template
9
+ {
10
+
11
+ public function getCurrentUser()
12
+ {
13
+ return trim(shell_exec('whoami'));
14
+ }
15
+
16
+ public function getMagentoRootpath()
17
+ {
18
+ return Mage::getBaseDir();
19
+ }
20
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Job.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Job block
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Job extends Mage_Adminhtml_Block_Widget_Grid_Container
9
+ {
10
+ /**
11
+ * Constructor for Job Adminhtml Block
12
+ */
13
+ public function __construct()
14
+ {
15
+ $this->_blockGroup = 'aoe_scheduler';
16
+ $this->_controller = 'adminhtml_job';
17
+ $this->_headerText = $this->__('Available Jobs');
18
+ parent::__construct();
19
+ }
20
+
21
+ /**
22
+ * Prepare layout
23
+ *
24
+ * @return $this
25
+ */
26
+ protected function _prepareLayout()
27
+ {
28
+ $this->removeButton('add');
29
+ $this->_addButton(
30
+ 'add_new_job',
31
+ array(
32
+ 'label' => $this->__('Create new job'),
33
+ 'onclick' => "setLocation('{$this->getUrl('*/*/new')}')",
34
+ 'class' => 'add'
35
+ )
36
+ );
37
+ $this->_addButton(
38
+ 'add_new',
39
+ array(
40
+ 'label' => $this->__('Generate Schedule'),
41
+ 'onclick' => "setLocation('{$this->getUrl('*/*/generateSchedule')}')",
42
+ )
43
+ );
44
+ $this->_addButton(
45
+ 'configure',
46
+ array(
47
+ 'label' => $this->__('Cron Configuration'),
48
+ 'onclick' => "setLocation('{$this->getUrl('adminhtml/system_config/edit', array('section' => 'system'))}#system_cron')",
49
+ )
50
+ );
51
+ return parent::_prepareLayout();
52
+ }
53
+
54
+
55
+ /**
56
+ * Returns the CSS class for the header
57
+ *
58
+ * Usually 'icon-head' and a more precise class is returned. We return
59
+ * only an empty string to avoid spacing on the left of the header as we
60
+ * don't have an icon.
61
+ *
62
+ * @return string
63
+ */
64
+ public function getHeaderCssClass()
65
+ {
66
+ return '';
67
+ }
68
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Job edit container
4
+ *
5
+ * @author Fabrizio Branca
6
+ * @since 2014-08-09
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Job_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
9
+ {
10
+
11
+ public function __construct()
12
+ {
13
+ parent::__construct();
14
+ if ($this->getJob()->isOverlay()) {
15
+ $this->updateButton('delete', 'label', $this->__('Reset overlay'));
16
+ } elseif ($this->getJob()->isXmlOnly()) {
17
+ $this->removeButton('delete');
18
+ }
19
+ $this->removeButton('reset');
20
+ }
21
+
22
+ /**
23
+ * Internal constructor
24
+ *
25
+ */
26
+ protected function _construct()
27
+ {
28
+ parent::_construct();
29
+ $this->_objectId = 'job_code';
30
+ $this->_blockGroup = 'aoe_scheduler';
31
+ $this->_controller = 'adminhtml_job';
32
+ }
33
+
34
+ /**
35
+ * Get job
36
+ *
37
+ * @return Aoe_Scheduler_Model_Job
38
+ */
39
+ public function getJob()
40
+ {
41
+ return Mage::registry('current_job_instance');
42
+ }
43
+
44
+
45
+ /**
46
+ * Return translated header text depending on creating/editing action
47
+ *
48
+ * @return string
49
+ */
50
+ public function getHeaderText()
51
+ {
52
+ if ($this->getJob()->getId()) {
53
+ return $this->__('Job "%s"', $this->escapeHtml($this->getJob()->getJobCode()));
54
+ } else {
55
+ return $this->__('New Job');
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Return save url for edit form
61
+ *
62
+ * @return string
63
+ */
64
+ public function getSaveUrl()
65
+ {
66
+ return $this->getUrl('*/*/save', array('_current'=>true, 'back'=>null));
67
+ }
68
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit/Form.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Job edit form
4
+ *
5
+ * @author Fabrizio Branca
6
+ * @since 2014-08-09
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Job_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
9
+ {
10
+ /**
11
+ * Prepare form before rendering HTML
12
+ *
13
+ * @return $this
14
+ */
15
+ protected function _prepareForm()
16
+ {
17
+ $form = new Varien_Data_Form(array(
18
+ 'id' => 'edit_form',
19
+ 'action' => $this->getData('action'),
20
+ 'method' => 'post'
21
+ ));
22
+ $form->setUseContainer(true);
23
+ $this->setForm($form);
24
+ return parent::_prepareForm();
25
+ }
26
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit/Tab/Form.php ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Job form tab block
5
+ *
6
+ * @author Fabrizio Branca
7
+ * @since 2014-08-09
8
+ */
9
+ class Aoe_Scheduler_Block_Adminhtml_Job_Edit_Tab_Form extends Mage_Adminhtml_Block_Widget_Form implements Mage_Adminhtml_Block_Widget_Tab_Interface
10
+ {
11
+ /**
12
+ * Internal constructor
13
+ *
14
+ */
15
+ protected function _construct()
16
+ {
17
+ parent::_construct();
18
+ $this->setActive(true);
19
+ }
20
+
21
+ /**
22
+ * Prepare label for tab
23
+ *
24
+ * @return string
25
+ */
26
+ public function getTabLabel()
27
+ {
28
+ return $this->__('General');
29
+ }
30
+
31
+ /**
32
+ * Prepare title for tab
33
+ *
34
+ * @return string
35
+ */
36
+ public function getTabTitle()
37
+ {
38
+ return $this->__('General');
39
+ }
40
+
41
+ /**
42
+ * Returns status flag about this tab can be shown or not
43
+ *
44
+ * @return true
45
+ */
46
+ public function canShowTab()
47
+ {
48
+ return true;
49
+ }
50
+
51
+ /**
52
+ * Returns status flag about this tab hidden or not
53
+ *
54
+ * @return true
55
+ */
56
+ public function isHidden()
57
+ {
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Get job
63
+ *
64
+ * @return Aoe_Scheduler_Model_Job
65
+ */
66
+ public function getJob()
67
+ {
68
+ return Mage::registry('current_job_instance');
69
+ }
70
+
71
+
72
+ /**
73
+ * Prepare form before rendering HTML
74
+ *
75
+ * @return Mage_Widget_Block_Adminhtml_Widget_Instance_Edit_Tab_Main
76
+ */
77
+ protected function _prepareForm()
78
+ {
79
+ $job = $this->getJob();
80
+ $form = new Varien_Data_Form(
81
+ array(
82
+ 'id' => 'edit_form',
83
+ 'action' => $this->getData('action'),
84
+ 'method' => 'post'
85
+ )
86
+ );
87
+
88
+ $fieldset = $form->addFieldset('base_fieldset', array('legend' => $this->__('General')));
89
+ $this->_addElementTypes($fieldset);
90
+
91
+ $fieldset->addField(
92
+ 'job_code',
93
+ 'text',
94
+ array(
95
+ 'name' => 'job_code',
96
+ 'label' => $this->__('Job code'),
97
+ 'title' => $this->__('Job code'),
98
+ 'class' => '',
99
+ 'required' => true,
100
+ 'disabled' => $job->getJobCode() ? true : false,
101
+ )
102
+ );
103
+
104
+ $fieldset->addField(
105
+ 'name',
106
+ 'text',
107
+ array(
108
+ 'name' => 'name',
109
+ 'label' => $this->__('Name'),
110
+ 'title' => $this->__('Name'),
111
+ 'class' => '',
112
+ 'required' => false,
113
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'name'),
114
+ )
115
+ );
116
+
117
+ $fieldset->addField(
118
+ 'short_description',
119
+ 'textarea',
120
+ array(
121
+ 'name' => 'short_description',
122
+ 'label' => $this->__('Short description'),
123
+ 'title' => $this->__('Short description'),
124
+ 'class' => '',
125
+ 'required' => false,
126
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'short_description'),
127
+ )
128
+ );
129
+
130
+ $fieldset->addField(
131
+ 'description',
132
+ 'textarea',
133
+ array(
134
+ 'name' => 'description',
135
+ 'label' => $this->__('Description'),
136
+ 'title' => $this->__('Description'),
137
+ 'class' => '',
138
+ 'required' => false,
139
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'description'),
140
+ )
141
+ );
142
+
143
+ $fieldset->addField(
144
+ 'run_model',
145
+ 'text',
146
+ array(
147
+ 'name' => 'run_model',
148
+ 'label' => $this->__('Run model'),
149
+ 'title' => $this->__('Run model'),
150
+ 'class' => '',
151
+ 'required' => true,
152
+ 'note' => $this->__('e.g. "aoe_scheduler/task_heartbeat::run"'),
153
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'run/model'),
154
+ )
155
+ );
156
+
157
+ $fieldset->addField(
158
+ 'is_active',
159
+ 'select',
160
+ array(
161
+ 'name' => 'is_active',
162
+ 'label' => $this->__('Status'),
163
+ 'title' => $this->__('Status'),
164
+ 'required' => true,
165
+ 'options' => array(
166
+ 0 => $this->__('Disabled'),
167
+ 1 => $this->__('Enabled')
168
+ ),
169
+ 'after_element_html' => $this->getOriginalValueSnippetFlag($job, 'is_active', 'Enabled', 'Disabled'),
170
+ )
171
+ );
172
+
173
+ $fieldset = $form->addFieldset('cron_fieldset', array('legend' => $this->__('Scheduling')));
174
+ $this->_addElementTypes($fieldset);
175
+
176
+ $fieldset->addField(
177
+ 'schedule_config_path',
178
+ 'text',
179
+ array(
180
+ 'name' => 'schedule_config_path',
181
+ 'label' => $this->__('Cron configuration path'),
182
+ 'title' => $this->__('Cron configuration path'),
183
+ 'class' => '',
184
+ 'required' => false,
185
+ 'note' => $this->__(
186
+ 'Path to system configuration containing the cron configuration for this job. (e.g. system/cron/scheduler_cron_expr_heartbeat) This configuration - if set - has a higher priority over the cron expression configured with the job directly.'
187
+ ),
188
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'schedule/config_path'),
189
+ )
190
+ );
191
+
192
+ $fieldset->addField(
193
+ 'schedule_cron_expr',
194
+ 'text',
195
+ array(
196
+ 'name' => 'schedule_cron_expr',
197
+ 'label' => $this->__('Cron expression'),
198
+ 'title' => $this->__('Cron expression'),
199
+ 'required' => false,
200
+ 'note' => $this->__('e.g "*/5 * * * *" or "always"'),
201
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'schedule/cron_expr'),
202
+ )
203
+ );
204
+
205
+ $fieldset = $form->addFieldset('parameter_fieldset', array('legend' => $this->__('Extras')));
206
+ $this->_addElementTypes($fieldset);
207
+
208
+ $fieldset->addField(
209
+ 'parameters',
210
+ 'textarea',
211
+ array(
212
+ 'name' => 'parameters',
213
+ 'label' => $this->__('Parameters'),
214
+ 'title' => $this->__('Parameters'),
215
+ 'class' => 'textarea',
216
+ 'required' => false,
217
+ 'note' => $this->__('These parameters will be passed to the model. It is up to the model to specify the format of these parameters (e.g. json/xml/...'),
218
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'parameters'),
219
+ )
220
+ );
221
+
222
+ $fieldset->addField(
223
+ 'groups',
224
+ 'textarea',
225
+ array(
226
+ 'name' => 'groups',
227
+ 'label' => $this->__('Groups'),
228
+ 'title' => $this->__('Groups'),
229
+ 'class' => 'textarea',
230
+ 'required' => false,
231
+ 'note' => $this->__('Comma-separated list of groups (tags) that can be used with the include/exclude command line options of scheduler.php'),
232
+ 'after_element_html' => $this->getOriginalValueSnippet($job, 'groups'),
233
+ )
234
+ );
235
+
236
+ $this->setForm($form);
237
+
238
+ return parent::_prepareForm();
239
+ }
240
+
241
+ protected function getOriginalValueSnippet(Aoe_Scheduler_Model_Job $job, $key)
242
+ {
243
+ if ($job->isDbOnly()) {
244
+ return '';
245
+ }
246
+
247
+ $xmlJobData = $job->getXmlJobData();
248
+ if (!array_key_exists($key, $xmlJobData)) {
249
+ return '';
250
+ }
251
+
252
+ $value = $xmlJobData[$key];
253
+ if ($value === null || $value === '') {
254
+ $value = '<em>empty</em>';
255
+ }
256
+
257
+ return '<p class="original" style="background-color: white"><strong>Original:</strong> ' . $value . '</p>';
258
+ }
259
+
260
+ protected function getOriginalValueSnippetFlag(Aoe_Scheduler_Model_Job $job, $key, $trueLabel, $falseLabel)
261
+ {
262
+ if ($job->isDbOnly()) {
263
+ return '';
264
+ }
265
+
266
+ $xmlJobData = $job->getXmlJobData();
267
+ if (!array_key_exists($key, $xmlJobData)) {
268
+ return '';
269
+ }
270
+
271
+ $value = $this->__(!in_array($xmlJobData[$key], array(false, 'false', 0, '0'), true) ? $trueLabel : $falseLabel);
272
+
273
+ return '<p class="original" style="background-color: white"><strong>Original:</strong> ' . $value . '</p>';
274
+ }
275
+
276
+ /**
277
+ * Initialize form fields values
278
+ *
279
+ * @return $this
280
+ */
281
+ protected function _initFormValues()
282
+ {
283
+ $this->getForm()->addValues($this->getJob()->getData());
284
+ return parent::_initFormValues();
285
+ }
286
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Edit/Tabs.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Job edit tabs container
4
+ *
5
+ * @author Fabrizio Branca
6
+ * @since 2014-08-09
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Job_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs
9
+ {
10
+ /**
11
+ * Internal constructor
12
+ *
13
+ */
14
+ protected function _construct()
15
+ {
16
+ parent::_construct();
17
+ $this->setId('job_tabs');
18
+ $this->setDestElementId('edit_form');
19
+ $this->setTitle($this->__('Job'));
20
+ }
21
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Job/Grid.php ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Block: Job grid
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Block_Adminhtml_Job_Grid extends Mage_Adminhtml_Block_Widget_Grid
9
+ {
10
+ /**
11
+ * Constructor
12
+ */
13
+ public function __construct()
14
+ {
15
+ parent::__construct();
16
+ $this->setId('job_grid');
17
+ $this->_filterVisibility = false;
18
+ $this->_pagerVisibility = false;
19
+ }
20
+
21
+
22
+ /**
23
+ * Preparation of the data that is displayed by the grid.
24
+ *
25
+ * @return $this
26
+ */
27
+ protected function _prepareCollection()
28
+ {
29
+ /** @var Aoe_Scheduler_Model_Resource_Job_Collection $collection */
30
+ $collection = Mage::getSingleton('aoe_scheduler/job')->getCollection();
31
+ $this->setCollection($collection);
32
+ return parent::_prepareCollection();
33
+ }
34
+
35
+
36
+ /**
37
+ * Add mass-actions to grid
38
+ *
39
+ * @return $this
40
+ */
41
+ protected function _prepareMassaction()
42
+ {
43
+ $this->setMassactionIdField('id');
44
+ $this->getMassactionBlock()->setFormFieldName('codes');
45
+ $this->getMassactionBlock()->addItem(
46
+ 'schedule',
47
+ array(
48
+ 'label' => $this->__('Schedule now'),
49
+ 'url' => $this->getUrl('*/*/scheduleNow'),
50
+ )
51
+ );
52
+ if (Mage::getStoreConfig('system/cron/enableRunNow')) {
53
+ $this->getMassactionBlock()->addItem(
54
+ 'run',
55
+ array(
56
+ 'label' => $this->__('Run now'),
57
+ 'url' => $this->getUrl('*/*/runNow'),
58
+ )
59
+ );
60
+ }
61
+ $this->getMassactionBlock()->addItem(
62
+ 'disable',
63
+ array(
64
+ 'label' => $this->__('Disable'),
65
+ 'url' => $this->getUrl('*/*/disable'),
66
+ )
67
+ );
68
+ $this->getMassactionBlock()->addItem(
69
+ 'enable',
70
+ array(
71
+ 'label' => $this->__('Enable'),
72
+ 'url' => $this->getUrl('*/*/enable'),
73
+ )
74
+ );
75
+ return $this;
76
+ }
77
+
78
+
79
+ /**
80
+ * Preparation of the requested columns of the grid
81
+ *
82
+ * @return $this
83
+ */
84
+ protected function _prepareColumns()
85
+ {
86
+ $this->addColumn(
87
+ 'job_code',
88
+ array(
89
+ 'header' => $this->__('Job code'),
90
+ 'index' => 'job_code',
91
+ 'sortable' => false,
92
+ )
93
+ );
94
+
95
+ $this->addColumn(
96
+ 'name',
97
+ array(
98
+ 'header' => $this->__('Name'),
99
+ 'index' => 'name',
100
+ 'sortable' => false,
101
+ )
102
+ );
103
+
104
+ $this->addColumn(
105
+ 'short_description',
106
+ array(
107
+ 'header' => $this->__('Short Description'),
108
+ 'index' => 'short_description',
109
+ 'sortable' => false,
110
+ )
111
+ );
112
+
113
+ $this->addColumn(
114
+ 'schedule_cron_expr',
115
+ array(
116
+ 'header' => $this->__('Cron expression'),
117
+ 'index' => 'schedule_cron_expr',
118
+ 'sortable' => false,
119
+ 'frame_callback' => array($this, 'decorateCronExpression'),
120
+ )
121
+ );
122
+ $this->addColumn(
123
+ 'run_model',
124
+ array(
125
+ 'header' => $this->__('Run model'),
126
+ 'index' => 'run_model',
127
+ 'sortable' => false,
128
+ )
129
+ );
130
+ $this->addColumn(
131
+ 'parameters',
132
+ array(
133
+ 'header' => $this->__('Parameters'),
134
+ 'index' => 'parameters',
135
+ 'sortable' => false,
136
+ 'frame_callback' => array($this, 'decorateTrim'),
137
+ )
138
+ );
139
+ $this->addColumn(
140
+ 'groups',
141
+ array(
142
+ 'header' => $this->__('Groups'),
143
+ 'index' => 'groups',
144
+ 'sortable' => false,
145
+ 'frame_callback' => array($this, 'decorateTrim'),
146
+ )
147
+ );
148
+ $this->addColumn(
149
+ 'type',
150
+ array(
151
+ 'header' => $this->__('Type'),
152
+ 'sortable' => false,
153
+ 'frame_callback' => array($this, 'decorateType'),
154
+ )
155
+ );
156
+ $this->addColumn(
157
+ 'is_active',
158
+ array(
159
+ 'header' => $this->__('Status'),
160
+ 'index' => 'is_active',
161
+ 'sortable' => false,
162
+ 'frame_callback' => array($this, 'decorateStatus'),
163
+ )
164
+ );
165
+ return parent::_prepareColumns();
166
+ }
167
+
168
+
169
+ /**
170
+ * Decorate status column values
171
+ *
172
+ * @param $value
173
+ *
174
+ * @return string
175
+ */
176
+ public function decorateStatus($value)
177
+ {
178
+ $cell = sprintf(
179
+ '<span class="grid-severity-%s"><span>%s</span></span>',
180
+ $value ? 'notice' : 'critical',
181
+ $this->__($value ? 'Enabled' : 'Disabled')
182
+ );
183
+ return $cell;
184
+ }
185
+
186
+
187
+ /**
188
+ * Decorate cron expression
189
+ *
190
+ * @param $value
191
+ * @param Aoe_Scheduler_Model_Job $job
192
+ *
193
+ * @return string
194
+ */
195
+ public function decorateCronExpression($value, Aoe_Scheduler_Model_Job $job)
196
+ {
197
+ return $job->getCronExpression();
198
+ }
199
+
200
+
201
+ /**
202
+ * Decorate cron expression
203
+ *
204
+ * @param $value
205
+ *
206
+ * @return string
207
+ */
208
+ public function decorateTrim($value)
209
+ {
210
+ return sprintf('<span title="%s">%s</span>', $value, mb_strimwidth($value, 0, 40, "..."));
211
+ }
212
+
213
+
214
+ /**
215
+ * Decorate cron expression
216
+ *
217
+ * @param $value
218
+ * @param Aoe_Scheduler_Model_Job $job
219
+ *
220
+ * @return string
221
+ */
222
+ public function decorateType($value, Aoe_Scheduler_Model_Job $job)
223
+ {
224
+ return $job->getType();
225
+ }
226
+
227
+ /**
228
+ * Row click url
229
+ *
230
+ * @param object $row
231
+ *
232
+ * @return string
233
+ */
234
+ public function getRowUrl($row)
235
+ {
236
+ return $this->getUrl('*/*/edit', array('job_code' => $row->getJobCode()));
237
+ }
238
+
239
+ /**
240
+ * Helper function to receive grid functionality urls for current grid
241
+ *
242
+ * @return string Requested URL
243
+ */
244
+ public function getGridUrl()
245
+ {
246
+ return $this->getUrl('adminhtml/job/index', array('_current' => true));
247
+ }
248
+ }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler.php CHANGED
@@ -5,53 +5,57 @@
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
  }
5
  *
6
  * @author Fabrizio Branca
7
  */
8
+ class Aoe_Scheduler_Block_Adminhtml_Scheduler extends Mage_Adminhtml_Block_Widget_Grid_Container
9
+ {
10
+ /**
11
+ * Constructor for Scheduler Adminhtml Block
12
+ */
13
+ public function __construct()
14
+ {
15
+ $this->_blockGroup = 'aoe_scheduler';
16
+ $this->_controller = 'adminhtml_scheduler';
17
+ $this->_headerText = $this->__('Scheduled tasks');
18
+ parent::__construct();
19
+ }
20
+
21
+
22
+ /**
23
+ * Prepare layout
24
+ *
25
+ * @return $this
26
+ */
27
+ protected function _prepareLayout()
28
+ {
29
+ $this->removeButton('add');
30
+ $this->_addButton(
31
+ 'add_new',
32
+ array(
33
+ 'label' => $this->__('Generate Schedule'),
34
+ 'onclick' => "setLocation('{$this->getUrl('*/*/generateSchedule')}')",
35
+ )
36
+ );
37
+ $this->_addButton(
38
+ 'configure',
39
+ array(
40
+ 'label' => $this->__('Cron Configuration'),
41
+ 'onclick' => "setLocation('{$this->getUrl('adminhtml/system_config/edit', array('section' => 'system'))}#system_cron')",
42
+ )
43
+ );
44
+ return parent::_prepareLayout();
45
+ }
46
+
47
+
48
+ /**
49
+ * Returns the CSS class for the header
50
+ *
51
+ * Usually 'icon-head' and a more precise class is returned. We return
52
+ * only an empty string to avoid spacing on the left of the header as we
53
+ * don't have an icon.
54
+ *
55
+ * @return string
56
+ */
57
+ public function getHeaderCssClass()
58
+ {
59
+ return '';
60
+ }
61
  }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Scheduler/Grid.php CHANGED
@@ -3,168 +3,239 @@
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
  }
3
  /**
4
  * Block: Scheduler Grid
5
  *
6
+ * @author Fabrizio Branca
7
  */
8
+ class Aoe_Scheduler_Block_Adminhtml_Scheduler_Grid extends Mage_Adminhtml_Block_Widget_Grid
9
+ {
10
+ /**
11
+ * Constructor. Set basic parameters
12
+ */
13
+ public function __construct()
14
+ {
15
+ parent::__construct();
16
+ $this->setId('scheduler_grid');
17
+ $this->setUseAjax(false);
18
+ $this->setDefaultSort('scheduled_at');
19
+ $this->setDefaultDir('DESC');
20
+ $this->setSaveParametersInSession(true);
21
+ }
22
+
23
+
24
+ /**
25
+ * Preparation of the data that is displayed by the grid.
26
+ *
27
+ * @return $this
28
+ */
29
+ protected function _prepareCollection()
30
+ {
31
+ /** @var Mage_Cron_Model_Resource_Schedule_Collection $collection */
32
+ $collection = Mage::getModel('cron/schedule')->getCollection();
33
+ $this->setCollection($collection);
34
+ return parent::_prepareCollection();
35
+ }
36
+
37
+
38
+ /**
39
+ * Add mass-actions to grid
40
+ *
41
+ * @return $this
42
+ */
43
+ protected function _prepareMassaction()
44
+ {
45
+ $this->setMassactionIdField('schedule_id');
46
+ $this->getMassactionBlock()->setFormFieldName('schedule_ids');
47
+ $this->getMassactionBlock()->addItem(
48
+ 'delete',
49
+ array(
50
+ 'label' => $this->__('Delete'),
51
+ 'url' => $this->getUrl('*/*/delete'),
52
+ )
53
+ );
54
+ $this->getMassactionBlock()->addItem(
55
+ 'kill',
56
+ array(
57
+ 'label' => $this->__('Kill'),
58
+ 'url' => $this->getUrl('*/*/kill'),
59
+ )
60
+ );
61
+ return $this;
62
+ }
63
+
64
+
65
+ /**
66
+ * Preparation of the requested columns of the grid
67
+ *
68
+ * @return $this
69
+ */
70
+ protected function _prepareColumns()
71
+ {
72
+ $viewHelper = $this->helper('aoe_scheduler/data');
73
+
74
+ $this->addColumn(
75
+ 'schedule_id',
76
+ array(
77
+ 'header' => $this->__('Id'),
78
+ 'index' => 'schedule_id',
79
+ )
80
+ );
81
+ $config = array(
82
+ 'header' => $this->__('Job'),
83
+ 'index' => 'job_code',
84
+ );
85
+ switch (Mage::getStoreConfig('system/cron/listCodeFilterType')) {
86
+ case Aoe_Scheduler_Model_Adminhtml_System_Config_Source_List_Code_Filtertype::SELECT:
87
+ $config['type'] = 'options';
88
+ $config['options'] = Mage::getSingleton('aoe_scheduler/job')->getCollection()->toOptionHash('job_code', 'name');
89
+ break;
90
+ case Aoe_Scheduler_Model_Adminhtml_System_Config_Source_List_Code_Filtertype::TEXT:
91
+ default:
92
+ $config['type'] = 'text';
93
+ }
94
+ $this->addColumn(
95
+ 'job_code',
96
+ $config
97
+ );
98
+ $this->addColumn(
99
+ 'created_at',
100
+ array(
101
+ 'header' => $this->__('Created'),
102
+ 'index' => 'created_at',
103
+ 'type' => 'datetime'
104
+ )
105
+ );
106
+ $this->addColumn(
107
+ 'scheduled_at',
108
+ array(
109
+ 'header' => $this->__('Scheduled'),
110
+ 'index' => 'scheduled_at',
111
+ 'type' => 'datetime'
112
+ )
113
+ );
114
+ $this->addColumn(
115
+ 'executed_at',
116
+ array(
117
+ 'header' => $this->__('Executed'),
118
+ 'index' => 'executed_at',
119
+ 'type' => 'datetime'
120
+ )
121
+ );
122
+ $this->addColumn(
123
+ 'last_seen',
124
+ array(
125
+ 'header' => $this->__('Last seen'),
126
+ 'index' => 'last_seen',
127
+ 'type' => 'datetime'
128
+ )
129
+ );
130
+ $this->addColumn(
131
+ 'eta',
132
+ array(
133
+ 'header' => $this->__('ETA'),
134
+ 'index' => 'eta',
135
+ 'type' => 'datetime'
136
+ )
137
+ );
138
+ $this->addColumn(
139
+ 'finished_at',
140
+ array(
141
+ 'header' => $this->__('Finished'),
142
+ 'index' => 'finished_at',
143
+ 'type' => 'datetime'
144
+ )
145
+ );
146
+ $this->addColumn(
147
+ 'messages',
148
+ array(
149
+ 'header' => $this->__('Messages'),
150
+ 'index' => 'messages',
151
+ 'frame_callback' => array($this, 'decorateMessages')
152
+ )
153
+ );
154
+ $this->addColumn(
155
+ 'host',
156
+ array(
157
+ 'header' => $this->__('Host'),
158
+ 'index' => 'host',
159
+ )
160
+ );
161
+ $this->addColumn(
162
+ 'pid',
163
+ array(
164
+ 'header' => $this->__('Pid'),
165
+ 'index' => 'pid',
166
+ 'width' => '50',
167
+ )
168
+ );
169
+ $this->addColumn(
170
+ 'status',
171
+ array(
172
+ 'header' => $this->__('Status'),
173
+ 'index' => 'status',
174
+ 'frame_callback' => array($viewHelper, 'decorateStatus'),
175
+ 'type' => 'options',
176
+ 'options' => Mage::getSingleton('cron/schedule')->getStatuses()
177
+ )
178
+ );
179
+
180
+ return parent::_prepareColumns();
181
+ }
182
+
183
+
184
+ /**
185
+ * Decorate message
186
+ *
187
+ * @param string $value
188
+ * @param Aoe_Scheduler_Model_Schedule $row
189
+ *
190
+ * @return string
191
+ */
192
+ public function decorateMessages($value, Aoe_Scheduler_Model_Schedule $row)
193
+ {
194
+ $return = '';
195
+ if (!empty($value)) {
196
+ $return .= '<a href="#" onclick="$(\'messages_' . $row->getScheduleId() . '\').toggle(); return false;">' . $this->__('Message') . '</a>';
197
+ $return .= '<div class="schedule-message" id="messages_' . $row->getScheduleId() . '" style="display: none; width: 300px; overflow: auto; font-size: small;"><pre>' . $value . '</pre></div>';
198
+ }
199
+ return $return;
200
+ }
201
+
202
+
203
+ /**
204
+ * Helper function to do after load modifications
205
+ *
206
+ * @return void
207
+ */
208
+ protected function _afterLoadCollection()
209
+ {
210
+ $this->getCollection()->walk('afterLoad');
211
+ parent::_afterLoadCollection();
212
+ }
213
+
214
+
215
+ /**
216
+ * Helper function to add store filter condition
217
+ *
218
+ * @param Mage_Core_Model_Mysql4_Collection_Abstract $collection Data collection
219
+ * @param Mage_Adminhtml_Block_Widget_Grid_Column $column Column information to be filtered
220
+ *
221
+ * @return void
222
+ */
223
+ protected function _filterStoreCondition($collection, $column)
224
+ {
225
+ if (!$value = $column->getFilter()->getValue()) {
226
+ return;
227
+ }
228
+ $this->getCollection()->addStoreFilter($value);
229
+ }
230
+
231
+
232
+ /**
233
+ * Helper function to receive grid functionality urls for current grid
234
+ *
235
+ * @return string Requested URL
236
+ */
237
+ public function getGridUrl()
238
+ {
239
+ return $this->getUrl('adminhtml/scheduler/index', array('_current' => true));
240
+ }
241
  }
app/code/community/Aoe/Scheduler/Block/Adminhtml/Timeline.php CHANGED
@@ -5,207 +5,244 @@
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
  }
5
  *
6
  * @author Fabrizio Branca
7
  */
8
+ class Aoe_Scheduler_Block_Adminhtml_Timeline extends Mage_Adminhtml_Block_Widget_Container
9
+ {
10
+
11
+ /**
12
+ * @var int amount of seconds per pixel
13
+ */
14
+ protected $zoom = 15;
15
+
16
+ /**
17
+ * @var int starttime
18
+ */
19
+ protected $starttime;
20
+
21
+ /**
22
+ * @var int endtime
23
+ */
24
+ protected $endtime;
25
+
26
+ /**
27
+ * @var array schedules
28
+ */
29
+ protected $schedules = array();
30
+
31
+
32
+ /**
33
+ * Constructor
34
+ *
35
+ * @return void
36
+ */
37
+ protected function _construct()
38
+ {
39
+ $this->_headerText = $this->__('Scheduler Timeline');
40
+ $this->loadSchedules();
41
+ parent::_construct();
42
+ }
43
+
44
+
45
+ /**
46
+ * Prepare layout
47
+ *
48
+ * @return $this
49
+ */
50
+ protected function _prepareLayout()
51
+ {
52
+ $this->removeButton('add');
53
+ $this->_addButton('add_new', array(
54
+ 'label' => $this->__('Generate Schedule'),
55
+ 'onclick' => "setLocation('{$this->getUrl('*/*/generateSchedule')}')",
56
+ ));
57
+ $this->_addButton('configure', array(
58
+ 'label' => $this->__('Cron Configuration'),
59
+ 'onclick' => "setLocation('{$this->getUrl('adminhtml/system_config/edit', array('section' => 'system'))}#system_cron')",
60
+ ));
61
+ return parent::_prepareLayout();
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
+ {
73
+ return mktime(date('H', $timestamp), 0, 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
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
+ {
85
+ return mktime(date('H', $timestamp) + 1, 0, 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
86
+ }
87
+
88
+
89
+ /**
90
+ * Load schedules
91
+ *
92
+ * @return void
93
+ */
94
+ protected function loadSchedules()
95
+ {
96
+ /* @var Mage_Cron_Model_Resource_Schedule_Collection $collection */
97
+ $collection = Mage::getModel('cron/schedule')->getCollection();
98
+
99
+ $minDate = null;
100
+ $maxDate = null;
101
+
102
+ foreach ($collection as $schedule) {
103
+ /* @var Aoe_Scheduler_Model_Schedule $schedule */
104
+ $startTime = $schedule->getStarttime();
105
+ if (empty($startTime)) {
106
+ continue;
107
+ }
108
+ $minDate = is_null($minDate) ? $startTime : min($minDate, $startTime);
109
+ $maxDate = is_null($maxDate) ? $startTime : max($maxDate, $startTime);
110
+ $this->schedules[$schedule->getJobCode()][] = $schedule;
111
+ }
112
+
113
+ $this->starttime = $this->hourFloor(strtotime($minDate));
114
+ $this->endtime = $this->hourCeil(strtotime($maxDate));
115
+ }
116
+
117
+
118
+ /**
119
+ * Get timeline panel width
120
+ *
121
+ * @return int
122
+ */
123
+ public function getTimelinePanelWidth()
124
+ {
125
+ return ($this->endtime - $this->starttime) / $this->zoom;
126
+ }
127
+
128
+
129
+ /**
130
+ * Get "now" line
131
+ *
132
+ * @return float
133
+ */
134
+ public function getNowline()
135
+ {
136
+ return (time() - $this->starttime) / $this->zoom;
137
+ }
138
+
139
+
140
+ /**
141
+ * Get all available job codes
142
+ *
143
+ * @return array
144
+ */
145
+ public function getAvailableJobCodes()
146
+ {
147
+ return array_keys($this->schedules);
148
+ }
149
+
150
+
151
+ /**
152
+ * Get schedules for given code
153
+ *
154
+ * @param string $code
155
+ * @return array
156
+ */
157
+ public function getSchedulesForCode($code)
158
+ {
159
+ return $this->schedules[$code];
160
+ }
161
+
162
+
163
+ /**
164
+ * Get starttime
165
+ *
166
+ * @return int
167
+ */
168
+ public function getStarttime()
169
+ {
170
+ return $this->starttime;
171
+ }
172
+
173
+
174
+ /**
175
+ * Get endtime
176
+ *
177
+ * @return int
178
+ */
179
+ public function getEndtime()
180
+ {
181
+ return $this->endtime;
182
+ }
183
+
184
+
185
+ /**
186
+ * Get attributes for div representing a gantt element
187
+ *
188
+ * @param Aoe_Scheduler_Model_Schedule $schedule
189
+ * @return string
190
+ */
191
+ public function getGanttDivAttributes(Aoe_Scheduler_Model_Schedule $schedule)
192
+ {
193
+
194
+ if ($schedule->getStatus() == Aoe_Scheduler_Model_Schedule::STATUS_RUNNING) {
195
+ $duration = time() - strtotime($schedule->getStarttime());
196
+ } else {
197
+ $duration = $schedule->getDuration() ? $schedule->getDuration() : 0;
198
+ }
199
+ $duration = $duration / $this->zoom;
200
+ $duration = ceil($duration / 4) * 4 - 1; // round to numbers dividable by 4, then remove 1 px border
201
+ $duration = max($duration, 3);
202
+
203
+ $offset = (strtotime($schedule->getStarttime()) - $this->starttime) / $this->zoom;
204
+
205
+ if ($offset < 0) { // cut bar
206
+ $duration += $offset;
207
+ $offset = 0;
208
+ }
209
+
210
+ $result = sprintf(
211
+ '<div class="task %s" id="id_%s" style="width: %spx; left: %spx;" ></div>',
212
+ $schedule->getStatus(),
213
+ $schedule->getScheduleId(),
214
+ $duration,
215
+ $offset
216
+ );
217
+
218
+ if ($schedule->getStatus() == Aoe_Scheduler_Model_Schedule::STATUS_RUNNING) {
219
+ $offset += $duration;
220
+
221
+ $duration = strtotime($schedule->getEta()) - time();
222
+ $duration = $duration / $this->zoom;
223
+
224
+ $result = sprintf(
225
+ '<div class="estimation" style="width: %spx; left: %spx;" ></div>',
226
+ $duration,
227
+ $offset
228
+ ) . $result;
229
+ }
230
+
231
+ return $result;
232
+ }
233
+
234
+ /**
235
+ * Check if symlinks are allowed
236
+ *
237
+ * @return string
238
+ */
239
+ public function _toHtml()
240
+ {
241
+ $html = parent::_toHtml();
242
+ if (!$html && !Mage::getStoreConfigFlag('dev/template/allow_symlink')) {
243
+ $url = $this->getUrl('adminhtml/system_config/edit', array('section' => 'dev')) . '#dev_template';
244
+ $html = $this->__('Warning: You installed Aoe_Scheduler using symlinks (e.g. via modman), but forgot to allow symlinks for template files! Please go to <a href="%s">System > Configuration > Advanced > Developer > Template Settings</a> and set "Allow Symlinks" to "yes"', $url);
245
+ }
246
+ return $html;
247
+ }
248
  }
app/code/community/Aoe/Scheduler/Block/Adminhtml/TimelineDetail.php CHANGED
@@ -5,40 +5,40 @@
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
  }
5
  *
6
  * @author Fabrizio Branca
7
  */
8
+ class Aoe_Scheduler_Block_Adminhtml_TimelineDetail extends Mage_Adminhtml_Block_Template
9
+ {
10
+
11
+ /**
12
+ * @var string path to default template
13
+ */
14
+ protected $_template = 'aoe_scheduler/timeline_detail.phtml';
15
+
16
+ /**
17
+ * @var Aoe_Scheduler_Model_Schedule
18
+ */
19
+ protected $schedule;
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
+ {
30
+ $this->schedule = $schedule;
31
+ return $this;
32
+ }
33
+
34
+
35
+ /**
36
+ * Get schedule
37
+ *
38
+ * @return Aoe_Scheduler_Block_Adminhtml_TimelineDetail
39
+ */
40
+ public function getSchedule()
41
+ {
42
+ return $this->schedule;
43
+ }
44
  }
app/code/community/Aoe/Scheduler/Controller/AbstractController.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Abstract controller
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ abstract class Aoe_Scheduler_Controller_AbstractController extends Mage_Adminhtml_Controller_Action
9
+ {
10
+
11
+ public function preDispatch()
12
+ {
13
+ parent::preDispatch();
14
+ if ($this->getRequest()->getActionName() != 'error' && !$this->checkLocalCodePool()) {
15
+ $this->_forward('error');
16
+ }
17
+ }
18
+
19
+ public function errorAction()
20
+ {
21
+ $this->loadLayout();
22
+ $this->_setActiveMenu('system');
23
+ $this->renderLayout();
24
+ }
25
+
26
+ /**
27
+ * Index action
28
+ *
29
+ * @return void
30
+ */
31
+ public function indexAction()
32
+ {
33
+
34
+ if (!Mage::getStoreConfigFlag('system/cron/enable')) {
35
+ $this->_getSession()->addNotice($this->__('Scheduler is disabled in configuration (system/cron/enable). No schedules will be executed.'));
36
+ } else {
37
+ $this->checkHeartbeat();
38
+ }
39
+
40
+ // check configuration
41
+ if (Mage::getStoreConfig('system/cron/schedule_generate_every') > Mage::getStoreConfig('system/cron/schedule_ahead_for')) {
42
+ $this->_getSession()->addError($this->__('Configuration problem. "Generate Schedules Every" is higher than "Schedule Ahead for". Please check your <a href="%s">configuration settings</a>.', $this->getUrl('adminhtml/system_config/edit', array('section' => 'system')) . '#system_cron'));
43
+ }
44
+
45
+ $this->loadLayout();
46
+
47
+ $this->_setActiveMenu('system');
48
+ $this->renderLayout();
49
+ }
50
+
51
+ /**
52
+ * Aoe_Scheduler used to live in the local code pool.
53
+ * When newer version are installed without removing the old files Aoe_Scheduler will produce fatal errors.
54
+ * This is an attempt to handle this a little better.
55
+ */
56
+ protected function checkLocalCodePool()
57
+ {
58
+ $helper = Mage::helper('aoe_scheduler/compatibility'); /* @var $helper Aoe_Scheduler_Helper_Compatibility */
59
+ if ($helper->oldConfigXmlExists()) {
60
+ $this->_getSession()->addError($this->__('Looks like you have an older version of Aoe_Scheduler installed that lived in the local code pool. Please delete everything under "%s"', $helper->getLocalCodeDir()));
61
+ return false;
62
+ }
63
+ return true;
64
+ }
65
+
66
+ /**
67
+ * Check heartbeat
68
+ */
69
+ protected function checkHeartbeat()
70
+ {
71
+ if (!Mage::helper('aoe_scheduler')->isDisabled('aoescheduler_heartbeat')) {
72
+ $lastHeartbeat = Mage::helper('aoe_scheduler')->getLastHeartbeat();
73
+ if ($lastHeartbeat === false) {
74
+ // no heartbeat task found
75
+ $this->_getSession()->addError($this->__('No heartbeat task found. Check if cron is configured correctly. (<a href="%s">See Instructions</a>)', $this->getUrl('adminhtml/instructions/index')));
76
+ } else {
77
+ $timespan = Mage::helper('aoe_scheduler')->dateDiff($lastHeartbeat);
78
+ if ($timespan <= 5 * 60) {
79
+ $this->_getSession()->addSuccess($this->__('Scheduler is working. (Last heart beat: %s minute(s) ago)', round($timespan / 60)));
80
+ } elseif ($timespan > 5 * 60 && $timespan <= 60 * 60) {
81
+ // heartbeat wasn't executed in the last 5 minutes. Heartbeat schedule could have been modified to not run every five minutes!
82
+ $this->_getSession()->addNotice($this->__('Last heartbeat is older than %s minutes.', round($timespan / 60)));
83
+ } else {
84
+ // everything ok
85
+ $this->_getSession()->addError($this->__('Last heartbeat is older than one hour. Please check your settings and your configuration!'));
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Generate schedule now
93
+ *
94
+ * @return void
95
+ */
96
+ public function generateScheduleAction()
97
+ {
98
+ Mage::app()->removeCache(Mage_Cron_Model_Observer::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT);
99
+
100
+ /* @var Aoe_Scheduler_Model_ScheduleManager $scheduleManager */
101
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager');
102
+ $scheduleManager->generateSchedules();
103
+
104
+ $this->_getSession()->addSuccess($this->__('Generated schedule'));
105
+ $this->_redirect('*/*/index');
106
+ }
107
+ }
app/code/community/Aoe/Scheduler/Helper/Compatibility.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Helper
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Helper_Compatibility extends Mage_Core_Helper_Abstract
9
+ {
10
+
11
+ public function getLocalCodeDir()
12
+ {
13
+ return Mage::getBaseDir('code') . DS . 'local' . DS . 'Aoe' . DS . 'Scheduler';
14
+ }
15
+
16
+ public function oldConfigXmlExists()
17
+ {
18
+ return is_file($this->getLocalCodeDir() . DS . 'etc' . DS . 'config.xml');
19
+ }
20
+ }
app/code/community/Aoe/Scheduler/Helper/Data.php CHANGED
@@ -3,159 +3,322 @@
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
  }
3
  /**
4
  * Helper
5
  *
6
+ * @author Fabrizio Branca
7
  */
8
+ class Aoe_Scheduler_Helper_Data extends Mage_Core_Helper_Abstract
9
+ {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ const XML_PATH_MAX_RUNNING_TIME = 'system/cron/max_running_time';
12
+ const XML_PATH_EMAIL_TEMPLATE = 'system/cron/error_email_template';
13
+ const XML_PATH_EMAIL_IDENTITY = 'system/cron/error_email_identity';
14
+ const XML_PATH_EMAIL_RECIPIENT = 'system/cron/error_email';
15
+
16
+ protected $groupsToJobsMap = null;
17
+
18
+ /**
19
+ * Explodes a string and trims all values for whitespace in the ends.
20
+ * If $onlyNonEmptyValues is set, then all blank ('') values are removed.
21
+ *
22
+ * @see t3lib_div::trimExplode() in TYPO3
23
+ * @param $delim
24
+ * @param string $string
25
+ * @param bool $removeEmptyValues If set, all empty values will be removed in output
26
+ * @return array Exploded values
27
+ */
28
+ public function trimExplode($delim, $string, $removeEmptyValues = false)
29
+ {
30
+ $explodedValues = explode($delim, $string);
31
+
32
+ $result = array_map('trim', $explodedValues);
33
+
34
+ if ($removeEmptyValues) {
35
+ $temp = array();
36
+ foreach ($result as $value) {
37
+ if ($value !== '') {
38
+ $temp[] = $value;
39
+ }
40
+ }
41
+ $result = $temp;
42
+ }
43
+
44
+ return $result;
45
+ }
46
+
47
+ /**
48
+ * Decorate status values
49
+ *
50
+ * @param $status
51
+ * @return string
52
+ */
53
+ public function decorateStatus($status)
54
+ {
55
+ switch ($status) {
56
+ case Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS:
57
+ case Aoe_Scheduler_Model_Schedule::STATUS_DIDNTDOANYTHING:
58
+ $result = '<span class="bar-green"><span>' . $status . '</span></span>';
59
+ break;
60
+ case Aoe_Scheduler_Model_Schedule::STATUS_PENDING:
61
+ $result = '<span class="bar-lightgray"><span>' . $status . '</span></span>';
62
+ break;
63
+ case Aoe_Scheduler_Model_Schedule::STATUS_RUNNING:
64
+ $result = '<span class="bar-yellow"><span>' . $status . '</span></span>';
65
+ break;
66
+ case Aoe_Scheduler_Model_Schedule::STATUS_SKIP_OTHERJOBRUNNING:
67
+ case Aoe_Scheduler_Model_Schedule::STATUS_SKIP_LOCKED:
68
+ case Aoe_Scheduler_Model_Schedule::STATUS_MISSED:
69
+ $result = '<span class="bar-orange"><span>' . $status . '</span></span>';
70
+ break;
71
+ case Aoe_Scheduler_Model_Schedule::STATUS_ERROR:
72
+ case Aoe_Scheduler_Model_Schedule::STATUS_DISAPPEARED:
73
+ case Aoe_Scheduler_Model_Schedule::STATUS_KILLED:
74
+ $result = '<span class="bar-red"><span>' . $status . '</span></span>';
75
+ break;
76
+ default:
77
+ $result = $status;
78
+ break;
79
+ }
80
+ return $result;
81
+ }
82
+
83
+ /**
84
+ * Wrapper for decorateTime to be used a frame_callback to avoid that additional parameters
85
+ * conflict with the method's optional ones
86
+ *
87
+ * @param string $value
88
+ * @return string
89
+ */
90
+ public function decorateTimeFrameCallBack($value)
91
+ {
92
+ return $this->decorateTime($value, false, null);
93
+ }
94
+
95
+ /**
96
+ * Decorate time values
97
+ *
98
+ * @param string $value
99
+ * @param bool $echoToday if true "Today" will be added
100
+ * @param string $dateFormat make sure Y-m-d is in it, if you want to have it replaced
101
+ * @return string
102
+ */
103
+ public function decorateTime($value, $echoToday = false, $dateFormat = null)
104
+ {
105
+ if (empty($value) || $value == '0000-00-00 00:00:00') {
106
+ $value = '';
107
+ } else {
108
+ $value = Mage::getModel('core/date')->date($dateFormat, $value);
109
+ $replace = array(
110
+ Mage::getModel('core/date')->date('Y-m-d ', time()) => $echoToday ? Mage::helper('aoe_scheduler')->__('Today') . ', ' : '', // today
111
+ Mage::getModel('core/date')->date('Y-m-d ', strtotime('+1 day')) => Mage::helper('aoe_scheduler')->__('Tomorrow') . ', ',
112
+ Mage::getModel('core/date')->date('Y-m-d ', strtotime('-1 day')) => Mage::helper('aoe_scheduler')->__('Yesterday') . ', ',
113
+ );
114
+ $value = str_replace(array_keys($replace), array_values($replace), $value);
115
+ }
116
+ return $value;
117
+ }
118
+
119
+ /**
120
+ * Get last heartbeat
121
+ */
122
+ public function getLastHeartbeat()
123
+ {
124
+ return $this->getLastExecutionTime('aoescheduler_heartbeat');
125
+ }
126
+
127
+ /**
128
+ * Get last execution time
129
+ *
130
+ * @param $jobCode
131
+ * @return bool
132
+ */
133
+ public function getLastExecutionTime($jobCode)
134
+ {
135
+ if ($this->isDisabled($jobCode)) {
136
+ return false;
137
+ }
138
+ $schedules = Mage::getModel('cron/schedule')->getCollection(); /* @var $schedules Mage_Cron_Model_Mysql4_Schedule_Collection */
139
+ $schedules->getSelect()->limit(1)->order('executed_at DESC');
140
+ $schedules->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS);
141
+ $schedules->addFieldToFilter('job_code', $jobCode);
142
+ $schedules->load();
143
+ if (count($schedules) == 0) {
144
+ return false;
145
+ }
146
+ $executedAt = $schedules->getFirstItem()->getExecutedAt();
147
+ $value = Mage::getModel('core/date')->date(null, $executedAt);
148
+ return $value;
149
+ }
150
+
151
+ /**
152
+ * Diff between to times;
153
+ *
154
+ * @param $time1
155
+ * @param $time2
156
+ * @return int
157
+ */
158
+ public function dateDiff($time1, $time2 = null)
159
+ {
160
+ if (is_null($time2)) {
161
+ $time2 = Mage::getModel('core/date')->date();
162
+ }
163
+ $time1 = strtotime($time1);
164
+ $time2 = strtotime($time2);
165
+ return $time2 - $time1;
166
+ }
167
+
168
+ /**
169
+ * Check if job code is disabled in configuration
170
+ *
171
+ * @param $jobCode
172
+ * @return bool
173
+ */
174
+ public function isDisabled($jobCode)
175
+ {
176
+ /* @var $job Aoe_Scheduler_Model_Job */
177
+ $job = Mage::getModel('aoe_scheduler/job')->load($jobCode);
178
+ return ($job->getJobCode() && !$job->getIsActive());
179
+ }
180
+
181
+ /**
182
+ * Check if a job matches the group include/exclude lists
183
+ *
184
+ * @param $jobCode
185
+ * @param array $include
186
+ * @param array $exclude
187
+ * @return mixed
188
+ */
189
+ public function matchesIncludeExclude($jobCode, array $include, array $exclude)
190
+ {
191
+ $include = array_filter(array_map('trim', $include));
192
+ $exclude = array_filter(array_map('trim', $exclude));
193
+
194
+ sort($include);
195
+ sort($exclude);
196
+
197
+ $key = $jobCode . '|' . implode(',', $include) . '|' . implode(',', $exclude);
198
+ static $cache = array();
199
+ if (!isset($cache[$key])) {
200
+ if (count($include) == 0 && count($exclude) == 0) {
201
+ $cache[$key] = true;
202
+ } else {
203
+ $cache[$key] = true;
204
+ /* @var $job Aoe_Scheduler_Model_Job */
205
+ $job = Mage::getModel('aoe_scheduler/job')->load($jobCode);
206
+ $groups = $this->trimExplode(',', $job->getGroups(), true);
207
+ if (count($include) > 0) {
208
+ $cache[$key] = (count(array_intersect($groups, $include)) > 0);
209
+ }
210
+ if (count($exclude) > 0) {
211
+ if (count(array_intersect($groups, $exclude)) > 0) {
212
+ $cache[$key] = false;
213
+ }
214
+ }
215
+ }
216
+
217
+ }
218
+ return $cache[$key];
219
+ }
220
+
221
+ public function getGroupsToJobsMap($forceRebuild = false)
222
+ {
223
+ if ($this->groupsToJobsMap === null || $forceRebuild) {
224
+ $map = array();
225
+
226
+ /* @var $jobs Aoe_Scheduler_Model_Resource_Job_Collection */
227
+ $jobs = Mage::getSingleton('aoe_scheduler/job')->getCollection();
228
+ foreach ($jobs as $job) {
229
+ /* @var Aoe_Scheduler_Model_Job $job */
230
+ $groups = $this->trimExplode(',', $job->getGroups(), true);
231
+ foreach ($groups as $group) {
232
+ $map[$group][] = $job->getJobCode();
233
+ }
234
+ }
235
+
236
+ $this->groupsToJobsMap = $map;
237
+ }
238
+
239
+ return $this->groupsToJobsMap;
240
+ }
241
+
242
+ public function addGroupJobs(array $jobs, array $groups)
243
+ {
244
+ $map = $this->getGroupsToJobsMap();
245
+
246
+ foreach ($groups as $group) {
247
+ if (isset($map[$group])) {
248
+ foreach ($map[$group] as $jobCode) {
249
+ $jobs[] = $jobCode;
250
+ }
251
+ }
252
+ }
253
+
254
+ return $jobs;
255
+ }
256
+
257
+ /**
258
+ * Send error mail
259
+ *
260
+ * @param Aoe_Scheduler_Model_Schedule $schedule
261
+ * @param $error
262
+ * @return void
263
+ */
264
+ public function sendErrorMail(Aoe_Scheduler_Model_Schedule $schedule, $error)
265
+ {
266
+ if (!Mage::getStoreConfig(self::XML_PATH_EMAIL_RECIPIENT)) {
267
+ return;
268
+ }
269
+
270
+ $translate = Mage::getSingleton('core/translate'); /* @var $translate Mage_Core_Model_Translate */
271
+ $translate->setTranslateInline(false);
272
+
273
+ $emailTemplate = Mage::getModel('core/email_template'); /* @var $emailTemplate Mage_Core_Model_Email_Template */
274
+ $emailTemplate->setDesignConfig(array('area' => 'backend'));
275
+ $emailTemplate->sendTransactional(
276
+ Mage::getStoreConfig(self::XML_PATH_EMAIL_TEMPLATE),
277
+ Mage::getStoreConfig(self::XML_PATH_EMAIL_IDENTITY),
278
+ Mage::getStoreConfig(self::XML_PATH_EMAIL_RECIPIENT),
279
+ null,
280
+ array('error' => $error, 'schedule' => $schedule)
281
+ );
282
+
283
+ $translate->setTranslateInline(true);
284
+ }
285
+
286
+ /**
287
+ * Get callback from runModel
288
+ *
289
+ * @param $runModel
290
+ * @return array
291
+ */
292
+ public function getCallBack($runModel)
293
+ {
294
+ if (!preg_match(Mage_Cron_Model_Observer::REGEX_RUN_MODEL, (string) $runModel, $run)) {
295
+ Mage::throwException(Mage::helper('cron')->__('Invalid model/method definition, expecting "model/class::method", got "' . $runModel . '" instead.'));
296
+ }
297
+ if (!($model = Mage::getModel($run[1]))) {
298
+ Mage::throwException(Mage::helper('cron')->__('Invalid callback: Model for %s::%s does not exist', $run[1], $run[2]));
299
+ }
300
+ if (!method_exists($model, $run[2])) {
301
+ Mage::throwException(Mage::helper('cron')->__('Invalid callback: Method for %s::%s does not exist', $run[1], $run[2]));
302
+ }
303
+ $callback = array($model, $run[2]);
304
+ return $callback;
305
+ }
306
+
307
+ /**
308
+ * Validate cron expression
309
+ *
310
+ * @param $cronExpression
311
+ * @return bool
312
+ */
313
+ public function validateCronExpression($cronExpression)
314
+ {
315
+ try {
316
+ $schedule = Mage::getModel('cron/schedule');
317
+ /* @var $schedule Mage_Cron_Model_Schedule */
318
+ $schedule->setCronExpr($cronExpression);
319
+ } catch (Exception $e) {
320
+ return false;
321
+ }
322
+ return true;
323
+ }
324
  }
app/code/community/Aoe/Scheduler/Helper/GracefulDead.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Graceful Dead Helper
5
+ *
6
+ * @author Fabrizio Branca
7
+ * @since 2015-07-02
8
+ */
9
+ class Aoe_Scheduler_Helper_GracefulDead
10
+ {
11
+
12
+ /**
13
+ * Configure graceful dead
14
+ */
15
+ public static function configure()
16
+ {
17
+ static $configured = false;
18
+ if (!$configured) {
19
+ register_shutdown_function(array('Aoe_Scheduler_Helper_GracefulDead', 'beforeDyingShutdown'));
20
+ if (extension_loaded('pcntl')) {
21
+ declare(ticks = 1);
22
+ pcntl_signal(SIGINT, array('Aoe_Scheduler_Helper_GracefulDead', 'beforeDyingSigint')); // CTRL + C
23
+ pcntl_signal(SIGTERM, array('Aoe_Scheduler_Helper_GracefulDead', 'beforeDyingSigterm')); // kill <pid>
24
+ }
25
+ $configured = true;
26
+ }
27
+ }
28
+
29
+ public static function beforeDying($message = null, $exit = false)
30
+ {
31
+ $schedule = Mage::registry('currently_running_schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
32
+ if ($schedule !== null) {
33
+ if ($message) {
34
+ $schedule->addMessages($message);
35
+ }
36
+ $schedule
37
+ ->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_DIED)
38
+ ->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
39
+ ->save();
40
+ Mage::unregister('currently_running_schedule');
41
+ }
42
+ if ($exit) {
43
+ exit;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Callback
49
+ */
50
+ public static function beforeDyingShutdown()
51
+ {
52
+ self::beforeDying('TRIGGER: shutdown function', false);
53
+ }
54
+
55
+ /**
56
+ * Callback
57
+ */
58
+ public static function beforeDyingSigint()
59
+ {
60
+ self::beforeDying('TRIGGER: Signal SIGINT', true);
61
+ }
62
+
63
+ /**
64
+ * Callback
65
+ */
66
+ public static function beforeDyingSigterm()
67
+ {
68
+ self::beforeDying('TRIGGER: Signal SIGTERM', true);
69
+ }
70
+ }
app/code/community/Aoe/Scheduler/Model/Adminhtml/System/Config/Source/List/Code/Filtertype.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Used in creating options for list code filter type config value selection
5
+ *
6
+ */
7
+ class Aoe_Scheduler_Model_Adminhtml_System_Config_Source_List_Code_Filtertype
8
+ {
9
+ const SELECT = 'select';
10
+ const TEXT = 'text';
11
+
12
+ /**
13
+ * Options getter
14
+ *
15
+ * @return array
16
+ */
17
+ public function toOptionArray()
18
+ {
19
+ return array(
20
+ array('value' => self::SELECT, 'label' => Mage::helper('adminhtml')->__('Select')),
21
+ array('value' => self::TEXT, 'label' => Mage::helper('adminhtml')->__('Text')),
22
+ );
23
+ }
24
+ }
app/code/community/Aoe/Scheduler/Model/Api.php CHANGED
@@ -1,49 +1,59 @@
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
  }
1
  <?php
2
+
3
  /**
4
  * Scheduler API
5
  *
6
+ * @author Fabrizio Branca
7
  */
8
+ class Aoe_Scheduler_Model_Api extends Mage_Api_Model_Resource_Abstract
9
+ {
10
+ /**
11
+ * Run task
12
+ *
13
+ * @param $code
14
+ * @return array
15
+ */
16
+ public function runNow($code)
17
+ {
18
+ if (!Mage::getStoreConfig('system/cron/enableRunNow')) {
19
+ Mage::throwException("'Run now' disabled by configuration (system/cron/enableRunNow)");
20
+ }
21
 
22
+ $schedule = Mage::getModel('cron/schedule')/* @var $schedule Aoe_Scheduler_Model_Schedule */
23
+ ->setJobCode($code)
24
+ ->setScheduledReason(Aoe_Scheduler_Model_Schedule::REASON_RUNNOW_API)
25
+ ->runNow(false) // without trying to lock the job
26
+ ->save();
 
 
 
 
 
 
 
 
27
 
28
+ return $schedule->getData();
29
+ }
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ /**
32
+ * Schedule task
33
+ *
34
+ * @param $code
35
+ * @param null $time
36
+ * @return array
37
+ */
38
+ public function schedule($code, $time = null)
39
+ {
40
+ $schedule = Mage::getModel('cron/schedule')/* @var $schedule Aoe_Scheduler_Model_Schedule */
41
+ ->setJobCode($code)
42
+ ->setScheduledReason(Aoe_Scheduler_Model_Schedule::REASON_SCHEDULENOW_API)
43
+ ->schedule($time)
44
+ ->save();
45
+ return $schedule->getData();
46
+ }
47
 
48
+ /**
49
+ * Get info
50
+ *
51
+ * @param $id
52
+ * @return string
53
+ */
54
+ public function info($id)
55
+ {
56
+ $schedule = Mage::getModel('cron/schedule')->load($id); /* @var $schedule Aoe_Scheduler_Model_Schedule */
57
+ return $schedule->getData();
58
+ }
59
  }
app/code/community/Aoe/Scheduler/Model/Collection/Crons.php DELETED
@@ -1,62 +0,0 @@
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 DELETED
@@ -1,129 +0,0 @@
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 DELETED
@@ -1,9 +0,0 @@
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/Job.php ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @method Aoe_Scheduler_Model_Job setJobCode($jobCode)
5
+ * @method string getJobCode()
6
+ * @method Aoe_Scheduler_Model_Job setName($name)
7
+ * @method Aoe_Scheduler_Model_Job setDescription($description)
8
+ * @method string getDescription()
9
+ * @method Aoe_Scheduler_Model_Job setShortDescription($shortDescription)
10
+ * @method string getShortDescription()
11
+ * @method Aoe_Scheduler_Model_Job setScheduleCronExpr($scheduleCronExpr)
12
+ * @method string getScheduleCronExpr()
13
+ * @method Aoe_Scheduler_Model_Job setScheduleConfigPath($scheduleConfigPath)
14
+ * @method string getScheduleConfigPath()
15
+ * @method Aoe_Scheduler_Model_Job setRunModel($runModel)
16
+ * @method string getRunModel()
17
+ * @method Aoe_Scheduler_Model_Job setParameters($parameters)
18
+ * @method string getParameters()
19
+ * @method Aoe_Scheduler_Model_Job setGroups($groups)
20
+ * @method array getGroups()
21
+ * @method Aoe_Scheduler_Model_Job load($jobCode)
22
+ * @method Aoe_Scheduler_Model_Resource_Job getResource()
23
+ * @method Aoe_Scheduler_Model_Resource_Job_Collection getCollection()
24
+ * @method Aoe_Scheduler_Model_Resource_Job_Collection getResourceCollection()
25
+ */
26
+ class Aoe_Scheduler_Model_Job extends Mage_Core_Model_Abstract
27
+ {
28
+ /**
29
+ * Prefix of model events names
30
+ *
31
+ * @var string
32
+ */
33
+ protected $_eventPrefix = 'aoe_scheduler_job';
34
+
35
+ /**
36
+ * Parameter name in event
37
+ *
38
+ * @var string
39
+ */
40
+ protected $_eventObject = 'job';
41
+
42
+ protected function _construct()
43
+ {
44
+ $this->_setResourceModel('aoe_scheduler/job', 'aoe_scheduler/job_collection');
45
+ }
46
+
47
+ public function getName()
48
+ {
49
+ $name = $this->getData('name');
50
+ if (empty($name)) {
51
+ $name = $this->getJobCode();
52
+ }
53
+ return $name;
54
+ }
55
+
56
+ /**
57
+ * @param bool $flag
58
+ *
59
+ * @return $this
60
+ */
61
+ public function setIsActive($flag)
62
+ {
63
+ return $this->setData('is_active', !in_array($flag, array(false, 'false', 0, '0'), true));
64
+ }
65
+
66
+ /**
67
+ * @return bool
68
+ */
69
+ public function getIsActive()
70
+ {
71
+ return !in_array($this->getData('is_active'), array(false, 'false', 0, '0'), true);
72
+ }
73
+
74
+ /**
75
+ * @param string[] $jobData
76
+ *
77
+ * @return $this
78
+ */
79
+ public function setXmlJobData(array $jobData)
80
+ {
81
+ return $this->setData('xml_job_data', $jobData);
82
+ }
83
+
84
+ /**
85
+ * @return string[]
86
+ */
87
+ public function getXmlJobData()
88
+ {
89
+ $jobData = $this->getData('xml_job_data');
90
+ return (is_array($jobData) ? $jobData : array());
91
+ }
92
+
93
+ /**
94
+ * @param string[] $jobData
95
+ *
96
+ * @return $this
97
+ */
98
+ public function setDbJobData(array $jobData)
99
+ {
100
+ return $this->setData('db_job_data', $jobData);
101
+ }
102
+
103
+ /**
104
+ * @return string[]
105
+ */
106
+ public function getDbJobData()
107
+ {
108
+ $jobData = $this->getData('db_job_data');
109
+ return (is_array($jobData) ? $jobData : array());
110
+ }
111
+
112
+ /**
113
+ * Returns cron expression (and fetches it from configuration if required)
114
+ *
115
+ * @return string
116
+ */
117
+ public function getCronExpression()
118
+ {
119
+ $cronExpr = null;
120
+ if ($this->getScheduleConfigPath()) {
121
+ $cronExpr = Mage::getStoreConfig($this->getScheduleConfigPath(), Mage_Core_Model_Store::ADMIN_CODE);
122
+ }
123
+ if (empty($cronExpr) && $this->getScheduleCronExpr()) {
124
+ $cronExpr = $this->getScheduleCronExpr();
125
+ }
126
+ return trim($cronExpr);
127
+ }
128
+
129
+ /**
130
+ * Is always task
131
+ *
132
+ * @return bool
133
+ */
134
+ public function isAlwaysTask()
135
+ {
136
+ return $this->getCronExpression() == 'always';
137
+ }
138
+
139
+ public function getCallback()
140
+ {
141
+ $helper = Mage::helper('aoe_scheduler');
142
+ /* @var $helper Aoe_Scheduler_Helper_Data */
143
+ return $helper->getCallBack($this->getRunModel());
144
+ }
145
+
146
+ public function canBeScheduled()
147
+ {
148
+ return $this->getIsActive() && $this->getCronExpression() && !$this->isAlwaysTask();
149
+ }
150
+
151
+ /**
152
+ * @return bool
153
+ */
154
+ public function isDbOnly()
155
+ {
156
+ $xmlJobData = $this->getXmlJobData();
157
+ $dbJobData = $this->getDbJobData();
158
+ return empty($xmlJobData) && !empty($dbJobData);
159
+ }
160
+
161
+ /**
162
+ * @return bool
163
+ */
164
+ public function isXmlOnly()
165
+ {
166
+ $xmlJobData = $this->getXmlJobData();
167
+ $dbJobData = $this->getDbJobData();
168
+ return !empty($xmlJobData) && empty($dbJobData);
169
+ }
170
+
171
+ /**
172
+ * @return bool
173
+ */
174
+ public function isOverlay()
175
+ {
176
+ $xmlJobData = $this->getXmlJobData();
177
+ $dbJobData = $this->getDbJobData();
178
+ return !empty($xmlJobData) && !empty($dbJobData);
179
+ }
180
+
181
+ /**
182
+ * @return string
183
+ */
184
+ public function getType()
185
+ {
186
+ if ($this->isDbOnly()) {
187
+ return 'db';
188
+ } elseif ($this->isXmlOnly()) {
189
+ return 'xml';
190
+ } else {
191
+ return 'db_xml';
192
+ }
193
+ }
194
+ }
app/code/community/Aoe/Scheduler/Model/Observer.php CHANGED
@@ -3,233 +3,82 @@
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
  }
3
  /**
4
  * Crontab observer.
5
  *
6
+ * @author Fabrizio Branca
7
  */
8
+ class Aoe_Scheduler_Model_Observer /* extends Mage_Cron_Model_Observer */
9
+ {
10
+ /**
11
+ * Process cron queue
12
+ * Generate tasks schedule
13
+ * Cleanup tasks schedule
14
+ *
15
+ * @param Varien_Event_Observer $observer
16
+ */
17
+ public function dispatch(Varien_Event_Observer $observer)
18
+ {
19
+ if (!Mage::getStoreConfigFlag('system/cron/enable')) {
20
+ return;
21
+ }
22
+
23
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
24
+ $processManager->watchdog();
25
+
26
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager'); /* @var $scheduleManager Aoe_Scheduler_Model_ScheduleManager */
27
+ $scheduleManager->logRun();
28
+
29
+ $helper = Mage::helper('aoe_scheduler'); /* @var Aoe_Scheduler_Helper_Data $helper */
30
+ $includeJobs = $helper->addGroupJobs((array) $observer->getIncludeJobs(), (array) $observer->getIncludeGroups());
31
+ $excludeJobs = $helper->addGroupJobs((array) $observer->getExcludeJobs(), (array) $observer->getExcludeGroups());
32
+
33
+ // Coalesce all jobs that should have run before now, by job code, by marking the oldest entries as missed.
34
+ $scheduleManager->cleanMissedSchedules();
35
+
36
+ // Iterate over all pending jobs
37
+ foreach ($scheduleManager->getPendingSchedules($includeJobs, $excludeJobs) as $schedule) {
38
+ /* @var Aoe_Scheduler_Model_Schedule $schedule */
39
+ $schedule->process();
40
+ }
41
+
42
+ // Generate new schedules
43
+ $scheduleManager->generateSchedules();
44
+
45
+ // Clean up schedule history
46
+ $scheduleManager->cleanup();
47
+ }
48
+
49
+ /**
50
+ * Process cron queue for tasks marked as 'always'
51
+ *
52
+ * @param Varien_Event_Observer $observer
53
+ */
54
+ public function dispatchAlways(Varien_Event_Observer $observer)
55
+ {
56
+ if (!Mage::getStoreConfigFlag('system/cron/enable')) {
57
+ return;
58
+ }
59
+
60
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
61
+ $processManager->watchdog();
62
+
63
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager'); /* @var $scheduleManager Aoe_Scheduler_Model_ScheduleManager */
64
+
65
+ $helper = Mage::helper('aoe_scheduler'); /* @var Aoe_Scheduler_Helper_Data $helper */
66
+ $includeJobs = $helper->addGroupJobs((array) $observer->getIncludeJobs(), (array) $observer->getIncludeGroups());
67
+ $excludeJobs = $helper->addGroupJobs((array) $observer->getExcludeJobs(), (array) $observer->getExcludeGroups());
68
+
69
+ /* @var $jobs Aoe_Scheduler_Model_Resource_Job_Collection */
70
+ $jobs = Mage::getSingleton('aoe_scheduler/job')->getCollection();
71
+ $jobs->setWhiteList($includeJobs);
72
+ $jobs->setBlackList($excludeJobs);
73
+ $jobs->setActiveOnly(true);
74
+ foreach ($jobs as $job) {
75
+ /* @var Aoe_Scheduler_Model_Job $job */
76
+ if ($job->isAlwaysTask() && $job->getRunModel()) {
77
+ $schedule = $scheduleManager->getScheduleForAlwaysJob($job->getJobCode());
78
+ if ($schedule !== false) {
79
+ $schedule->process();
80
+ }
81
+ }
82
+ }
83
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
app/code/community/Aoe/Scheduler/Model/ProcessManager.php ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Process Manager
5
+ *
6
+ * @author Fabrizio Branca
7
+ * @since 2013-10-11
8
+ */
9
+ class Aoe_Scheduler_Model_ProcessManager
10
+ {
11
+
12
+ const XML_PATH_MARK_AS_ERROR = 'system/cron/mark_as_error_after';
13
+ const XML_PATH_MAX_JOB_RUNTIME = 'system/cron/max_job_runtime';
14
+
15
+ /**
16
+ * Get all schedules running on this server
17
+ *
18
+ * @param string $host
19
+ * @return object
20
+ */
21
+ public function getAllRunningSchedules($host = null)
22
+ {
23
+ $collection = Mage::getModel('cron/schedule')->getCollection();
24
+ $collection->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_RUNNING);
25
+ if (!is_null($host)) {
26
+ $collection->addFieldToFilter('host', $host);
27
+ }
28
+ return $collection;
29
+ }
30
+
31
+ /**
32
+ * Get all schedules marked as to be killed
33
+ *
34
+ * @param string|null $host
35
+ * @return object
36
+ */
37
+ public function getAllKillRequests($host = null)
38
+ {
39
+ $collection = $this->getAllRunningSchedules($host);
40
+ $collection->addFieldToFilter('kill_request', array('lt' => strftime('%Y-%m-%d %H:%M:00', time()+60)));
41
+ return $collection;
42
+ }
43
+
44
+ /**
45
+ * Check if there's already a job running with the given code
46
+ *
47
+ * @param string $jobCode
48
+ * @param int $ignoreId
49
+ * @return bool
50
+ */
51
+ public function isJobCodeRunning($jobCode, $ignoreId = null)
52
+ {
53
+ $collection = Mage::getModel('cron/schedule')
54
+ ->getCollection()
55
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_RUNNING)
56
+ ->addFieldToFilter('job_code', $jobCode);
57
+ if (!is_null($ignoreId)) {
58
+ $collection->addFieldToFilter('schedule_id', array('neq' => $ignoreId));
59
+ }
60
+ foreach ($collection as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
61
+ $alive = $schedule->isAlive();
62
+ if ($alive !== false) { // TODO: how do we handle null (= we don't know because might be running on a different server?
63
+ return true;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+
69
+
70
+ /**
71
+ * Check running jobs
72
+ *
73
+ * @return void
74
+ */
75
+ public function checkRunningJobs()
76
+ {
77
+ $maxJobRuntime = intval(Mage::getStoreConfig(self::XML_PATH_MAX_JOB_RUNTIME));
78
+
79
+ if ($maxJobRuntime) {
80
+ foreach ($this->getAllRunningSchedules(gethostname()) as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
81
+ // checking if the job isn't running too long
82
+ if ($schedule->isAlive()) {
83
+ if ($schedule->getDuration() > $maxJobRuntime * 60 && !$schedule->getKillRequest()) {
84
+ $schedule->requestKill(null, 'Kill requested because job exceeded the max job runtime of ' . $maxJobRuntime . ' minutes.');
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ // fallback (where process cannot be checked or if one of the servers disappeared)
91
+ // if a task wasn't seen for some time it will be marked as error
92
+ $markAsErrorAfter = intval(Mage::getStoreConfig(self::XML_PATH_MARK_AS_ERROR));
93
+ $maxAge = time() - min($markAsErrorAfter, 5) * 60;
94
+ if ($markAsErrorAfter) {
95
+ $schedules = Mage::getModel('cron/schedule')->getCollection()/* @var $schedules Mage_Cron_Model_Resource_Schedule_Collection */
96
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_RUNNING)
97
+ ->addFieldToFilter('last_seen', array('lt' => strftime('%Y-%m-%d %H:%M:00', $maxAge)))
98
+ ->load();
99
+
100
+ foreach ($schedules as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
101
+ // check one more time
102
+ if ($schedule->isAlive() !== true) {
103
+ $schedule->markAsDisappeared(sprintf('Host "%s" has not been available for a while now to update the status of this task and the task is not reporting back by itself', $schedule->getHost()));
104
+ $schedule->save();
105
+ }
106
+ }
107
+ }
108
+
109
+ // clean up "running"(!?) tasks that have never been seen (for whatever reason) and have been scheduled before maxAge
110
+ // by robinfritze. @see https://github.com/AOEpeople/Aoe_Scheduler/issues/40#issuecomment-67749476
111
+ $schedules = Mage::getModel('cron/schedule')->getCollection() /* @var $schedules Mage_Cron_Model_Resource_Schedule_Collection */
112
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_RUNNING)
113
+ ->addFieldToFilter('last_seen', array('null' => true))
114
+ ->addFieldToFilter('host', array('null' => true))
115
+ ->addFieldToFilter('pid', array('null' => true))
116
+ ->addFieldToFilter('scheduled_at', array('lt' => strftime('%Y-%m-%d %H:%M:00', $maxAge)))
117
+ ->load();
118
+
119
+ foreach ($schedules->getIterator() as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
120
+ $schedule->setLastSeen(strftime('%Y-%m-%d %H:%M:%S', time()));
121
+ $schedule->markAsDisappeared(sprintf('Process "%s" (id: %s) cannot be found anymore', $schedule->getJobCode(), $schedule->getId()));
122
+ }
123
+
124
+ }
125
+
126
+ /**
127
+ * Process kill requests
128
+ *
129
+ * @return void
130
+ */
131
+ public function processKillRequests()
132
+ {
133
+ foreach ($this->getAllKillRequests(gethostname()) as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
134
+ $schedule->kill();
135
+ }
136
+ }
137
+
138
+
139
+ /**
140
+ * Run maintenance
141
+ */
142
+ public function watchdog()
143
+ {
144
+ $this->checkRunningJobs();
145
+ $this->processKillRequests();
146
+ }
147
+ }
app/code/community/Aoe/Scheduler/Model/Resource/Job.php ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Model_Resource_Job extends Mage_Core_Model_Resource_Db_Abstract
4
+ {
5
+ /** @var bool */
6
+ protected $loaded = false;
7
+
8
+ /** @var Aoe_Scheduler_Model_Job[] */
9
+ protected $jobs = array();
10
+
11
+ /**
12
+ * Resource initialization
13
+ */
14
+ protected function _construct()
15
+ {
16
+ $this->_init('core/config_data', 'job_code');
17
+ }
18
+
19
+ public function getJobCodes()
20
+ {
21
+ $codes = array();
22
+
23
+ $nodes = array('crontab/jobs', 'default/crontab/jobs');
24
+ foreach ($nodes as $node) {
25
+ $jobs = Mage::getConfig()->getNode($node);
26
+ if ($jobs && $jobs->hasChildren()) {
27
+ foreach ($jobs->children() as $code => $child) {
28
+ $codes[] = trim($code);
29
+ }
30
+ }
31
+ }
32
+
33
+ // Remove empties and de-dupe
34
+ $codes = array_unique(array_filter($codes));
35
+
36
+ // Sort
37
+ sort($codes);
38
+
39
+ return $codes;
40
+ }
41
+
42
+ /**
43
+ * @param Aoe_Scheduler_Model_Job $object
44
+ * @param mixed $value
45
+ * @param null $field
46
+ *
47
+ * @return $this
48
+ */
49
+ public function load(Mage_Core_Model_Abstract $object, $value, $field = null)
50
+ {
51
+ if (!$object instanceof Aoe_Scheduler_Model_Job) {
52
+ throw new InvalidArgumentException(sprintf("Expected object of type 'Aoe_Scheduler_Model_Job' got '%s'", get_class($object)));
53
+ }
54
+
55
+ /** @var Aoe_Scheduler_Model_Job $object */
56
+
57
+ if (!empty($field)) {
58
+ throw new InvalidArgumentException('Aoe_Scheduler_Model_Resource_Job cannot load by any field except the job code.');
59
+ }
60
+
61
+ if (empty($value)) {
62
+ $this->setModelFromJobData($object, array());
63
+ $object->setJobCode('');
64
+ $object->setXmlJobData(array());
65
+ $object->setDbJobData(array());
66
+ return $this;
67
+ }
68
+
69
+ $xmlJobData = $this->getJobDataFromXml($value);
70
+ $dbJobData = $this->getJobDataFromDb($value);
71
+ $jobData = array_merge($xmlJobData, $this->getJobDataFromConfig($value, true), $dbJobData);
72
+
73
+ $this->setModelFromJobData($object, $jobData);
74
+ $object->setJobCode($value);
75
+ $object->setXmlJobData($xmlJobData);
76
+ $object->setDbJobData($dbJobData);
77
+
78
+ $this->unserializeFields($object);
79
+ $this->_afterLoad($object);
80
+
81
+ return $this;
82
+ }
83
+
84
+ /**
85
+ * @param Aoe_Scheduler_Model_Job $object
86
+ *
87
+ * @return $this
88
+ */
89
+ public function save(Mage_Core_Model_Abstract $object)
90
+ {
91
+ if ($object->isDeleted()) {
92
+ return $this->delete($object);
93
+ }
94
+
95
+ if (!$object instanceof Aoe_Scheduler_Model_Job) {
96
+ throw new InvalidArgumentException(sprintf("Expected object of type 'Aoe_Scheduler_Model_Job' got '%s'", get_class($object)));
97
+ }
98
+
99
+ if (!$object->getJobCode()) {
100
+ Mage::throwException('Invalid data. Must have job code.');
101
+ }
102
+
103
+ $this->_serializeFields($object);
104
+ $this->_beforeSave($object);
105
+
106
+ $newValues = $this->getJobDataFromModel($object);
107
+ $oldValues = $this->getJobDataFromDb($object->getJobCode());
108
+ $defaultValues = $this->getJobDataFromXml($object->getJobCode());
109
+
110
+ // Generate key/value lists for Update and Insert
111
+ $updateValues = array_intersect_key($newValues, $oldValues);
112
+ $insertValues = array_diff_key($newValues, $oldValues);
113
+
114
+ // Remove Updates and Inserts that match defaults
115
+ $updateValues = array_diff_assoc($updateValues, $defaultValues);
116
+ $insertValues = array_diff_assoc($insertValues, $defaultValues);
117
+
118
+ // Remove empty value inserts if this is a DB only job
119
+ if (empty($defaultValues)) {
120
+ foreach ($insertValues as $k => $v) {
121
+ if ($v === '' || $v === null) {
122
+ unset($insertValues[$k]);
123
+ }
124
+ }
125
+ }
126
+
127
+ // Generate key/value lists for Delete (Old values, not being updated, that are identical to default values)
128
+ $deleteValues = array_intersect_assoc(array_diff_key($oldValues, $updateValues), $defaultValues);
129
+
130
+ $pathPrefix = $this->getJobPathPrefix($object->getJobCode()) . '/';
131
+
132
+ $adapter = $this->_getWriteAdapter();
133
+ foreach ($updateValues as $k => $v) {
134
+ $adapter->update(
135
+ $this->getMainTable(),
136
+ array('value' => $v),
137
+ array(
138
+ 'scope = ?' => 'default',
139
+ 'scope_id = ?' => 0,
140
+ 'path = ?' => $pathPrefix . $k
141
+ )
142
+ );
143
+ }
144
+ foreach ($insertValues as $k => $v) {
145
+ $adapter->insert(
146
+ $this->getMainTable(),
147
+ array(
148
+ 'scope' => 'default',
149
+ 'scope_id' => 0,
150
+ 'path' => $pathPrefix . $k,
151
+ 'value' => $v
152
+ )
153
+ );
154
+ }
155
+ foreach ($deleteValues as $k => $v) {
156
+ $adapter->delete(
157
+ $this->getMainTable(),
158
+ array(
159
+ 'scope = ?' => 'default',
160
+ 'scope_id = ?' => 0,
161
+ 'path = ?' => $pathPrefix . $k
162
+ )
163
+ );
164
+ }
165
+
166
+ if (count($updateValues) || count($insertValues) || count($deleteValues)) {
167
+ Mage::getConfig()->reinit();
168
+ }
169
+
170
+ $this->unserializeFields($object);
171
+ $this->_afterSave($object);
172
+
173
+ return $this;
174
+ }
175
+
176
+ /**
177
+ * {@inheritdoc}
178
+ */
179
+ public function forsedSave(Mage_Core_Model_Abstract $object)
180
+ {
181
+ throw new RuntimeException('Method no longer exists');
182
+ }
183
+
184
+ /**
185
+ * @param Aoe_Scheduler_Model_Job $object
186
+ *
187
+ * @return $this
188
+ */
189
+ public function delete(Mage_Core_Model_Abstract $object)
190
+ {
191
+ if (!$object instanceof Aoe_Scheduler_Model_Job) {
192
+ throw new InvalidArgumentException(sprintf("Expected object of type 'Aoe_Scheduler_Model_Job' got '%s'", get_class($object)));
193
+ }
194
+
195
+ $this->_beforeDelete($object);
196
+
197
+ if (!$object->getJobCode()) {
198
+ Mage::throwException('Invalid data. Must have job code.');
199
+ }
200
+
201
+ $adapter = $this->_getWriteAdapter();
202
+ $adapter->delete(
203
+ $this->getMainTable(),
204
+ array(
205
+ 'path LIKE ?' => $this->getJobSearchPath($object->getJobCode()),
206
+ 'scope = ?' => 'default',
207
+ 'scope_id = ?' => 0
208
+ )
209
+ );
210
+
211
+ Mage::getConfig()->reinit();
212
+
213
+ $this->_afterDelete($object);
214
+
215
+ return $this;
216
+ }
217
+
218
+ protected function getJobPathPrefix($jobCode)
219
+ {
220
+ return 'crontab/jobs/' . $jobCode;
221
+ }
222
+
223
+ protected function getJobSearchPath($jobCode)
224
+ {
225
+ return str_replace(array('\\', '%', '_'), array('\\\\', '\\%', '\\_'), $this->getJobPathPrefix($jobCode)) . '/%';
226
+ }
227
+
228
+ private function getJobDataFromConfig($jobCode, $useDefaultScope = false, $default = null)
229
+ {
230
+ $config = Mage::getConfig()->getNode(($useDefaultScope ? 'default/' : '') . $this->getJobPathPrefix($jobCode));
231
+ if (!$config) {
232
+ return array();
233
+ }
234
+
235
+ $config = $config->asArray();
236
+
237
+ $values = array();
238
+
239
+ if (isset($config['name'])) {
240
+ $values['name'] = $config['name'];
241
+ } elseif ($default !== null) {
242
+ $values['name'] = $default;
243
+ }
244
+ if (isset($config['description'])) {
245
+ $values['description'] = $config['description'];
246
+ } elseif ($default !== null) {
247
+ $values['description'] = $default;
248
+ }
249
+ if (isset($config['short_description'])) {
250
+ $values['short_description'] = $config['short_description'];
251
+ } elseif ($default !== null) {
252
+ $values['short_description'] = $default;
253
+ }
254
+ if (isset($config['run']['model'])) {
255
+ $values['run/model'] = $config['run']['model'];
256
+ } elseif ($default !== null) {
257
+ $values['run/model'] = $default;
258
+ }
259
+ if (isset($config['schedule']['config_path'])) {
260
+ $values['schedule/config_path'] = $config['schedule']['config_path'];
261
+ } elseif ($default !== null) {
262
+ $values['schedule/config_path'] = $default;
263
+ }
264
+ if (isset($config['schedule']['cron_expr'])) {
265
+ $values['schedule/cron_expr'] = $config['schedule']['cron_expr'];
266
+ } elseif ($default !== null) {
267
+ $values['schedule/cron_expr'] = $default;
268
+ }
269
+ if (isset($config['parameters'])) {
270
+ $values['parameters'] = $config['parameters'];
271
+ } elseif ($default !== null) {
272
+ $values['parameters'] = $default;
273
+ }
274
+ if (isset($config['groups'])) {
275
+ $values['groups'] = $config['groups'];
276
+ } elseif ($default !== null) {
277
+ $values['groups'] = $default;
278
+ }
279
+ if (isset($config['is_active'])) {
280
+ $values['is_active'] = $config['is_active'];
281
+ } elseif ($default !== null) {
282
+ $values['is_active'] = $default;
283
+ }
284
+
285
+ // Clean up each entry to being a trimmed string
286
+ $values = array_map('trim', $values);
287
+
288
+ return $values;
289
+ }
290
+
291
+ public function getJobDataFromXml($jobCode)
292
+ {
293
+ return $this->getJobDataFromConfig($jobCode, false, null);
294
+ }
295
+
296
+ public function getJobDataFromDb($jobCode)
297
+ {
298
+ $adapter = $this->_getWriteAdapter();
299
+
300
+ $select = $adapter->select()
301
+ ->from($this->getMainTable(), array('path', 'value'))
302
+ ->where('scope = ?', 'default')
303
+ ->where('scope_id = ?', '0')
304
+ ->where('path LIKE ?', $this->getJobSearchPath($jobCode));
305
+
306
+ $pathPrefix = $this->getJobPathPrefix($jobCode) . '/';
307
+ $values = array();
308
+ foreach ($adapter->query($select)->fetchAll() as $row) {
309
+ if (strpos($row['path'], $pathPrefix) === 0) {
310
+ $values[substr($row['path'], strlen($pathPrefix))] = $row['value'];
311
+ }
312
+ }
313
+
314
+ // Clean up each entry to being a trimmed string
315
+ $values = array_map('trim', $values);
316
+
317
+ return $values;
318
+ }
319
+
320
+ public function getJobDataFromModel(Aoe_Scheduler_Model_Job $job)
321
+ {
322
+ $values = array(
323
+ 'name' => $job->getName(),
324
+ 'description' => $job->getDescription(),
325
+ 'short_description' => $job->getShortDescription(),
326
+ 'run/model' => $job->getRunModel(),
327
+ 'schedule/config_path' => $job->getScheduleConfigPath(),
328
+ 'schedule/cron_expr' => $job->getScheduleCronExpr(),
329
+ 'parameters' => $job->getParameters(),
330
+ 'groups' => $job->getGroups(),
331
+ 'is_active' => ($job->getIsActive() ? '1' : '0'),
332
+ );
333
+
334
+ // Strip out the auto-generated name
335
+ if ($values['name'] === $job->getJobCode()) {
336
+ $values['name'] = '';
337
+ }
338
+
339
+ // Clean up each entry to being a trimmed string
340
+ $values = array_map('trim', $values);
341
+
342
+ return $values;
343
+ }
344
+
345
+ public function setModelFromJobData(Aoe_Scheduler_Model_Job $job, array $data)
346
+ {
347
+ $job->setName(isset($data['name']) ? $data['name'] : '');
348
+ $job->setDescription(isset($data['description']) ? $data['description'] : '');
349
+ $job->setShortDescription(isset($data['short_description']) ? $data['short_description'] : '');
350
+ $job->setRunModel(isset($data['run/model']) ? $data['run/model'] : '');
351
+ $job->setScheduleConfigPath(isset($data['schedule/config_path']) ? $data['schedule/config_path'] : '');
352
+ $job->setScheduleCronExpr(isset($data['schedule/cron_expr']) ? $data['schedule/cron_expr'] : '');
353
+ $job->setParameters(isset($data['parameters']) ? $data['parameters'] : '');
354
+ $job->setGroups(isset($data['groups']) ? $data['groups'] : '');
355
+ $job->setIsActive(isset($data['is_active']) ? $data['is_active'] : '');
356
+ return $job;
357
+ }
358
+ }
app/code/community/Aoe/Scheduler/Model/Resource/Job/Collection.php ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Model_Resource_Job_Collection extends Varien_Data_Collection
4
+ {
5
+ protected $model;
6
+ protected $resourceModel;
7
+ protected $whiteList = array();
8
+ protected $blackList = array();
9
+ protected $activeOnly = false;
10
+ protected $dbOnly = false;
11
+
12
+ public function __construct()
13
+ {
14
+ $this->model = 'aoe_scheduler/job';
15
+ $this->resourceModel = 'aoe_scheduler/job';
16
+ }
17
+
18
+ /**
19
+ * @return Aoe_Scheduler_Model_Resource_Job
20
+ */
21
+ public function getResource()
22
+ {
23
+ $resource = Mage::getResourceSingleton($this->resourceModel);
24
+ if (!$resource instanceof Aoe_Scheduler_Model_Resource_Job) {
25
+ Mage::throwException(
26
+ sprintf(
27
+ 'Invalid resource class. Expected "%s" and received "%s".',
28
+ 'Aoe_Scheduler_Model_Resource_Job',
29
+ (is_object($resource) ? get_class($resource) : 'UNKNOWN')
30
+ )
31
+ );
32
+ }
33
+ return $resource;
34
+ }
35
+
36
+ /**
37
+ * Retrieve collection empty item
38
+ *
39
+ * @return Aoe_Scheduler_Model_Job
40
+ */
41
+ public function getNewEmptyItem()
42
+ {
43
+ $model = Mage::getModel($this->model);
44
+
45
+ if (!$model instanceof Aoe_Scheduler_Model_Job) {
46
+ Mage::throwException(
47
+ sprintf(
48
+ 'Invalid model class. Expected "%s" and received "%s".',
49
+ 'Aoe_Scheduler_Model_Job',
50
+ (is_object($model) ? get_class($model) : 'UNKNOWN')
51
+ )
52
+ );
53
+ }
54
+
55
+ return $model;
56
+ }
57
+
58
+ /**
59
+ * Load data
60
+ *
61
+ * @return $this
62
+ */
63
+ public function loadData($printQuery = false, $logQuery = false)
64
+ {
65
+ if ($this->isLoaded()) {
66
+ return $this;
67
+ }
68
+
69
+ $this->clear();
70
+
71
+ foreach ($this->getResource()->getJobCodes() as $jobCode) {
72
+ if (!empty($this->whiteList) && !in_array($jobCode, $this->whiteList)) {
73
+ continue;
74
+ }
75
+ if (!empty($this->blackList) && in_array($jobCode, $this->blackList)) {
76
+ continue;
77
+ }
78
+
79
+ $job = $this->getNewEmptyItem()->load($jobCode);
80
+ if ($this->activeOnly && !$job->getIsActive()) {
81
+ continue;
82
+ }
83
+ if ($this->dbOnly && !$job->isDbOnly()) {
84
+ continue;
85
+ }
86
+
87
+ $this->addItem($job);
88
+ }
89
+
90
+ ksort($this->_items);
91
+
92
+ $this->_setIsLoaded(true);
93
+
94
+ return $this;
95
+ }
96
+
97
+ /**
98
+ * @return string[]
99
+ */
100
+ public function getWhiteList()
101
+ {
102
+ return $this->whiteList;
103
+ }
104
+
105
+ /**
106
+ * @param string[] $list
107
+ *
108
+ * @return $this
109
+ */
110
+ public function setWhiteList(array $list)
111
+ {
112
+ $list = array_unique(array_filter(array_map('trim', array_values($list))));
113
+ sort($list);
114
+ if ($this->whiteList !== $list) {
115
+ $this->clear();
116
+ $this->whiteList = $list;
117
+ }
118
+ return $this;
119
+ }
120
+
121
+ /**
122
+ * @return string[]
123
+ */
124
+ public function getBlackList()
125
+ {
126
+ return $this->blackList;
127
+ }
128
+
129
+ /**
130
+ * @param string[] $list
131
+ *
132
+ * @return $this
133
+ */
134
+ public function setBlackList(array $list)
135
+ {
136
+ $list = array_unique(array_filter(array_map('trim', array_values($list))));
137
+ sort($list);
138
+ if ($this->blackList !== $list) {
139
+ $this->clear();
140
+ $this->blackList = $list;
141
+ }
142
+ return $this;
143
+ }
144
+
145
+ /**
146
+ * @return bool
147
+ */
148
+ public function getActiveOnly()
149
+ {
150
+ return $this->activeOnly;
151
+ }
152
+
153
+ /**
154
+ * @param bool $flag
155
+ *
156
+ * @return $this
157
+ */
158
+ public function setActiveOnly($flag = true)
159
+ {
160
+ $flag = (bool)$flag;
161
+ if ($this->activeOnly !== $flag) {
162
+ $this->clear();
163
+ $this->activeOnly = $flag;
164
+ }
165
+ return $this;
166
+ }
167
+
168
+ /**
169
+ * @return bool
170
+ */
171
+ public function getDbOnly()
172
+ {
173
+ return $this->dbOnly;
174
+ }
175
+
176
+ /**
177
+ * @param bool $flag
178
+ *
179
+ * @return $this
180
+ */
181
+ public function setDbOnly($flag = true)
182
+ {
183
+ $flag = (bool)$flag;
184
+ if ($this->dbOnly !== $flag) {
185
+ $this->clear();
186
+ $this->dbOnly = $flag;
187
+ }
188
+ return $this;
189
+ }
190
+
191
+ /**
192
+ * Adding item to item array
193
+ *
194
+ * @param Aoe_Scheduler_Model_Job $item
195
+ *
196
+ * @return $this
197
+ */
198
+ public function addItem(Varien_Object $item)
199
+ {
200
+ if (!$item instanceof Aoe_Scheduler_Model_Job) {
201
+ Mage::throwException(
202
+ sprintf(
203
+ 'Invalid model class. Expected "%s" and received "%s".',
204
+ 'Aoe_Scheduler_Model_Job',
205
+ get_class($item)
206
+ )
207
+ );
208
+ }
209
+
210
+ $jobCode = $item->getJobCode();
211
+ if (!$jobCode) {
212
+ Mage::throwException('Jobs must have a job code');
213
+ }
214
+
215
+ if (isset($this->_items[$jobCode])) {
216
+ Mage::throwException('Job with the same job code "' . $item->getJobCode() . '" already exist');
217
+ }
218
+
219
+ $this->_items[$jobCode] = $item;
220
+
221
+ return $this;
222
+ }
223
+
224
+ /**
225
+ * @return string[]
226
+ */
227
+ public function getAllIds()
228
+ {
229
+ return array_keys($this->_items);
230
+ }
231
+
232
+ /**
233
+ * Retrieve field values from all items
234
+ *
235
+ * @param string $column
236
+ *
237
+ * @return array
238
+ */
239
+ public function getColumnValues($column)
240
+ {
241
+ $values = array();
242
+ foreach ($this as $item) {
243
+ /** @var Aoe_Scheduler_Model_Job $item */
244
+ $values[] = $item->getDataUsingMethod($column);
245
+ }
246
+ return $values;
247
+ }
248
+
249
+ /**
250
+ * Search all items by field value
251
+ *
252
+ * @param string $column
253
+ * @param mixed $value
254
+ *
255
+ * @return Aoe_Scheduler_Model_Job[]
256
+ */
257
+ public function getItemsByColumnValue($column, $value)
258
+ {
259
+ $items = array();
260
+ foreach ($this as $item) {
261
+ /** @var Aoe_Scheduler_Model_Job $item */
262
+ if ($item->getDataUsingMethod($column) == $value) {
263
+ $items[] = $item;
264
+ }
265
+ }
266
+ return $items;
267
+ }
268
+
269
+ /**
270
+ * Search first item by field value
271
+ *
272
+ * @param string $column
273
+ * @param mixed $value
274
+ *
275
+ * @return Aoe_Scheduler_Model_Job|null
276
+ */
277
+ public function getItemByColumnValue($column, $value)
278
+ {
279
+ foreach ($this as $item) {
280
+ /** @var Aoe_Scheduler_Model_Job $item */
281
+ if ($item->getDataUsingMethod($column) == $value) {
282
+ return $item;
283
+ }
284
+ }
285
+ return null;
286
+ }
287
+
288
+ /**
289
+ * Setting data for all collection items
290
+ *
291
+ * @param mixed $key
292
+ * @param mixed $value
293
+ *
294
+ * @return $this
295
+ */
296
+ public function setDataToAll($key, $value = null)
297
+ {
298
+ if (is_array($key)) {
299
+ foreach ($key as $k => $v) {
300
+ $this->setDataToAll($k, $v);
301
+ }
302
+ return $this;
303
+ }
304
+
305
+ foreach ($this->getItems() as $item) {
306
+ /** @var Aoe_Scheduler_Model_Job $item */
307
+ $item->setDataUsingMethod($key, $value);
308
+ }
309
+
310
+ return $this;
311
+ }
312
+
313
+ public function toOptionArray($valueField = 'job_code', $labelField = 'name', array $additional = array())
314
+ {
315
+ return $this->_toOptionArray($valueField, $labelField, $additional);
316
+ }
317
+
318
+ public function toOptionHash($valueField = 'job_code', $labelField = 'name')
319
+ {
320
+ return $this->_toOptionHash($valueField, $labelField);
321
+ }
322
+
323
+
324
+ /**
325
+ * Convert items array to array for select options
326
+ *
327
+ * @param string $valueField
328
+ * @param string $labelField
329
+ *
330
+ * @return array
331
+ */
332
+ protected function _toOptionArray($valueField = 'job_code', $labelField = 'name', $additional = array())
333
+ {
334
+ $options = array();
335
+
336
+ $additional['value'] = $valueField;
337
+ $additional['label'] = $labelField;
338
+
339
+ foreach ($this as $item) {
340
+ /** @var Aoe_Scheduler_Model_Job $item */
341
+ $data = array();
342
+ foreach ($additional as $code => $field) {
343
+ $data[$code] = $item->getDataUsingMethod($field);
344
+ }
345
+ $options[] = $data;
346
+ }
347
+ return $options;
348
+ }
349
+
350
+
351
+ /**
352
+ * Convert items array to hash for select options
353
+ *
354
+ * @param string $valueField
355
+ * @param string $labelField
356
+ *
357
+ * @return array
358
+ */
359
+ protected function _toOptionHash($valueField = 'job_code', $labelField = 'name')
360
+ {
361
+ $res = array();
362
+ foreach ($this as $item) {
363
+ /** @var Aoe_Scheduler_Model_Job $item */
364
+ $res[$item->getDataUsingMethod($valueField)] = $item->getDataUsingMethod($labelField);
365
+ }
366
+ return $res;
367
+ }
368
+ }
app/code/community/Aoe/Scheduler/Model/Resource/Schedule/Collection.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Model_Resource_Schedule_Collection extends Mage_Cron_Model_Resource_Schedule_Collection
4
+ {
5
+ /**
6
+ * Event name prefix for events that are dispatched by this class
7
+ *
8
+ * @var string
9
+ */
10
+ protected $_eventPrefix = 'aoe_scheduler_schedule_collection';
11
+
12
+ /**
13
+ * Event parameter name that references this object in an event
14
+ *
15
+ * In an observer method you can use $observer->getData('collection') or $observer->getData('data_object') to get this object
16
+ *
17
+ * @var string
18
+ */
19
+ protected $_eventObject = 'collection';
20
+ }
app/code/community/Aoe/Scheduler/Model/Schedule.php CHANGED
@@ -9,153 +9,837 @@
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  * @method string getMessages()
10
  * @method string getCreatedAt()
11
  * @method string getScheduledAt()
12
+ * @method string setJobCode($jobCode)
13
  * @method string getJobCode()
14
+ * @method $this setMessages()
15
+ * @method $this setExecutedAt()
16
+ * @method $this setCreatedAt()
17
+ * @method $this setScheduledAt()
18
+ * @method $this setStatus()
19
+ * @method $this setFinishedAt()
20
+ * @method $this setParameters()
21
+ * @method $this setEta()
22
+ * @method string getEta()
23
+ * @method $this setHost()
24
+ * @method string getHost()
25
+ * @method $this setPid()
26
+ * @method string getPid()
27
+ * @method $this setProgressMessage()
28
+ * @method string getProgressMessage()
29
+ * @method string getLastSeen()
30
+ * @method $this setLastSeen()
31
+ * @method string getScheduledBy()
32
+ * @method $this setScheduledBy($scheduledBy)
33
+ * @method string getScheduledReason()
34
+ * @method $this setScheduledReason($scheduledReason)
35
+ * @method string getKillRequest()
36
+ * @method $this setKillRequest($killRequest)
37
  */
38
+ class Aoe_Scheduler_Model_Schedule extends Mage_Cron_Model_Schedule
39
+ {
40
+
41
+ const STATUS_KILLED = 'killed';
42
+ const STATUS_DISAPPEARED = 'gone';
43
+ const STATUS_DIDNTDOANYTHING = 'nothing';
44
+
45
+ const STATUS_SKIP_LOCKED = 'locked';
46
+ const STATUS_SKIP_OTHERJOBRUNNING = 'other_job_running';
47
+
48
+ const STATUS_DIED = 'died'; // note that died != killed
49
+
50
+ const REASON_RUNNOW_WEB = 'run_now_web';
51
+ const REASON_SCHEDULENOW_WEB = 'schedule_now_web';
52
+ const REASON_RUNNOW_CLI = 'run_now_cli';
53
+ const REASON_SCHEDULENOW_CLI = 'schedule_now_cli';
54
+ const REASON_RUNNOW_API = 'run_now_api';
55
+ const REASON_SCHEDULENOW_API = 'schedule_now_api';
56
+ const REASON_GENERATESCHEDULES = 'generate_schedules';
57
+ const REASON_DEPENDENCY_ALL = 'dependency_all';
58
+ const REASON_DEPENDENCY_SUCCESS = 'dependency_success';
59
+ const REASON_DEPENDENCY_FAILURE = 'dependency_failure';
60
+
61
+ /**
62
+ * Event name prefix for events that are dispatched by this class
63
+ *
64
+ * @var string
65
+ */
66
+ protected $_eventPrefix = 'aoe_scheduler_schedule';
67
+
68
+ /**
69
+ * Event parameter name that references this object in an event
70
+ *
71
+ * In an observer method you can use $observer->getData('schedule') or $observer->getData('data_object') to get this object
72
+ *
73
+ * @var string
74
+ */
75
+ protected $_eventObject = 'schedule';
76
+
77
+ /**
78
+ * @var Aoe_Scheduler_Model_Job
79
+ */
80
+ protected $job;
81
+
82
+ /**
83
+ * @var bool
84
+ */
85
+ protected $jobWasLocked = false;
86
+
87
+ /**
88
+ * Placeholder to keep track of active redirect buffer.
89
+ *
90
+ * @var bool
91
+ */
92
+ protected $_redirect = false;
93
+
94
+ /**
95
+ * The buffer will be flushed after any output call which causes
96
+ * the buffer's length to equal or exceed this value.
97
+ *
98
+ * Prior to PHP 5.4.0, the value 1 set the chunk size to 4096 bytes.
99
+ */
100
+ protected $_redirectOutputHandlerChunkSize = 100; // bytes
101
+
102
+ /**
103
+ * Backup of the original error settings
104
+ *
105
+ * @var array
106
+ */
107
+ protected $errorSettingsBackup = array();
108
+
109
+
110
+ /**
111
+ * Initialize from job
112
+ *
113
+ * @param Aoe_Scheduler_Model_Job $job
114
+ * @return $this
115
+ */
116
+ public function initializeFromJob(Aoe_Scheduler_Model_Job $job)
117
+ {
118
+ $this->setJobCode($job->getJobCode());
119
+ $this->setCronExpr($job->getCronExpression());
120
+ $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_PENDING);
121
+ return $this;
122
+ }
123
+
124
+
125
+ /**
126
+ * Run this task now
127
+ *
128
+ * @param bool $tryLockJob
129
+ * @param bool $forceRun
130
+ * @return Aoe_Scheduler_Model_Schedule
131
+ */
132
+ public function runNow($tryLockJob = true, $forceRun = false)
133
+ {
134
+ // if this schedule doesn't exist yet, create it
135
+ if (!$this->getCreatedAt()) {
136
+ $this->schedule();
137
+ }
138
+
139
+ if (!$forceRun) {
140
+ // lock job (see below) prevents the exact same schedule from being executed from more than one process (or server)
141
+ // the following check will prevent multiple schedules of the same type to be run in parallel
142
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
143
+ if ($processManager->isJobCodeRunning($this->getJobCode(), $this->getId())) {
144
+ $this->setStatus(self::STATUS_SKIP_OTHERJOBRUNNING);
145
+ $this->log(sprintf('Job "%s" (id: %s) will not be executed because there is already another process with the same job code running. Skipping.', $this->getJobCode(), $this->getId()));
146
+ return $this;
147
+ }
148
+ }
149
+
150
+ // lock job requires the record to be saved and having status Aoe_Scheduler_Model_Schedule::STATUS_PENDING
151
+ // workaround could be to do this: $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_PENDING)->save();
152
+ $this->jobWasLocked = false;
153
+ if ($tryLockJob && !$this->tryLockJob()) {
154
+ $this->setStatus(self::STATUS_SKIP_LOCKED);
155
+ // another cron started this job intermittently, so skip it
156
+ $this->jobWasLocked = true;
157
+ $this->log(sprintf('Job "%s" (id: %s) is locked. Skipping.', $this->getJobCode(), $this->getId()));
158
+ return $this;
159
+ }
160
+
161
+ try {
162
+ $job = $this->getJob();
163
+
164
+ if (!$job) {
165
+ Mage::throwException(sprintf("Could not create job with jobCode '%s'", $this->getJobCode()));
166
+ }
167
+
168
+ $startTime = time();
169
+ $this
170
+ ->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', $startTime))
171
+ ->setLastSeen(strftime('%Y-%m-%d %H:%M:%S', $startTime))
172
+ ->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_RUNNING)
173
+ ->setHost(gethostname())
174
+ ->setPid(getmypid())
175
+ ->save();
176
+
177
+ Aoe_Scheduler_Helper_GracefulDead::configure();
178
+
179
+ Mage::register('currently_running_schedule', $this);
180
+
181
+ Mage::dispatchEvent('cron_' . $this->getJobCode() . '_before', array('schedule' => $this));
182
+ Mage::dispatchEvent('cron_before', array('schedule' => $this));
183
+
184
+ Mage::unregister('current_cron_task');
185
+ Mage::register('current_cron_task', $this);
186
+
187
+ $this->log('Start: ' . $this->getJobCode());
188
+
189
+ $this->_startBufferToMessages();
190
+ $this->jobErrorContext();
191
+
192
+ $callback = $job->getCallback();
193
+
194
+ try {
195
+ // this is where the magic happens
196
+ $messages = call_user_func_array($callback, array($this));
197
+
198
+ $this->restoreErrorContext();
199
+ $this->_stopBufferToMessages();
200
+ } catch (Exception $e) {
201
+ $this->restoreErrorContext();
202
+ $this->_stopBufferToMessages();
203
+ throw $e;
204
+ }
205
+
206
+ $this->log('Stop: ' . $this->getJobCode());
207
+
208
+ if (!empty($messages)) {
209
+ if (is_object($messages)) {
210
+ $messages = get_class($messages);
211
+ } elseif (!is_scalar($messages)) {
212
+ $messages = var_export($messages, 1);
213
+ }
214
+ $this->addMessages(PHP_EOL . '---RETURN_VALUE---' . PHP_EOL . $messages);
215
+ }
216
+
217
+ // schedules can report an error state by returning a string that starts with "ERROR:"
218
+ if ((is_string($messages) && strtoupper(substr($messages, 0, 6)) == 'ERROR:') || $this->getStatus() === Aoe_Scheduler_Model_Schedule::STATUS_ERROR) {
219
+ $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_ERROR);
220
+ Mage::helper('aoe_scheduler')->sendErrorMail($this, $messages);
221
+ Mage::dispatchEvent('cron_' . $this->getJobCode() . '_after_error', array('schedule' => $this));
222
+ Mage::dispatchEvent('cron_after_error', array('schedule' => $this));
223
+ } elseif ((is_string($messages) && strtoupper(substr($messages, 0, 7)) == 'NOTHING') || $this->getStatus() === Aoe_Scheduler_Model_Schedule::STATUS_DIDNTDOANYTHING) {
224
+ $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_DIDNTDOANYTHING);
225
+ Mage::dispatchEvent('cron_' . $this->getJobCode() . '_after_nothing', array('schedule' => $this));
226
+ Mage::dispatchEvent('cron_after_nothing', array('schedule' => $this));
227
+ } else {
228
+ $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS);
229
+ Mage::dispatchEvent('cron_' . $this->getJobCode() . '_after_success', array('schedule' => $this));
230
+ Mage::dispatchEvent('cron_after_success', array('schedule' => $this));
231
+ }
232
+
233
+ } catch (Exception $e) {
234
+ $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_ERROR);
235
+ $this->addMessages(PHP_EOL . '---EXCEPTION---' . PHP_EOL . $e->__toString());
236
+ Mage::dispatchEvent('cron_' . $this->getJobCode() . '_exception', array('schedule' => $this, 'exception' => $e));
237
+ Mage::dispatchEvent('cron_exception', array('schedule' => $this, 'exception' => $e));
238
+ Mage::helper('aoe_scheduler')->sendErrorMail($this, $e->__toString());
239
+ }
240
+
241
+ $this->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()));
242
+ Mage::dispatchEvent('cron_' . $this->getJobCode() . '_after', array('schedule' => $this));
243
+ Mage::dispatchEvent('cron_after', array('schedule' => $this));
244
+
245
+ $this->save();
246
+ Mage::unregister('currently_running_schedule');
247
+
248
+ return $this;
249
+ }
250
+
251
+
252
+ /**
253
+ * Flag that shows that a previous execution was prevented because the job was locked
254
+ *
255
+ * @return bool
256
+ */
257
+ public function getJobWasLocked()
258
+ {
259
+ return $this->jobWasLocked;
260
+ }
261
+
262
+
263
+ /**
264
+ * Schedule this task to be executed as soon as possible
265
+ *
266
+ * @deprecated use Aoe_Scheduler_Model_Schedule::schedule() instead
267
+ * @return Aoe_Scheduler_Model_Schedule
268
+ */
269
+ public function scheduleNow()
270
+ {
271
+ return $this->schedule();
272
+ }
273
+
274
+
275
+ /**
276
+ * Schedule this task to be executed at a given time
277
+ *
278
+ * @param int $time
279
+ * @return Aoe_Scheduler_Model_Schedule
280
+ */
281
+ public function schedule($time = null)
282
+ {
283
+ if (is_null($time)) {
284
+ $time = time();
285
+ }
286
+ $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_PENDING)
287
+ ->setCreatedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
288
+ ->setScheduledAt(strftime('%Y-%m-%d %H:%M:00', $time))
289
+ ->save();
290
+ return $this;
291
+ }
292
+
293
+
294
+ /**
295
+ * Get job configuration
296
+ *
297
+ * @return Aoe_Scheduler_Model_Job
298
+ */
299
+ public function getJob()
300
+ {
301
+ if (is_null($this->job)) {
302
+ $this->job = Mage::getModel('aoe_scheduler/job')->load($this->getJobCode());
303
+ }
304
+ return $this->job;
305
+ }
306
+
307
+
308
+
309
+ /**
310
+ * Get start time (planned or actual)
311
+ *
312
+ * @return string
313
+ */
314
+ public function getStarttime()
315
+ {
316
+ $starttime = $this->getExecutedAt();
317
+ if (empty($starttime) || $starttime == '0000-00-00 00:00:00') {
318
+ $starttime = $this->getScheduledAt();
319
+ }
320
+ return $starttime;
321
+ }
322
+
323
+
324
+ /**
325
+ * Get job duration.
326
+ *
327
+ * @return bool|int time in seconds, or false
328
+ */
329
+ public function getDuration()
330
+ {
331
+ $duration = false;
332
+ if ($this->getExecutedAt() && ($this->getExecutedAt() != '0000-00-00 00:00:00')) {
333
+ if ($this->getFinishedAt() && ($this->getFinishedAt() != '0000-00-00 00:00:00')) {
334
+ $time = strtotime($this->getFinishedAt());
335
+ } elseif ($this->getStatus() == Aoe_Scheduler_Model_Schedule::STATUS_RUNNING) {
336
+ $time = time();
337
+ } else {
338
+ // Mage::throwException('No finish time found, but the job is not running');
339
+ return false;
340
+ }
341
+ $duration = $time - strtotime($this->getExecutedAt());
342
+ }
343
+ return $duration;
344
+ }
345
+
346
+ /**
347
+ * Is this process still alive?
348
+ *
349
+ * true -> alive
350
+ * false -> dead
351
+ * null -> we don't know because the task is running on a different server
352
+ *
353
+ * @return bool|null
354
+ */
355
+ public function isAlive()
356
+ {
357
+ if ($this->getStatus() == Aoe_Scheduler_Model_Schedule::STATUS_RUNNING) {
358
+ if (time() - strtotime($this->getLastSeen()) < 2 * 60) { // TODO: make this configurable
359
+ return true;
360
+ } elseif ($this->getHost() == gethostname()) {
361
+ if ($this->checkPid()) {
362
+ $this
363
+ ->setLastSeen(strftime('%Y-%m-%d %H:%M:%S', time()))
364
+ ->save();
365
+ return true;
366
+ } else {
367
+ $this->markAsDisappeared(sprintf('Process "%s" on host "%s" cannot be found anymore', $this->getPid(), $this->getHost()));
368
+ return false; // dead
369
+ }
370
+ } else {
371
+ // we don't know because the task is running on a different server
372
+ return null;
373
+ }
374
+ }
375
+ return false;
376
+ }
377
+
378
+ /**
379
+ * Mark task as disappeared
380
+ *
381
+ * @param string $message
382
+ * @return void
383
+ */
384
+ public function markAsDisappeared($message = null)
385
+ {
386
+ if (!is_null($message)) {
387
+ $this->setMessages($message);
388
+ }
389
+ $this
390
+ ->setStatus(self::STATUS_DISAPPEARED)
391
+ ->setFinishedAt($this->getLastSeen())
392
+ ->save();
393
+
394
+ $this->log(sprintf('Job "%s" (id: %s) disappeared. Message: ', $this->getJobCode(), $this->getId(), $message));
395
+ }
396
+
397
+ /**
398
+ * Check if process is running (linux only)
399
+ *
400
+ * @return bool
401
+ */
402
+ public function checkPid()
403
+ {
404
+ $pid = intval($this->getPid());
405
+ return $pid && file_exists('/proc/' . $pid);
406
+ }
407
+
408
+ /**
409
+ * Request kill
410
+ *
411
+ * @param int $time
412
+ * @param string $message
413
+ * @return $this
414
+ */
415
+ public function requestKill($time = null, $message = null)
416
+ {
417
+ if (is_null($time)) {
418
+ $time = time();
419
+ }
420
+ if (!is_null($message)) {
421
+ $this->addMessages($message);
422
+ }
423
+ $this->setKillRequest(strftime('%Y-%m-%d %H:%M:%S', $time))
424
+ ->save();
425
+ return $this;
426
+ }
427
+
428
+ /**
429
+ * Kill this process
430
+ *
431
+ * @return void
432
+ */
433
+ public function kill()
434
+ {
435
+
436
+ if (!$this->checkPid()) {
437
+ // already dead
438
+ $this->markAsDisappeared(sprintf('Did not kill job "%s" (id: %s), because it was already dead.', $this->getJobCode(), $this->getId()));
439
+ return;
440
+ }
441
+
442
+ // let's be nice first (a.k.a. "Could you please stop running now?")
443
+ if (posix_kill($this->getPid(), SIGINT)) {
444
+ $this->log(sprintf('Sending SIGINT to job "%s" (id: %s)', $this->getJobCode(), $this->getId()));
445
+ } else {
446
+ $this->log(sprintf('Error while sending SIGINT to job "%s" (id: %s)', $this->getJobCode(), $this->getId()), Zend_Log::ERR);
447
+ }
448
+
449
+ // check if process terminates within 30 seconds
450
+ $startTime = time();
451
+ while (($waitTime = (time() - $startTime) < 30) && $this->checkPid()) {
452
+ sleep(2);
453
+ }
454
+
455
+ if ($this->checkPid()) {
456
+ // What, you're still alive? OK, time to say goodbye now. You had your chance...
457
+ if (posix_kill($this->getPid(), SIGKILL)) {
458
+ $this->log(sprintf('Sending SIGKILL to job "%s" (id: %s)', $this->getJobCode(), $this->getId()));
459
+ } else {
460
+ $this->log(sprintf('Error while sending SIGKILL to job "%s" (id: %s)', $this->getJobCode(), $this->getId()), Zend_Log::ERR);
461
+ }
462
+ } else {
463
+ $this->log(sprintf('Killed job "%s" (id: %s) with SIGINT. Job terminated after %s second(s)', $this->getJobCode(), $this->getId(), $waitTime));
464
+ }
465
+
466
+ if ($this->checkPid()) {
467
+ sleep(5);
468
+ if ($this->checkPid()) {
469
+ $this->log(sprintf('Killed job "%s" (id: %s) is still alive!', $this->getJobCode(), $this->getId()), Zend_Log::ERR);
470
+ return; // without setting the status to "killed"
471
+ }
472
+ }
473
+
474
+ $this
475
+ ->setStatus(self::STATUS_KILLED)
476
+ ->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
477
+ ->save();
478
+ }
479
+
480
+ /**
481
+ * Log message to configured log file (or skip)
482
+ *
483
+ * @param $message
484
+ * @param null $level
485
+ */
486
+ protected function log($message, $level = null)
487
+ {
488
+ if ($logFile = Mage::getStoreConfig('system/cron/logFile')) {
489
+ Mage::log($message, $level, $logFile);
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Check if this is an "always" task
495
+ *
496
+ * @return bool
497
+ */
498
+ public function isAlwaysTask()
499
+ {
500
+ $isAlwaysTask = false;
501
+ try {
502
+ $job = $this->getJob();
503
+ $isAlwaysTask = $job && $job->isAlwaysTask();
504
+ } catch (Exception $e) {
505
+ Mage::logException($e);
506
+ }
507
+ return $isAlwaysTask;
508
+ }
509
+
510
+ /**
511
+ * Processing object before save data
512
+ *
513
+ * Check if there are other schedules for the same job at the same time and skip saving in this case.
514
+ *
515
+ * @return Mage_Core_Model_Abstract
516
+ */
517
+ protected function _beforeSave()
518
+ {
519
+ if (!$this->getScheduledBy() && php_sapi_name() !== 'cli' && Mage::getSingleton('admin/session')->isLoggedIn()) {
520
+ $this->setScheduledBy(Mage::getSingleton('admin/session')->getUser()->getId());
521
+ }
522
+
523
+ $collection = Mage::getModel('cron/schedule')/* @var $collection Mage_Cron_Model_Resource_Schedule_Collection */
524
+ ->getCollection()
525
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_PENDING)
526
+ ->addFieldToFilter('job_code', $this->getJobCode())
527
+ ->addFieldToFilter('scheduled_at', $this->getScheduledAt());
528
+ if ($this->getId() !== null) {
529
+ $collection->addFieldToFilter('schedule_id', array('neq' => $this->getId()));
530
+ }
531
+ $count = $collection->count();
532
+ if ($count > 0) {
533
+ $this->_dataSaveAllowed = false; // prevents this object from being stored to database
534
+ $this->log(sprintf('Pending schedule for "%s" at "%s" already exists %s times. Skipping.', $this->getJobCode(), $this->getScheduledAt(), $count));
535
+ } else {
536
+ $this->_dataSaveAllowed = true; // allow the next object to save (because it's not reset automatically)
537
+ }
538
+ return parent::_beforeSave();
539
+ }
540
+
541
+ /**
542
+ * Check if this schedule can be run
543
+ *
544
+ * @param bool $throwException
545
+ * @return bool
546
+ * @throws Exception
547
+ * @throws Mage_Core_Exception
548
+ */
549
+ public function canRun($throwException = false)
550
+ {
551
+ if ($this->isAlwaysTask()) {
552
+ return true;
553
+ }
554
+ $now = time();
555
+ $time = strtotime($this->getScheduledAt());
556
+ if ($time > $now) {
557
+ // not scheduled yet
558
+ return false;
559
+ }
560
+ $scheduleLifetime = Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_SCHEDULE_LIFETIME) * 60;
561
+ if ($time < $now - $scheduleLifetime) {
562
+ $this->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_MISSED);
563
+ $this->save();
564
+ if ($throwException) {
565
+ Mage::throwException(Mage::helper('cron')->__('Too late for the schedule.'));
566
+ }
567
+ return false;
568
+ }
569
+ return true;
570
+ }
571
+
572
+ /**
573
+ * Process schedule
574
+ *
575
+ * @return $this
576
+ */
577
+ public function process()
578
+ {
579
+ if (!$this->canRun(false)) {
580
+ return $this;
581
+ }
582
+ $this->runNow(!$this->isAlwaysTask());
583
+ return $this;
584
+ }
585
+
586
+ /**
587
+ * Get parameters (and fallback to job)
588
+ *
589
+ * @return mixed
590
+ */
591
+ public function getParameters()
592
+ {
593
+ if ($this->getData('parameters')) {
594
+ return $this->getData('parameters');
595
+ }
596
+ // fallback to job
597
+ $job = $this->getJob();
598
+ if ($job) {
599
+ return $job->getParameters();
600
+ } else {
601
+ return false;
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Switch the job error context
607
+ */
608
+ protected function jobErrorContext()
609
+ {
610
+ if (!Mage::getStoreConfigFlag('system/cron/enableErrorLog')) {
611
+ return;
612
+ }
613
+
614
+ $settings = array(
615
+ 'error_reporting' => intval(Mage::getStoreConfig('system/cron/errorLevel')),
616
+ 'log_errors' => true,
617
+ 'display_errors' => true,
618
+ 'error_log' => $this->getErrorLogFile()
619
+ );
620
+
621
+ restore_error_handler();
622
+ // (doesn't work for PHP 5.3) set_error_handler(null); // switch to PHP default error handling
623
+
624
+ if (!is_dir(dirname($settings['error_log']))) {
625
+ mkdir(dirname($settings['error_log']), 0775, true);
626
+ }
627
+
628
+ foreach ($settings as $key => $value) {
629
+ // backup original settings first
630
+ $this->errorSettingsBackup[$key] = ini_get($key);
631
+ // set new value
632
+ ini_set($key, $value);
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Restore the original error context
638
+ */
639
+ protected function restoreErrorContext()
640
+ {
641
+ if (!Mage::getStoreConfigFlag('system/cron/enableErrorLog')) {
642
+ return;
643
+ }
644
+
645
+ restore_error_handler();
646
+ foreach ($this->errorSettingsBackup as $key => $value) {
647
+ ini_set($key, $value);
648
+ }
649
+ }
650
+
651
+ /**
652
+ * Get error log filename
653
+ *
654
+ * @return string
655
+ */
656
+ public function getErrorLogFile()
657
+ {
658
+ $replace = array(
659
+ '###PID###' => $this->getPid(),
660
+ '###ID###' => $this->getId(),
661
+ '###JOBCODE###' => $this->getJobCode()
662
+ );
663
+ $basedir = Mage::getBaseDir('log') . DS . 'cron' . DS;
664
+ return $basedir . str_replace(array_keys($replace), array_values($replace), Mage::getStoreConfig('system/cron/errorLogFilename'));
665
+ }
666
+
667
+ /**
668
+ * Redirect all output to the messages field of this Schedule.
669
+ *
670
+ * We use ob_start with `_addBufferToMessages` to redirect the output.
671
+ *
672
+ * @return $this
673
+ */
674
+ protected function _startBufferToMessages()
675
+ {
676
+ if (!Mage::getStoreConfigFlag('system/cron/enableJobOutputBuffer')) {
677
+ return $this;
678
+ }
679
+
680
+ if ($this->_redirect) {
681
+ return $this;
682
+ }
683
+
684
+ $this->addMessages('---START---' . PHP_EOL);
685
+
686
+ ob_start(
687
+ array($this, '_addBufferToMessages'),
688
+ $this->_redirectOutputHandlerChunkSize
689
+ );
690
+
691
+ $this->_redirect = true;
692
+ }
693
+
694
+ /**
695
+ * Stop redirecting all output to the messages field of this Schedule.
696
+ *
697
+ * We use ob_end_flush to stop redirecting the output.
698
+ *
699
+ * @return $this
700
+ */
701
+ protected function _stopBufferToMessages()
702
+ {
703
+ if (!Mage::getStoreConfigFlag('system/cron/enableJobOutputBuffer')) {
704
+ return $this;
705
+ }
706
+
707
+ if (!$this->_redirect) {
708
+ return $this;
709
+ }
710
+
711
+ ob_end_flush();
712
+ $this->addMessages('---END---' . PHP_EOL);
713
+
714
+ $this->_redirect = false;
715
+ }
716
+
717
+ /**
718
+ * Used as callback function to redirect the output buffer
719
+ * directly into the messages field of this schedule.
720
+ *
721
+ * @param $buffer
722
+ *
723
+ * @return string
724
+ */
725
+ public function _addBufferToMessages($buffer)
726
+ {
727
+ $this->addMessages($buffer)
728
+ ->saveMessages(); // Save the directly to the schedule record.
729
+
730
+ return $buffer;
731
+ }
732
+
733
+ /**
734
+ * Append data to the current messages field.
735
+ *
736
+ * @param $messages
737
+ *
738
+ * @return $this
739
+ */
740
+ public function addMessages($messages)
741
+ {
742
+ $this->setMessages($this->getMessages() . $messages);
743
+
744
+ return $this;
745
+ }
746
+
747
+ /**
748
+ * Save the messages directly to the schedule record.
749
+ *
750
+ * If the `messages` field was not updated in the database,
751
+ * check if this is because of `data truncation` and fix the message length.
752
+ *
753
+ * @return $this
754
+ */
755
+ public function saveMessages()
756
+ {
757
+ if (!$this->getId()) {
758
+ return $this->save();
759
+ }
760
+
761
+ $connection = Mage::getSingleton('core/resource')
762
+ ->getConnection('core_write');
763
+
764
+ $count = $connection
765
+ ->update(
766
+ $this->getResource()->getMainTable(),
767
+ array('messages' => $this->getMessages()),
768
+ array('schedule_id = ?' => $this->getId())
769
+ );
770
+
771
+ if (!$count) {
772
+ /**
773
+ * Check if the row was not updated because of data truncation.
774
+ */
775
+ $warning = $this->_getPdoWarning($connection->getConnection());
776
+ if ($warning && $warning->Code = 1265) {
777
+ $maxLength = strlen($this->getMessages()) - 5000;
778
+ $this->setMessages($warning->Level . ': ' .
779
+ str_replace(' at row 1', '.', $warning->Message) . PHP_EOL . PHP_EOL .
780
+ '...' . substr($this->getMessages(), -$maxLength));
781
+ }
782
+ }
783
+
784
+ return $this;
785
+ }
786
+
787
+ /**
788
+ * Retrieve the last PDO warning.
789
+ *
790
+ * @param PDO $pdo
791
+ * @return mixed
792
+ */
793
+ protected function _getPdoWarning(PDO $pdo)
794
+ {
795
+ $originalErrorMode = $pdo->getAttribute(PDO::ATTR_ERRMODE);
796
+
797
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
798
+
799
+ $stm = $pdo->query('SHOW WARNINGS');
800
+
801
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, $originalErrorMode);
802
+
803
+ return $stm->fetchObject();
804
+ }
805
+
806
+ /**
807
+ * Bypass parent's setCronExpr is the expression is "always"
808
+ * This will break trySchedule, but always tasks will never be tried to scheduled anyway
809
+ *
810
+ * @param $expr
811
+ * @return $this
812
+ * @throws Mage_Core_Exception
813
+ */
814
+ public function setCronExpr($expr)
815
+ {
816
+ if ($expr == 'always') {
817
+ $this->setData('cron_expr', $expr);
818
+ } else {
819
+ parent::setCronExpr($expr);
820
+ }
821
+ return $this;
822
+ }
823
+
824
+ /**
825
+ * Gets statuses that are currently in the scheduler table
826
+ *
827
+ * @return array
828
+ */
829
+ public function getStatuses()
830
+ {
831
+ $schedules = clone $this->getCollection()
832
+ ->setOrder('status', Zend_Db_Select::SQL_ASC);
833
+ $schedules->getSelect()
834
+ ->group('status')
835
+ ->reset(Zend_Db_Select::COLUMNS)
836
+ ->columns('status');
837
+ $statuses = $schedules->getConnection()
838
+ ->fetchCol($schedules->getSelect());
839
+ $statusArray = array();
840
+ foreach ($statuses as $status) {
841
+ $statusArray[$status] = $status;
842
+ }
843
+ return $statusArray;
844
+ }
845
+ }
app/code/community/Aoe/Scheduler/Model/ScheduleManager.php ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Schedule Manager
5
+ *
6
+ * @author Fabrizio Branca
7
+ * @since 2014-08-14
8
+ */
9
+ class Aoe_Scheduler_Model_ScheduleManager
10
+ {
11
+ const XML_PATH_HISTORY_MAXNO = 'system/cron/maxNoOfSuccessfulTasks';
12
+ const CACHE_KEY_SCHEDULER_LASTRUNS = 'cron_lastruns';
13
+
14
+ /**
15
+ * Mark missed schedule records by changing status
16
+ *
17
+ * @return $this
18
+ */
19
+ public function cleanMissedSchedules()
20
+ {
21
+ $schedules = Mage::getModel('cron/schedule')->getCollection()
22
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_PENDING)
23
+ ->addFieldToFilter('scheduled_at', array('lt' => strftime('%Y-%m-%d %H:%M:%S', time())))
24
+ ->addOrder('scheduled_at', 'DESC');
25
+
26
+ $seenJobs = array();
27
+ foreach ($schedules as $key => $schedule) {
28
+ /* @var Aoe_Scheduler_Model_Schedule $schedule */
29
+ if (isset($seenJobs[$schedule->getJobCode()])) {
30
+ $schedule
31
+ ->setMessages('Multiple tasks with the same job code were piling up. Skipping execution of duplicates.')
32
+ ->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_MISSED)
33
+ ->save();
34
+ } else {
35
+ $seenJobs[$schedule->getJobCode()] = 1;
36
+ }
37
+ }
38
+
39
+ return $this;
40
+ }
41
+
42
+ /**
43
+ * Get pending schedules
44
+ *
45
+ * @param array $whitelist
46
+ * @param array $blacklist
47
+ *
48
+ * @return Mage_Cron_Model_Resource_Schedule_Collection
49
+ */
50
+ public function getPendingSchedules(array $whitelist = array(), array $blacklist = array())
51
+ {
52
+ $pendingSchedules = Mage::getModel('cron/schedule')->getCollection()
53
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_PENDING)
54
+ ->addFieldToFilter('scheduled_at', array('lt' => strftime('%Y-%m-%d %H:%M:%S', time())))
55
+ ->addOrder('scheduled_at', 'ASC');
56
+
57
+ $whitelist = array_filter(array_map('trim', $whitelist));
58
+ if (!empty($whitelist)) {
59
+ $pendingSchedules->addFieldToFilter('job_code', array('in' => $whitelist));
60
+ }
61
+
62
+ $blacklist = array_filter(array_map('trim', $blacklist));
63
+ if (!empty($blacklist)) {
64
+ $pendingSchedules->addFieldToFilter('job_code', array('nin' => $blacklist));
65
+ }
66
+
67
+ return $pendingSchedules;
68
+ }
69
+
70
+ /**
71
+ * Get job for task marked as always
72
+ *
73
+ * (Instead of reusing existing one - which results in loosing the history - create a new one every time)
74
+ *
75
+ * @param $jobCode
76
+ * @return Aoe_Scheduler_Model_Schedule|false
77
+ */
78
+ public function getScheduleForAlwaysJob($jobCode)
79
+ {
80
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
81
+ if (!$processManager->isJobCodeRunning($jobCode)) {
82
+ $ts = strftime('%Y-%m-%d %H:%M:00', time());
83
+ $schedule = Mage::getModel('cron/schedule') /* @var $schedule Aoe_Scheduler_Model_Schedule */
84
+ ->setJobCode($jobCode)
85
+ ->setStatus(Aoe_Scheduler_Model_Schedule::STATUS_RUNNING)
86
+ ->setCreatedAt($ts)
87
+ ->setScheduledAt($ts)
88
+ ->save();
89
+ return $schedule;
90
+ }
91
+ return false;
92
+ }
93
+
94
+ /**
95
+ * Delete duplicate crons
96
+ *
97
+ * @throws Exception
98
+ */
99
+ public function deleteDuplicates()
100
+ {
101
+ $cron_schedule = Mage::getSingleton('core/resource')->getTableName('cron_schedule');
102
+ $conn = Mage::getSingleton('core/resource')->getConnection('core_read');
103
+
104
+ // TODO: Direct sql is not nice. We can do better... :)
105
+ $results = $conn->fetchAll("
106
+ SELECT
107
+ GROUP_CONCAT(schedule_id) AS ids,
108
+ CONCAT(job_code, scheduled_at) AS jobkey,
109
+ count(*) AS qty
110
+ FROM {$cron_schedule}
111
+ WHERE status = '" . Aoe_Scheduler_Model_Schedule::STATUS_PENDING . "'
112
+ GROUP BY jobkey
113
+ HAVING qty > 1;
114
+ ");
115
+ foreach ($results as $row) {
116
+ $ids = explode(',', $row['ids']);
117
+ $removeIds = array_slice($ids, 1);
118
+ foreach ($removeIds as $id) {
119
+ Mage::getModel('cron/schedule')->load($id)->delete();
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Generate cron schedule.
126
+ * Rewrites the original method to remove duplicates afterwards (that exists because of a bug)
127
+ *
128
+ * @return $this
129
+ */
130
+ public function generateSchedules()
131
+ {
132
+ /**
133
+ * check if schedule generation is needed
134
+ */
135
+ $lastRun = Mage::app()->loadCache(Mage_Cron_Model_Observer::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT);
136
+ if ($lastRun > time() - Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_SCHEDULE_GENERATE_EVERY) * 60) {
137
+ return $this;
138
+ }
139
+
140
+ $startTime = microtime(true);
141
+
142
+ /* @var $jobs Aoe_Scheduler_Model_Resource_Job_Collection */
143
+ $jobs = Mage::getSingleton('aoe_scheduler/job')->getCollection();
144
+ $jobs->setActiveOnly(true);
145
+ foreach ($jobs as $job) {
146
+ /* @var Aoe_Scheduler_Model_Job $job */
147
+ $this->generateSchedulesForJob($job);
148
+ }
149
+
150
+ /**
151
+ * save time schedules generation was ran with no expiration
152
+ */
153
+ Mage::app()->saveCache(time(), Mage_Cron_Model_Observer::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT, array('crontab'), null);
154
+
155
+ $this->deleteDuplicates();
156
+
157
+ if ($logFile = Mage::getStoreConfig('system/cron/logFile')) {
158
+ $history = Mage::getModel('cron/schedule')->getCollection()
159
+ ->setPageSize(1)
160
+ ->setOrder('scheduled_at', 'desc')
161
+ ->load();
162
+
163
+ $newestSchedule = $history->getFirstItem(); /* @var $newestSchedule Aoe_Scheduler_Model_Schedule */
164
+
165
+ $duration = microtime(true) - $startTime;
166
+ Mage::log('Generated schedule. Newest task is scheduled at "' . $newestSchedule->getScheduledAt() . '". (Duration: ' . round($duration, 2) . ' sec)', null, $logFile);
167
+ }
168
+
169
+ return $this;
170
+ }
171
+
172
+ /**
173
+ * Flushed all future pending schedules.
174
+ *
175
+ * @param string $jobCode
176
+ * @return $this
177
+ */
178
+ public function flushSchedules($jobCode = null)
179
+ {
180
+ /* @var $pendingSchedules Mage_Cron_Model_Resource_Schedule_Collection */
181
+ $pendingSchedules = Mage::getModel('cron/schedule')->getCollection()
182
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_PENDING)
183
+ ->addFieldToFilter('scheduled_at', array('gt' => strftime('%Y-%m-%d %H:%M:%S', time())))
184
+ ->addOrder('scheduled_at', 'ASC');
185
+ if (!empty($jobCode)) {
186
+ $pendingSchedules->addFieldToFilter('job_code', $jobCode);
187
+ }
188
+ foreach ($pendingSchedules as $key => $schedule) {
189
+ /* @var Aoe_Scheduler_Model_Schedule $schedule */
190
+ $schedule->delete();
191
+ }
192
+ Mage::app()->saveCache(0, Mage_Cron_Model_Observer::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT, array('crontab'), null);
193
+ return $this;
194
+ }
195
+
196
+ /**
197
+ * Delete all schedules
198
+ *
199
+ * @return $this
200
+ */
201
+ public function deleteAll()
202
+ {
203
+ /* @var $schedules Mage_Cron_Model_Resource_Schedule_Collection */
204
+ $schedules = Mage::getModel('cron/schedule')->getCollection();
205
+ foreach ($schedules as $key => $schedule) { /* @var Aoe_Scheduler_Model_Schedule $schedule */
206
+ $schedule->delete();
207
+ }
208
+ Mage::app()->saveCache(0, Mage_Cron_Model_Observer::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT, array('crontab'), null);
209
+ return $this;
210
+ }
211
+
212
+ /**
213
+ * Generate jobs for config information
214
+ *
215
+ * @param Aoe_Scheduler_Model_Job $job
216
+ *
217
+ * @return $this
218
+ */
219
+ public function generateSchedulesForJob(Aoe_Scheduler_Model_Job $job)
220
+ {
221
+ if (!$job->canBeScheduled()) {
222
+ return $this;
223
+ }
224
+
225
+ $exists = array();
226
+ foreach ($this->getPendingSchedules(array($job->getJobCode()), array()) as $schedule) {
227
+ /* @var Aoe_Scheduler_Model_Schedule $schedule */
228
+ $exists[$schedule->getJobCode() . '/' . $schedule->getScheduledAt()] = 1;
229
+ }
230
+
231
+ $now = time();
232
+ $scheduleAheadFor = Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_SCHEDULE_AHEAD_FOR)*60;
233
+ $timeAhead = $now + $scheduleAheadFor;
234
+
235
+ $schedule = Mage::getModel('cron/schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
236
+ $schedule->initializeFromJob($job);
237
+ $schedule->setScheduledReason(Aoe_Scheduler_Model_Schedule::REASON_GENERATESCHEDULES);
238
+
239
+ for ($time = $now; $time < $timeAhead; $time += 60) {
240
+ $ts = strftime('%Y-%m-%d %H:%M:00', $time);
241
+ if (!empty($exists[$job->getJobCode().'/'.$ts])) {
242
+ // already scheduled
243
+ continue;
244
+ }
245
+ if (!$schedule->trySchedule($time)) {
246
+ // time does not match cron expression
247
+ continue;
248
+ }
249
+ $schedule->unsScheduleId()->save();
250
+ }
251
+
252
+ return $this;
253
+ }
254
+
255
+ /**
256
+ * Clean up the history of tasks
257
+ * This override deals with custom states added in Aoe_Scheduler
258
+ *
259
+ * @return Mage_Cron_Model_Observer
260
+ */
261
+ public function cleanup()
262
+ {
263
+ // check if history cleanup is needed
264
+ $lastCleanup = Mage::app()->loadCache(Mage_Cron_Model_Observer::CACHE_KEY_LAST_HISTORY_CLEANUP_AT);
265
+ if ($lastCleanup > time() - Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_CLEANUP_EVERY) * 60) {
266
+ return $this;
267
+ }
268
+
269
+ $startTime = microtime(true);
270
+
271
+ $history = Mage::getModel('cron/schedule')->getCollection()
272
+ ->addFieldToFilter('status', array('nin' => array(
273
+ Aoe_Scheduler_Model_Schedule::STATUS_PENDING,
274
+ Aoe_Scheduler_Model_Schedule::STATUS_RUNNING
275
+ )))
276
+ ->load();
277
+
278
+ $historyLifetimes = array(
279
+ Aoe_Scheduler_Model_Schedule::STATUS_KILLED => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_SUCCESS)*60,
280
+ Aoe_Scheduler_Model_Schedule::STATUS_DISAPPEARED => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_FAILURE)*60,
281
+ Aoe_Scheduler_Model_Schedule::STATUS_DIDNTDOANYTHING => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_SUCCESS)*60,
282
+ Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_SUCCESS)*60,
283
+ Aoe_Scheduler_Model_Schedule::STATUS_MISSED => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_FAILURE)*60,
284
+ Aoe_Scheduler_Model_Schedule::STATUS_ERROR => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_FAILURE)*60,
285
+ Aoe_Scheduler_Model_Schedule::STATUS_DIED => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_FAILURE)*60,
286
+ Aoe_Scheduler_Model_Schedule::STATUS_SKIP_LOCKED => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_FAILURE)*60,
287
+ Aoe_Scheduler_Model_Schedule::STATUS_SKIP_OTHERJOBRUNNING => Mage::getStoreConfig(Mage_Cron_Model_Observer::XML_PATH_HISTORY_FAILURE)*60,
288
+ );
289
+
290
+ $now = time();
291
+ foreach ($history->getIterator() as $record) { /* @var $record Aoe_Scheduler_Model_Schedule */
292
+ if (isset($historyLifetimes[$record->getStatus()])) {
293
+ if (strtotime($record->getExecutedAt()) < $now - $historyLifetimes[$record->getStatus()]) {
294
+ $record->delete();
295
+ }
296
+ }
297
+ }
298
+
299
+ // save time history cleanup was ran with no expiration
300
+ Mage::app()->saveCache(time(), Mage_Cron_Model_Observer::CACHE_KEY_LAST_HISTORY_CLEANUP_AT, array('crontab'), null);
301
+
302
+
303
+ // delete successful tasks (beyond the configured max number of tasks to keep)
304
+ $maxNo = Mage::getStoreConfig(self::XML_PATH_HISTORY_MAXNO);
305
+ if ($maxNo) {
306
+ $history = Mage::getModel('cron/schedule')->getCollection()
307
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS)
308
+ ->setOrder('finished_at', 'desc')
309
+ ->load();
310
+ $counter = array();
311
+ foreach ($history->getIterator() as $record) { /* @var $record Aoe_Scheduler_Model_Schedule */
312
+ $jobCode = $record->getJobCode();
313
+ if (!isset($counter[$jobCode])) {
314
+ $counter[$jobCode] = 0;
315
+ }
316
+ $counter[$jobCode]++;
317
+ if ($counter[$jobCode] > $maxNo) {
318
+ $record->delete();
319
+ }
320
+ }
321
+ }
322
+
323
+ if ($logFile = Mage::getStoreConfig('system/cron/logFile')) {
324
+ $duration = microtime(true) - $startTime;
325
+ Mage::log('History cleanup (Duration: ' . round($duration, 2) . ' sec)', null, $logFile);
326
+ }
327
+
328
+ return $this;
329
+ }
330
+
331
+ /**
332
+ * Log run
333
+ */
334
+ public function logRun()
335
+ {
336
+ $lastRuns = Mage::app()->loadCache(self::CACHE_KEY_SCHEDULER_LASTRUNS);
337
+ $lastRuns = explode(',', $lastRuns);
338
+ $lastRuns[] = time();
339
+ $lastRuns = array_slice($lastRuns, -100);
340
+ Mage::app()->saveCache(implode(',', $lastRuns), self::CACHE_KEY_SCHEDULER_LASTRUNS, array('crontab'), null);
341
+ }
342
+
343
+ /**
344
+ * Create some statistics based on self::CACHE_KEY_SCHEDULER_LASTRUNS
345
+ *
346
+ * @return array|bool
347
+ */
348
+ public function getMeasuredCronInterval()
349
+ {
350
+ $lastRuns = Mage::app()->loadCache(self::CACHE_KEY_SCHEDULER_LASTRUNS);
351
+ $lastRuns = array_values(array_filter(explode(',', $lastRuns)));
352
+ if (count($lastRuns) < 3) {
353
+ // not enough data points
354
+ return false;
355
+ }
356
+ $gaps = array();
357
+ foreach ($lastRuns as $index => $run) {
358
+ if ($index > 0) {
359
+ $gaps[$index] = intval($lastRuns[$index]) - intval($lastRuns[$index-1]);
360
+ }
361
+ }
362
+ return array(
363
+ 'average' => round((array_sum($gaps) / count($gaps)) / 60, 2),
364
+ 'max' => round(max($gaps) / 60, 2),
365
+ 'min' => round(min($gaps) / 60, 2),
366
+ 'count' => count($gaps),
367
+ 'last' => end($lastRuns)
368
+ );
369
+ }
370
+ }
app/code/community/Aoe/Scheduler/Model/Task/Heartbeat.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class Aoe_Scheduler_Model_HeartbeatTask
5
+ *
6
+ * @author Fabrizio Branca
7
+ */
8
+ class Aoe_Scheduler_Model_Task_Heartbeat
9
+ {
10
+
11
+ public function run()
12
+ {
13
+ return true;
14
+ }
15
+ }
app/code/community/Aoe/Scheduler/Model/Task/Test.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Test Task
5
+ *
6
+ * @author Fabrizio Branca
7
+ * @since 2013-10-10
8
+ */
9
+ class Aoe_Scheduler_Model_Task_Test
10
+ {
11
+
12
+ /**
13
+ * General purpose test task.
14
+ * Behavior can be controlled via parameters
15
+ *
16
+ * @param Aoe_Scheduler_Model_Schedule $schedule
17
+ * @return string
18
+ * @throws Exception
19
+ */
20
+ public function run(Aoe_Scheduler_Model_Schedule $schedule)
21
+ {
22
+
23
+ $parameters = $schedule->getParameters();
24
+ if ($parameters) {
25
+ $parameters = unserialize($parameters);
26
+ }
27
+
28
+ // fake duration
29
+ $duration = 0;
30
+ if ($parameters && isset($parameters['duration'])) {
31
+ $duration = $parameters['duration'];
32
+ }
33
+ sleep($duration);
34
+
35
+ /* // testing the error log feature...
36
+ array_keys('ssdsd');
37
+ error_log( "Hello, errors!" );
38
+ $t = I_AM_NOT_DEFINED;
39
+ */
40
+
41
+ if ($parameters && $parameters['outcome'] == 'error') {
42
+ return 'ERROR: This schedule has failed.';
43
+ }
44
+
45
+ if ($parameters && $parameters['outcome'] == 'nothing') {
46
+ return 'NOTHING: Did not do anything';
47
+ }
48
+
49
+ if ($parameters && $parameters['outcome'] == 'exception') {
50
+ throw new Exception('This is a dummy exception');
51
+ }
52
+
53
+
54
+ // Simulating ETA;
55
+ // $starttime = time();
56
+ // // $endtime = $starttime + rand(180, 360);
57
+ // $endtime = $starttime + $duration;
58
+ // $schedule
59
+ // ->setEta(strftime('%Y-%m-%d %H:%M:%S', $endtime))
60
+ // ->save();
61
+ // while ($endtime > time()) {
62
+ // sleep(5);
63
+ // $schedule
64
+ // ->setProgressMessage('Work in progress. Time spent: ' . (time() - $starttime))
65
+ // ->setEta(strftime('%Y-%m-%d %H:%M:%S', $endtime))
66
+ // ->save();
67
+ // }
68
+ //
69
+ // $schedule
70
+ // ->setProgressMessage('')
71
+ // ->save();
72
+ }
73
+ }
app/code/community/Aoe/Scheduler/Model/TestTask.php DELETED
@@ -1,12 +0,0 @@
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/Test/Helper/Data.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Test_Helper_Data extends EcomDev_PHPUnit_Test_Case
4
+ {
5
+ /**
6
+ * @test
7
+ * @return Aoe_Scheduler_Helper_Data
8
+ */
9
+ public function checkClass()
10
+ {
11
+ /* @var Aoe_Scheduler_Helper_Data $helper */
12
+ $helper = Mage::helper('aoe_scheduler');
13
+
14
+ $this->assertInstanceOf('Aoe_Scheduler_Helper_Data', $helper);
15
+
16
+ return $helper;
17
+ }
18
+ }
app/code/community/Aoe/Scheduler/Test/Model/Schedule/Runnow.php ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Test_Model_Schedule_Runnow extends EcomDev_PHPUnit_Test_Case
4
+ {
5
+
6
+ public function setup()
7
+ {
8
+ // delete all schedules
9
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager'); /* @var Aoe_Scheduler_Model_ScheduleManager $scheduleManager */
10
+ $scheduleManager->deleteAll();
11
+ }
12
+
13
+ /**
14
+ * @test
15
+ * @return Aoe_Scheduler_Model_Schedule
16
+ */
17
+ public function checkClass()
18
+ {
19
+ /* @var Aoe_Scheduler_Model_Schedule $schedule */
20
+ $schedule = Mage::getModel('cron/schedule');
21
+ $this->assertInstanceOf('Aoe_Scheduler_Model_Schedule', $schedule);
22
+ return $schedule;
23
+ }
24
+
25
+ /**
26
+ * @test
27
+ */
28
+ public function runJob()
29
+ {
30
+ $schedule = Mage::getModel('cron/schedule');
31
+
32
+ $jobCode = 'aoescheduler_testtask';
33
+
34
+ $schedule->setJobCode($jobCode);
35
+ $schedule->runNow(false);
36
+
37
+ $scheduleId = $schedule->getId();
38
+ $this->assertGreaterThan(0, intval($schedule->getId()));
39
+
40
+ /* @var Aoe_Scheduler_Model_Schedule $loadedSchedule */
41
+ $loadedSchedule = Mage::getModel('cron/schedule')->load($scheduleId);
42
+ $this->assertEquals($scheduleId, $loadedSchedule->getId());
43
+
44
+ $this->assertEquals(gethostname(), $loadedSchedule->getHost());
45
+ $this->assertEquals(getmypid(), $loadedSchedule->getPid());
46
+
47
+ $this->assertEquals(Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS, $loadedSchedule->getStatus());
48
+
49
+ $this->assertEventDispatched(
50
+ array(
51
+ 'cron_after',
52
+ 'cron_after_success',
53
+ 'cron_' . $jobCode . '_after',
54
+ 'cron_' . $jobCode . '_after_success',
55
+ 'cron_before',
56
+ 'cron_' . $jobCode . '_before',
57
+ )
58
+ );
59
+ }
60
+
61
+ /**
62
+ * @test
63
+ */
64
+ public function runJobWithError()
65
+ {
66
+ $schedule = Mage::getModel('cron/schedule');
67
+
68
+ $jobCode = 'aoescheduler_testtask';
69
+
70
+ $parameter = array('outcome' => 'error');
71
+
72
+ $schedule->setJobCode($jobCode);
73
+ $schedule->setParameters(serialize($parameter));
74
+ $schedule->runNow(false);
75
+
76
+ $scheduleId = $schedule->getId();
77
+ $this->assertGreaterThan(0, intval($schedule->getId()));
78
+
79
+ /* @var Aoe_Scheduler_Model_Schedule $loadedSchedule */
80
+ $loadedSchedule = Mage::getModel('cron/schedule')->load($scheduleId);
81
+ $this->assertEquals($scheduleId, $loadedSchedule->getId());
82
+
83
+ $this->assertEquals(gethostname(), $loadedSchedule->getHost());
84
+ $this->assertEquals(getmypid(), $loadedSchedule->getPid());
85
+
86
+ $this->assertEquals(Aoe_Scheduler_Model_Schedule::STATUS_ERROR, $loadedSchedule->getStatus());
87
+
88
+ $this->assertEventDispatched(
89
+ array(
90
+ 'cron_after',
91
+ 'cron_after_error',
92
+ 'cron_' . $jobCode . '_after',
93
+ 'cron_' . $jobCode . '_after_error',
94
+ 'cron_before',
95
+ 'cron_' . $jobCode . '_before',
96
+ )
97
+ );
98
+ }
99
+
100
+ /**
101
+ * @test
102
+ */
103
+ public function runJobWithNothing()
104
+ {
105
+ $schedule = Mage::getModel('cron/schedule');
106
+
107
+ $jobCode = 'aoescheduler_testtask';
108
+
109
+ $parameter = array('outcome' => 'nothing');
110
+
111
+ $schedule->setJobCode($jobCode);
112
+ $schedule->setParameters(serialize($parameter));
113
+ $schedule->runNow(false);
114
+
115
+ $scheduleId = $schedule->getId();
116
+ $this->assertGreaterThan(0, intval($schedule->getId()));
117
+
118
+ /* @var Aoe_Scheduler_Model_Schedule $loadedSchedule */
119
+ $loadedSchedule = Mage::getModel('cron/schedule')->load($scheduleId);
120
+ $this->assertEquals($scheduleId, $loadedSchedule->getId());
121
+
122
+ $this->assertEquals(gethostname(), $loadedSchedule->getHost());
123
+ $this->assertEquals(getmypid(), $loadedSchedule->getPid());
124
+
125
+ $this->assertEquals(Aoe_Scheduler_Model_Schedule::STATUS_DIDNTDOANYTHING, $loadedSchedule->getStatus());
126
+
127
+ $this->assertEventDispatched(
128
+ array(
129
+ 'cron_after',
130
+ 'cron_after_nothing',
131
+ 'cron_' . $jobCode . '_after',
132
+ 'cron_' . $jobCode . '_after_nothing',
133
+ 'cron_before',
134
+ 'cron_' . $jobCode . '_before',
135
+ )
136
+ );
137
+ }
138
+
139
+ /**
140
+ * @test
141
+ */
142
+ public function runJobWithException()
143
+ {
144
+ $schedule = Mage::getModel('cron/schedule');
145
+
146
+ $jobCode = 'aoescheduler_testtask';
147
+
148
+ $parameter = array('outcome' => 'exception');
149
+
150
+ $schedule->setJobCode($jobCode);
151
+ $schedule->setParameters(serialize($parameter));
152
+ $schedule->runNow(false);
153
+
154
+ $scheduleId = $schedule->getId();
155
+ $this->assertGreaterThan(0, intval($schedule->getId()));
156
+
157
+ /* @var Aoe_Scheduler_Model_Schedule $loadedSchedule */
158
+ $loadedSchedule = Mage::getModel('cron/schedule')->load($scheduleId);
159
+ $this->assertEquals($scheduleId, $loadedSchedule->getId());
160
+
161
+ $this->assertEquals(gethostname(), $loadedSchedule->getHost());
162
+ $this->assertEquals(getmypid(), $loadedSchedule->getPid());
163
+
164
+ $this->assertEquals(Aoe_Scheduler_Model_Schedule::STATUS_ERROR, $loadedSchedule->getStatus());
165
+
166
+ $this->assertEventDispatched(
167
+ array(
168
+ 'cron_after',
169
+ 'cron_exception',
170
+ 'cron_' . $jobCode . '_after',
171
+ 'cron_' . $jobCode . '_exception',
172
+ 'cron_before',
173
+ 'cron_' . $jobCode . '_before',
174
+ )
175
+ );
176
+ }
177
+ }
app/code/community/Aoe/Scheduler/Test/Model/Schedule/Scheduling.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Aoe_Scheduler_Test_Model_Schedule_Scheduling extends EcomDev_PHPUnit_Test_Case
4
+ {
5
+
6
+ /**
7
+ * @test
8
+ */
9
+ public function generateSchedule()
10
+ {
11
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager'); /* @var Aoe_Scheduler_Model_ScheduleManager $scheduleManager */
12
+
13
+ $scheduleManager->deleteAll();
14
+ $collection = Mage::getModel('cron/schedule')->getCollection();
15
+ $this->assertCount(0, $collection);
16
+
17
+ $scheduleManager->generateSchedules();
18
+ $collection = Mage::getModel('cron/schedule')->getCollection(); /* @var $collection Mage_Cron_Model_Resource_Schedule_Collection */
19
+ $this->assertGreaterThan(0, $collection->count());
20
+
21
+ $scheduleManager->deleteAll();
22
+ $collection = Mage::getModel('cron/schedule')->getCollection();
23
+ $this->assertCount(0, $collection);
24
+ }
25
+
26
+ /**
27
+ * @param $runCronCallBack callable
28
+ * @dataProvider runCronProvider
29
+ * @test
30
+ */
31
+ public function scheduleJobAndRunCron($runCronCallBack)
32
+ {
33
+ // delete all schedules
34
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager'); /* @var Aoe_Scheduler_Model_ScheduleManager $scheduleManager */
35
+ $scheduleManager->deleteAll();
36
+
37
+ // fake schedule generation to avoid it to be generated on the next run:
38
+ Mage::app()->saveCache(time(), Mage_Cron_Model_Observer::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT, array('crontab'), null);
39
+
40
+ $schedule = Mage::getModel('cron/schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
41
+ $jobCode = 'aoescheduler_testtask';
42
+ $schedule->setJobCode($jobCode);
43
+ $schedule->schedule();
44
+ $schedule->setScheduledReason('unittest');
45
+ $schedule->save();
46
+ $scheduleId = $schedule->getId();
47
+ $this->assertGreaterThan(0, intval($scheduleId));
48
+
49
+ // check for pending status
50
+ $loadedSchedule = Mage::getModel('cron/schedule')->load($scheduleId); /* @var Aoe_Scheduler_Model_Schedule $loadedSchedule */
51
+ $this->assertEquals($scheduleId, $loadedSchedule->getId());
52
+ $this->assertEquals(Aoe_Scheduler_Model_Schedule::STATUS_PENDING, $loadedSchedule->getStatus());
53
+
54
+ // run cron
55
+ $runCronCallBack();
56
+
57
+ // check for success status
58
+ $loadedSchedule = Mage::getModel('cron/schedule')->load($scheduleId); /* @var Aoe_Scheduler_Model_Schedule $loadedSchedule */
59
+ $this->assertEquals($scheduleId, $loadedSchedule->getId());
60
+ $this->assertEquals(Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS, $loadedSchedule->getStatus());
61
+ }
62
+
63
+ /**
64
+ * Provider for a callback that executed a cron run
65
+ *
66
+ * @return array
67
+ */
68
+ public function runCronProvider()
69
+ {
70
+ return array(
71
+ array(function () {
72
+ // trigger dispatch
73
+ $observer = Mage::getModel('aoe_scheduler/observer'); /* @var $observers Aoe_Scheduler_Model_Observer */
74
+ $observer->dispatch(new Varien_Event_Observer());
75
+ }),
76
+ array(function () {
77
+ shell_exec('/usr/bin/php ' . Mage::getBaseDir() . '/cron.php');
78
+ shell_exec('cd ' . Mage::getBaseDir() . '/shell && /usr/bin/php scheduler.php --action wait');
79
+ }),
80
+ array(function () {
81
+ shell_exec('/bin/sh ' . Mage::getBaseDir() . '/cron.sh');
82
+ shell_exec('cd ' . Mage::getBaseDir() . '/shell && /usr/bin/php scheduler.php --action wait');
83
+ }),
84
+ array(function () {
85
+ shell_exec('/bin/sh ' . Mage::getBaseDir() . '/cron.sh cron.php -mdefault 1');
86
+ shell_exec('cd ' . Mage::getBaseDir() . '/shell && /usr/bin/php scheduler.php --action wait');
87
+ }),
88
+ array(function () {
89
+ shell_exec('cd ' . Mage::getBaseDir() . '/shell && /usr/bin/php scheduler.php --action cron --mode default');
90
+ shell_exec('cd ' . Mage::getBaseDir() . '/shell && /usr/bin/php scheduler.php --action wait');
91
+ }),
92
+ array(function () {
93
+ shell_exec('/bin/bash ' . Mage::getBaseDir() . '/scheduler_cron.sh');
94
+ shell_exec('cd ' . Mage::getBaseDir() . '/shell && /usr/bin/php scheduler.php --action wait');
95
+ }),
96
+ array(function () {
97
+ shell_exec('/bin/bash ' . Mage::getBaseDir() . '/scheduler_cron.sh --mode default');
98
+ shell_exec('cd ' . Mage::getBaseDir() . '/shell && /usr/bin/php scheduler.php --action wait');
99
+ })
100
+ );
101
+ }
102
+ }
app/code/community/Aoe/Scheduler/controllers/Adminhtml/AbstractController.php DELETED
@@ -1,66 +0,0 @@
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 DELETED
@@ -1,112 +0,0 @@
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/InstructionsController.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Instructions controller
4
+ *
5
+ * @author Fabrizio Branca
6
+ */
7
+ class Aoe_Scheduler_Adminhtml_InstructionsController extends Aoe_Scheduler_Controller_AbstractController
8
+ {
9
+
10
+ /**
11
+ * Acl checking
12
+ *
13
+ * @return bool
14
+ */
15
+ protected function _isAllowed()
16
+ {
17
+ return Mage::getSingleton('admin/session')->isAllowed('system/aoe_scheduler/aoe_scheduler_instructions');
18
+ }
19
+ }
app/code/community/Aoe/Scheduler/controllers/Adminhtml/JobController.php ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cron controller
4
+ *
5
+ * @author Fabrizio Branca
6
+ */
7
+ class Aoe_Scheduler_Adminhtml_JobController extends Aoe_Scheduler_Controller_AbstractController
8
+ {
9
+ /**
10
+ * Mass action: disable
11
+ *
12
+ * @return void
13
+ */
14
+ public function disableAction()
15
+ {
16
+ $codes = $this->getMassActionCodes();
17
+ foreach ($codes as $code) {
18
+ /** @var Aoe_Scheduler_Model_Job $job */
19
+ $job = Mage::getModel('aoe_scheduler/job')->load($code);
20
+ if ($job->getJobCode() && $job->getIsActive()) {
21
+ $job->setIsActive(false)->save();
22
+ $this->_getSession()->addSuccess($this->__('Disabled "%s"', $code));
23
+
24
+ /* @var Aoe_Scheduler_Model_ScheduleManager $scheduleManager */
25
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager');
26
+ $scheduleManager->flushSchedules($job->getJobCode());
27
+ $this->_getSession()->addNotice($this->__('Pending schedules for "%s" have been flushed.', $job->getJobCode()));
28
+ }
29
+ }
30
+ $this->_redirect('*/*/index');
31
+ }
32
+
33
+ /**
34
+ * Mass action: enable
35
+ *
36
+ * @return void
37
+ */
38
+ public function enableAction()
39
+ {
40
+ $codes = $this->getMassActionCodes();
41
+ foreach ($codes as $code) {
42
+ /** @var Aoe_Scheduler_Model_Job $job */
43
+ $job = Mage::getModel('aoe_scheduler/job')->load($code);
44
+ if ($job->getJobCode() && !$job->getIsActive()) {
45
+ $job->setIsActive(true)->save();
46
+ $this->_getSession()->addSuccess($this->__('Enabled "%s"', $code));
47
+
48
+ /* @var Aoe_Scheduler_Model_ScheduleManager $scheduleManager */
49
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager');
50
+ $scheduleManager->generateSchedulesForJob($job);
51
+ $this->_getSession()->addNotice($this->__('Job "%s" has been scheduled.', $job->getJobCode()));
52
+ }
53
+ }
54
+ $this->_redirect('*/*/index');
55
+ }
56
+
57
+ /**
58
+ * Mass action: schedule now
59
+ *
60
+ * @return void
61
+ */
62
+ public function scheduleNowAction()
63
+ {
64
+ $codes = $this->getMassActionCodes();
65
+ foreach ($codes as $key) {
66
+ Mage::getModel('cron/schedule')
67
+ ->setJobCode($key)
68
+ ->setScheduledReason(Aoe_Scheduler_Model_Schedule::REASON_SCHEDULENOW_WEB)
69
+ ->schedule()
70
+ ->save();
71
+
72
+ $this->_getSession()->addSuccess($this->__('Job "%s" has been scheduled.', $key));
73
+ }
74
+ $this->_redirect('*/*/index');
75
+ }
76
+
77
+ /**
78
+ * Mass action: run now
79
+ *
80
+ * @return void
81
+ */
82
+ public function runNowAction()
83
+ {
84
+ if (!Mage::getStoreConfig('system/cron/enableRunNow')) {
85
+ Mage::throwException("'Run now' disabled by configuration (system/cron/enableRunNow)");
86
+ }
87
+ $codes = $this->getMassActionCodes();
88
+ foreach ($codes as $key) {
89
+ $schedule = Mage::getModel('cron/schedule')
90
+ ->setJobCode($key)
91
+ ->setScheduledReason(Aoe_Scheduler_Model_Schedule::REASON_RUNNOW_WEB)
92
+ ->runNow(false)// without trying to lock the job
93
+ ->save();
94
+
95
+ $messages = $schedule->getMessages();
96
+
97
+ if (in_array($schedule->getStatus(), array(Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS, Aoe_Scheduler_Model_Schedule::STATUS_DIDNTDOANYTHING))) {
98
+ $this->_getSession()->addSuccess($this->__('Ran "%s" (Duration: %s sec)', $key, intval($schedule->getDuration())));
99
+ if ($messages) {
100
+ $this->_getSession()->addSuccess($this->__('"%s" messages:<pre>%s</pre>', $key, $messages));
101
+ }
102
+ } else {
103
+ $this->_getSession()->addError($this->__('Error while running "%s"', $key));
104
+ if ($messages) {
105
+ $this->_getSession()->addError($this->__('"%s" messages:<pre>%s</pre>', $key, $messages));
106
+ }
107
+ }
108
+ }
109
+ $this->_redirect('*/*/index');
110
+ }
111
+
112
+ /**
113
+ * Init job instance and set it to registry
114
+ *
115
+ * @return Aoe_Scheduler_Model_Job
116
+ */
117
+ protected function _initJob()
118
+ {
119
+ $jobCode = $this->getRequest()->getParam('job_code', null);
120
+ $job = Mage::getModel('aoe_scheduler/job')->load($jobCode);
121
+ Mage::register('current_job_instance', $job);
122
+ return $job;
123
+ }
124
+
125
+ protected function getMassActionCodes($key = 'codes')
126
+ {
127
+ $codes = $this->getRequest()->getParam($key);
128
+ if (!is_array($codes)) {
129
+ return array();
130
+ }
131
+ $allowedCodes = Mage::getSingleton('aoe_scheduler/job')->getResource()->getJobCodes();
132
+ $codes = array_intersect(array_unique(array_filter(array_map('trim', $codes))), $allowedCodes);
133
+ return $codes;
134
+ }
135
+
136
+ /**
137
+ * New cron (forward to edit action)
138
+ */
139
+ public function newAction()
140
+ {
141
+ $this->_forward('edit');
142
+ }
143
+
144
+ /**
145
+ * Edit cron action
146
+ */
147
+ public function editAction()
148
+ {
149
+ $this->_initJob();
150
+ $this->loadLayout();
151
+ $this->renderLayout();
152
+ }
153
+
154
+ protected function _filterPostData($data)
155
+ {
156
+ return $data;
157
+ }
158
+
159
+ protected function _validatePostData($data)
160
+ {
161
+ try {
162
+ /* @var Aoe_Scheduler_Helper_Data $helper */
163
+ $helper = Mage::helper('aoe_scheduler');
164
+ $helper->getCallBack($data['run_model']);
165
+ if (!empty($data['schedule_cron_expr'])) {
166
+ if (!$helper->validateCronExpression($data['schedule_cron_expr'])) {
167
+ Mage::throwException("Invalid cron expression");
168
+ }
169
+ }
170
+ } catch (Exception $e) {
171
+ $this->_getSession()->addError($e->getMessage());
172
+ return false;
173
+ }
174
+ // TODO: implement!
175
+ return true;
176
+ }
177
+
178
+ /**
179
+ * Save action
180
+ *
181
+ */
182
+ public function saveAction()
183
+ {
184
+ if ($data = $this->getRequest()->getPost()) {
185
+ $data = $this->_filterPostData($data);
186
+ $job = $this->_initJob();
187
+ $job->addData($data);
188
+ //validating
189
+ if (!$this->_validatePostData($data)) {
190
+ $this->_redirect('*/*/edit', array('job_code' => $job->getJobCode(), '_current' => true));
191
+ return;
192
+ }
193
+
194
+ try {
195
+ // save the data
196
+ $job->save();
197
+
198
+ // display success message
199
+ $this->_getSession()->addSuccess(
200
+ Mage::helper('aoe_scheduler')->__('The job has been saved.')
201
+ );
202
+ // clear previously saved data from session
203
+ $this->_getSession()->setFormData(false);
204
+ // check if 'Save and Continue'
205
+ if ($this->getRequest()->getParam('back', false)) {
206
+ $this->_redirect('*/*/edit', array('job_code' => $job->getJobCode(), '_current' => true));
207
+ return;
208
+ }
209
+
210
+ /* @var $scheduleManager Aoe_Scheduler_Model_ScheduleManager */
211
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager');
212
+ $scheduleManager->flushSchedules($job->getJobCode());
213
+ $scheduleManager->generateSchedulesForJob($job);
214
+ $this->_getSession()->addNotice($this->__('Pending schedules for "%s" have been flushed.', $job->getJobCode()));
215
+ $this->_getSession()->addNotice($this->__('Job "%s" has been scheduled.', $job->getJobCode()));
216
+
217
+ // go to grid
218
+ $this->_redirect('*/*');
219
+ return;
220
+ } catch (Mage_Core_Exception $e) {
221
+ Mage::logException($e);
222
+ $this->_getSession()->addError($e->getMessage());
223
+ } catch (Exception $e) {
224
+ Mage::logException($e);
225
+ $this->_getSession()->addError($this->__('An error occurred during saving a job: %s', $e->getMessage()));
226
+ }
227
+
228
+ $this->_getSession()->setFormData($data);
229
+ $this->_redirect('*/*/edit', array('job_code' => $this->getRequest()->getParam('job_code')));
230
+ return;
231
+ }
232
+
233
+ $this->_redirect('*/*/', array('_current' => true));
234
+ }
235
+
236
+ /**
237
+ * Delete Action
238
+ *
239
+ */
240
+ public function deleteAction()
241
+ {
242
+ $job = $this->_initJob();
243
+ try {
244
+ $job->delete();
245
+ $this->_getSession()->addSuccess($this->__('The job has been deleted.'));
246
+
247
+ /* @var Aoe_Scheduler_Model_ScheduleManager $scheduleManager */
248
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager');
249
+ $scheduleManager->flushSchedules($job->getJobCode());
250
+ $this->_getSession()->addNotice($this->__('Pending schedules for "%s" have been flushed.', $job->getJobCode()));
251
+ } catch (Exception $e) {
252
+ $this->_getSession()->addError($e->getMessage());
253
+ }
254
+ $this->_redirect('*/*/');
255
+ return;
256
+ }
257
+
258
+ /**
259
+ * ACL checking
260
+ *
261
+ * @return bool
262
+ */
263
+ protected function _isAllowed()
264
+ {
265
+ return Mage::getSingleton('admin/session')->isAllowed('system/aoe_scheduler/aoe_scheduler_cron');
266
+ }
267
+ }
app/code/community/Aoe/Scheduler/controllers/Adminhtml/SchedulerController.php CHANGED
@@ -1,28 +1,59 @@
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
  }
1
  <?php
 
 
 
2
  /**
3
  * Scheduler controller
4
  *
5
+ * @author Fabrizio Branca
6
  */
7
+ class Aoe_Scheduler_Adminhtml_SchedulerController extends Aoe_Scheduler_Controller_AbstractController
8
+ {
9
 
10
+ /**
11
+ * Mass action: delete
12
+ *
13
+ * @return void
14
+ */
15
+ public function deleteAction()
16
+ {
17
+ $ids = $this->getRequest()->getParam('schedule_ids');
18
+ foreach ($ids as $id) {
19
+ Mage::getModel('cron/schedule')->load($id)
20
+ ->delete();
21
+ }
22
+ $message = $this->__('Deleted task(s) "%s"', implode(', ', $ids));
23
+ $this->_getSession()->addSuccess($message);
24
+ if ($logFile = Mage::getStoreConfig('system/cron/logFile')) {
25
+ Mage::log($message, null, $logFile);
26
+ }
27
+ $this->_redirect('*/*/index');
28
+ }
29
 
30
+ /**
31
+ * Mass action: kill
32
+ *
33
+ * @return void
34
+ */
35
+ public function killAction()
36
+ {
37
+ $ids = $this->getRequest()->getParam('schedule_ids');
38
+ foreach ($ids as $id) {
39
+ $schedule = Mage::getModel('cron/schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
40
+ $schedule->load($id)->requestKill();
41
+ }
42
+ $message = $this->__('Kill requests saved for task(s) "%s" (will be killed via cron)', implode(', ', $ids));
43
+ $this->_getSession()->addSuccess($message);
44
+ if ($logFile = Mage::getStoreConfig('system/cron/logFile')) {
45
+ Mage::log($message, null, $logFile);
46
+ }
47
+ $this->_redirect('*/*/index');
48
+ }
49
 
50
+ /**
51
+ * Acl checking
52
+ *
53
+ * @return bool
54
+ */
55
+ protected function _isAllowed()
56
+ {
57
+ return Mage::getSingleton('admin/session')->isAllowed('system/aoe_scheduler/aoe_scheduler_scheduler');
58
+ }
59
  }
app/code/community/Aoe/Scheduler/controllers/Adminhtml/TimelineController.php CHANGED
@@ -1,12 +1,19 @@
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
  }
1
  <?php
 
 
 
2
  /**
3
  * Timeline controller
4
  *
5
+ * @author Fabrizio Branca
6
  */
7
+ class Aoe_Scheduler_Adminhtml_TimelineController extends Aoe_Scheduler_Controller_AbstractController
8
+ {
9
 
10
+ /**
11
+ * Acl checking
12
+ *
13
+ * @return bool
14
+ */
15
+ protected function _isAllowed()
16
+ {
17
+ return Mage::getSingleton('admin/session')->isAllowed('system/aoe_scheduler/aoe_scheduler_timeline');
18
+ }
19
  }
app/code/community/Aoe/Scheduler/data/aoescheduler_setup/data-upgrade-0.5.4-0.5.5.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* @var $this Mage_Core_Model_Resource_Setup */
3
+
4
+ // Migrate to new settings
5
+ $node = Mage::getConfig()->getNode('default/system/cron/disabled_crons');
6
+ if ($node) {
7
+ $allowedCodes = Mage::getSingleton('aoe_scheduler/job')->getResource()->getJobCodes();
8
+ $codes = array_intersect(array_unique(array_filter(array_map('trim', explode(',', trim($node))))), $allowedCodes);
9
+ foreach ($codes as $code) {
10
+ $this->getConnection()->insertOnDuplicate(
11
+ $this->getTable('core/config_data'),
12
+ array(
13
+ 'scope' => 'default',
14
+ 'scope_id' => 0,
15
+ 'path' => 'crontab/jobs/' . $code . '/is_active',
16
+ 'value' => 0,
17
+ )
18
+ );
19
+ }
20
+ }
21
+
22
+ // Remove old config setting
23
+ $this->getConnection()->delete(
24
+ $this->getTable('core/config_data'),
25
+ array(
26
+ 'scope = ?' => 'default',
27
+ 'scope_id = ?' => 0,
28
+ 'path = ?' => 'system/cron/disabled_crons'
29
+ )
30
+ );
app/code/community/Aoe/Scheduler/etc/adminhtml.xml CHANGED
@@ -1,61 +1,69 @@
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>
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>Job Configuration</title>
12
+ <sort_order>10</sort_order>
13
+ <action>adminhtml/job/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
+ <aoe_scheduler_instructions translate="title">
26
+ <title>Instructions</title>
27
+ <sort_order>40</sort_order>
28
+ <action>adminhtml/instructions/index</action>
29
+ </aoe_scheduler_instructions>
30
+ </children>
31
+ </aoe_scheduler>
32
+ </children>
33
+ </system>
34
+ </menu>
35
+ <acl>
36
+ <resources>
37
+ <admin>
38
+ <children>
39
+ <system>
40
+ <children>
41
+ <aoe_scheduler translate="title" module="aoe_scheduler">
42
+ <title>AOE Scheduler</title>
43
+ <sort_order>1</sort_order>
44
+ <children>
45
+ <aoe_scheduler_cron translate="title">
46
+ <title>Schedule Configuration</title>
47
+ <sort_order>10</sort_order>
48
+ </aoe_scheduler_cron>
49
+ <aoe_scheduler_scheduler translate="title">
50
+ <title>List View</title>
51
+ <sort_order>20</sort_order>
52
+ </aoe_scheduler_scheduler>
53
+ <aoe_scheduler_timeline translate="title">
54
+ <title>Timeline View</title>
55
+ <sort_order>30</sort_order>
56
+ </aoe_scheduler_timeline>
57
+ <aoe_scheduler_instructions translate="title">
58
+ <title>Instructions</title>
59
+ <sort_order>40</sort_order>
60
+ </aoe_scheduler_instructions>
61
+ </children>
62
+ </aoe_scheduler>
63
+ </children>
64
+ </system>
65
+ </children>
66
+ </admin>
67
+ </resources>
68
+ </acl>
69
  </config>
app/code/community/Aoe/Scheduler/etc/config.xml CHANGED
@@ -1,105 +1,179 @@
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>
1
  <?xml version="1.0" ?>
2
  <config>
3
+ <modules>
4
+ <Aoe_Scheduler>
5
+ <version>1.3.0</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
+ <resourceModel>aoe_scheduler_resource</resourceModel>
26
+ </aoe_scheduler>
27
+
28
+ <aoe_scheduler_resource>
29
+ <class>Aoe_Scheduler_Model_Resource</class>
30
+ </aoe_scheduler_resource>
31
+
32
+ <cron>
33
+ <rewrite>
34
+ <observer>Aoe_Scheduler_Model_Observer</observer>
35
+ <schedule>Aoe_Scheduler_Model_Schedule</schedule>
36
+ </rewrite>
37
+ </cron>
38
+ <cron_resource>
39
+ <rewrite>
40
+ <schedule_collection>Aoe_Scheduler_Model_Resource_Schedule_Collection</schedule_collection>
41
+ </rewrite>
42
+ </cron_resource>
43
+ </models>
44
+
45
+ <template>
46
+ <email>
47
+ <system_cron_error_email_template translate="label" module="aoe_scheduler">
48
+ <label>Cron error</label>
49
+ <file>aoe_scheduler/cron_error.html</file>
50
+ <type>text</type>
51
+ </system_cron_error_email_template>
52
+ </email>
53
+ </template>
54
+
55
+ <resources>
56
+ <aoescheduler_setup>
57
+ <setup>
58
+ <module>Aoe_Scheduler</module>
59
+ </setup>
60
+ <connection>
61
+ <use>core_setup</use>
62
+ </connection>
63
+ </aoescheduler_setup>
64
+ </resources>
65
+
66
+ <aoe_logviewer>
67
+ <logs>
68
+ <cron.log>
69
+ <label>cron.log</label>
70
+ <file_path>###LOGDIR###/cron.log</file_path>
71
+ <model>aoe_logviewer/log_file</model>
72
+ <sort_order>10</sort_order>
73
+ <!--<disabled>1</disabled>-->
74
+ <commands>
75
+ <tail>
76
+ <label>Last n lines (newest first)</label>
77
+ <command_string><![CDATA[tail -n %2$s '%1$s' | tac]]></command_string>
78
+ <model>aoe_logviewer/command_shell</model>
79
+ <sort_order>10</sort_order>
80
+ <!--<disabled>1</disabled>-->
81
+ </tail>
82
+ <tail_err>
83
+ <label>Last n error lines (newest first)</label>
84
+ <command_string><![CDATA[grep 'ERR (3)' '%1$s' | tail -n %2$s | tac]]></command_string>
85
+ <model>aoe_logviewer/command_shell</model>
86
+ <sort_order>20</sort_order>
87
+ </tail_err>
88
+ <duplicates>
89
+ <label>Find duplicates</label>
90
+ <command_string><![CDATA[cat '%1$s' | cut -c27- | sort | uniq -c | sort -rn | head -n %2$s]]></command_string>
91
+ <model>aoe_logviewer/command_shell</model>
92
+ <sort_order>30</sort_order>
93
+ </duplicates>
94
+ </commands>
95
+ </cron.log>
96
+ </logs>
97
+ </aoe_logviewer>
98
+
99
+ </global>
100
+
101
+ <admin>
102
+ <routers>
103
+ <adminhtml>
104
+ <args>
105
+ <modules>
106
+ <Aoe_Scheduler before="Mage_Adminhtml">Aoe_Scheduler_Adminhtml</Aoe_Scheduler>
107
+ </modules>
108
+ </args>
109
+ </adminhtml>
110
+ </routers>
111
+ </admin>
112
+
113
+ <adminhtml>
114
+ <translate>
115
+ <modules>
116
+ <aoe_scheduler>
117
+ <files>
118
+ <default>Aoe_Scheduler.csv</default>
119
+ </files>
120
+ </aoe_scheduler>
121
+ </modules>
122
+ </translate>
123
+ <layout>
124
+ <updates>
125
+ <aoe_scheduler>
126
+ <file>aoe_scheduler/aoe_scheduler.xml</file>
127
+ </aoe_scheduler>
128
+ </updates>
129
+ </layout>
130
+ </adminhtml>
131
+
132
+ <crontab>
133
+ <jobs>
134
+ <aoescheduler_testtask>
135
+ <!--<schedule><cron_expr>*/5 * * * *</cron_expr></schedule>-->
136
+ <run>
137
+ <model>aoe_scheduler/task_test::run</model>
138
+ </run>
139
+ </aoescheduler_testtask>
140
+
141
+ <aoescheduler_heartbeat>
142
+ <schedule>
143
+ <config_path>system/cron/scheduler_cron_expr_heartbeat</config_path>
144
+ </schedule>
145
+ <run>
146
+ <model>aoe_scheduler/task_heartbeat::run</model>
147
+ </run>
148
+ <groups>all</groups>
149
+ </aoescheduler_heartbeat>
150
+ </jobs>
151
+ </crontab>
152
+
153
+ <default>
154
+ <system>
155
+ <cron>
156
+ <enable>1</enable>
157
+ <mark_as_error_after>15</mark_as_error_after>
158
+ <scheduler_cron_expr_heartbeat>*/5 * * * *</scheduler_cron_expr_heartbeat>
159
+ <error_email_identity>general</error_email_identity>
160
+ <error_email_template>system_cron_error_email_template</error_email_template>
161
+ <logFile>cron.log</logFile>
162
+ <enableRunNow>0</enableRunNow>
163
+ <enableErrorLog>0</enableErrorLog>
164
+ <errorLevel>-1</errorLevel>
165
+ <errorLogFilename>###JOBCODE###.log</errorLogFilename>
166
+ <listCodeFilterType>select</listCodeFilterType>
167
+ </cron>
168
+ </system>
169
+ </default>
170
+
171
+ <phpunit>
172
+ <suite>
173
+ <modules>
174
+ <Aoe_Scheduler/>
175
+ </modules>
176
+ </suite>
177
+ </phpunit>
178
 
179
  </config>
app/code/community/Aoe/Scheduler/etc/system.xml CHANGED
@@ -1,67 +1,154 @@
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>
1
  <?xml version="1.0"?>
2
  <config>
3
+ <sections>
4
+ <system>
5
+ <groups>
6
+ <cron translate="label comment" module="cron">
7
+ <fields>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ <enable translate="label">
10
+ <label>Enable Scheduler</label>
11
+ <sort_order>1</sort_order>
12
+ <comment><![CDATA[If this is disabled no scheduled jobs will be executed]]></comment>
13
+ <show_in_default>1</show_in_default>
14
+ <frontend_type>select</frontend_type>
15
+ <source_model>adminhtml/system_config_source_yesno</source_model>
16
+ <show_in_website>0</show_in_website>
17
+ <show_in_store>0</show_in_store>
18
+ </enable>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ <mark_as_error_after translate="label">
21
+ <label>Mark job as failed after</label>
22
+ <comment><![CDATA[A job will be marked es failed after it hasn't been seen for this many minutes (in cases where the process cannot be checked)]]></comment>
23
+ <frontend_type>text</frontend_type>
24
+ <sort_order>110</sort_order>
25
+ <show_in_default>1</show_in_default>
26
+ <show_in_website>0</show_in_website>
27
+ <show_in_store>0</show_in_store>
28
+ </mark_as_error_after>
29
+
30
+ <max_job_runtime translate="label">
31
+ <label>Maximum job runtime</label>
32
+ <comment><![CDATA[If set a single job can't run longer than this time (in minutes). If the job is actually still running this process will be actively killed.]]></comment>
33
+ <frontend_type>text</frontend_type>
34
+ <sort_order>111</sort_order>
35
+ <show_in_default>1</show_in_default>
36
+ <show_in_website>0</show_in_website>
37
+ <show_in_store>0</show_in_store>
38
+ </max_job_runtime>
39
+
40
+ <scheduler_cron_expr_heartbeat>
41
+ <label>Heartbeat task schedule (cron syntax)</label>
42
+ <frontend_type>text</frontend_type>
43
+ <sort_order>120</sort_order>
44
+ <show_in_default>1</show_in_default>
45
+ <show_in_website>0</show_in_website>
46
+ <show_in_store>0</show_in_store>
47
+ </scheduler_cron_expr_heartbeat>
48
+
49
+ <error_email translate="label">
50
+ <label>Error Email Recipient</label>
51
+ <frontend_type>text</frontend_type>
52
+ <sort_order>130</sort_order>
53
+ <show_in_default>1</show_in_default>
54
+ <show_in_website>0</show_in_website>
55
+ <show_in_store>0</show_in_store>
56
+ </error_email>
57
+ <error_email_identity translate="label">
58
+ <label>Error Email Sender</label>
59
+ <frontend_type>select</frontend_type>
60
+ <source_model>adminhtml/system_config_source_email_identity</source_model>
61
+ <sort_order>140</sort_order>
62
+ <show_in_default>1</show_in_default>
63
+ <show_in_website>0</show_in_website>
64
+ <show_in_store>0</show_in_store>
65
+ </error_email_identity>
66
+ <error_email_template translate="label">
67
+ <label>Error Email Template</label>
68
+ <frontend_type>select</frontend_type>
69
+ <source_model>adminhtml/system_config_source_email_template</source_model>
70
+ <sort_order>150</sort_order>
71
+ <show_in_default>1</show_in_default>
72
+ <show_in_website>0</show_in_website>
73
+ <show_in_store>0</show_in_store>
74
+ </error_email_template>
75
+
76
+ <logFile translate="label">
77
+ <label>Log file</label>
78
+ <frontend_type>text</frontend_type>
79
+ <sort_order>160</sort_order>
80
+ <comment><![CDATA[Keep empty to disable logging]]></comment>
81
+ <show_in_default>1</show_in_default>
82
+ </logFile>
83
+
84
+ <maxNoOfSuccessfulTasks translate="label">
85
+ <label>Maximum number of successful schedules to keep</label>
86
+ <frontend_type>text</frontend_type>
87
+ <sort_order>170</sort_order>
88
+ <comment><![CDATA[Only keeps this number of entries for every schedule even if it is shorter than the configured success lifetime. Set to 0 to keep them all until they hit the lifetime.]]></comment>
89
+ <show_in_default>1</show_in_default>
90
+ </maxNoOfSuccessfulTasks>
91
+
92
+ <enableRunNow translate="label">
93
+ <label>Enable "run now"</label>
94
+ <frontend_type>select</frontend_type>
95
+ <source_model>adminhtml/system_config_source_yesno</source_model>
96
+ <sort_order>180</sort_order>
97
+ <comment><![CDATA[Cron tasks are usually designed to run in CLI context and not in the context of the webserver. Also they could run longer than the configured maximum execution time. This is why you should rather use "scheduleNow" instead of "runNow" and let the scheduler pick up the task on the next run. However, if you know what you're doing and/or if you don't want to wait while testing and developing a cron task you can enable this feature here.]]></comment>
98
+ <show_in_default>1</show_in_default>
99
+ </enableRunNow>
100
+
101
+ <enableJobOutputBuffer translate="label">
102
+ <label>Enable Job Output Buffering</label>
103
+ <comment><![CDATA[If this is enabled jobs will capture normal output for storage in the schedule record messages field]]></comment>
104
+ <frontend_type>select</frontend_type>
105
+ <source_model>adminhtml/system_config_source_yesno</source_model>
106
+ <sort_order>200</sort_order>
107
+ <show_in_default>1</show_in_default>
108
+ </enableJobOutputBuffer>
109
+
110
+ <enableErrorLog translate="label">
111
+ <label>Enable Error Log</label>
112
+ <comment><![CDATA[If enabled AOE_Scheduler will bypass Magento's error handling and will capture the error log in a schedule specific file in var/log/cron/]]></comment>
113
+ <frontend_type>select</frontend_type>
114
+ <source_model>adminhtml/system_config_source_yesno</source_model>
115
+ <sort_order>210</sort_order>
116
+ <show_in_default>1</show_in_default>
117
+ </enableErrorLog>
118
+
119
+ <errorLevel translate="label">
120
+ <label>Error Level</label>
121
+ <comment><![CDATA[Error level accepted by 'error_reporting'. This needs to be the int value. Constants are not supported! Example: -1 or 32767 (E_ALL) to log everything.]]></comment>
122
+ <frontend_type>text</frontend_type>
123
+ <sort_order>220</sort_order>
124
+ <show_in_default>1</show_in_default>
125
+ <depends>
126
+ <enableErrorLog>1</enableErrorLog>
127
+ </depends>
128
+ </errorLevel>
129
+
130
+ <errorLogFilename translate="label">
131
+ <label>Error log filename</label>
132
+ <comment><![CDATA[###PID### will be replaced with the process id.<br />###ID### will be replaced with the job id.<br />###JOBCODE### will be replaced with the job code.]]></comment>
133
+ <frontend_type>text</frontend_type>
134
+ <sort_order>230</sort_order>
135
+ <show_in_default>1</show_in_default>
136
+ <depends>
137
+ <enableErrorLog>1</enableErrorLog>
138
+ </depends>
139
+ </errorLogFilename>
140
+
141
+ <listCodeFilterType translate="label">
142
+ <label>Job code filter type in list view</label>
143
+ <frontend_type>select</frontend_type>
144
+ <source_model>aoe_scheduler/adminhtml_system_config_source_list_code_filtertype</source_model>
145
+ <sort_order>240</sort_order>
146
+ <show_in_default>1</show_in_default>
147
+ </listCodeFilterType>
148
+
149
+ </fields>
150
+ </cron>
151
+ </groups>
152
+ </system>
153
+ </sections>
154
  </config>
app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/install-1.0.0.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* @var $this Mage_Core_Model_Resource_Setup */
3
+ $this->startSetup();
4
+
5
+ $tableName = $this->getTable('cron/schedule');
6
+
7
+ $this->getConnection()->dropColumn(
8
+ $tableName,
9
+ 'parameters'
10
+ );
11
+
12
+ $this->getConnection()->addColumn(
13
+ $tableName,
14
+ 'parameters',
15
+ "TEXT NULL COMMENT 'Serialized Parameters'"
16
+ );
17
+
18
+ $this->getConnection()->addColumn(
19
+ $tableName,
20
+ 'eta',
21
+ "timestamp NULL DEFAULT NULL COMMENT 'Estimated Time of Arrival'"
22
+ );
23
+
24
+ $this->getConnection()->addColumn(
25
+ $tableName,
26
+ 'host',
27
+ "varchar(255) NULL COMMENT 'Host running this job'"
28
+ );
29
+
30
+ $this->getConnection()->addColumn(
31
+ $tableName,
32
+ 'pid',
33
+ "varchar(255) NULL COMMENT 'Process id of this job'"
34
+ );
35
+
36
+ $this->getConnection()->addColumn(
37
+ $tableName,
38
+ 'progress_message',
39
+ "TEXT NULL COMMENT 'Progress message'"
40
+ );
41
+
42
+ $this->getConnection()->addColumn(
43
+ $tableName,
44
+ 'last_seen',
45
+ "timestamp NULL DEFAULT NULL COMMENT 'Last seen'"
46
+ );
47
+
48
+ $this->getConnection()->addColumn(
49
+ $tableName,
50
+ 'kill_request',
51
+ "timestamp NULL DEFAULT NULL COMMENT 'Kill Request'"
52
+ );
53
+
54
+ $this->getConnection()->addColumn(
55
+ $tableName,
56
+ 'scheduled_by',
57
+ array(
58
+ 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER,
59
+ 'unsigned' => true,
60
+ 'nullable' => true,
61
+ 'default' => null,
62
+ 'comment' => 'Scheduled by'
63
+ )
64
+ );
65
+
66
+ $this->getConnection()->addColumn(
67
+ $tableName,
68
+ 'scheduled_reason',
69
+ array(
70
+ 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
71
+ 'length' => 256,
72
+ 'nullable' => true,
73
+ 'default' => null,
74
+ 'comment' => 'Scheduled Reason'
75
+ )
76
+ );
77
+
78
+ $this->endSetup();
app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.4.0-0.4.1.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this;
4
+ /* @var $installer Mage_Core_Model_Resource_Setup */
5
+
6
+ $installer->startSetup();
7
+
8
+ $tableName = $installer->getTable('cron_schedule');
9
+
10
+ try {
11
+ $installer->getConnection()->dropColumn($tableName, 'parameters');
12
+ } catch (Exception $e) {
13
+ // ignored intentionally
14
+ }
15
+
16
+ $installer->getConnection()->addColumn(
17
+ $tableName,
18
+ 'parameters',
19
+ "TEXT NULL COMMENT 'Serialized Parameters'"
20
+ );
21
+
22
+ $installer->getConnection()->addColumn(
23
+ $tableName,
24
+ 'eta',
25
+ "timestamp NULL DEFAULT NULL COMMENT 'Estimated Time of Arrival'"
26
+ );
27
+
28
+ $installer->getConnection()->addColumn(
29
+ $tableName,
30
+ 'host',
31
+ "varchar(255) NULL COMMENT 'Host running this job'"
32
+ );
33
+
34
+ $installer->getConnection()->addColumn(
35
+ $tableName,
36
+ 'pid',
37
+ "varchar(255) NULL COMMENT 'Process id of this job'"
38
+ );
39
+
40
+ $installer->getConnection()->addColumn(
41
+ $tableName,
42
+ 'progress_message',
43
+ "TEXT NULL COMMENT 'Progress message'"
44
+ );
45
+
46
+ $installer->endSetup();
app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.4.1-0.4.2.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this; /* @var $installer Mage_Core_Model_Resource_Setup */
4
+
5
+ $installer->startSetup();
6
+
7
+ $tableName = $installer->getTable('cron_schedule');
8
+
9
+ $installer->getConnection()->addColumn(
10
+ $tableName,
11
+ 'last_seen',
12
+ "timestamp NULL DEFAULT NULL COMMENT 'Last seen'"
13
+ );
14
+
15
+ $installer->endSetup();
app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.4.2-0.4.3.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this; /* @var $installer Mage_Core_Model_Resource_Setup */
4
+
5
+ $installer->startSetup();
6
+
7
+ $tableName = $installer->getTable('cron_schedule');
8
+
9
+ $installer->getConnection()->addColumn(
10
+ $tableName,
11
+ 'kill_request',
12
+ "timestamp NULL DEFAULT NULL COMMENT 'Kill Request'"
13
+ );
14
+
15
+ $installer->endSetup();
app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.5.0-0.5.1.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this; /* @var $installer Mage_Core_Model_Resource_Setup */
4
+
5
+ $installer->startSetup();
6
+
7
+ $installer->getConnection()->addColumn($installer->getTable('cron/schedule'), 'scheduled_by', array(
8
+ 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER,
9
+ 'unsigned' => true,
10
+ 'nullable' => true,
11
+ 'default' => null,
12
+ 'comment' => 'Scheduled by'
13
+ ));
14
+
15
+ $installer->getConnection()->addColumn($installer->getTable('cron/schedule'), 'scheduled_reason', array(
16
+ 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
17
+ 'length' => 256,
18
+ 'nullable' => true,
19
+ 'default' => null,
20
+ 'comment' => 'Scheduled Reason'
21
+ ));
22
+
23
+ $installer->endSetup();
app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.5.3-0.5.4.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+ /* @var $this Mage_Core_Model_Resource_Setup */
3
+ $this->startSetup();
4
+
5
+ $this->getConnection()->dropTable($this->getTable('cron_job'));
6
+
7
+ $this->endSetup();
app/code/community/Aoe/Scheduler/sql/aoescheduler_setup/mysql4-upgrade-0.5.4-1.1.0.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* @var $this Mage_Core_Model_Resource_Setup */
3
+ $this->startSetup();
4
+
5
+ $this->getConnection()->modifyColumn(
6
+ $this->getTable('cron/schedule'),
7
+ 'status',
8
+ array(
9
+ 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
10
+ 'length' => 30,
11
+ 'nullable' => false,
12
+ 'default' => 'pending',
13
+ 'comment' => 'Status'
14
+ )
15
+ );
16
+
17
+ $this->endSetup();
app/design/adminhtml/default/default/layout/aoe_scheduler/aoe_scheduler.xml CHANGED
@@ -1,48 +1,72 @@
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <layout>
3
 
4
- <adminhtml_timeline_index>
5
- <reference name="head">
6
- <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/timeline.css</stylesheet></action>
7
- <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/bars.css</stylesheet></action>
8
 
9
- <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/jquery-1.6.2.min.js</script></action>
10
- <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/tooltip.js</script></action>
11
- <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/tooltip.dynamic.js</script></action>
12
- <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/common.js</script></action>
13
- </reference>
14
- <reference name="content">
15
- <block type="aoe_scheduler/adminhtml_timeline" name="aoe_scheduler.timeline" template="aoe_scheduler/timeline.phtml" />
16
- <!--<block type="core/text" name="fix.console" as="fix.console">
17
- <action method="setText">
18
- <text><![CDATA[<script type="text/javascript">
19
- iframe = document.createElement('iframe');
20
- iframe.style = 'display:none';
21
- document.getElementsByTagName('body')[0].appendChild(iframe);
22
- window.console = iframe.contentWindow.console;
23
- console.firebug = "faketrue";
24
- </script>]]></text>
25
- </action>
26
- </block>
27
- --></reference>
28
- </adminhtml_timeline_index>
 
29
 
30
- <adminhtml_scheduler_index>
31
- <reference name="head">
32
- <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/bars.css</stylesheet></action>
33
- </reference>
34
- <reference name="content">
35
- <block type="aoe_scheduler/adminhtml_scheduler" name="aoe_scheduler.scheduler" />
36
- </reference>
37
- </adminhtml_scheduler_index>
38
 
39
- <adminhtml_cron_index>
40
- <reference name="head">
41
- <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/bars.css</stylesheet></action>
42
- </reference>
43
- <reference name="content">
44
- <block type="aoe_scheduler/adminhtml_cron" name="aoe_scheduler.cron" />
45
- </reference>
46
- </adminhtml_cron_index>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  </layout>
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <layout>
3
 
4
+ <adminhtml_timeline_index>
5
+ <reference name="head">
6
+ <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/timeline.css</stylesheet></action>
7
+ <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/bars.css</stylesheet></action>
8
 
9
+ <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/jquery-1.6.2.min.js</script></action>
10
+ <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/tooltip.js</script></action>
11
+ <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/tooltip.dynamic.js</script></action>
12
+ <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/common.js</script></action>
13
+ </reference>
14
+ <reference name="content">
15
+ <block type="aoe_scheduler/adminhtml_timeline" name="aoe_scheduler.timeline" template="aoe_scheduler/timeline.phtml"/>
16
+ <!--<block type="core/text" name="fix.console" as="fix.console">
17
+ <action method="setText">
18
+ <text><![CDATA[<script type="text/javascript">
19
+ iframe = document.createElement('iframe');
20
+ iframe.style = 'display:none';
21
+ document.getElementsByTagName('body')[0].appendChild(iframe);
22
+ window.console = iframe.contentWindow.console;
23
+ console.firebug = "faketrue";
24
+ </script>]]></text>
25
+ </action>
26
+ </block>
27
+ -->
28
+ </reference>
29
+ </adminhtml_timeline_index>
30
 
31
+ <adminhtml_scheduler_index>
32
+ <reference name="head">
33
+ <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/bars.css</stylesheet></action>
34
+ </reference>
35
+ <reference name="content">
36
+ <block type="aoe_scheduler/adminhtml_scheduler" name="aoe_scheduler.scheduler"/>
37
+ </reference>
38
+ </adminhtml_scheduler_index>
39
 
40
+ <adminhtml_job_index>
41
+ <reference name="head">
42
+ <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/bars.css</stylesheet></action>
43
+ </reference>
44
+ <reference name="content">
45
+ <block type="aoe_scheduler/adminhtml_job" name="aoe_scheduler.job"/>
46
+ </reference>
47
+ </adminhtml_job_index>
48
+
49
+ <adminhtml_job_edit>
50
+ <reference name="left">
51
+ <block type="aoe_scheduler/adminhtml_job_edit_tabs" name="aoe_scheduler_edit_tabs">
52
+ <block type="aoe_scheduler/adminhtml_job_edit_tab_form" name="aoe_scheduler_edit_tab_form"/>
53
+ <action method="addTab"><name>information_section</name><block>aoe_scheduler_edit_tab_form</block></action>
54
+ </block>
55
+ </reference>
56
+ <reference name="content">
57
+ <block type="aoe_scheduler/adminhtml_job_edit" name="aoe_scheduler_edit_information"/>
58
+ </reference>
59
+ </adminhtml_job_edit>
60
+
61
+ <adminhtml_instructions_index>
62
+ <reference name="head">
63
+ <action method="addCss"><stylesheet>aoe_scheduler/StyleSheet/instructions.css</stylesheet></action>
64
+ <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/jquery-1.6.2.min.js</script></action>
65
+ <action method="addItem"><type>skin_js</type><script>aoe_scheduler/JavaScript/instructions.js</script></action>
66
+ </reference>
67
+ <reference name="content">
68
+ <block type="aoe_scheduler/adminhtml_instructions" name="aoe_scheduler.instructions" template="aoe_scheduler/instructions.phtml"/>
69
+ </reference>
70
+ </adminhtml_instructions_index>
71
 
72
  </layout>
app/design/adminhtml/default/default/template/aoe_scheduler/instructions.phtml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /* @var $this Aoe_Scheduler_Block_Adminhtml_Instructions */
3
+
4
+ $maintenanceModeCheck = '<span class="maintenance-check-command">! test -e '.$this->getMagentoRootpath() .'/maintenance.flag && </span>';
5
+ $schedule = '*<span class="every-five-minutes">/5</span> * * * *';
6
+ ?>
7
+
8
+ <div class="content-header">
9
+ <table cellspacing="0">
10
+ <tr>
11
+ <td style="<?php echo $this->getHeaderWidth() ?>"><h3 class="head-empty"><?php echo $this->__('Setup Instructions') ?></h3></td>
12
+ <td class="form-buttons"><?php echo $this->getButtonsHtml() ?></td>
13
+ </tr>
14
+ </table>
15
+ </div>
16
+
17
+ <div id="maincontainer">
18
+
19
+ <div class="configuration">
20
+ <p><input type="checkbox" name="scheduler-cron" checked="checked"><label for="scheduler-cron"><?php echo $this->__('Run scheduler_cron.sh instead cron.sh (recommended)') ?></label></p>
21
+ <p><input type="checkbox" name="every-minute" checked="checked"><label for="every-minute"><?php echo $this->__('Run every minute (recommended)') ?></label></p>
22
+ <p><input type="checkbox" name="maintenance-check" checked="checked"><label for="maintenance-check"><?php echo $this->__('Check maintenance mode (recommended)') ?></label></p>
23
+ <p><input type="checkbox" name="use-crongroups"><label for="use-crongroups"><?php echo $this->__('Use multiple cron groups (example)') ?></label></p>
24
+ <p><input type="checkbox" name="use-watchdog"><label for="use-watchdog"><?php echo $this->__('Use watchdog') ?></label></p>
25
+ <p><input type="checkbox" name="add-mailto"><label for="add-mailto"><?php echo $this->__('Include email address for output messages') ?></label></p>
26
+ </div>
27
+
28
+ <h5><?php echo $this->__('Edit your crontab:') ?></h5>
29
+ <pre>
30
+ sudo crontab -u <?php echo $this->getCurrentUser() ?> -e
31
+ </pre>
32
+
33
+
34
+ <h5><?php echo $this->__('Add following configuration:') ?></h5>
35
+ <pre class="cron-configuration">
36
+ <span class="croncommand mailto">MAILTO="<?php echo Mage::getSingleton('admin/session')->getUser()->getEmail(); ?>"
37
+ </span><span class="croncommand classic"><?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/sh <?php echo $this->getMagentoRootpath() ?>/cron.sh
38
+ </span><span class="croncommand scheduler"><?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/bash <?php echo $this->getMagentoRootpath() ?>/scheduler_cron.sh --mode always
39
+ <?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/bash <?php echo $this->getMagentoRootpath() ?>/scheduler_cron.sh --mode default
40
+ </span><span class="croncommand crongroups"><?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/bash <?php echo $this->getMagentoRootpath() ?>/scheduler_cron.sh --mode always --includeGroups my_queue_jobs
41
+ <?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/bash <?php echo $this->getMagentoRootpath() ?>/scheduler_cron.sh --mode always --excludeGroups my_queue_jobs
42
+ <?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/bash <?php echo $this->getMagentoRootpath() ?>/scheduler_cron.sh --mode default --includeGroups groupA,groupB
43
+ <?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/bash <?php echo $this->getMagentoRootpath() ?>/scheduler_cron.sh --mode default --includeGroups groupC
44
+ <?php echo $schedule ?> <?php echo $maintenanceModeCheck ?>/bin/bash <?php echo $this->getMagentoRootpath() ?>/scheduler_cron.sh --mode default --excludeGroups groupA,groupB,groupC
45
+ </span><span class="watchdog">*/10 * * * * <?php echo $maintenanceModeCheck ?>cd <?php echo $this->getMagentoRootpath() ?>/shell && /usr/bin/php scheduler.php --action watchdog
46
+ </span></pre>
47
+
48
+ </div>
app/design/adminhtml/default/default/template/aoe_scheduler/timeline.phtml CHANGED
@@ -1,72 +1,75 @@
1
  <?php
2
  /* @var $this Aoe_Scheduler_Block_Adminhtml_Timeline */
3
- $_helper = $this->helper('aoe_scheduler/data'); /* @var $_helper Aoe_Scheduler_Helper_Data */
 
4
  $_jobCodes = $this->getAvailableJobCodes(); /* @var $_jobCodes array */
5
  ?>
6
 
7
  <div class="content-header">
8
- <table cellspacing="0">
9
- <tr>
10
- <td style="<?php echo $this->getHeaderWidth() ?>"><?php echo $this->getHeaderHtml() ?></td>
11
- <td class="form-buttons"><?php echo $this->getButtonsHtml() ?></td>
12
- </tr>
13
- </table>
14
  </div>
15
 
16
  <div id="maincontainer">
17
 
18
- <?php if (count($_jobCodes) > 0): ?>
19
 
20
- <div id="contentwrapper">
21
- <div id="contentcolumn">
22
- <div class="timeline-box">
23
- <div class="timeline-panel" style="width: <?php echo $this->getTimelinePanelWidth(); ?>px;">
24
 
25
- <div id="now" style="left: <?php echo $this->getNowline(); ?>px"></div>
26
 
27
- <div class="row hours">
28
- <div class="timeline">
29
- <?php for($i = $this->getStarttime(); $i < $this->getEndtime(); $i+=60*60): ?>
30
- <div class="hour"><?php echo $_helper->decorateTime($i, false, 'Y-m-d H:i'); ?></div>
31
- <?php endfor; ?>
32
- </div>
33
- </div>
34
 
35
- <?php foreach ($_jobCodes as $jobCode): /* @var $jobCode string */ ?>
36
- <?php $_schedules = $this->getSchedulesForCode($jobCode); ?>
37
- <div class="row">
38
- <div class="timeline timeline_<?php echo $jobCode; ?>">
39
- <?php foreach ($_schedules as $_schedule): /* @var $_schedule Aoe_Scheduler_Model_Schedule */ ?>
40
- <div <?php echo $this->getGanttDivAttributes($_schedule); ?>></div>
41
- <?php echo $this->getLayout()->createBlock('aoe_scheduler/adminhtml_timelineDetail')->setSchedule($_schedule)->toHtml(); ?>
42
- <?php endforeach; ?>
43
- </div>
44
- </div>
45
- <?php endforeach; ?>
46
 
47
- </div>
48
- </div>
49
- </div><!-- id="contentcolumn" -->
50
- </div><!-- id="contentwrapper" -->
 
51
 
52
- <div id="leftcolumn">
53
 
54
- <div class="row hours">
55
- <div class="caption"><?php echo $_helper->decorateTime($this->getStarttime(), true, 'Y-m-d H:i'); ?> - <?php echo $_helper->decorateTime($this->getEndtime(), true, 'Y-m-d H:i'); ?></div>
56
- </div>
 
57
 
58
- <?php foreach ($this->getAvailableJobCodes() as $jobCode): /* @var $jobCode string */ ?>
59
- <div class="row">
60
- <div class="configuration">
61
- <?php echo $jobCode; ?>
62
- </div>
63
- </div>
64
- <?php endforeach; ?>
65
 
66
- </div><!-- id="leftcolumn" -->
67
 
68
- <?php else: ?>
69
- <?php echo $this->__('No tasks found.'); ?>
70
- <?php endif; ?>
71
 
72
  </div><!-- id="maincontainer" -->
1
  <?php
2
  /* @var $this Aoe_Scheduler_Block_Adminhtml_Timeline */
3
+ $_helper = $this->helper('aoe_scheduler/data');
4
+ /* @var $_helper Aoe_Scheduler_Helper_Data */
5
  $_jobCodes = $this->getAvailableJobCodes(); /* @var $_jobCodes array */
6
  ?>
7
 
8
  <div class="content-header">
9
+ <table cellspacing="0">
10
+ <tr>
11
+ <td style="<?php echo $this->getHeaderWidth() ?>"><?php echo $this->getHeaderHtml() ?></td>
12
+ <td class="form-buttons"><?php echo $this->getButtonsHtml() ?></td>
13
+ </tr>
14
+ </table>
15
  </div>
16
 
17
  <div id="maincontainer">
18
 
19
+ <?php if (count($_jobCodes) > 0): ?>
20
 
21
+ <div id="contentwrapper">
22
+ <div id="contentcolumn">
23
+ <div class="timeline-box">
24
+ <div class="timeline-panel" style="width: <?php echo $this->getTimelinePanelWidth(); ?>px;">
25
 
26
+ <div id="now" style="left: <?php echo $this->getNowline(); ?>px"><div class="arrow"></div></div>
27
 
28
+ <div class="row hours">
29
+ <div class="timeline">
30
+ <?php for ($i = $this->getStarttime(); $i < $this->getEndtime(); $i += 60 * 60): ?>
31
+ <div class="hour"><span><?php echo $_helper->decorateTime($i, false, 'Y-m-d H:i'); ?></span></div>
32
+ <?php endfor; ?>
33
+ </div>
34
+ </div>
35
 
36
+ <?php foreach ($_jobCodes as $jobCode): /* @var $jobCode string */ ?>
37
+ <?php $_schedules = $this->getSchedulesForCode($jobCode); ?>
38
+ <div class="row">
39
+ <div class="timeline timeline_<?php echo $jobCode; ?>">
40
+ <?php foreach ($_schedules as $_schedule): /* @var $_schedule Aoe_Scheduler_Model_Schedule */ ?>
41
+ <?php echo $this->getGanttDivAttributes($_schedule); ?>
42
+ <?php echo $this->getLayout()->createBlock('aoe_scheduler/adminhtml_timelineDetail')->setSchedule($_schedule)->toHtml(); ?>
43
+ <?php endforeach; ?>
44
+ </div>
45
+ </div>
46
+ <?php endforeach; ?>
47
 
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <!-- id="contentcolumn" -->
52
+ </div><!-- id="contentwrapper" -->
53
 
54
+ <div id="leftcolumn">
55
 
56
+ <div class="row hours">
57
+ <div class="caption"><?php echo $_helper->decorateTime($this->getStarttime(), true, 'Y-m-d H:i'); ?>
58
+ - <?php echo $_helper->decorateTime($this->getEndtime(), true, 'Y-m-d H:i'); ?></div>
59
+ </div>
60
 
61
+ <?php foreach ($_jobCodes as $jobCode): /* @var $jobCode string */ ?>
62
+ <div class="row">
63
+ <div class="configuration">
64
+ <?php echo $jobCode; ?>
65
+ </div>
66
+ </div>
67
+ <?php endforeach; ?>
68
 
69
+ </div><!-- id="leftcolumn" -->
70
 
71
+ <?php else: ?>
72
+ <?php echo $this->__('No tasks found.'); ?>
73
+ <?php endif; ?>
74
 
75
  </div><!-- id="maincontainer" -->
app/design/adminhtml/default/default/template/aoe_scheduler/timeline_detail.phtml CHANGED
@@ -1,41 +1,63 @@
1
  <?php
2
  /* @var $this Aoe_Scheduler_Block_Adminhtml_TimelineDetail */
3
- $_schedule = $this->getSchedule();
4
  $_helper = $this->helper('aoe_scheduler/data'); /* @var $_helper Aoe_Scheduler_Helper_Data */
5
  ?>
6
 
7
  <div class="details">
8
- <div class="details-headline <?php echo $_schedule->getStatus(); ?>">
9
- <h3><?php echo $_schedule->getJobCode(); ?></h3>
10
- </div>
11
- <div class="details-content">
12
- <table>
13
- <tr>
14
- <td class="label">Status:</td><td><div class="status"><?php echo $_helper->decorateStatus($_schedule->getStatus()) ?></div></td>
15
- </tr>
16
- <tr>
17
- <td class="label">Created at:</td><td><?php echo $_helper->decorateTime($_schedule->getCreatedAt()); ?></td>
18
- </tr>
19
- <tr>
20
- <td class="label">Scheduled at:</td><td><?php echo $_helper->decorateTime($_schedule->getScheduledAt()); ?></td>
21
- </tr>
22
- <tr>
23
- <td class="label">Executed at:</td><td><?php echo $_helper->decorateTime($_schedule->getExecutedAt()); ?></td>
24
- </tr>
25
- <tr>
26
- <td class="label">Finished at:</td><td><?php echo $_helper->decorateTime($_schedule->getFinishedAt()); ?></td>
27
- </tr>
28
- <tr>
29
- <td class="label">Id:</td><td><?php echo $_schedule->getId(); ?></td>
30
- </tr>
31
- <?php if ($_schedule->getMessages()): ?>
32
- <tr>
33
- <td class="label" colspan="2">Message:</td>
34
- </tr>
35
- <tr>
36
- <td colspan="2"><pre><?php echo $_schedule->getMessages(); ?></pre></td>
37
- </tr>
38
- <?php endif; ?>
39
- </table>
40
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  </div>
1
  <?php
2
  /* @var $this Aoe_Scheduler_Block_Adminhtml_TimelineDetail */
3
+ $_schedule = $this->getSchedule(); /* @var $_schedule Aoe_Scheduler_Model_Schedule */
4
  $_helper = $this->helper('aoe_scheduler/data'); /* @var $_helper Aoe_Scheduler_Helper_Data */
5
  ?>
6
 
7
  <div class="details">
8
+ <div class="details-headline <?php echo $_schedule->getStatus(); ?>">
9
+ <h3><?php echo $_schedule->getJobCode(); ?><?php if ($_schedule->isAlwaysTask()): ?>*<?php endif; ?></h3>
10
+ </div>
11
+ <div class="details-content">
12
+ <table>
13
+ <tr>
14
+ <td class="label"><?php echo $this->__('Status') ?>:</td>
15
+ <td>
16
+ <div class="status"><?php echo $_helper->decorateStatus($_schedule->getStatus()) ?></div>
17
+ </td>
18
+ </tr>
19
+ <tr>
20
+ <td class="label"><?php echo $this->__('Created at') ?>:</td>
21
+ <td><?php echo $_helper->decorateTime($_schedule->getCreatedAt()); ?></td>
22
+ </tr>
23
+ <tr>
24
+ <td class="label"><?php echo $this->__('Scheduled at') ?>:</td>
25
+ <td><?php echo $_helper->decorateTime($_schedule->getScheduledAt()); ?></td>
26
+ </tr>
27
+ <tr>
28
+ <td class="label"><?php echo $this->__('Executed at') ?>:</td>
29
+ <td><?php echo $_helper->decorateTime($_schedule->getExecutedAt()); ?></td>
30
+ </tr>
31
+ <tr>
32
+ <td class="label"><?php echo $this->__('Finished at') ?>:</td>
33
+ <td><?php echo $_helper->decorateTime($_schedule->getFinishedAt()); ?></td>
34
+ </tr>
35
+ <tr>
36
+ <td class="label"><?php echo $this->__('Last seen at') ?>:</td>
37
+ <td><?php echo $_helper->decorateTime($_schedule->getLastSeen()); ?></td>
38
+ </tr>
39
+ <tr>
40
+ <td class="label"><?php echo $this->__('Id') ?>:</td>
41
+ <td><?php echo $_schedule->getId(); ?></td>
42
+ </tr>
43
+ <tr>
44
+ <td class="label"><?php echo $this->__('Pid') ?>:</td>
45
+ <td><?php echo $_schedule->getPid(); ?></td>
46
+ </tr>
47
+ <tr>
48
+ <td class="label"><?php echo $this->__('Host') ?>:</td>
49
+ <td><?php echo $_schedule->getHost(); ?></td>
50
+ </tr>
51
+ <?php if ($_schedule->getMessages()): ?>
52
+ <tr>
53
+ <td class="label" colspan="2"><?php echo $this->__('Message') ?>:</td>
54
+ </tr>
55
+ <tr>
56
+ <td colspan="2">
57
+ <pre><?php echo $_schedule->getMessages(); ?></pre>
58
+ </td>
59
+ </tr>
60
+ <?php endif; ?>
61
+ </table>
62
+ </div>
63
  </div>
app/etc/modules/Aoe_Scheduler.xml CHANGED
@@ -1,9 +1,9 @@
1
  <?xml version="1.0" ?>
2
  <config>
3
- <modules>
4
- <Aoe_Scheduler>
5
- <active>true</active>
6
- <codePool>community</codePool>
7
- </Aoe_Scheduler>
8
- </modules>
9
  </config>
1
  <?xml version="1.0" ?>
2
  <config>
3
+ <modules>
4
+ <Aoe_Scheduler>
5
+ <active>true</active>
6
+ <codePool>community</codePool>
7
+ </Aoe_Scheduler>
8
+ </modules>
9
  </config>
app/locale/sv_SE/Aoe_Scheduler.csv ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ "Available tasks","Tillgängliga uppgifter"
2
+ "Delete","Radera"
3
+ "Disabled ""%s""","Inaktiverade ""%s"""
4
+ "Disabled cron tasks","Schemalagda uppgifter som inaktiverats"
5
+ "Generated schedule","Genererat schema"
6
+ "Invalid model/method definition, expecting ""model/class::method"".","Ogiltig definition av modell/metod, ""modell/klass::metod"" förväntas."
7
+ "Schedule now","Schemalägg nu"
8
+ "Scheduled tasks","Schemalagda uppgifter"
9
+ "Scheduler","Schemaläggare"
10
+ "Unknown error.","Okänt fel."
package.xml CHANGED
@@ -1,18 +1,18 @@
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>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Aoe_Scheduler</name>
4
+ <version>1.3.0</version>
5
  <stability>stable</stability>
6
+ <license uri="http://www.gnu.org/licenses/gpl-3.0.html">GPL v3.0</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Scheduler / Cron Management</summary>
10
+ <description>This extension allows you to manage and visualize cron scheduler tasks.</description>
11
+ <notes>GIT Revision: 4bb4776a589c7cf707be13bb4f67f4d7e5067146, Build Date: 2015-11-09 21:32:36</notes>
12
  <authors><author><name>Fabrizio Branca</name><user>fbrnc</user><email>magento@fabrizio-branca.de</email></author></authors>
13
+ <date>2015-11-09</date>
14
+ <time>21:32:36</time>
15
+ <contents><target name="magecommunity"><dir><dir name="Aoe"><dir name="Scheduler"><dir><dir name="Block"><dir name="Adminhtml"><file name="Instructions.php" hash="06faaecc45fd29a0b68d19ae20ec5ac9"/><dir name="Job"><dir name="Edit"><file name="Form.php" hash="208a89d9909fae432fdf95a527408813"/><dir name="Tab"><file name="Form.php" hash="a89f73d055d1e0a664fb4bcee7d97c57"/></dir><file name="Tabs.php" hash="3b60dfa6535639dec95d1aaad720b8de"/></dir><file name="Edit.php" hash="9be686807fc622d4aae3b23af1631583"/><file name="Grid.php" hash="420de6bb372b3906f8969da67c0052ea"/></dir><file name="Job.php" hash="77a6d165f734292e34de409a929cdec7"/><dir name="Scheduler"><file name="Grid.php" hash="39eef84aa80a8b11957d3d415abedcac"/></dir><file name="Scheduler.php" hash="86d38975946960b5f4925f48be1af3f9"/><file name="Timeline.php" hash="f55d6784f26acd11c27404e617a84794"/><file name="TimelineDetail.php" hash="8275c4750e000a6db86febdb7d701ca6"/></dir></dir><dir name="Controller"><file name="AbstractController.php" hash="40bf08707c436df442bd85ad240de525"/></dir><dir name="Helper"><file name="Compatibility.php" hash="213d2d6d9fc6909104d6b95fdc9db1cd"/><file name="Data.php" hash="4243982588876f6bf26dcc1ddd8590cb"/><file name="GracefulDead.php" hash="dbc7b814d1b5001600c01a5c8ecbf84f"/></dir><dir name="Model"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Source"><dir name="List"><dir name="Code"><file name="Filtertype.php" hash="81d4d5c81828add26c16c12f4f2ada55"/></dir></dir></dir></dir></dir></dir><file name="Api.php" hash="7377ee0d852af3b7a5ea858f52bca5b6"/><file name="Job.php" hash="31eab4ff034a864e200bf8928fa4aacc"/><file name="Observer.php" hash="10b9ad529f7b24cffe067dc886910b9f"/><file name="ProcessManager.php" hash="842774abff735162a4585bbd214078a0"/><dir name="Resource"><dir name="Job"><file name="Collection.php" hash="b34c2f6a771ccb4358338a1976edc0e9"/></dir><file name="Job.php" hash="4213cd25da8c0fb99a746b8b494c6335"/><dir name="Schedule"><file name="Collection.php" hash="e7fbddf6a9d77544f4bac8dde268a793"/></dir></dir><file name="Schedule.php" hash="2ec06562ebe1a70cc5f54e33422bc3d8"/><file name="ScheduleManager.php" hash="6e90aba20f0105e1cb87bac32caf01f9"/><dir name="Task"><file name="Heartbeat.php" hash="6ff27ead78e4242a498344837fdde044"/><file name="Test.php" hash="ce5492bd62c4173bc6f0972a4561c6cd"/></dir></dir><dir name="Test"><dir name="Helper"><file name="Data.php" hash="c32182b6f44f642bb956b0e074453677"/></dir><dir name="Model"><dir name="Schedule"><file name="Runnow.php" hash="1e6c1bcfa943c3fa88922a76fceef896"/><file name="Scheduling.php" hash="66f4461968982ef1736d797b6c5c9d4f"/></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="InstructionsController.php" hash="fa35d0bcebf81670c8c727126fc1c175"/><file name="JobController.php" hash="be1315dc49ed360e1ad0fc9a66dfa694"/><file name="SchedulerController.php" hash="3e9a178be9dd7dd61a15488c9dbb454a"/><file name="TimelineController.php" hash="11f8cd380ea04ed6213dae32f04b4f5b"/></dir></dir><dir name="data"><dir name="aoescheduler_setup"><file name="data-upgrade-0.5.4-0.5.5.php" hash="9a1ff96466dbc2fed5f04496d7212da5"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="7d914fe555e46d66958ceeaa92e383ad"/><file name="api.xml" hash="cc6cc05874a1277ba53e07c3450541c5"/><file name="config.xml" hash="eb943352cdd2f61926a260a92ddff9e5"/><file name="system.xml" hash="0a4ff787cdb6d400edb62d68ab1d75f0"/></dir><dir name="sql"><dir name="aoescheduler_setup"><file name="install-1.0.0.php" hash="e8c7cf7e088e52603b74eb337ed75f82"/><file name="mysql4-upgrade-0.4.0-0.4.1.php" hash="8e43f3a42359b6a2f134dbdb9be346de"/><file name="mysql4-upgrade-0.4.1-0.4.2.php" hash="d0d515397c7229822995e547d43d347f"/><file name="mysql4-upgrade-0.4.2-0.4.3.php" hash="c71d4443d7791728ae98b0c0b0a629a9"/><file name="mysql4-upgrade-0.5.0-0.5.1.php" hash="55ccd4aab57b88e3890d239aabc79389"/><file name="mysql4-upgrade-0.5.3-0.5.4.php" hash="13ac6cf731bf750b4b022874af6e1e2b"/><file name="mysql4-upgrade-0.5.4-1.1.0.php" hash="27c1201a3e8a426df1fb62e2bdc50ead"/></dir></dir></dir></dir></dir></dir></target><target name="mageetc"><dir><dir name="modules"><file name="Aoe_Scheduler.xml" hash="34ec6f37fb42feda3a164dc26bac521e"/></dir></dir></target><target name="mageweb"><dir><dir name="shell"><file name="scheduler.php" hash="4dd04b41cce26823decd95e19de84c39"/></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 name="sv_SE"><file name="Aoe_Scheduler.csv" hash="feb09dbeadfc1853df5dd0486a06b896"/></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="instructions.phtml" hash="74ad585711a519eb643b0ccd586c0ef3"/><file name="timeline.phtml" hash="b2daab6a137a460a9302bbe8f2d3c643"/><file name="timeline_detail.phtml" hash="5a58dd32b5a56ae9cf96a904250120cb"/></dir></dir><dir name="layout"><dir name="aoe_scheduler"><file name="aoe_scheduler.xml" hash="5d29825ad82e6d023beebcf845a41c38"/></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="animation.gif" hash="01ecf91547d85d738cbe64c2ff7a2f6f"/><file name="animation2.gif" hash="520e233263997c981bcb54d72a0b398f"/><file name="bg_notifications.gif" hash="df73b8aa7e48bb56e0a644245aa3683f"/><file name="gradient.png" hash="17f6c6ef64b1682a06060950933561c9"/><file name="hour.gif" hash="91a63b82b2b41a046ca938aea3238a41"/><file name="red.png" hash="7644c4b5b491cf69cef0df76bea06482"/></dir><dir name="JavaScript"><file name="common.js" hash="eaa0b86481d3cc4fb39b54989428f9c5"/><file name="instructions.js" hash="111e27ffd2b8dd364c8b074091b271de"/><file name="jquery-1.6.2.min.js" hash="a1a8cb16a060f6280a767187fd22e037"/><file name="tooltip.dynamic.js" hash="c6737dd54890ca0cac816f3fecaf33d9"/><file name="tooltip.js" hash="b438265d9a3fb7b6ae5713fc098ccab6"/></dir><dir name="StyleSheet"><file name="bars.css" hash="17137a4a35e90fa227d9ac6d905e0c52"/><file name="instructions.css" hash="d1f1edcb2c5a84cec429390ea103a069"/><file name="timeline.css" hash="25150cbe4e082704613a50342b046526"/></dir></dir></dir></dir></dir></dir></dir></target><target name="mage"><dir><dir><file name="scheduler_cron.sh" hash="d06305c65844781bbc57bab60f7b893a"/></dir></dir></target></contents>
16
  <compatible/>
17
+ <dependencies><required><php><min>5.3.0</min><max>6.0.0</max></php></required></dependencies>
18
  </package>
scheduler_cron.sh ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Generate an error if any variable doesn't exist
4
+ set -o nounset
5
+
6
+ delete_lock() {
7
+ LOCKDIR=$1
8
+ rm -rf "${LOCKDIR}"
9
+ if [ $? -ne 0 ]; then
10
+ echo "Could not remove lock dir '${LOCKDIR}'. (Check permissions...)"; >&2
11
+ exit 1;
12
+ fi
13
+ }
14
+
15
+ # @see http://wiki.bash-hackers.org/howto/mutex
16
+ acquire_lock () {
17
+ LOCKDIR=$1
18
+ PIDFILE="${LOCKDIR}/PID"
19
+
20
+ #echo "Trying to acquire lock '${LOCKDIR}'."
21
+ if mkdir "${LOCKDIR}" &>/dev/null; then
22
+
23
+ #echo "Successfully created '${LOCKDIR}'. Lock acquired"
24
+
25
+ # lock succeeded
26
+ trap 'delete_lock "${LOCKDIR}"; exit $?' INT TERM EXIT
27
+ echo "$$" >"${PIDFILE}"
28
+
29
+ else
30
+ #echo "Failed creating ${LOCKDIR}."
31
+ if [ ! -f "${PIDFILE}" ]; then
32
+
33
+ # no PID file found. This could be
34
+ # a) because you just updated from a previous version that didn't write a PID file, but a process is legitimetly running
35
+ # b) because you just updated from a previous version that didn't write a PID file and there's an abandoned lock
36
+ # c) because you hit the exact time between another process creating the dir and writing the PID file
37
+
38
+ # let's wait for a while to acount for c) and see if the file shows up
39
+ # and this also solves the problem of any old abandoned lock in b)
40
+ # only problem here is that there's a minimal chance of having two concurrent processes right after updating in case
41
+ # a new process expecting a PID file overlaps with an old process that didn't write a PID file
42
+ sleep 5
43
+ # if there's still no PID file we grab the process
44
+ if [ ! -f "${PIDFILE}" ]; then
45
+ #echo "No PID file found. Claiming lock now"
46
+ delete_lock "${LOCKDIR}"
47
+ # now try acquire new lock recursively...
48
+ #echo "Now acquiring new lock"
49
+ acquire_lock $LOCKDIR;
50
+ return
51
+ fi
52
+ fi
53
+
54
+ # lock failed, check if the other PID is alive
55
+ OTHERPID="$(cat "${PIDFILE}")"
56
+ # if cat isn't able to read the file, another instance is probably about to remove the lock -- exit, we're *still* locked
57
+ if [ $? != 0 ]; then
58
+ #echo "lock failed, PID ${OTHERPID} is active" >&2
59
+ exit 1;
60
+ fi
61
+
62
+ # check is the other process is still alive
63
+ if ! kill -0 $OTHERPID &>/dev/null; then
64
+ # lock is stale, remove it and restart
65
+ #echo "removing stale lock of nonexistant PID ${OTHERPID}" >&2
66
+ delete_lock "${LOCKDIR}"
67
+ # now try acquire new lock recursively...
68
+ acquire_lock $LOCKDIR;
69
+ else
70
+ # lock is valid and OTHERPID is active - exit, we're locked!
71
+ #echo "Other process is alive. Still locked"
72
+ exit 1
73
+ fi
74
+ fi
75
+ }
76
+
77
+
78
+ # Location of the php binary
79
+ PHP_BIN=$(which php || true)
80
+ if [ -z "${PHP_BIN}" ]; then
81
+ echo "Could not find a binary for php" 1>&2
82
+ exit 1
83
+ fi
84
+
85
+ # Location of the md5sum binary
86
+ MD5SUM_BIN=$(which md5sum || true)
87
+ if [ -z "${MD5SUM_BIN}" ]; then
88
+ echo "Could not find a binary for md5sum" 1>&2
89
+ exit 1
90
+ fi
91
+
92
+ # Absolute path to Magento installation shell scripts
93
+ DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/shell" && pwd)
94
+ if [[ -z "${DIR}" || ! -d "${DIR}" ]]; then
95
+ echo "Could not resolve base shell directory" 1>&2
96
+ exit 1
97
+ fi
98
+
99
+ # The scheduler.php script
100
+ SCHEDULER="scheduler.php"
101
+ if [[ ! -e "${DIR}/${SCHEDULER}" || ! -r "${DIR}/${SCHEDULER}" ]]; then
102
+ echo "Could not find scheduler.php script" 1>&2
103
+ exit 1
104
+ fi
105
+
106
+ # Defaults
107
+ MODE="default"
108
+ INCLUDE_GROUPS=""
109
+ EXCLUDE_GROUPS=""
110
+ INCLUDE_JOBS=""
111
+ EXCLUDE_JOBS=""
112
+
113
+ # Parse command line args (very simplistic)
114
+ while [ $# -gt 0 ]; do
115
+ case "$1" in
116
+ --mode)
117
+ MODE=$2
118
+ shift 2
119
+ ;;
120
+ --includeGroups)
121
+ INCLUDE_GROUPS=$2
122
+ shift 2
123
+ ;;
124
+ --excludeGroups)
125
+ EXCLUDE_GROUPS=$2
126
+ shift 2
127
+ ;;
128
+ --includeJobs)
129
+ INCLUDE_JOBS=$2
130
+ shift 2
131
+ ;;
132
+ --excludeJobs)
133
+ EXCLUDE_JOBS=$2
134
+ shift 2
135
+ ;;
136
+ --)
137
+ shift
138
+ break
139
+ ;;
140
+ *)
141
+ echo "Invalid arguments." >&2
142
+ exit 1
143
+ ;;
144
+ esac
145
+ done
146
+
147
+ # Verify we have a MODE parameter
148
+ if [ -z "${MODE}" ]; then
149
+ echo "Cron run mode MUST be defined." 1>&2
150
+ exit 1
151
+ fi
152
+
153
+ # Lock process to one run per set of options
154
+ # This is to prevent multiple processes for the same cron parameters (And the only reason we don't call PHP directly)
155
+
156
+ # Unique identifier for this cron job run
157
+ IDENTIFIER=$(echo -n "${DIR}|${MODE}|${INCLUDE_GROUPS}|${EXCLUDE_GROUPS}|${INCLUDE_JOBS}|${EXCLUDE_JOBS}" | "${MD5SUM_BIN}" - | cut -f1 -d' ')
158
+ acquire_lock "/tmp/magento.aoe_scheduler.${IDENTIFIER}.lock";
159
+
160
+ # Needed because PHP resolves symlinks before setting __FILE__
161
+ cd "${DIR}"
162
+
163
+ # Build the options
164
+ OPTIONS=""
165
+ if [ -n "${INCLUDE_GROUPS}" ]; then
166
+ OPTIONS="${OPTIONS} --includeGroups ${INCLUDE_GROUPS}"
167
+ fi
168
+ if [ -n "${EXCLUDE_GROUPS}" ]; then
169
+ OPTIONS="${OPTIONS} --excludeGroups ${EXCLUDE_GROUPS}"
170
+ fi
171
+ if [ -n "${INCLUDE_JOBS}" ]; then
172
+ OPTIONS="${OPTIONS} --includeJobs ${INCLUDE_JOBS}"
173
+ fi
174
+ if [ -n "${EXCLUDE_JOBS}" ]; then
175
+ OPTIONS="${OPTIONS} --excludeJobs ${EXCLUDE_JOBS}"
176
+ fi
177
+
178
+ # Run the job in the foreground
179
+ "${PHP_BIN}" "${SCHEDULER}" --action cron --mode ${MODE} ${OPTIONS}
shell/scheduler.php CHANGED
@@ -1,177 +1,403 @@
1
  <?php
2
 
3
- require_once 'abstract.php';
4
-
5
- class Aoe_Scheduler_Shell_Scheduler extends Mage_Shell_Abstract {
6
-
7
- /**
8
- * Run script
9
- *
10
- * @return void
11
- */
12
- public function run() {
13
- $action = $this->getArg('action');
14
- if (empty($action)) {
15
- echo $this->usageHelp();
16
- } else {
17
- $actionMethodName = $action.'Action';
18
- if (method_exists($this, $actionMethodName)) {
19
- $this->$actionMethodName();
20
- } else {
21
- echo "Action $action not found!\n";
22
- echo $this->usageHelp();
23
- exit(1);
24
- }
25
- }
26
- }
27
-
28
-
29
-
30
- /**
31
- * Retrieve Usage Help Message
32
- *
33
- * @return string
34
- */
35
- public function usageHelp() {
36
- $help = 'Available actions: ' . "\n";
37
- $methods = get_class_methods($this);
38
- foreach ($methods as $method) {
39
- if (substr($method, -6) == 'Action') {
40
- $help .= ' -action ' . substr($method, 0, -6);
41
- $helpMethod = $method.'Help';
42
- if (method_exists($this, $helpMethod)) {
43
- $help .= $this->$helpMethod();
44
- }
45
- $help .= "\n";
46
- }
47
- }
48
- return $help;
49
- }
50
-
51
-
52
-
53
- /**
54
- * List all availables codes / jobs
55
- *
56
- * @return void
57
- */
58
- public function listAllCodesAction() {
59
- $collection = Mage::getModel('aoe_scheduler/collection_crons');
60
- foreach ($collection as $configuration) { /* @var $configuration Aoe_Scheduler_Model_Configuration */
61
- echo sprintf("%-50s %-20s %s\n", $configuration->getId(), $configuration->getCronExpr(), $configuration->getStatus());
62
- }
63
- }
64
-
65
-
66
-
67
- /**
68
- * Returns the timestamp of the last run of a given job
69
- *
70
- * @return void
71
- */
72
- public function lastRunAction() {
73
-
74
- $code = $this->getArg('code');
75
- if (empty($code)) {
76
- echo "\nNo code found!\n\n";
77
- echo $this->usageHelp();
78
- exit(1);
79
- }
80
-
81
- $collection = Mage::getModel('cron/schedule')->getCollection(); /* @var $collection Mage_Cron_Model_Resource_Schedule_Collection */
82
- $collection->addFieldToFilter('job_code', $code)
83
- ->addFieldToFilter('status', Mage_Cron_Model_Schedule::STATUS_SUCCESS)
84
- ->addOrder('finished_at', Varien_Data_Collection_Db::SORT_ORDER_DESC)
85
- ->getSelect()->limit(1);
86
- $schedule = $collection->getFirstItem(); /* @var $schedule Aoe_Scheduler_Model_Schedule */
87
- if (!$schedule || !$schedule->getId()) {
88
- echo "\nNo schedule found\n\n";
89
- exit(1);
90
- }
91
-
92
- $time = strtotime($schedule->getFinishedAt());
93
-
94
- if ($this->getArg('secondsFromNow')) {
95
- $time = time() - $time;
96
- }
97
-
98
- echo $time . PHP_EOL;
99
- }
100
-
101
-
102
-
103
- /**
104
- * Display extra help
105
- *
106
- * @return string
107
- */
108
- public function lastRunActionHelp() {
109
- return " -code <code> [-secondsFromNow] Get the timestamp of the last successful run of a job for a given code";
110
- }
111
-
112
-
113
-
114
- /**
115
- * Schedule a job now
116
- *
117
- * @return void
118
- */
119
- public function scheduleNowAction() {
120
- $code = $this->getArg('code');
121
- if (empty($code)) {
122
- echo "\nNo code found!\n\n";
123
- echo $this->usageHelp();
124
- exit(1);
125
- }
126
- $schedule = Mage::getModel('cron/schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
127
- $schedule->setJobCode($code);
128
- $schedule->scheduleNow();
129
- $schedule->save();
130
- }
131
-
132
-
133
-
134
- /**
135
- * Display extra help
136
- *
137
- * @return string
138
- */
139
- public function scheduleNowActionHelp() {
140
- return " -code <code> Schedule a job to be executed as soon as possible";
141
- }
142
-
143
-
144
-
145
- /**
146
- * Run a job now
147
- *
148
- * @return void
149
- */
150
- public function runNowAction() {
151
- $code = $this->getArg('code');
152
- if (empty($code)) {
153
- echo "\nNo code found!\n\n";
154
- echo $this->usageHelp();
155
- exit(1);
156
- }
157
- $schedule = Mage::getModel('cron/schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
158
- $schedule->setJobCode($code);
159
- $schedule->runNow();
160
- $schedule->save();
161
- }
162
-
163
-
164
-
165
- /**
166
- * Display extra help
167
- *
168
- * @return string
169
- */
170
- public function runNowActionHelp() {
171
- return " -code <code> Run a job directly";
172
- }
173
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  }
175
 
176
  $shell = new Aoe_Scheduler_Shell_Scheduler();
177
- $shell->run();
1
  <?php
2
 
3
+ require_once dirname($_SERVER['SCRIPT_NAME']) . DIRECTORY_SEPARATOR . 'abstract.php';
4
+
5
+ class Aoe_Scheduler_Shell_Scheduler extends Mage_Shell_Abstract
6
+ {
7
+ /**
8
+ * Run script
9
+ *
10
+ * @return void
11
+ */
12
+ public function run()
13
+ {
14
+ try {
15
+ $helper = Mage::helper('aoe_scheduler/compatibility'); /* @var $helper Aoe_Scheduler_Helper_Compatibility */
16
+ if ($helper->oldConfigXmlExists()) {
17
+ echo 'Looks like you have an older version of Aoe_Scheduler installed that lived in the local code pool. Please delete everything under "' .$helper->getLocalCodeDir(). '"';
18
+ exit(1);
19
+ }
20
+ $action = $this->getArg('action');
21
+ if (empty($action)) {
22
+ echo $this->usageHelp();
23
+ } else {
24
+ $actionMethodName = $action . 'Action';
25
+ if (method_exists($this, $actionMethodName)) {
26
+ // emulate index.php entry point for correct URLs generation in scheduled cronjobs
27
+ Mage::register('custom_entry_point', true);
28
+ // Disable use of SID in generated URLs - This is standard for cron job bootstrapping
29
+ Mage::app()->setUseSessionInUrl(false);
30
+ // Disable permissions masking by default - This is Magento standard, but not recommended for security reasons
31
+ umask(0);
32
+ // Load the global event area - This is non-standard be should be standard
33
+ Mage::app()->addEventArea(Mage_Core_Model_App_Area::AREA_GLOBAL);
34
+ // Load the crontab event area - This is standard for cron job bootstrapping
35
+ Mage::app()->addEventArea('crontab');
36
+ // Run the command
37
+ $this->$actionMethodName();
38
+ } else {
39
+ echo "Action $action not found!\n";
40
+ echo $this->usageHelp();
41
+ exit(1);
42
+ }
43
+ }
44
+ } catch (Exception $e) {
45
+ $fh = fopen('php://stderr', 'w');
46
+ fputs($fh, $e->__toString());
47
+ fclose($fh);
48
+ exit(255);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Retrieve Usage Help Message
54
+ *
55
+ * @return string
56
+ */
57
+ public function usageHelp()
58
+ {
59
+ $help = 'Available actions: ' . "\n";
60
+ $methods = get_class_methods($this);
61
+ foreach ($methods as $method) {
62
+ if (substr($method, -6) == 'Action') {
63
+ $help .= ' --action ' . substr($method, 0, -6);
64
+ $helpMethod = $method . 'Help';
65
+ if (method_exists($this, $helpMethod)) {
66
+ $help .= ' ' . $this->$helpMethod();
67
+ }
68
+ $help .= "\n";
69
+ }
70
+ }
71
+ return $help;
72
+ }
73
+
74
+ /**
75
+ * List all availables codes / jobs
76
+ *
77
+ * @return void
78
+ */
79
+ public function listAllCodesAction()
80
+ {
81
+ /** @var Aoe_Scheduler_Model_Resource_Job_Collection $jobs */
82
+ $jobs = Mage::getSingleton('aoe_scheduler/job')->getCollection();
83
+ foreach ($jobs as $job) {
84
+ /* @var $job Aoe_Scheduler_Model_Job */
85
+ echo sprintf("%-50s %-20s %s\n", $job->getJobCode(), $job->getCronExpression(), $job->getIsActive() ? 'Enabled' : 'Disabled');
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Returns the timestamp of the last run of a given job
91
+ *
92
+ * @return void
93
+ */
94
+ public function lastRunAction()
95
+ {
96
+ $code = $this->getArg('code');
97
+ if (empty($code)) {
98
+ echo "\nNo code found!\n\n";
99
+ echo $this->usageHelp();
100
+ exit(1);
101
+ }
102
+
103
+ $collection = Mage::getModel('cron/schedule')->getCollection(); /* @var $collection Mage_Cron_Model_Resource_Schedule_Collection */
104
+
105
+ $collection->addFieldToFilter('job_code', $code)
106
+ ->addFieldToFilter('status', Aoe_Scheduler_Model_Schedule::STATUS_SUCCESS)
107
+ ->addOrder('finished_at', Varien_Data_Collection_Db::SORT_ORDER_DESC)
108
+ ->getSelect()->limit(1);
109
+ $schedule = $collection->getFirstItem(); /* @var $schedule Aoe_Scheduler_Model_Schedule */
110
+ if (!$schedule || !$schedule->getId()) {
111
+ echo "\nNo schedule found\n\n";
112
+ exit(1);
113
+ }
114
+
115
+ $time = strtotime($schedule->getFinishedAt());
116
+
117
+ if ($this->getArg('secondsFromNow')) {
118
+ $time = time() - $time;
119
+ }
120
+
121
+ echo $time . PHP_EOL;
122
+ }
123
+
124
+ /**
125
+ * Display extra help
126
+ *
127
+ * @return string
128
+ */
129
+ public function lastRunActionHelp()
130
+ {
131
+ return "--code <code> [--secondsFromNow] Get the timestamp of the last successful run of a job for a given code";
132
+ }
133
+
134
+ /**
135
+ * Schedule a job now
136
+ *
137
+ * @return void
138
+ */
139
+ public function scheduleNowAction()
140
+ {
141
+ $code = $this->getArg('code');
142
+ if (empty($code)) {
143
+ echo "\nNo code found!\n\n";
144
+ echo $this->usageHelp();
145
+ exit(1);
146
+ }
147
+
148
+ $allowedCodes = Mage::getSingleton('aoe_scheduler/job')->getResource()->getJobCodes();
149
+ if (!in_array($code, $allowedCodes)) {
150
+ echo "\nNo valid job found!\n\n";
151
+ echo $this->usageHelp();
152
+ exit(1);
153
+ }
154
+
155
+ $schedule = Mage::getModel('cron/schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
156
+ $schedule->setScheduledReason(Aoe_Scheduler_Model_Schedule::REASON_SCHEDULENOW_CLI);
157
+ $schedule->setJobCode($code);
158
+ $schedule->schedule();
159
+ $schedule->save();
160
+ }
161
+
162
+ /**
163
+ * Display extra help
164
+ *
165
+ * @return string
166
+ */
167
+ public function scheduleNowActionHelp()
168
+ {
169
+ return "--code <code> Schedule a job to be executed as soon as possible";
170
+ }
171
+
172
+ /**
173
+ * Run a job now
174
+ *
175
+ * @return void
176
+ */
177
+ public function runNowAction()
178
+ {
179
+ $code = $this->getArg('code');
180
+ if (empty($code)) {
181
+ echo "\nNo code found!\n\n";
182
+ echo $this->usageHelp();
183
+ exit(1);
184
+ }
185
+
186
+ $allowedCodes = Mage::getSingleton('aoe_scheduler/job')->getResource()->getJobCodes();
187
+ if (!in_array($code, $allowedCodes)) {
188
+ echo "\nNo valid job found!\n\n";
189
+ echo $this->usageHelp();
190
+ exit(1);
191
+ }
192
+
193
+ $forceRun = ($this->getArg('force'));
194
+ $tryLock = ($this->getArg('tryLock'));
195
+
196
+ $schedule = Mage::getModel('cron/schedule'); /* @var $schedule Aoe_Scheduler_Model_Schedule */
197
+ $schedule->setJobCode($code);
198
+ $schedule->setScheduledReason(Aoe_Scheduler_Model_Schedule::REASON_RUNNOW_CLI);
199
+ $schedule->runNow($tryLock, $forceRun);
200
+ if ($schedule->getJobWasLocked()) {
201
+ echo "\nJob was not executed because it was locked!\n\n";
202
+ exit(1);
203
+ }
204
+ $schedule->save();
205
+
206
+ echo "\nStatus: " . $schedule->getStatus() . "\n";
207
+ echo "Messages:\n" . trim($schedule->getMessages(), "\n") . "\n";
208
+ }
209
+
210
+ /**
211
+ * Display extra help
212
+ *
213
+ * @return string
214
+ */
215
+ public function runNowActionHelp()
216
+ {
217
+ return "--code <code> [--tryLock] [--force] Run a job directly";
218
+ }
219
+
220
+ /**
221
+ * Active wait until no schedules are running
222
+ */
223
+ public function waitAction()
224
+ {
225
+ $timeout = $this->getArg('timeout') ? $this->getArg('timeout') : 60;
226
+ $startTime = time();
227
+ $sleepBetweenPolls = 2;
228
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
229
+ do {
230
+ sleep($sleepBetweenPolls);
231
+ $aliveSchedules = 0;
232
+ echo "Currently running schedules:\n";
233
+ foreach ($processManager->getAllRunningSchedules() as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
234
+ $status = $schedule->isAlive();
235
+ if (is_null($status)) {
236
+ $status = '?';
237
+ } else {
238
+ $status = $status ? 'alive' : 'dead (updating status to "disappeared")';
239
+ }
240
+ if ($status) {
241
+ $aliveSchedules++;
242
+ }
243
+ echo sprintf(
244
+ "%-30s %-10s %-10s %-10s %-10s\n",
245
+ $schedule->getJobCode(),
246
+ $schedule->getHost() ? $schedule->getHost() : '(no host)',
247
+ $schedule->getPid() ? $schedule->getPid() : '(no pid)',
248
+ $schedule->getLastSeen() ? $schedule->getLastSeen() : '(never)',
249
+ $status
250
+ );
251
+ }
252
+ if ($aliveSchedules == 0) {
253
+ echo "No schedules found\n";
254
+ return;
255
+ }
256
+ } while (time() - $startTime < $timeout);
257
+ echo "Timeout reached\n";
258
+ exit(1);
259
+ }
260
+
261
+ /**
262
+ * Display extra help
263
+ *
264
+ * @return string
265
+ */
266
+ public function waitActionHelp()
267
+ {
268
+ return "[--timout <timeout=60>] Active wait until no schedules are running.";
269
+ }
270
+
271
+ /**
272
+ * Flush schedules
273
+ */
274
+ public function flushSchedulesAction()
275
+ {
276
+ $scheduleManager = Mage::getModel('aoe_scheduler/scheduleManager'); /* @var $scheduleManager Aoe_Scheduler_Model_ScheduleManager */
277
+ switch ($this->getArg('mode')) {
278
+ case 'future':
279
+ $scheduleManager->flushSchedules();
280
+ break;
281
+ case 'all':
282
+ $scheduleManager->deleteAll();
283
+ break;
284
+ default:
285
+ echo "\nInvalid mode!\n\n";
286
+ echo $this->usageHelp();
287
+ exit(1);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Display extra help
293
+ *
294
+ * @return string
295
+ */
296
+ public function flushSchedulesActionHelp()
297
+ {
298
+ return "--mode (future|all) Flush schedules.";
299
+ }
300
+
301
+ /**
302
+ * Print all running schedules
303
+ *
304
+ * @return void
305
+ */
306
+ public function listAllRunningSchedulesAction()
307
+ {
308
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
309
+ foreach ($processManager->getAllRunningSchedules() as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
310
+ $status = $schedule->isAlive();
311
+ if (is_null($status)) {
312
+ $status = '?';
313
+ } else {
314
+ $status = $status ? 'alive' : 'dead (updating status to "disappeared")';
315
+ }
316
+ echo sprintf(
317
+ "%-30s %-10s %-10s %-10s %-10s\n",
318
+ $schedule->getJobCode(),
319
+ $schedule->getHost() ? $schedule->getHost() : '(no host)',
320
+ $schedule->getPid() ? $schedule->getPid() : '(no pid)',
321
+ $schedule->getLastSeen() ? $schedule->getLastSeen() : '(never)',
322
+ $status
323
+ );
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Kill all
329
+ *
330
+ * @return void
331
+ */
332
+ public function killAllAction()
333
+ {
334
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
335
+ foreach ($processManager->getAllRunningSchedules(gethostname()) as $schedule) { /* @var $schedule Aoe_Scheduler_Model_Schedule */
336
+ if ($schedule->isAlive() === true) {
337
+ $schedule->kill();
338
+ echo sprintf(
339
+ "%-30s %-10s %-10s: Killed\n",
340
+ $schedule->getJobCode(),
341
+ $schedule->getHost(),
342
+ $schedule->getPid()
343
+ );
344
+ }
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Runs watchdog
350
+ */
351
+ public function watchdogAction()
352
+ {
353
+ $processManager = Mage::getModel('aoe_scheduler/processManager'); /* @var $processManager Aoe_Scheduler_Model_ProcessManager */
354
+ $processManager->watchdog();
355
+ }
356
+
357
+ /**
358
+ * Cron action
359
+ *
360
+ *
361
+ */
362
+ public function cronAction()
363
+ {
364
+ $mode = $this->getArg('mode');
365
+ switch ($mode) {
366
+ case 'always':
367
+ case 'default':
368
+ $includeGroups = array_filter(array_map('trim', explode(',', $this->getArg('includeGroups'))));
369
+ $excludeGroups = array_filter(array_map('trim', explode(',', $this->getArg('excludeGroups'))));
370
+ $includeJobs = array_filter(array_map('trim', explode(',', $this->getArg('includeJobs'))));
371
+ $excludeJobs = array_filter(array_map('trim', explode(',', $this->getArg('excludeJobs'))));
372
+ Mage::dispatchEvent($mode, array(
373
+ 'include_groups' => $includeGroups,
374
+ 'exclude_groups' => $excludeGroups,
375
+ 'include_jobs' => $includeJobs,
376
+ 'exclude_jobs' => $excludeJobs,
377
+ ));
378
+ break;
379
+ default:
380
+ echo "\nInvalid mode!\n\n";
381
+ echo $this->usageHelp();
382
+ exit(1);
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Display extra help
388
+ *
389
+ * @return string
390
+ */
391
+ public function cronActionHelp()
392
+ {
393
+ return "--mode (always|default) [--includeJobs <comma separated list of jobs>] [--excludeJobs <comma separated list of jobs>] [--includeGroups <comma separated list of groups>] [--excludeGroups <comma separated list of groups>]";
394
+ }
395
+
396
+ protected function _applyPhpVariables()
397
+ {
398
+ // Disable this feature as cron jobs should run with CLI settings only
399
+ }
400
  }
401
 
402
  $shell = new Aoe_Scheduler_Shell_Scheduler();
403
+ $shell->run();
skin/adminhtml/default/default/aoe_scheduler/Images/animation.gif ADDED
Binary file
skin/adminhtml/default/default/aoe_scheduler/Images/animation2.gif ADDED
Binary file
skin/adminhtml/default/default/aoe_scheduler/Images/red.png ADDED
Binary file
skin/adminhtml/default/default/aoe_scheduler/JavaScript/common.js CHANGED
@@ -1,56 +1,59 @@
1
  $.noConflict();
2
- jQuery(function() {
3
- jQuery('.timeline-box').scrollLeft(jQuery('.timeline-panel').width());
4
-
5
- jQuery('.task').tooltip({
6
- offsetParent: 'body',
7
- predelay: 100,
8
- position: 'bottom center',
9
- onShow: function() { this.getTrigger().addClass('active'); },
10
- onHide: function() { this.getTrigger().removeClass('active'); }
11
- }).dynamic();
12
-
13
- // collision detection
14
- function getPositions(box) {
15
- var $box = jQuery(box);
16
- var pos = $box.position();
17
- var width = $box.width();
18
- var height = $box.height();
19
- return [ [ pos.left, pos.left + width ], [ pos.top, pos.top + height ] ];
20
- }
21
-
22
- function comparePositions(p1, p2) {
23
- var x1 = p1[0] < p2[0] ? p1 : p2;
24
- var x2 = p1[0] < p2[0] ? p2 : p1;
25
- return x1[1] > x2[0] || x1[0] === x2[0] ? true : false;
26
- }
27
-
28
- function collision(a, b) {
29
- var posA = getPositions(a);
30
- var posB = getPositions(b);
31
-
32
- return (posA[1][0] == posB[1][0]) && comparePositions(posA[0], posB[0]);
33
- }
34
-
35
- jQuery('.timeline').each(function() {
36
- var $tasks = jQuery('.task', jQuery(this));
37
- var numberOfTasks = $tasks.length;
38
- for (var i=0; i<numberOfTasks; i++) {
39
- var u = Math.min(i+10, numberOfTasks);
40
- for (var j = i+1; j < u; j++) {
41
- if (collision($tasks[i], $tasks[j])) {
42
- var $subject = jQuery($tasks[i]);
43
- var $object = jQuery($tasks[j]);
44
-
45
- var objectTop = parseInt($subject.css('top'));
46
-
47
- $object.css('top', (objectTop+4) + 'px');
48
-
49
- $subject.css('height', 18);
50
- $object.css('height', 18);
51
- }
52
- }
53
- }
54
- });
55
-
56
- })
 
 
 
1
  $.noConflict();
2
+ jQuery(function () {
3
+ jQuery('.timeline-box').scrollLeft(jQuery('.timeline-panel').width());
4
+
5
+ jQuery('.task').tooltip({
6
+ offsetParent: 'body',
7
+ predelay: 100,
8
+ position: 'bottom center',
9
+ onShow: function () {
10
+ this.getTrigger().addClass('active');
11
+ },
12
+ onHide: function () {
13
+ this.getTrigger().removeClass('active');
14
+ }
15
+ }).dynamic();
16
+
17
+ // collision detection
18
+ function getPositions(box) {
19
+ var $box = jQuery(box);
20
+ var pos = $box.position();
21
+ var width = $box.width();
22
+ var height = $box.height();
23
+ return [[pos.left, pos.left + width], [pos.top, pos.top + height]];
24
+ }
25
+
26
+ function comparePositions(p1, p2) {
27
+ var x1 = p1[0] < p2[0] ? p1 : p2;
28
+ var x2 = p1[0] < p2[0] ? p2 : p1;
29
+ return (x1[1] > x2[0] || x1[0] === x2[0]);
30
+ }
31
+
32
+ function collision(a, b) {
33
+ var posA = getPositions(a);
34
+ var posB = getPositions(b);
35
+
36
+ return (posA[1][0] == posB[1][0]) && comparePositions(posA[0], posB[0]);
37
+ }
38
+
39
+ jQuery('.timeline').each(function () {
40
+ var $tasks = jQuery('.task', jQuery(this));
41
+ var numberOfTasks = $tasks.length;
42
+ for (var i = 0; i < numberOfTasks; i++) {
43
+ var u = Math.min(i + 10, numberOfTasks);
44
+ for (var j = i + 1; j < u; j++) {
45
+ if (collision($tasks[i], $tasks[j])) {
46
+ var $subject = jQuery($tasks[i]);
47
+ var $object = jQuery($tasks[j]);
48
+
49
+ var objectTop = parseInt($subject.css('top'));
50
+
51
+ $object.css('top', (objectTop + 4) + 'px');
52
+
53
+ $subject.css('height', 18);
54
+ $object.css('height', 18);
55
+ }
56
+ }
57
+ }
58
+ });
59
+ });
skin/adminhtml/default/default/aoe_scheduler/JavaScript/instructions.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $.noConflict();
2
+ jQuery(function() {
3
+ function updateInstructions() {
4
+ jQuery('.croncommand').hide();
5
+ jQuery('.configuration input').removeAttr("disabled");
6
+ if (!jQuery('.configuration input[name="scheduler-cron"]').is(':checked')) {
7
+ jQuery('.classic').show();
8
+ jQuery('.configuration input[name="use-crongroups"]').attr('checked', false).attr("disabled", true);
9
+ }
10
+ if (jQuery('.configuration input[name="use-crongroups"]').is(':checked')) {
11
+ jQuery('.configuration input[name="scheduler-cron"]').attr('checked', true).attr("disabled", true);
12
+ jQuery('.crongroups').show();
13
+ } else if (jQuery('.configuration input[name="scheduler-cron"]').is(':checked')) {
14
+ jQuery('.scheduler').show();
15
+ }
16
+ jQuery('.every-five-minutes').toggle(!jQuery('.configuration input[name="every-minute"]').is(':checked'));
17
+ jQuery('.maintenance-check-command').toggle(jQuery('.configuration input[name="maintenance-check"]').is(':checked'));
18
+ jQuery('.watchdog').toggle(jQuery('.configuration input[name="use-watchdog"]').is(':checked'));
19
+ jQuery('.mailto').toggle(jQuery('.configuration input[name="add-mailto"]').is(':checked'));
20
+
21
+ }
22
+ updateInstructions();
23
+ jQuery('.configuration input').change(updateInstructions);
24
+ });
skin/adminhtml/default/default/aoe_scheduler/JavaScript/tooltip.js CHANGED
@@ -1,215 +1,215 @@
1
  /**
2
- * @license
3
  * jQuery Tools @VERSION Tooltip - UI essentials
4
- *
5
  * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
6
- *
7
  * http://flowplayer.org/tools/tooltip/
8
  *
9
  * Since: November 2008
10
- * Date: @DATE
11
  */
12
- (function($) {
13
  // static constructs
14
  $.tools = $.tools || {version: '@VERSION'};
15
-
16
  $.tools.tooltip = {
17
-
18
- conf: {
19
-
20
  // default effect variables
21
- effect: 'toggle',
22
  fadeOutSpeed: "fast",
23
  predelay: 0,
24
  delay: 30,
25
- opacity: 1,
26
  tip: 0,
27
-
28
  // 'top', 'bottom', 'right', 'left', 'center'
29
- position: ['top', 'center'],
30
  offset: [0, 0],
31
  relative: false,
32
  offsetParent: null,
33
  cancelDefault: true,
34
-
35
- // type to event mapping
36
  events: {
37
  def: "mouseenter,mouseleave",
38
  input: "focus,blur",
39
  widget: "focus mouseenter,blur mouseleave",
40
  tooltip: "mouseenter,mouseleave"
41
  },
42
-
43
  // 1.2
44
- layout: '<div/>',
45
  tipClass: 'tooltip'
46
  },
47
-
48
  addEffect: function(name, loadFn, hideFn) {
49
- effects[name] = [loadFn, hideFn];
50
- }
51
  };
52
-
53
-
54
- var effects = {
55
- toggle: [
56
- function(done) {
57
  var conf = this.getConf(), tip = this.getTip(), o = conf.opacity;
58
  if (o < 1) { tip.css({opacity: o}); }
59
  tip.show();
60
  done.call();
61
  },
62
-
63
- function(done) {
64
  this.getTip().hide();
65
  done.call();
66
- }
67
  ],
68
-
69
  fade: [
70
- function(done) {
71
  var conf = this.getConf();
72
- this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done);
73
- },
74
- function(done) {
75
- this.getTip().fadeOut(this.getConf().fadeOutSpeed, done);
76
- }
77
- ]
78
- };
79
-
80
-
81
- /* calculate tip position relative to the trigger */
82
- function getPosition(trigger, tip, conf) {
83
-
84
-
85
- // get origin top/left position
86
- var top = conf.relative ? trigger.position().top : trigger.offset().top,
87
  left = conf.relative ? trigger.position().left : trigger.offset().left,
88
  pos = conf.position[0];
89
 
90
  top -= tip.outerHeight() - conf.offset[0];
91
  left += trigger.outerWidth() + conf.offset[1];
92
-
93
  // iPad position fix
94
  if (/iPad/i.test(navigator.userAgent)) {
95
  top -= $(window).scrollTop();
96
  }
97
-
98
- // adjust Y
99
  var height = tip.outerHeight() + trigger.outerHeight();
100
  if (pos == 'center') { top += height / 2; }
101
  if (pos == 'bottom') { top += height; }
102
-
103
-
104
  // adjust X
105
- pos = conf.position[1];
106
  var width = tip.outerWidth() + trigger.outerWidth();
107
  if (pos == 'center') { left -= width / 2; }
108
- if (pos == 'left') { left -= width; }
109
-
110
  return {top: top, left: left};
111
- }
 
 
112
 
113
-
114
-
115
  function Tooltip(trigger, conf) {
116
 
117
- var self = this,
118
  fire = trigger.add(self),
119
  tip,
120
  timer = 0,
121
- pretimer = 0,
122
  title = trigger.attr("title"),
123
  tipAttr = trigger.attr("data-tooltip"),
124
  effect = effects[conf.effect],
125
  shown,
126
-
127
  // get show/hide configuration
128
- isInput = trigger.is(":input"),
129
- isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"),
130
  type = trigger.attr("type"),
131
- evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def'];
132
-
133
-
134
  // check that configuration is sane
135
- if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; }
136
-
137
- evt = evt.split(/,\s*/);
138
- if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; }
139
-
140
-
141
- // trigger --> show
142
  trigger.bind(evt[0], function(e) {
143
 
144
  clearTimeout(timer);
145
  if (conf.predelay) {
146
- pretimer = setTimeout(function() { self.show(e); }, conf.predelay);
147
-
148
  } else {
149
- self.show(e);
150
  }
151
-
152
  // trigger --> hide
153
  }).bind(evt[1], function(e) {
154
  clearTimeout(pretimer);
155
  if (conf.delay) {
156
- timer = setTimeout(function() { self.hide(e); }, conf.delay);
157
-
158
  } else {
159
- self.hide(e);
160
  }
161
-
162
- });
163
-
164
-
165
  // remove default title
166
- if (title && conf.cancelDefault) {
167
  trigger.removeAttr("title");
168
- trigger.data("title", title);
169
- }
170
-
171
  $.extend(self, {
172
-
173
- show: function(e) {
174
 
175
  // tip not initialized yet
176
  if (!tip) {
177
-
178
- // data-tooltip
179
  if (tipAttr) {
180
  tip = $(tipAttr);
181
 
182
  // single tip element for all
183
- } else if (conf.tip) {
184
  tip = $(conf.tip).eq(0);
185
-
186
  // autogenerated tooltip
187
- } else if (title) {
188
  tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body)
189
  .hide().append(title);
190
 
191
  // manual tooltip
192
- } else {
193
- tip = trigger.next();
194
  if (!tip.length) { tip = trigger.parent().next(); }
195
-
196
  }
197
-
198
  if (!tip.length) { throw "Cannot find tooltip for " + trigger; }
199
-
200
  if (conf.offsetParent) {
201
  tip.appendTo($(conf.offsetParent));
202
  }
203
- }
204
-
205
- if (self.isShown()) { return self; }
206
-
207
  // stop previous animation
208
- tip.stop(true, true);
209
-
210
  // get position
211
- var pos = getPosition(trigger, tip, conf);
212
-
213
  // restore title for single tooltip element
214
  if (conf.tip) {
215
  tip.html(trigger.data("title"));
@@ -218,96 +218,96 @@
218
  // onBeforeShow
219
  e = e || $.Event();
220
  e.type = "onBeforeShow";
221
- fire.trigger(e, [pos]);
222
  if (e.isDefaultPrevented()) { return self; }
223
-
224
-
225
  // onBeforeShow may have altered the configuration
226
  pos = getPosition(trigger, tip, conf);
227
-
228
  // set position
229
- tip.css({position:'absolute', top: pos.top, left: pos.left});
230
-
231
  shown = true;
232
-
233
- // invoke effect
234
  effect[0].call(self, function() {
235
  e.type = "onShow";
236
  shown = 'full';
237
- fire.trigger(e);
238
- });
 
239
 
240
-
241
- // tooltip events
242
  var event = conf.events.tooltip.split(/,\s*/);
243
 
244
  if (!tip.data("__set")) {
245
-
246
- tip.bind(event[0], function() {
247
  clearTimeout(timer);
248
  clearTimeout(pretimer);
249
  });
250
-
251
- if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) {
252
  tip.bind(event[1], function(e) {
253
-
254
  // being moved to the trigger element
255
  if (e.relatedTarget != trigger[0]) {
256
  trigger.trigger(evt[1].split(" ")[0]);
257
  }
258
- });
259
- }
260
-
261
  tip.data("__set", true);
262
  }
263
-
264
  return self;
265
  },
266
-
267
  hide: function(e) {
268
 
269
  if (!tip || !self.isShown()) { return self; }
270
-
271
  // onBeforeHide
272
  e = e || $.Event();
273
  e.type = "onBeforeHide";
274
- fire.trigger(e);
275
  if (e.isDefaultPrevented()) { return; }
276
-
277
  shown = false;
278
-
279
  effects[conf.effect][1].call(self, function() {
280
  e.type = "onHide";
281
- fire.trigger(e);
282
  });
283
-
284
  return self;
285
  },
286
-
287
  isShown: function(fully) {
288
- return fully ? shown == 'full' : shown;
289
  },
290
-
291
  getConf: function() {
292
- return conf;
293
  },
294
-
295
  getTip: function() {
296
- return tip;
297
  },
298
-
299
  getTrigger: function() {
300
- return trigger;
301
- }
302
 
303
- });
304
 
305
- // callbacks
306
  $.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) {
307
-
308
  // configuration
309
- if ($.isFunction(conf[name])) {
310
- $(self).bind(name, conf[name]);
311
  }
312
 
313
  // API
@@ -316,34 +316,34 @@
316
  return self;
317
  };
318
  });
319
-
320
  }
321
-
322
-
323
  // jQuery plugin implementation
324
  $.fn.tooltip = function(conf) {
325
-
326
  // return existing instance
327
  var api = this.data("tooltip");
328
  if (api) { return api; }
329
 
330
  conf = $.extend(true, {}, $.tools.tooltip.conf, conf);
331
-
332
  // position can also be given as string
333
  if (typeof conf.position == 'string') {
334
- conf.position = conf.position.split(/,?\s/);
335
  }
336
-
337
  // install tooltip for each entry in jQuery object
338
  this.each(function() {
339
- api = new Tooltip($(this), conf);
340
- $(this).data("tooltip", api);
341
  });
342
-
343
- return conf.api ? api: this;
344
  };
345
-
346
  }) (jQuery);
347
 
348
-
349
 
1
  /**
2
+ * @license
3
  * jQuery Tools @VERSION Tooltip - UI essentials
4
+ *
5
  * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
6
+ *
7
  * http://flowplayer.org/tools/tooltip/
8
  *
9
  * Since: November 2008
10
+ * Date: @DATE
11
  */
12
+ (function($) {
13
  // static constructs
14
  $.tools = $.tools || {version: '@VERSION'};
15
+
16
  $.tools.tooltip = {
17
+
18
+ conf: {
19
+
20
  // default effect variables
21
+ effect: 'toggle',
22
  fadeOutSpeed: "fast",
23
  predelay: 0,
24
  delay: 30,
25
+ opacity: 1,
26
  tip: 0,
27
+
28
  // 'top', 'bottom', 'right', 'left', 'center'
29
+ position: ['top', 'center'],
30
  offset: [0, 0],
31
  relative: false,
32
  offsetParent: null,
33
  cancelDefault: true,
34
+
35
+ // type to event mapping
36
  events: {
37
  def: "mouseenter,mouseleave",
38
  input: "focus,blur",
39
  widget: "focus mouseenter,blur mouseleave",
40
  tooltip: "mouseenter,mouseleave"
41
  },
42
+
43
  // 1.2
44
+ layout: '<div></div>',
45
  tipClass: 'tooltip'
46
  },
47
+
48
  addEffect: function(name, loadFn, hideFn) {
49
+ effects[name] = [loadFn, hideFn];
50
+ }
51
  };
52
+
53
+
54
+ var effects = {
55
+ toggle: [
56
+ function(done) {
57
  var conf = this.getConf(), tip = this.getTip(), o = conf.opacity;
58
  if (o < 1) { tip.css({opacity: o}); }
59
  tip.show();
60
  done.call();
61
  },
62
+
63
+ function(done) {
64
  this.getTip().hide();
65
  done.call();
66
+ }
67
  ],
68
+
69
  fade: [
70
+ function(done) {
71
  var conf = this.getConf();
72
+ this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done);
73
+ },
74
+ function(done) {
75
+ this.getTip().fadeOut(this.getConf().fadeOutSpeed, done);
76
+ }
77
+ ]
78
+ };
79
+
80
+
81
+ /* calculate tip position relative to the trigger */
82
+ function getPosition(trigger, tip, conf) {
83
+
84
+
85
+ // get origin top/left position
86
+ var top = conf.relative ? trigger.position().top : trigger.offset().top,
87
  left = conf.relative ? trigger.position().left : trigger.offset().left,
88
  pos = conf.position[0];
89
 
90
  top -= tip.outerHeight() - conf.offset[0];
91
  left += trigger.outerWidth() + conf.offset[1];
92
+
93
  // iPad position fix
94
  if (/iPad/i.test(navigator.userAgent)) {
95
  top -= $(window).scrollTop();
96
  }
97
+
98
+ // adjust Y
99
  var height = tip.outerHeight() + trigger.outerHeight();
100
  if (pos == 'center') { top += height / 2; }
101
  if (pos == 'bottom') { top += height; }
102
+
103
+
104
  // adjust X
105
+ pos = conf.position[1];
106
  var width = tip.outerWidth() + trigger.outerWidth();
107
  if (pos == 'center') { left -= width / 2; }
108
+ if (pos == 'left') { left -= width; }
109
+
110
  return {top: top, left: left};
111
+ }
112
+
113
+
114
 
 
 
115
  function Tooltip(trigger, conf) {
116
 
117
+ var self = this,
118
  fire = trigger.add(self),
119
  tip,
120
  timer = 0,
121
+ pretimer = 0,
122
  title = trigger.attr("title"),
123
  tipAttr = trigger.attr("data-tooltip"),
124
  effect = effects[conf.effect],
125
  shown,
126
+
127
  // get show/hide configuration
128
+ isInput = trigger.is(":input"),
129
+ isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"),
130
  type = trigger.attr("type"),
131
+ evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def'];
132
+
133
+
134
  // check that configuration is sane
135
+ if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; }
136
+
137
+ evt = evt.split(/,\s*/);
138
+ if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; }
139
+
140
+
141
+ // trigger --> show
142
  trigger.bind(evt[0], function(e) {
143
 
144
  clearTimeout(timer);
145
  if (conf.predelay) {
146
+ pretimer = setTimeout(function() { self.show(e); }, conf.predelay);
147
+
148
  } else {
149
+ self.show(e);
150
  }
151
+
152
  // trigger --> hide
153
  }).bind(evt[1], function(e) {
154
  clearTimeout(pretimer);
155
  if (conf.delay) {
156
+ timer = setTimeout(function() { self.hide(e); }, conf.delay);
157
+
158
  } else {
159
+ self.hide(e);
160
  }
161
+
162
+ });
163
+
164
+
165
  // remove default title
166
+ if (title && conf.cancelDefault) {
167
  trigger.removeAttr("title");
168
+ trigger.data("title", title);
169
+ }
170
+
171
  $.extend(self, {
172
+
173
+ show: function(e) {
174
 
175
  // tip not initialized yet
176
  if (!tip) {
177
+
178
+ // data-tooltip
179
  if (tipAttr) {
180
  tip = $(tipAttr);
181
 
182
  // single tip element for all
183
+ } else if (conf.tip) {
184
  tip = $(conf.tip).eq(0);
185
+
186
  // autogenerated tooltip
187
+ } else if (title) {
188
  tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body)
189
  .hide().append(title);
190
 
191
  // manual tooltip
192
+ } else {
193
+ tip = trigger.next();
194
  if (!tip.length) { tip = trigger.parent().next(); }
195
+
196
  }
197
+
198
  if (!tip.length) { throw "Cannot find tooltip for " + trigger; }
199
+
200
  if (conf.offsetParent) {
201
  tip.appendTo($(conf.offsetParent));
202
  }
203
+ }
204
+
205
+ if (self.isShown()) { return self; }
206
+
207
  // stop previous animation
208
+ tip.stop(true, true);
209
+
210
  // get position
211
+ var pos = getPosition(trigger, tip, conf);
212
+
213
  // restore title for single tooltip element
214
  if (conf.tip) {
215
  tip.html(trigger.data("title"));
218
  // onBeforeShow
219
  e = e || $.Event();
220
  e.type = "onBeforeShow";
221
+ fire.trigger(e, [pos]);
222
  if (e.isDefaultPrevented()) { return self; }
223
+
224
+
225
  // onBeforeShow may have altered the configuration
226
  pos = getPosition(trigger, tip, conf);
227
+
228
  // set position
229
+ tip.css({position:'absolute', top: pos.top, left: pos.left});
230
+
231
  shown = true;
232
+
233
+ // invoke effect
234
  effect[0].call(self, function() {
235
  e.type = "onShow";
236
  shown = 'full';
237
+ fire.trigger(e);
238
+ });
239
+
240
 
241
+ // tooltip events
 
242
  var event = conf.events.tooltip.split(/,\s*/);
243
 
244
  if (!tip.data("__set")) {
245
+
246
+ tip.bind(event[0], function() {
247
  clearTimeout(timer);
248
  clearTimeout(pretimer);
249
  });
250
+
251
+ if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) {
252
  tip.bind(event[1], function(e) {
253
+
254
  // being moved to the trigger element
255
  if (e.relatedTarget != trigger[0]) {
256
  trigger.trigger(evt[1].split(" ")[0]);
257
  }
258
+ });
259
+ }
260
+
261
  tip.data("__set", true);
262
  }
263
+
264
  return self;
265
  },
266
+
267
  hide: function(e) {
268
 
269
  if (!tip || !self.isShown()) { return self; }
270
+
271
  // onBeforeHide
272
  e = e || $.Event();
273
  e.type = "onBeforeHide";
274
+ fire.trigger(e);
275
  if (e.isDefaultPrevented()) { return; }
276
+
277
  shown = false;
278
+
279
  effects[conf.effect][1].call(self, function() {
280
  e.type = "onHide";
281
+ fire.trigger(e);
282
  });
283
+
284
  return self;
285
  },
286
+
287
  isShown: function(fully) {
288
+ return fully ? shown == 'full' : shown;
289
  },
290
+
291
  getConf: function() {
292
+ return conf;
293
  },
294
+
295
  getTip: function() {
296
+ return tip;
297
  },
298
+
299
  getTrigger: function() {
300
+ return trigger;
301
+ }
302
 
303
+ });
304
 
305
+ // callbacks
306
  $.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) {
307
+
308
  // configuration
309
+ if ($.isFunction(conf[name])) {
310
+ $(self).bind(name, conf[name]);
311
  }
312
 
313
  // API
316
  return self;
317
  };
318
  });
319
+
320
  }
321
+
322
+
323
  // jQuery plugin implementation
324
  $.fn.tooltip = function(conf) {
325
+
326
  // return existing instance
327
  var api = this.data("tooltip");
328
  if (api) { return api; }
329
 
330
  conf = $.extend(true, {}, $.tools.tooltip.conf, conf);
331
+
332
  // position can also be given as string
333
  if (typeof conf.position == 'string') {
334
+ conf.position = conf.position.split(/,?\s/);
335
  }
336
+
337
  // install tooltip for each entry in jQuery object
338
  this.each(function() {
339
+ api = new Tooltip($(this), conf);
340
+ $(this).data("tooltip", api);
341
  });
342
+
343
+ return conf.api ? api: this;
344
  };
345
+
346
  }) (jQuery);
347
 
348
+
349
 
skin/adminhtml/default/default/aoe_scheduler/StyleSheet/bars.css CHANGED
@@ -13,7 +13,19 @@
13
  .bar-lightgray,
14
  .bar-lightgray span,
15
  .bar-gray,
16
- .bar-gray span { display:block; height:16px; background-image:url(../Images/bg_notifications.gif); background-repeat:no-repeat; font:bold 10px/16px Arial, Helvetica, sans-serif; text-transform:uppercase; text-align:center; padding:0 0 0 7px; margin:1px 0; white-space:nowrap; color:#fff; }
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  .bar-red { background-position:0 0; }
19
  .bar-red span { background-position:100% 0; padding:0 7px 0 0; }
13
  .bar-lightgray,
14
  .bar-lightgray span,
15
  .bar-gray,
16
+ .bar-gray span {
17
+ display:block;
18
+ height:16px;
19
+ background-image:url(../Images/bg_notifications.gif);
20
+ background-repeat:no-repeat;
21
+ font:bold 10px/16px Arial, Helvetica, sans-serif;
22
+ text-transform:uppercase;
23
+ text-align:center;
24
+ padding:0 0 0 7px;
25
+ margin:1px 0;
26
+ white-space:nowrap;
27
+ color:#fff;
28
+ }
29
 
30
  .bar-red { background-position:0 0; }
31
  .bar-red span { background-position:100% 0; padding:0 7px 0 0; }
skin/adminhtml/default/default/aoe_scheduler/StyleSheet/instructions.css ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .configuration label {
2
+ padding-left: 5px;
3
+ }
4
+
5
+ h5 {
6
+ margin-top: 20px;
7
+ }
8
+
9
+ pre {
10
+ padding: 5px 10px;
11
+ background-color: black;
12
+ color: #eee;
13
+ font-size: 15px;
14
+ overflow: auto;
15
+ word-wrap: normal;
16
+ white-space: pre;
17
+ }
skin/adminhtml/default/default/aoe_scheduler/StyleSheet/timeline.css CHANGED
@@ -1,134 +1,158 @@
1
  #contentwrapper {
2
- float: left;
3
- width: 100%;
4
  }
5
 
6
  #contentcolumn {
7
- margin-left: 300px; /* Set left margin to LeftColumnWidth */
8
  }
9
 
10
  #leftcolumn {
11
- float: left;
12
- width: 300px; /* Width of left column */
13
- margin-left: -100%;
14
  }
15
 
16
  .configuration {
17
- padding: 5px;
18
  }
19
 
20
  #leftcolumn .row {
21
- background-color: #F6F6F6;
22
- border-right: 1px solid #DADFE0;
23
  }
24
 
25
  #leftcolumn .row.hours {
26
- background-color: transparent;
27
- text-align: right;
28
- padding-right: 5px;
29
- font-size: 10px;
30
  }
31
 
32
  #now {
33
- position: absolute;
34
- width: 1px;
35
- height: 100%;
36
- background-color: red;
 
 
 
 
 
 
 
 
 
37
  }
38
 
39
  div.row {
40
- border-bottom: 1px solid #DADFE0;
41
- padding: 0;
42
- margin: 0;
43
- height: 40px;
44
  }
45
 
46
  div.timeline-box {
47
- overflow: auto;
48
  }
49
 
50
  div.timeline-panel {
51
- background-image: url(../Images/hour.gif);
52
- border-right: 1px solid black;
53
- position: relative;
54
  }
55
 
56
  div.timeline {
57
- position: relative;
58
  }
59
 
60
  div.hours {
61
- height: 20px;
62
  }
63
 
64
  .detailwrap {
65
- width: 100%;
66
- height: 100%;
67
  }
68
 
69
  .details {
70
- display: none;
71
- background-color: white;
72
- border: 1px solid black;
73
- z-index: 1000;
74
- max-width: 400px;
75
  }
76
 
77
  .details pre {
78
- background-color: white;
79
- overflow: auto;
80
- max-width: 385px;
81
- font-size: 8pt;
82
- line-height: 9pt;
83
  }
84
 
85
  .hour {
86
- width: 240px;
87
- float: left;
88
- text-align: center;
 
 
 
 
89
  }
90
 
91
  .details-headline {
92
- width: 100%;
93
- height: 28px;
 
94
  }
95
 
96
  .details-headline h3 {
97
- padding: 5px;
98
- color: white;
99
- margin: 0;
100
  }
101
 
102
  .details-content {
103
- padding: 5px;
104
  }
105
 
106
  .details-content td.label {
107
- font-weight: bold;
108
  }
109
 
110
  .caption-container {
111
- width: 500px;
112
- overflow: hidden;
113
  }
114
 
115
- .task {
116
- position: absolute;
117
- height: 24px;
118
- top: 8px;
119
- cursor: default;
120
  }
121
 
122
  .status {
123
- width: 100px;
124
  }
125
 
126
  .details-headline, .task { background-image: url(../Images/gradient.png); }
127
 
 
 
 
 
 
128
  .details-headline.success, .task.success { background-color: #36B963; }
 
129
  .details-headline.pending, .task.pending { background-color: #A9ABA8; }
130
- .details-headline.error, .task.error { background-color: #E41300; }
131
  .details-headline.missed, .task.missed { background-color: #F75300; }
132
- .details-headline.running, .task.running { background-color: #FE9D00; }
 
 
 
 
 
133
 
134
- div.task.active, div.task:hover { background-color: #00A2FA; }
1
  #contentwrapper {
2
+ float: left;
3
+ width: 100%;
4
  }
5
 
6
  #contentcolumn {
7
+ margin-left: 300px; /* Set left margin to LeftColumnWidth */
8
  }
9
 
10
  #leftcolumn {
11
+ float: left;
12
+ width: 300px; /* Width of left column */
13
+ margin-left: -100%;
14
  }
15
 
16
  .configuration {
17
+ padding: 5px;
18
  }
19
 
20
  #leftcolumn .row {
21
+ background-color: #F6F6F6;
22
+ border-right: 1px solid #DADFE0;
23
  }
24
 
25
  #leftcolumn .row.hours {
26
+ background-color: transparent;
27
+ text-align: right;
28
+ padding-right: 5px;
29
+ font-size: 10px;
30
  }
31
 
32
  #now {
33
+ position: absolute;
34
+ width: 1px;
35
+ height: 100%;
36
+ background-color: red;
37
+ }
38
+
39
+ #now .arrow {
40
+ background-image: url(../Images/red.png);
41
+ width: 16px;
42
+ height: 16px;
43
+ position: absolute;
44
+ top: -3px;
45
+ left: -7.5px;
46
  }
47
 
48
  div.row {
49
+ border-bottom: 1px solid #DADFE0;
50
+ padding: 0;
51
+ margin: 0;
52
+ height: 40px;
53
  }
54
 
55
  div.timeline-box {
56
+ overflow: auto;
57
  }
58
 
59
  div.timeline-panel {
60
+ background-image: url(../Images/hour.gif);
61
+ border-right: 1px solid black;
62
+ position: relative;
63
  }
64
 
65
  div.timeline {
66
+ position: relative;
67
  }
68
 
69
  div.hours {
70
+ height: 20px;
71
  }
72
 
73
  .detailwrap {
74
+ width: 100%;
75
+ height: 100%;
76
  }
77
 
78
  .details {
79
+ display: none;
80
+ background-color: white;
81
+ border: 1px solid black;
82
+ z-index: 1000;
83
+ max-width: 400px;
84
  }
85
 
86
  .details pre {
87
+ background-color: white;
88
+ overflow: auto;
89
+ max-width: 385px;
90
+ font-size: 8pt;
91
+ line-height: 9pt;
92
  }
93
 
94
  .hour {
95
+ width: 240px;
96
+ float: left;
97
+ }
98
+
99
+ .hour span {
100
+ margin-left: 5px;
101
+ font-weight: bold;
102
  }
103
 
104
  .details-headline {
105
+ width: 100%;
106
+ height: 24px;
107
+ background-position: top center;
108
  }
109
 
110
  .details-headline h3 {
111
+ padding: 2px 5px;
112
+ color: white;
113
+ margin: 0;
114
  }
115
 
116
  .details-content {
117
+ padding: 5px;
118
  }
119
 
120
  .details-content td.label {
121
+ font-weight: bold;
122
  }
123
 
124
  .caption-container {
125
+ width: 500px;
126
+ overflow: hidden;
127
  }
128
 
129
+ .task, .estimation {
130
+ position: absolute;
131
+ height: 24px;
132
+ top: 8px;
133
+ cursor: default;
134
  }
135
 
136
  .status {
137
+ width: 100px;
138
  }
139
 
140
  .details-headline, .task { background-image: url(../Images/gradient.png); }
141
 
142
+ .details-headline.error, .task.error,
143
+ .details-headline.killed, .task.killed,
144
+ .details-headline.died, .task.died,
145
+ .details-headline.gone, .task.gone { background-color: #E41300; }
146
+
147
  .details-headline.success, .task.success { background-color: #36B963; }
148
+ .details-headline.nothing, .task.nothing { background-color: #92D6A9; }
149
  .details-headline.pending, .task.pending { background-color: #A9ABA8; }
 
150
  .details-headline.missed, .task.missed { background-color: #F75300; }
151
+ .details-headline.running, .task.running { background-color: #FE9D00 !important; }
152
+ .details-headline.running, .task.running { background-image: url(../Images/animation.gif); background-position: center; background-repeat: repeat-x; }
153
+
154
+ .estimation { background-color: #B1C5D1; }
155
+ .estimation { background-image: url(../Images/animation2.gif); background-position: center; background-repeat: repeat-x; }
156
+
157
 
158
+ div.task.active, div.task:hover { background-color: #00A2FA; }