1 <?php
defined('SYSPATH') OR die('No direct access allowed.');
4 * Controller for scheduling reports
6 class Schedule_Controller
extends Authenticated_Controller
8 public function __construct() {
10 $this->template
->disable_refresh
= true;
13 * List all scheduled reports
15 public function show()
17 $this->template
->js_header
= $this->add_view('js_header');
18 $this->xtra_js
[] = 'application/media/js/jquery.datePicker.js';
19 $this->xtra_js
[] = 'application/media/js/jquery.timePicker.js';
20 $this->xtra_js
[] = $this->add_path('schedule/js/schedule.js');
21 $this->xtra_js
[] = $this->add_path('reports/js/common.js');
22 $this->template
->js_header
->js
= $this->xtra_js
;
24 $this->template
->content
= $this->add_view('schedule/schedules');
25 $available_schedules = $this->template
->content
;
27 $new_schedule = $this->add_view('schedule/new_schedule');
28 $new_schedule->available_schedule_periods
= Scheduled_reports_Model
::get_available_report_periods();
30 # we currently only have avail and SLA reports so hard-coding
31 # this somewhat here shouldn't be a big issue.
32 # Extend switch below if we add more schedulable reports.
33 $defined_report_types_res = Scheduled_reports_Model
::get_all_report_types();
34 $defined_report_types = false;
35 $report_types = false;
36 if ($defined_report_types_res !== false) {
37 foreach ($defined_report_types_res as $rpt_type) {
38 $report_types[$rpt_type->id
] = $rpt_type->identifier
; # needed for javascript json
39 switch ($rpt_type->identifier
) {
41 $defined_report_types[$rpt_type->identifier
] = _('Availability report');
44 $defined_report_types[$rpt_type->identifier
] = _('SLA report');
47 $defined_report_types[$rpt_type->identifier
] = _('Alert summary report');
53 $new_schedule->defined_report_types
= $defined_report_types;
54 $avail_reports = Saved_reports_Model
::get_saved_reports('avail');
55 $sla_reports = Saved_reports_Model
::get_saved_reports('sla');
56 $summary_reports = Saved_reports_Model
::get_saved_reports('summary');
59 # fetch ALL schedules (avail + SLA + Alert Summary)
60 $avail_schedules = Scheduled_reports_Model
::get_scheduled_reports('avail')->result_array(false);
61 $new_schedule->saved_reports
= $avail_reports;
63 # add new schedule template to available_schedules template
64 $available_schedules->new_schedule
= $new_schedule;
66 # we need some data available as json for javascript
67 $avail_reports_arr = false;
68 foreach ($avail_reports as $rep) {
69 $avail_reports_arr[$rep->id
] = $rep->report_name
;
72 $summary_reports_arr = false;
73 foreach ($summary_reports as $rep) {
74 $summary_reports_arr[$rep->id
] = $rep->report_name
;
77 $sla_reports_arr = false;
78 foreach ($sla_reports as $rep) {
79 $sla_reports_arr[$rep->id
] = $rep->report_name
;
82 $scheduled_label = _('Scheduled');
83 $this->js_strings
.= "var _report_types_json = ".json_encode($report_types).";\n";
84 $this->js_strings
.= "var _saved_avail_reports = ".json_encode($avail_reports_arr).";\n";
85 $this->js_strings
.= "var _saved_sla_reports = ".json_encode($sla_reports_arr).";\n";
86 $this->js_strings
.= "var _saved_summary_reports = ".json_encode($summary_reports_arr).";\n";
87 $this->js_strings
.= "var _scheduled_reports = ".json_encode(array('avail' => $avail_schedules, 'sla' => Scheduled_reports_Model
::get_scheduled_reports('sla')->result_array(false), 'summary' => Scheduled_reports_Model
::get_scheduled_reports('summary')->result_array(false))).";\n";
88 $this->js_strings
.= "var _reports_schedule_send_error = '"._('An error occurred when trying to send the scheduled report')."';\n";
89 $this->js_strings
.= "var _reports_schedule_update_ok = '"._('Your schedule has been successfully updated')."';\n";
90 $this->js_strings
.= "var _reports_schedule_send_ok = '"._('Your report was successfully sent')."';\n";
91 $this->js_strings
.= "var _reports_schedule_create_ok = '"._('Your schedule has been successfully created')."';\n";
92 $this->js_strings
.= "var _reports_fatal_err_str = '"._('It is not possible to schedule this report since some vital information is missing.')."';\n";
93 $this->js_strings
.= "var _reports_schedule_interval_error = '"._(' -Please select a schedule interval')."';\n";
94 $this->js_strings
.= "var _reports_schedule_recipient_error = '"._(' -Please enter at least one recipient')."';\n";
95 $this->js_strings
.= "var _reports_errors_found = '"._('Found the following error(s)')."';\n";
96 $this->js_strings
.= "var _reports_please_correct = '"._('Please correct this and try again')."';\n";
97 $this->js_strings
.= "var _reports_confirm_delete_schedule = \""._("Do you really want to delete this schedule?\\nThis action can't be undone.")."\";\n";
98 $this->js_strings
.= "var _reports_edit_information = '"._('Double click to edit')."';\n";
99 $this->js_strings
.= "var _scheduled_label = '".$scheduled_label."';\n";
101 $this->js_strings
.= reports
::js_strings();
103 $this->template
->js_strings
= $this->js_strings
;
105 $this->template
->title
= _('Reporting » Schedule');
109 * Kills request with headers and content à la json
111 * @param $type string
113 public function list_by_type($type)
115 $this->auto_render
= false;
116 $scheduled_reports = Saved_reports_Model
::get_saved_reports($type);
117 if(!$scheduled_reports) {
118 return json
::fail(_("No reports found for that type"));
120 return json
::ok($scheduled_reports);
126 public function schedule()
128 $this->auto_render
=false;
129 // collect input values
130 $report_id = arr
::search($_REQUEST, 'report_id'); // scheduled ID
131 $rep_type = arr
::search($_REQUEST, 'type');
132 $saved_report_id = arr
::search($_REQUEST, 'saved_report_id'); // ID for report module
133 $period = arr
::search($_REQUEST, 'period');
134 $recipients = arr
::search($_REQUEST, 'recipients');
135 $filename = arr
::search($_REQUEST, 'filename');
136 $description = arr
::search($_REQUEST, 'description');
137 $local_persistent_filepath = arr
::search($_REQUEST, 'local_persistent_filepath');
138 $attach_description = arr
::search($_REQUEST, 'attach_description');
139 $module_save = arr
::search($_REQUEST, 'module_save');
142 # if this parameter is set to false, we have to lookup
143 # $rep_type since it is passed as a string (avail/sla)
144 $rep_type = Scheduled_reports_Model
::get_report_type_id($rep_type);
146 $recipients = str_replace(';', ',', $recipients);
147 $rec_arr = explode(',', $recipients);
148 $a_recipients = false;
149 if (!empty($rec_arr)) {
150 foreach ($rec_arr as $recipient) {
151 if (trim($recipient)!='') {
152 $a_recipients[] = trim($recipient);
155 if (!empty($a_recipients)) {
156 $recipients = implode(',', $a_recipients);
157 $recipients = $this->_convert_special_chars($recipients);
161 $filename = $this->_convert_special_chars($filename);
162 $filename = $this->_check_filename($filename);
164 $ok = Scheduled_reports_Model
::edit_report($report_id, $rep_type, $saved_report_id, $period, $recipients, $filename, $description, $local_persistent_filepath, $attach_description);
167 return json
::fail(sprintf(_("An error occurred when saving scheduled report (%s)"), $ok));
169 return json
::ok(array('id' => $ok));
173 * Used in (at least) both CLI and XHR environments. That means you should be
174 * paranoid about where to output.
176 * @param $schedule_id int
178 public function send_now($schedule_id) {
179 $this->auto_render
= false;
180 $type = Scheduled_reports_Model
::get_typeof_report($schedule_id);
181 $opt_obj = Scheduled_reports_Model
::get_scheduled_data($schedule_id);
182 $report = Report_options
::setup_options_obj($type, $opt_obj);
183 $extension = substr($opt_obj['filename'], count($opt_obj['filename'])-5);
184 if ($extension == '.pdf')
185 $report['output_format'] = 'pdf';
186 else if ($extension == '.csv')
187 $report['output_format'] = 'csv';
189 0 => array('pipe', 'r'),
190 1 => array('pipe', 'w'),
191 2 => array('pipe', 'w'));
193 $cmd = 'php '.DOCROOT
.KOHANA
.' '.escapeshellarg($type.'/generate?schedule_id='.$schedule_id.'&output_format='.$report['output_format'].'&report_id='.$opt_obj['report_id']);
194 $process = proc_open($cmd, $pipe_desc, $pipes, DOCROOT
);
195 $this->log
->log('debug', $cmd);
196 if (is_resource($process)) {
197 fwrite($pipes[0], "\n");
199 $out = stream_get_contents($pipes[1]);
200 $err = stream_get_contents($pipes[2]);
202 $this->log
->log('error', $err);
206 $code = proc_close($process);
209 $this->log
->log('error', "Couldn't successfully execute this command:");
210 $this->log
->log('error', $cmd);
215 $months = date
::abbr_month_names();
216 $month = $months[date('m')-1]; // January is [0]
217 $filename = pathinfo($opt_obj['filename'], PATHINFO_FILENAME
)."_".date("Y_").$month.date("_d").'.'.pathinfo($opt_obj['filename'], PATHINFO_EXTENSION
);
219 if (request
::is_ajax()) {
220 return json
::fail(sprintf(_("Failed to run %s: %s"), $cmd, $out));
223 $this->log
->log('error', "Couldn't generate report for schedule {$schedule_id}: $out");
227 if ($opt_obj['local_persistent_filepath']) {
228 persist_pdf
::save($out, $opt_obj['local_persistent_filepath'].'/'.basename($opt_obj['filename']));
231 if ($opt_obj['recipients']) {
232 Send_report_Model
::send($out, $filename, $report['output_format'], $opt_obj['recipients']);
233 if(PHP_SAPI
== 'cli') {
234 echo "Mailing schedule id $schedule_id to ".$opt_obj['recipients']."\n";
238 if (request
::is_ajax()) {
240 $msg = _('Report was saved and emailed');
242 $msg = _('Report was saved');
244 $msg = _('Report was emailed');
246 $msg = _('Nothing to do');
247 return json
::ok($msg);
254 * Receive call from cron to check for scheduled reports
256 * @param $period_str string
258 public function cron($period_str)
260 if (PHP_SAPI
!== "cli") {
261 die("illegal call\n");
263 $this->auto_render
=false;
265 $schedules = Scheduled_reports_Model
::get_period_schedules($period_str);
268 echo "No scheduled reports found, not sending any emails.\n";
271 foreach ($schedules as $row) {
272 $this->send_now($row->id
);
277 * Save single item (key, value) from .editable
278 * fields regarding scheduled reports.
280 * (that is, edit schedule)
282 public function save_schedule_item()
284 $this->auto_render
= false;
287 $new_value = arr
::search($_REQUEST, 'newvalue');
288 $tmp_parts = arr
::search($_REQUEST, 'elementid');
291 # @@@FIXME: inform user via jGrowl and echo old value somehow?
292 echo _("Required data is missing, unable to save changes");
296 $parts = $this->_get_element_parts($tmp_parts);
297 if (!empty($parts)) {
299 $report_id = (int)$parts[1];
302 // check some fields a little extra
304 case 'local_persistent_filepath':
305 $new_value = trim($new_value);
306 if(!empty($new_value) && !is_writable(rtrim($new_value, '/').'/')) {
307 echo _("Can't write to '$new_value'. Provide another path.")."<br />";
311 case 'recipients': // convert ';' to ','
312 $new_value = str_replace(';', ',', $new_value);
313 $rec_arr = explode(',', $new_value);
315 if (!empty($rec_arr)) {
316 foreach ($rec_arr as $recipient) {
317 if (trim($recipient)!='') {
318 $recipients[] = trim($recipient);
321 if (!empty($recipients)) {
322 $new_value = implode(',', $recipients);
323 $new_value = $this->_convert_special_chars($new_value);
326 // check for required email field, rather lame check
327 // but it's better than nothing...
328 $recipient = explode(",", $new_value);
329 if (is_array($recipient) && !empty($recipient)) {
330 foreach ($recipient as $recip) {
331 if (strlen($recip) < 6 ||
!preg_match("/.+@.+/", $recip)) {
332 echo '<a title="'._('Fetch saved value').'" href="#" onclick="fetch_field_value(\''.$field.'\', '.$report_id.', \''.$_REQUEST['elementid'].'\');">';
333 echo sprintf(_("'%s' is not a valid email address.%sClick here to restore saved value."), $recip, '<br />')."\n</a>";
339 case 'filename': // remove spaces
340 $new_value = $this->_convert_special_chars($new_value);
341 if (strlen($new_value)>255) {
342 echo sprintf(_('The entered value is too long. Only 255 chars allowed for filename.%sValue %s not %s modified!'), '<br />', '<strong>', '</strong>').'<br />' .
343 _('Please').' <a title="'._('Fetch saved value').'" href="#" onclick="fetch_field_value(\''.$field.'\', '.$report_id.', \''.$_REQUEST['elementid'].'\');">'._('click here').'</a> '._('to view saved value').'.';
346 $new_value = $this->_check_filename($new_value);
348 case 'attach_description':
349 if(!is_numeric($new_value) ||
($new_value != 1 && $new_value != 0)) {
350 echo _("When attaching description, the value must be 0 or 1");
356 $ok = Scheduled_reports_Model
::update_report_field($report_id, $field, $new_value);
359 echo _('An error occurred')."<br />";
363 # decide how to interpret field and value, since we
364 # should print the correct value back.
365 # If the value is an integer it should indicate that
366 # we need to make a lookup in database to fetch correct value
367 # Let's say we have 'periodname' as field, then value is an
368 # integer and the return value should be Weekly/Monthly
369 # if we get a string we should return that string
370 # The problem is that all values will be passed as strings
372 # Possible input values:
375 # * recipients no changes needed
376 # * filename no changes needed
377 # * description/info no changes needed
382 $report_type = Scheduled_reports_Model
::get_typeof_report($report_id);
384 echo _("Unable to determine type for selected report");
387 $saved_reports = Saved_reports_Model
::get_saved_reports($report_type);
388 if (!count($saved_reports)) {
389 echo _("Unable to fetch list of saved reports");
392 foreach ($saved_reports as $report) {
393 if ($report->id
== $new_value) {
394 echo $report->report_name
;
399 $periods = Scheduled_reports_Model
::get_available_report_periods();
400 echo is_array($periods) && array_key_exists($new_value, $periods)
401 ?
$periods[$new_value]
405 $new_value = str_replace(',', ', ', $new_value);
408 case 'attach_description':
409 echo $new_value ?
'Yes' : 'No';
417 * Delete a schedule through ajax call
419 public function delete_schedule()
421 $this->auto_render
= false;
422 $id = $this->input
->post('id');
423 if (Scheduled_reports_Model
::delete_scheduled_report($id)) {
424 return json
::ok(_("Schedule deleted"));
426 return json
::fail(_('An error occurred - unable to delete selected schedule'));
431 * Fetch specific field value for a scheduled report
433 public function fetch_field_value()
435 $this->auto_render
=false;
436 $id = arr
::search($_REQUEST, 'id');
437 $type = arr
::search($_REQUEST, 'type');
438 if (empty($id) ||
empty($type))
440 $data = Scheduled_reports_Model
::fetch_scheduled_field_value($type, $id);
448 private function _get_element_parts($str=false)
450 if (empty($str)) return false;
451 if (!strstr($str, '-')) return false;
452 // check for report_name since it has '.' as element id
453 if (strstr($str, '.')) {
454 $dotparts = explode('.', $str);
455 if (is_array($dotparts)) {
457 for ($i=1;$i<sizeof($dotparts);$i++
) {
458 $str .= $dotparts[$i];
462 $parts = explode('-', $str);
463 if (is_array($parts)) {
469 private function _convert_special_chars($str=false) {
471 if (empty($str)) return false;
474 $str = str_replace(' ', '_', $str);
475 $str = str_replace('"', '', $str);
476 $str = str_replace('/', '_', $str);
477 $return_str = iconv('utf-8', 'us-ascii//TRANSLIT', $str);
478 // If your system is buggy, you'll just get to keep your utf-8
479 // Don't want it? Don't put it there!
480 if ($return_str === false)
485 private function _check_filename($str=false)
488 $str = str_replace(',', '_', $str);
489 if (empty($str)) return false;
490 $extensions = array('pdf', 'csv');
491 if(in_array(pathinfo($str, PATHINFO_EXTENSION
), $extensions)) {