Avail feature updated
[ninja.git] / modules / reports / controllers / schedule.php
blob08acd484fdb03dc6fec61746c08da967ff139589
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[] = '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) {
38 case 'avail':
39 $defined_report_types[$rpt_type->identifier] = _('Availability reports');
40 break;
41 case 'sla':
42 $defined_report_types[$rpt_type->identifier] = _('SLA reports');
43 break;
44 default:
45 $defined_report_types[$rpt_type->identifier] = $rpt_type->name;
46 break;
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');
85 /**
86 * Kills request with headers and content à la json
88 * @param $type string
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();
95 $result = array();
96 foreach ($scheduled_reports as $id => $name)
97 $result[] = array('id' => $id, 'report_name' => $name);
98 return json::ok($result);
102 * Schedule a report
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');
119 if (!$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);
144 if (!is_int($ok)) {
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);
161 if (!$report) {
162 $msg = sprintf(_("Failed to load %s report from schedule %s"), $type, $schedule_id);
163 if (request::is_ajax()) {
164 return json::fail($msg);
166 else {
167 $this->log->log('error', $msg);
168 return false;
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';
176 $pipe_desc = array(
177 0 => array('pipe', 'r'),
178 1 => array('pipe', 'w'),
179 2 => array('pipe', 'w'));
180 $pipes = false;
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");
186 fclose($pipes[0]);
187 $out = stream_get_contents($pipes[1]);
188 $err = stream_get_contents($pipes[2]);
189 if($err) {
190 $this->log->log('error', $err);
192 fclose($pipes[1]);
193 fclose($pipes[2]);
194 $code = proc_close($process);
196 else {
197 $this->log->log('error', "Couldn't successfully execute this command:");
198 $this->log->log('error', $cmd);
199 $code = -128;
201 $save = false;
202 $mail = false;
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);
206 if ($code != 0) {
207 if (request::is_ajax()) {
208 return json::fail(sprintf(_("Failed to run %s: %s"), $cmd, $out));
210 else {
211 $this->log->log('error', "Couldn't generate report for schedule {$schedule_id}: $out");
212 return false;
215 if ($opt_obj['local_persistent_filepath']) {
216 persist_pdf::save($out, $opt_obj['local_persistent_filepath'].'/'.basename($opt_obj['filename']));
217 $save = true;
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";
224 $mail = true;
226 if (request::is_ajax()) {
227 if ($save && $mail)
228 $msg = _('Report was saved and emailed');
229 else if ($save)
230 $msg = _('Report was saved');
231 else if ($mail)
232 $msg = _('Report was emailed');
233 else
234 $msg = _('Nothing to do');
235 return json::ok($msg);
236 } else {
237 return true;
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);
255 if(!$schedules) {
256 echo "No scheduled reports found, not sending any emails.\n";
257 return;
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;
273 $field = false;
274 $report_id = false;
275 $new_value = arr::search($_REQUEST, 'newvalue');
276 $tmp_parts = arr::search($_REQUEST, 'elementid');
278 if (!$tmp_parts) {
279 # @@@FIXME: inform user via jGrowl and echo old value somehow?
280 echo _("Required data is missing, unable to save changes");
281 return false;
284 $parts = $this->_get_element_parts($tmp_parts);
285 if (!empty($parts)) {
286 $field = $parts[0];
287 $report_id = (int)$parts[1];
290 // check some fields a little extra
291 switch ($field) {
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 />";
296 return;
298 break;
299 case 'recipients': // convert ';' to ','
300 $new_value = str_replace(';', ',', $new_value);
301 $rec_arr = explode(',', $new_value);
302 $recipients = false;
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>";
322 return;
326 break;
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').'.';
332 exit;
334 $new_value = $this->_check_filename($new_value);
335 break;
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");
339 return;
341 break;
344 $ok = Scheduled_reports_Model::update_report_field($report_id, $field, $new_value);
346 if ($ok!==true) {
347 echo _('An error occurred')."<br />";
348 return;
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:
361 # * report_id
362 # * period_id
363 # * recipients no changes needed
364 # * filename no changes needed
365 # * description/info no changes needed
368 switch ($field) {
369 case 'report_id':
370 $report_type = Scheduled_reports_Model::get_typeof_report($report_id);
371 if (!$report_type) {
372 echo _("Unable to determine type for selected report");
373 break;
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");
378 break;
380 echo $saved_report['report_name'];
381 break;
382 case 'period_id':
383 $periods = Scheduled_reports_Model::get_available_report_periods();
384 echo is_array($periods) && array_key_exists($new_value, $periods)
385 ? $periods[$new_value]
386 : '';
387 break;
388 case 'recipients':
389 $new_value = str_replace(',', ', ', $new_value);
390 echo $new_value;
391 break;
392 case 'attach_description':
393 echo $new_value ? 'Yes' : 'No';
394 break;
395 default:
396 echo $new_value;
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"));
409 } else {
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))
423 return false;
424 $data = Scheduled_reports_Model::fetch_scheduled_field_value($type, $id);
425 if (!empty($data)) {
426 echo $data;
427 } else {
428 echo 'error';
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)) {
440 $str = '';
441 for ($i=1;$i<sizeof($dotparts);$i++) {
442 $str .= $dotparts[$i];
446 $parts = explode('-', $str);
447 if (is_array($parts)) {
448 return $parts;
450 return false;
453 private function _convert_special_chars($str=false) {
454 $str = trim($str);
455 if (empty($str)) return false;
456 $return_str = '';
457 $str = trim($str);
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)
465 $return_str = $str;
466 return $return_str;
469 private function _check_filename($str=false)
471 $str = trim($str);
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)) {
476 return $str;
478 return $str.".pdf";