Merge "Make it possible to sort on simple custom columns"
[ninja.git] / modules / reports / controllers / schedule.php
blob9bfc767dc0a7190ef9d00ccd9c62e7b852baec1a
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
3 /**
4 * Controller for scheduling reports
5 */
6 class Schedule_Controller extends Authenticated_Controller
8 public function __construct() {
9 parent::__construct();
10 $this->template->disable_refresh = true;
12 /**
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) {
40 case 'avail':
41 $defined_report_types[$rpt_type->identifier] = _('Availability report');
42 break;
43 case 'sla':
44 $defined_report_types[$rpt_type->identifier] = _('SLA report');
45 break;
46 case 'summary':
47 $defined_report_types[$rpt_type->identifier] = _('Alert summary report');
48 break;
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);
124 * Schedule a report
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');
141 if (!$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);
166 if (!is_int($ok)) {
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';
188 $pipe_desc = array(
189 0 => array('pipe', 'r'),
190 1 => array('pipe', 'w'),
191 2 => array('pipe', 'w'));
192 $pipes = false;
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");
198 fclose($pipes[0]);
199 $out = stream_get_contents($pipes[1]);
200 $err = stream_get_contents($pipes[2]);
201 if($err) {
202 $this->log->log('error', $err);
204 fclose($pipes[1]);
205 fclose($pipes[2]);
206 $code = proc_close($process);
208 else {
209 $this->log->log('error', "Couldn't successfully execute this command:");
210 $this->log->log('error', $cmd);
211 $code = -128;
213 $save = false;
214 $mail = false;
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);
218 if ($code != 0) {
219 if (request::is_ajax()) {
220 return json::fail(sprintf(_("Failed to run %s: %s"), $cmd, $out));
222 else {
223 $this->log->log('error', "Couldn't generate report for schedule {$schedule_id}: $out");
224 return false;
227 if ($opt_obj['local_persistent_filepath']) {
228 persist_pdf::save($out, $opt_obj['local_persistent_filepath'].'/'.basename($opt_obj['filename']));
229 $save = true;
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";
236 $mail = true;
238 if (request::is_ajax()) {
239 if ($save && $mail)
240 $msg = _('Report was saved and emailed');
241 else if ($save)
242 $msg = _('Report was saved');
243 else if ($mail)
244 $msg = _('Report was emailed');
245 else
246 $msg = _('Nothing to do');
247 return json::ok($msg);
248 } else {
249 return true;
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);
267 if(!$schedules) {
268 echo "No scheduled reports found, not sending any emails.\n";
269 return;
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;
285 $field = false;
286 $report_id = false;
287 $new_value = arr::search($_REQUEST, 'newvalue');
288 $tmp_parts = arr::search($_REQUEST, 'elementid');
290 if (!$tmp_parts) {
291 # @@@FIXME: inform user via jGrowl and echo old value somehow?
292 echo _("Required data is missing, unable to save changes");
293 return false;
296 $parts = $this->_get_element_parts($tmp_parts);
297 if (!empty($parts)) {
298 $field = $parts[0];
299 $report_id = (int)$parts[1];
302 // check some fields a little extra
303 switch ($field) {
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 />";
308 return;
310 break;
311 case 'recipients': // convert ';' to ','
312 $new_value = str_replace(';', ',', $new_value);
313 $rec_arr = explode(',', $new_value);
314 $recipients = false;
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>";
334 return;
338 break;
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').'.';
344 exit;
346 $new_value = $this->_check_filename($new_value);
347 break;
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");
351 return;
353 break;
356 $ok = Scheduled_reports_Model::update_report_field($report_id, $field, $new_value);
358 if ($ok!==true) {
359 echo _('An error occurred')."<br />";
360 return;
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:
373 # * report_id
374 # * period_id
375 # * recipients no changes needed
376 # * filename no changes needed
377 # * description/info no changes needed
380 switch ($field) {
381 case 'report_id':
382 $report_type = Scheduled_reports_Model::get_typeof_report($report_id);
383 if (!$report_type) {
384 echo _("Unable to determine type for selected report");
385 return;
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");
390 return;
392 foreach ($saved_reports as $report) {
393 if ($report->id == $new_value) {
394 echo $report->report_name;
395 return;
398 case 'period_id':
399 $periods = Scheduled_reports_Model::get_available_report_periods();
400 echo is_array($periods) && array_key_exists($new_value, $periods)
401 ? $periods[$new_value]
402 : '';
403 break;
404 case 'recipients':
405 $new_value = str_replace(',', ', ', $new_value);
406 echo $new_value;
407 break;
408 case 'attach_description':
409 echo $new_value ? 'Yes' : 'No';
410 break;
411 default:
412 echo $new_value;
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"));
425 } else {
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))
439 return false;
440 $data = Scheduled_reports_Model::fetch_scheduled_field_value($type, $id);
441 if (!empty($data)) {
442 echo $data;
443 } else {
444 echo 'error';
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)) {
456 $str = '';
457 for ($i=1;$i<sizeof($dotparts);$i++) {
458 $str .= $dotparts[$i];
462 $parts = explode('-', $str);
463 if (is_array($parts)) {
464 return $parts;
466 return false;
469 private function _convert_special_chars($str=false) {
470 $str = trim($str);
471 if (empty($str)) return false;
472 $return_str = '';
473 $str = trim($str);
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)
481 $return_str = $str;
482 return $return_str;
485 private function _check_filename($str=false)
487 $str = trim($str);
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)) {
492 return $str;
494 return $str.".pdf";