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
[] = 'application/media/js/jquery.datePicker.js';
18 $this->template
->js
[] = 'application/media/js/jquery.timePicker.js';
19 $this->template
->js
[] = $this->add_path('schedule/js/schedule.js');
20 $this->template
->js
[] = $this->add_path('reports/js/common.js');
22 $this->template
->content
= $this->add_view('schedule/schedules');
23 $available_schedules = $this->template
->content
;
25 $new_schedule = $this->add_view('schedule/new_schedule');
26 $new_schedule->available_schedule_periods
= Scheduled_reports_Model
::get_available_report_periods();
28 # we currently only have avail and SLA reports so hard-coding
29 # this somewhat here shouldn't be a big issue.
30 # Extend switch below if we add more schedulable reports.
31 $defined_report_types_res = Scheduled_reports_Model
::get_all_report_types();
32 $defined_report_types = false;
33 $report_types = false;
34 if ($defined_report_types_res !== false) {
35 foreach ($defined_report_types_res as $rpt_type) {
36 $report_types[$rpt_type->id
] = $rpt_type->identifier
; # needed for javascript json
37 switch ($rpt_type->identifier
) {
39 $defined_report_types[$rpt_type->identifier
] = _('Availability reports');
42 $defined_report_types[$rpt_type->identifier
] = _('SLA reports');
45 $defined_report_types[$rpt_type->identifier
] = $rpt_type->name
;
51 $new_schedule->defined_report_types
= $defined_report_types;
53 # add new schedule template to available_schedules template
54 $available_schedules->new_schedule
= $new_schedule;
55 $available_schedules->defined_report_types
= $defined_report_types;
57 $scheduled_reports = array();
58 foreach ($defined_report_types as $type => $_) {
59 $scheduled_reports[$type] = Scheduled_reports_Model
::get_scheduled_reports($type)->result_array(false);
62 $scheduled_label = _('Scheduled');
63 $this->js_strings
.= "var _report_types_json = ".json_encode($report_types).";\n";
64 $this->js_strings
.= "var _scheduled_reports = ".json_encode($scheduled_reports).";\n";
65 $this->js_strings
.= "var _reports_schedule_send_error = '"._('An error occurred when trying to send the scheduled report')."';\n";
66 $this->js_strings
.= "var _reports_schedule_update_ok = '"._('Your schedule has been successfully updated')."';\n";
67 $this->js_strings
.= "var _reports_schedule_send_ok = '"._('Your report was successfully sent')."';\n";
68 $this->js_strings
.= "var _reports_schedule_create_ok = '"._('Your schedule has been successfully created')."';\n";
69 $this->js_strings
.= "var _reports_fatal_err_str = '"._('It is not possible to schedule this report since some vital information is missing.')."';\n";
70 $this->js_strings
.= "var _reports_schedule_interval_error = '"._(' -Please select a schedule interval')."';\n";
71 $this->js_strings
.= "var _reports_schedule_recipient_error = '"._(' -Please enter at least one recipient')."';\n";
72 $this->js_strings
.= "var _reports_errors_found = '"._('Found the following error(s)')."';\n";
73 $this->js_strings
.= "var _reports_please_correct = '"._('Please correct this and try again')."';\n";
74 $this->js_strings
.= "var _reports_confirm_delete_schedule = \""._("Do you really want to delete this schedule?\\nThis action can't be undone.")."\";\n";
75 $this->js_strings
.= "var _reports_edit_information = '"._('Double click to edit')."';\n";
76 $this->js_strings
.= "var _scheduled_label = '".$scheduled_label."';\n";
78 $this->js_strings
.= reports
::js_strings();
80 $this->template
->js_strings
= $this->js_strings
;
82 $this->template
->title
= _('Reporting » Schedule');
86 * Kills request with headers and content à la json
90 public function list_by_type($type)
92 $this->auto_render
= false;
93 $rpt = Report_options
::setup_options_obj($type);
94 $scheduled_reports = $rpt->get_all_saved();
96 foreach ($scheduled_reports as $id => $name)
97 $result[] = array('id' => $id, 'report_name' => $name);
98 return json
::ok($result);
104 public function schedule()
106 $this->auto_render
=false;
107 // collect input values
108 $report_id = arr
::search($_REQUEST, 'report_id'); // scheduled ID
109 $rep_type = arr
::search($_REQUEST, 'type');
110 $saved_report_id = arr
::search($_REQUEST, 'saved_report_id'); // ID for report module
111 $period = arr
::search($_REQUEST, 'period');
112 $recipients = arr
::search($_REQUEST, 'recipients');
113 $filename = arr
::search($_REQUEST, 'filename');
114 $description = arr
::search($_REQUEST, 'description');
115 $local_persistent_filepath = arr
::search($_REQUEST, 'local_persistent_filepath');
116 $attach_description = arr
::search($_REQUEST, 'attach_description');
117 $module_save = arr
::search($_REQUEST, 'module_save');
120 # if this parameter is set to false, we have to lookup
121 # $rep_type since it is passed as a string (avail/sla)
122 $rep_type = Scheduled_reports_Model
::get_report_type_id($rep_type);
124 $recipients = str_replace(';', ',', $recipients);
125 $rec_arr = explode(',', $recipients);
126 $a_recipients = false;
127 if (!empty($rec_arr)) {
128 foreach ($rec_arr as $recipient) {
129 if (trim($recipient)!='') {
130 $a_recipients[] = trim($recipient);
133 if (!empty($a_recipients)) {
134 $recipients = implode(',', $a_recipients);
135 $recipients = $this->_convert_special_chars($recipients);
139 $filename = $this->_convert_special_chars($filename);
140 $filename = $this->_check_filename($filename);
142 $ok = Scheduled_reports_Model
::edit_report($report_id, $rep_type, $saved_report_id, $period, $recipients, $filename, $description, $local_persistent_filepath, $attach_description);
145 return json
::fail(sprintf(_("An error occurred when saving scheduled report (%s)"), $ok));
147 return json
::ok(array('id' => $ok));
151 * Used in (at least) both CLI and XHR environments. That means you should be
152 * paranoid about where to output.
154 * @param $schedule_id int
156 public function send_now($schedule_id) {
157 $this->auto_render
= false;
158 $type = Scheduled_reports_Model
::get_typeof_report($schedule_id);
159 $opt_obj = Scheduled_reports_Model
::get_scheduled_data($schedule_id);
160 $report = Report_options
::setup_options_obj($type, $opt_obj);
162 $msg = sprintf(_("Failed to load %s report from schedule %s"), $type, $schedule_id);
163 if (request
::is_ajax()) {
164 return json
::fail($msg);
167 $this->log
->log('error', $msg);
171 $extension = substr($opt_obj['filename'], count($opt_obj['filename'])-5);
172 if ($extension == '.pdf')
173 $report['output_format'] = 'pdf';
174 else if ($extension == '.csv')
175 $report['output_format'] = 'csv';
177 0 => array('pipe', 'r'),
178 1 => array('pipe', 'w'),
179 2 => array('pipe', 'w'));
181 $cmd = 'php '.DOCROOT
.KOHANA
.' '.escapeshellarg($type.'/generate?schedule_id='.$schedule_id.'&output_format='.$report['output_format'].'&report_id='.$opt_obj['report_id']);
182 $process = proc_open($cmd, $pipe_desc, $pipes, DOCROOT
);
183 $this->log
->log('debug', $cmd);
184 if (is_resource($process)) {
185 fwrite($pipes[0], "\n");
187 $out = stream_get_contents($pipes[1]);
188 $err = stream_get_contents($pipes[2]);
190 $this->log
->log('error', $err);
194 $code = proc_close($process);
197 $this->log
->log('error', "Couldn't successfully execute this command:");
198 $this->log
->log('error', $cmd);
203 $months = date
::abbr_month_names();
204 $month = $months[date('m')-1]; // January is [0]
205 $filename = pathinfo($opt_obj['filename'], PATHINFO_FILENAME
)."_".date("Y_").$month.date("_d").'.'.pathinfo($opt_obj['filename'], PATHINFO_EXTENSION
);
207 if (request
::is_ajax()) {
208 return json
::fail(sprintf(_("Failed to run %s: %s"), $cmd, $out));
211 $this->log
->log('error', "Couldn't generate report for schedule {$schedule_id}: $out");
215 if ($opt_obj['local_persistent_filepath']) {
216 persist_pdf
::save($out, $opt_obj['local_persistent_filepath'].'/'.basename($opt_obj['filename']));
219 if ($opt_obj['recipients']) {
220 Send_report_Model
::send($out, $filename, $report['output_format'], $opt_obj['recipients']);
221 if(PHP_SAPI
== 'cli') {
222 echo "Mailing schedule id $schedule_id to ".$opt_obj['recipients']."\n";
226 if (request
::is_ajax()) {
228 $msg = _('Report was saved and emailed');
230 $msg = _('Report was saved');
232 $msg = _('Report was emailed');
234 $msg = _('Nothing to do');
235 return json
::ok($msg);
242 * Receive call from cron to check for scheduled reports
244 * @param $period_str string
246 public function cron($period_str)
248 if (PHP_SAPI
!== "cli") {
249 die("illegal call\n");
251 $this->auto_render
=false;
253 $schedules = Scheduled_reports_Model
::get_period_schedules($period_str);
256 echo "No scheduled reports found, not sending any emails.\n";
259 foreach ($schedules as $row) {
260 $this->send_now($row->id
);
265 * Save single item (key, value) from .editable
266 * fields regarding scheduled reports.
268 * (that is, edit schedule)
270 public function save_schedule_item()
272 $this->auto_render
= false;
275 $new_value = arr
::search($_REQUEST, 'newvalue');
276 $tmp_parts = arr
::search($_REQUEST, 'elementid');
279 # @@@FIXME: inform user via jGrowl and echo old value somehow?
280 echo _("Required data is missing, unable to save changes");
284 $parts = $this->_get_element_parts($tmp_parts);
285 if (!empty($parts)) {
287 $report_id = (int)$parts[1];
290 // check some fields a little extra
292 case 'local_persistent_filepath':
293 $new_value = trim($new_value);
294 if(!empty($new_value) && !is_writable(rtrim($new_value, '/').'/')) {
295 echo _("Can't write to '$new_value'. Provide another path.")."<br />";
299 case 'recipients': // convert ';' to ','
300 $new_value = str_replace(';', ',', $new_value);
301 $rec_arr = explode(',', $new_value);
303 if (!empty($rec_arr)) {
304 foreach ($rec_arr as $recipient) {
305 if (trim($recipient)!='') {
306 $recipients[] = trim($recipient);
309 if (!empty($recipients)) {
310 $new_value = implode(',', $recipients);
311 $new_value = $this->_convert_special_chars($new_value);
314 // check for required email field, rather lame check
315 // but it's better than nothing...
316 $recipient = explode(",", $new_value);
317 if (is_array($recipient) && !empty($recipient)) {
318 foreach ($recipient as $recip) {
319 if (strlen($recip) < 6 ||
!preg_match("/.+@.+/", $recip)) {
320 echo '<a title="'._('Fetch saved value').'" href="#" onclick="fetch_field_value(\''.$field.'\', '.$report_id.', \''.$_REQUEST['elementid'].'\');">';
321 echo sprintf(_("'%s' is not a valid email address.%sClick here to restore saved value."), $recip, '<br />')."\n</a>";
327 case 'filename': // remove spaces
328 $new_value = $this->_convert_special_chars($new_value);
329 if (strlen($new_value)>255) {
330 echo sprintf(_('The entered value is too long. Only 255 chars allowed for filename.%sValue %s not %s modified!'), '<br />', '<strong>', '</strong>').'<br />' .
331 _('Please').' <a title="'._('Fetch saved value').'" href="#" onclick="fetch_field_value(\''.$field.'\', '.$report_id.', \''.$_REQUEST['elementid'].'\');">'._('click here').'</a> '._('to view saved value').'.';
334 $new_value = $this->_check_filename($new_value);
336 case 'attach_description':
337 if(!is_numeric($new_value) ||
($new_value != 1 && $new_value != 0)) {
338 echo _("When attaching description, the value must be 0 or 1");
344 $ok = Scheduled_reports_Model
::update_report_field($report_id, $field, $new_value);
347 echo _('An error occurred')."<br />";
351 # decide how to interpret field and value, since we
352 # should print the correct value back.
353 # If the value is an integer it should indicate that
354 # we need to make a lookup in database to fetch correct value
355 # Let's say we have 'periodname' as field, then value is an
356 # integer and the return value should be Weekly/Monthly
357 # if we get a string we should return that string
358 # The problem is that all values will be passed as strings
360 # Possible input values:
363 # * recipients no changes needed
364 # * filename no changes needed
365 # * description/info no changes needed
370 $report_type = Scheduled_reports_Model
::get_typeof_report($report_id);
372 echo _("Unable to determine type for selected report");
375 $saved_report = Report_options
::setup_options_obj($report_type, array('report_id' => $report_id));
376 if (!$saved_report['report_name']) {
377 echo _("Unable to fetch list of saved reports");
380 echo $saved_report['report_name'];
383 $periods = Scheduled_reports_Model
::get_available_report_periods();
384 echo is_array($periods) && array_key_exists($new_value, $periods)
385 ?
$periods[$new_value]
389 $new_value = str_replace(',', ', ', $new_value);
392 case 'attach_description':
393 echo $new_value ?
'Yes' : 'No';
401 * Delete a schedule through ajax call
403 public function delete_schedule()
405 $this->auto_render
= false;
406 $id = $this->input
->post('id');
407 if (Scheduled_reports_Model
::delete_scheduled_report($id)) {
408 return json
::ok(_("Schedule deleted"));
410 return json
::fail(_('An error occurred - unable to delete selected schedule'));
415 * Fetch specific field value for a scheduled report
417 public function fetch_field_value()
419 $this->auto_render
=false;
420 $id = arr
::search($_REQUEST, 'id');
421 $type = arr
::search($_REQUEST, 'type');
422 if (empty($id) ||
empty($type))
424 $data = Scheduled_reports_Model
::fetch_scheduled_field_value($type, $id);
432 private function _get_element_parts($str=false)
434 if (empty($str)) return false;
435 if (!strstr($str, '-')) return false;
436 // check for report_name since it has '.' as element id
437 if (strstr($str, '.')) {
438 $dotparts = explode('.', $str);
439 if (is_array($dotparts)) {
441 for ($i=1;$i<sizeof($dotparts);$i++
) {
442 $str .= $dotparts[$i];
446 $parts = explode('-', $str);
447 if (is_array($parts)) {
453 private function _convert_special_chars($str=false) {
455 if (empty($str)) return false;
458 $str = str_replace(' ', '_', $str);
459 $str = str_replace('"', '', $str);
460 $str = str_replace('/', '_', $str);
461 $return_str = iconv('utf-8', 'us-ascii//TRANSLIT', $str);
462 // If your system is buggy, you'll just get to keep your utf-8
463 // Don't want it? Don't put it there!
464 if ($return_str === false)
469 private function _check_filename($str=false)
472 $str = str_replace(',', '_', $str);
473 if (empty($str)) return false;
474 $extensions = array('pdf', 'csv');
475 if(in_array(pathinfo($str, PATHINFO_EXTENSION
), $extensions)) {