mirror of
https://github.com/VTECRM/vtenext.git
synced 2026-02-26 16:18:47 +00:00
509 lines
16 KiB
PHP
509 lines
16 KiB
PHP
<?php
|
|
/*************************************
|
|
* SPDX-FileCopyrightText: 2009-2020 Vtenext S.r.l. <info@vtenext.com>
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
************************************/
|
|
|
|
/* crmv@139057 */
|
|
|
|
require_once 'modules/Reports/Reports.php';
|
|
require_once 'modules/Reports/ReportRun.php';
|
|
|
|
class ScheduledReports extends SDKExtendableUniqueClass {
|
|
|
|
const SCHEDULED_HOURLY = 1;
|
|
const SCHEDULED_DAILY = 2;
|
|
const SCHEDULED_WEEKLY = 3;
|
|
const SCHEDULED_BIWEEKLY = 4;
|
|
const SCHEDULED_MONTHLY = 5;
|
|
const SCHEDULED_YEARLY = 6;
|
|
|
|
public $zipThreshold = 5; // zip files if the total size is greater than this number of MB
|
|
public $reportTimeout = 120; // if a report is processing for more than this (minutes), it's rescheduled for the next time!
|
|
public $enableLog = true;
|
|
//public $logFile = 'logs/scheduled_reports.log';
|
|
|
|
protected $reports;
|
|
|
|
public function __construct() {
|
|
}
|
|
|
|
protected function log($text) {
|
|
if ($this->enableLog) {
|
|
// log is automatic with cron
|
|
//file_put_contents($this->logFile, '['.date('Y-m-d H:i:s').'] '.$text."\n", FILE_APPEND);
|
|
echo $text."\n";
|
|
//@ob_flush();
|
|
//flush();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute all the scheduled reports
|
|
*/
|
|
public function runScheduledReports() {
|
|
|
|
$this->rescheduleStaleReports();
|
|
$this->initGlobalVars();
|
|
|
|
while ($reportid = $this->getNextScheduledReport()) {
|
|
$this->executeReport($reportid);
|
|
}
|
|
|
|
$this->restoreGlobalVars();
|
|
}
|
|
|
|
protected function initGlobalVars() {
|
|
global $current_user, $currentModule, $current_language;
|
|
global $theme, $default_theme;
|
|
global $mod_strings, $app_strings;
|
|
|
|
$currentModule = 'Reports';
|
|
|
|
if (!$this->savedUser) {
|
|
$this->savedUser = $current_user;
|
|
}
|
|
$current_user = Users::getActiveAdminUser();
|
|
|
|
if (!$this->savedLanguage) {
|
|
$this->savedLanguage = $current_language;
|
|
}
|
|
if (empty($current_language)) $current_language = $current_user->default_language ?: 'en_us';
|
|
|
|
$app_strings = return_application_language($current_language);
|
|
$mod_strings = return_module_language($current_language, $currentModule);
|
|
|
|
$theme = $current_user->default_theme ?: $default_theme;
|
|
|
|
$this->log("Current user set to #{$current_user->id} and language to '{$current_language}'");
|
|
}
|
|
|
|
protected function restoreGlobalVars() {
|
|
global $current_user, $currentModule, $current_language;
|
|
|
|
$current_language = $this->savedLanguage;
|
|
$current_user = $this->savedUser;
|
|
|
|
$this->log("Current user and language restored");
|
|
}
|
|
|
|
/**
|
|
* Check for reports "running"" for too long and schedule them for the next cycle
|
|
*/
|
|
public function rescheduleStaleReports() {
|
|
global $adb, $table_prefix;
|
|
|
|
$timeoutTime = date('Y-m-d H:i:s', time()-$this->reportTimeout*60);
|
|
|
|
$result = $adb->pQuery(
|
|
"SELECT s.reportid, rc.scheduling
|
|
FROM {$table_prefix}_report_scheduled s
|
|
INNER JOIN {$table_prefix}_report r on r.reportid = s.reportid
|
|
INNER JOIN {$table_prefix}_reportconfig rc on rc.reportid = r.reportid
|
|
WHERE rc.scheduling IS NOT NULL AND s.status IN (?) AND (s.last_execution IS NOT NULL AND s.last_execution <= ?)",
|
|
array('PROCESSING', $timeoutTime)
|
|
);
|
|
|
|
$reportid = null;
|
|
if ($result && $adb->num_rows($result) > 0) {
|
|
while ($row = $adb->FetchByAssoc($result, -1, false)) {
|
|
$reportid = $row['reportid'];
|
|
$scheduling = Zend_Json::decode($row['scheduling']);
|
|
$this->updateNextExecution($reportid, $scheduling);
|
|
$adb->pquery("UPDATE {$table_prefix}_report_scheduled SET status = ? WHERE reportid = ?", array('', $reportid));
|
|
$this->log("Report #$reportid rescheduled");
|
|
}
|
|
}
|
|
}
|
|
|
|
public function calcNextExecution($scheduling, $now = null) {
|
|
|
|
if (is_null($now)) $now = time();
|
|
|
|
$weekDays = array(
|
|
'0'=>'Sunday','1'=>'Monday','2'=>'Tuesday','3'=>'Wednesday',
|
|
'4'=>'Thursday','5'=>'Friday','6'=>'Saturday'
|
|
);
|
|
$weekDays = array_flip($weekDays);
|
|
|
|
$scheduleType = $scheduling['schedule']['scheduletype'];
|
|
$scheduledMonth = $scheduling['schedule']['month'];
|
|
$scheduledDayOfMonth= $scheduling['schedule']['date'];
|
|
$scheduledDayOfWeek = $scheduling['schedule']['day'];
|
|
$scheduledTime = $scheduling['schedule']['time'];
|
|
|
|
if(empty($scheduledTime)) {
|
|
$scheduledTime = '10:00';
|
|
} elseif(stripos(':', $scheduledTime) === false) {
|
|
$scheduledTime = $scheduledTime .':00';
|
|
}
|
|
|
|
if($scheduleType == self::SCHEDULED_HOURLY) {
|
|
$now = strtotime(date('Y-m-d H:00:00'),$now);
|
|
return date("Y-m-d H:i:s",strtotime("+1 hour",$now));
|
|
} elseif($scheduleType == self::SCHEDULED_DAILY) {
|
|
if (strtotime(date("Y-m-d {$scheduledTime}",$now)) < $now){
|
|
return date("Y-m-d H:i:s",strtotime("+ 1 day ".$scheduledTime,$now));
|
|
}
|
|
else{
|
|
return date("Y-m-d H:i:s",strtotime($scheduledTime,$now));
|
|
}
|
|
} elseif ($scheduleType == self::SCHEDULED_WEEKLY) {
|
|
$scheduledDayOfWeekText = $scheduledDayOfWeek;
|
|
$scheduledDayOfWeek = $weekDays[$scheduledDayOfWeek];
|
|
if(date('w',time()) == $scheduledDayOfWeek) {
|
|
if (strtotime(date("Y-m-d {$scheduledTime}",$now)) < $now){
|
|
return date("Y-m-d H:i:s",strtotime('+1 week '.$scheduledTime,$now));
|
|
}
|
|
else{
|
|
return date("Y-m-d H:i:s",strtotime($scheduledTime,$now));
|
|
}
|
|
|
|
} else {
|
|
return date("Y-m-d H:i:s",strtotime("next ". $scheduledDayOfWeekText.' '.$scheduledTime,$now));
|
|
}
|
|
} elseif ($scheduleType == self::SCHEDULED_BIWEEKLY) {
|
|
$scheduledDayOfWeekText = $scheduledDayOfWeek;
|
|
$scheduledDayOfWeek = $weekDays[$scheduledDayOfWeek];
|
|
if(date('w',time()) == $scheduledDayOfWeek) {
|
|
if (strtotime(date("Y-m-d {$scheduledTime}",$now)) < $now){
|
|
return date("Y-m-d H:i:s",strtotime('+2 weeks '.$scheduledTime,$now));
|
|
}
|
|
else{
|
|
return date("Y-m-d H:i:s",strtotime($scheduledTime,$now));
|
|
}
|
|
} else {
|
|
return date("Y-m-d H:i:s",strtotime("next ". $scheduledDayOfWeekText.' '.$scheduledTime,$now));
|
|
}
|
|
} elseif ($scheduleType == self::SCHEDULED_MONTHLY) {
|
|
|
|
$currentTime = time();
|
|
$currentDayOfMonth = date('j',$currentTime);
|
|
|
|
if($scheduledDayOfMonth == $currentDayOfMonth) {
|
|
if (strtotime(date("Y-m-d {$scheduledTime}",$now)) < $now){
|
|
return date("Y-m-d H:i:s",strtotime('+1 month '.$scheduledTime,$now));
|
|
}
|
|
else{
|
|
return date("Y-m-d H:i:s",strtotime($scheduledTime,$now));
|
|
}
|
|
} else {
|
|
$monthInFullText = date('F',$currentTime);
|
|
$yearFullNumberic = date('Y',$currentTime);
|
|
if($scheduledDayOfMonth < $currentDayOfMonth) {
|
|
$nextMonth = date("Y-m-d H:i:s",strtotime('next month'));
|
|
$monthInFullText = date('F',strtotime($nextMonth));
|
|
}
|
|
return date("Y-m-d H:i:s",strtotime($scheduledDayOfMonth.' '.$monthInFullText.' '.$yearFullNumberic.' '.$scheduledTime,$now));
|
|
}
|
|
} elseif ($scheduleType == self::SCHEDULED_YEARLY) {
|
|
|
|
// crmv@189679
|
|
$scheduledMoment = date("Y-m-d H:i:s",strtotime(date('Y').'-'.$scheduledMonth.'-'.$scheduledDayOfMonth.' '.$scheduledTime,$now));
|
|
$nowMoment = date("Y-m-d H:i:s",$now);
|
|
if($scheduledMoment <= $nowMoment) $scheduledMoment = date("Y-m-d H:i:s",strtotime('+1 year ',strtotime($scheduledMoment)));
|
|
|
|
return $scheduledMoment;
|
|
// crmv@189679e
|
|
}
|
|
}
|
|
|
|
public function updateNextExecution($reportid, $scheduling, $now = null) {
|
|
global $adb, $table_prefix;
|
|
|
|
if (is_null($now)) $now = time();
|
|
|
|
$nextTime = $this->calcNextExecution($scheduling, $now);
|
|
$res = $adb->pquery("SELECT reportid FROM {$table_prefix}_report_scheduled WHERE reportid = ?", array($reportid));
|
|
if ($res && $adb->num_rows($res) > 0) {
|
|
$adb->pquery("UPDATE {$table_prefix}_report_scheduled SET next_execution=? WHERE reportid=?", array($nextTime, $reportid));
|
|
} else {
|
|
$params = array($reportid, '', $nextTime);
|
|
$adb->pquery("INSERT INTO {$table_prefix}_report_scheduled (reportid, status, next_execution) VALUES (?,?,?)", $params); // crmv@157618
|
|
}
|
|
return $nextTime;
|
|
}
|
|
|
|
public function setStatus($reportid, $status) {
|
|
global $adb, $table_prefix;
|
|
if ($status == 'PROCESSING') {
|
|
$sql = "UPDATE {$table_prefix}_report_scheduled SET status = ?, last_execution = ? WHERE reportid=?";
|
|
$params = array($status, date('Y-m-d H:i:s'), $reportid);
|
|
} else {
|
|
$sql = "UPDATE {$table_prefix}_report_scheduled SET status = ? WHERE reportid=?";
|
|
$params = array($status, $reportid);
|
|
}
|
|
$adb->pquery($sql, $params);
|
|
}
|
|
|
|
public function getNextScheduledReport() {
|
|
global $adb, $table_prefix;
|
|
|
|
$currentTime = date('Y-m-d H:i:s');
|
|
|
|
// crmv@203726 - mysql 8s
|
|
$result = $adb->limitpQuery(
|
|
"SELECT s.reportid
|
|
FROM {$table_prefix}_report_scheduled s
|
|
INNER JOIN {$table_prefix}_report r on r.reportid = s.reportid
|
|
INNER JOIN {$table_prefix}_reportconfig rc on rc.reportid = r.reportid
|
|
WHERE rc.scheduling IS NOT NULL AND s.status IN ('', 'ERROR') AND (s.next_execution IS NULL OR s.next_execution = ? OR s.next_execution <= ?)
|
|
ORDER BY s.last_execution ASC",
|
|
0,1,
|
|
array('0000-00-00 00:00:00',$currentTime)
|
|
);
|
|
// crmv@203726e
|
|
|
|
$reportid = null;
|
|
if ($result && $adb->num_rows($result) > 0) {
|
|
$reportid = $adb->query_result_no_html($result, 0, 'reportid');
|
|
}
|
|
|
|
return $reportid;
|
|
}
|
|
|
|
public function generateFileName($reportid, $config) {
|
|
static $nameCache = array();
|
|
|
|
if (empty($nameCache[$reportid])) {
|
|
$name = preg_replace('/[^a-zA-Z0-9_\s-]/', '', $config['reportname']).'_'. date('Ymd_Hms');
|
|
$nameCache[$reportid] = $name;
|
|
}
|
|
|
|
return $nameCache[$reportid];
|
|
}
|
|
|
|
public function executeReport($reportid) {
|
|
|
|
if (!$this->reports) $this->reports = Reports::getInstance();
|
|
|
|
$config = $this->reports->loadReport($reportid);
|
|
$folderid = $config['folderid'];
|
|
$scheduling = $config['scheduling'];
|
|
|
|
$this->log("Executing report #$reportid in folder #{$folderid}...");
|
|
$this->setStatus($reportid, 'PROCESSING');
|
|
|
|
$oReportRun = ReportRun::getInstance($reportid);
|
|
if ($reportinfo = SDK::getReport($reportid, $folderid)) {
|
|
require_once($reportinfo['reportrun']);
|
|
$oReportRun = new $reportinfo['runclass']($reportid);
|
|
}
|
|
|
|
// forge a special request
|
|
$_REQUEST = array();
|
|
$_REQUEST['module'] = 'Reports';
|
|
$_REQUEST['record'] = $reportid;
|
|
$_REQUEST['folder'] = $_REQUEST['folderid'] = $folderid; //fix sdk reports
|
|
$_REQUEST['batch_export'] = 1;
|
|
|
|
// what to export (all)
|
|
$_REQUEST['export_report_main'] = 1;
|
|
if ($oReportRun->hasSummary()){
|
|
$_REQUEST['export_report_summary'] = 1;
|
|
}
|
|
if ($oReportRun->hasTotals()){
|
|
$_REQUEST['export_report_totals'] = 1;
|
|
}
|
|
|
|
$baseFileName = $this->generateFileName($reportid, $config);
|
|
|
|
$reportFormat = $scheduling['format'];
|
|
$attachments = array();
|
|
|
|
$ts0 = microtime(true);
|
|
try{
|
|
if($reportFormat == 'pdf' || $reportFormat == 'both') {
|
|
$fileName = $baseFileName.'.pdf';
|
|
$filePath = 'storage/'.$fileName;
|
|
$attachments[$fileName] = $filePath;
|
|
$_REQUEST['action'] = 'CreatePDF';
|
|
@unlink($filePath);
|
|
$this->log("Generating PDF...");
|
|
include('modules/Reports/CreatePDF.php');
|
|
}
|
|
if ($reportFormat == 'excel' || $reportFormat == 'both') {
|
|
$fileName = $baseFileName.'.xls';
|
|
$filePath = 'storage/'.$fileName;
|
|
$attachments[$fileName] = $filePath;
|
|
$_REQUEST['action'] = 'CreateXL';
|
|
@unlink($filePath);
|
|
$this->log("Generating XLS...");
|
|
include('modules/Reports/CreateXL.php');
|
|
}
|
|
} catch(Exception $e) {
|
|
$this->log("Error generating report: ".$e->getMessage());
|
|
$this->setStatus($reportid, 'ERROR');
|
|
return false;
|
|
}
|
|
|
|
$ts1 = microtime(true);
|
|
$delta = round($ts1-$ts0);
|
|
$this->log("Report executed in {$delta}s");
|
|
|
|
// now check for files
|
|
foreach ($attachments as $name=>$path) {
|
|
if (!is_readable($path) || filesize($path) == 0) {
|
|
$this->log("Warning, missing or empty file: {$path}");
|
|
unset($attachments[$name]);
|
|
}
|
|
}
|
|
|
|
if (count($attachments) == 0) {
|
|
$this->log("Error, no files to attach");
|
|
$this->setStatus($reportid, 'ERROR');
|
|
return false;
|
|
}
|
|
|
|
$r = $this->sendEmail($config, $attachments);
|
|
|
|
// and remove attachments
|
|
foreach($attachments as $attachmentName => $path) {
|
|
@unlink($path);
|
|
}
|
|
|
|
// reschedule ignore if there were mail errors
|
|
$nextTime = $this->updateNextExecution($reportid, $config['scheduling']);
|
|
$this->log("Report rescheduled for $nextTime");
|
|
|
|
if ($r) {
|
|
$this->setStatus($reportid, '');
|
|
} else {
|
|
$this->setStatus($reportid, 'ERROR');
|
|
}
|
|
|
|
return $r;
|
|
}
|
|
|
|
public function sendEmail($config, $attachments) {
|
|
global $current_user;
|
|
|
|
require_once('vtlib/Vtecrm/Mailer.php');
|
|
require_once('vtlib/Vtecrm/Zip.php');
|
|
|
|
$mailer = new Vtecrm_Mailer();
|
|
$mailer->ConfigSenderInfo(getUserEmail($current_user->id),'Report Batch Send');
|
|
|
|
$recipientEmails = $this->getRecipientEmails($config['scheduling']);
|
|
foreach($recipientEmails as $name => $email) {
|
|
$mailer->AddAddress($email, $name);
|
|
}
|
|
|
|
$currentTime = date('Y-m-d H:i:s');
|
|
$subject = $config['reportname'] .' - '. $currentTime .' ('. DateTimeField::getDBTimeZone() .')';
|
|
|
|
$contents = getTranslatedString('LBL_AUTO_GENERATED_REPORT_EMAIL', 'Reports') .'<br/><br/>';
|
|
$contents .= '<b>'.getTranslatedString('LBL_REPORT_NAME', 'Reports') .' :</b> '. $config['reportname'] .'<br/>';
|
|
$contents .= '<b>'.getTranslatedString('LBL_DESCRIPTION', 'Reports') .' :</b><br/>'. $config['description'] .'<br/><br/>';
|
|
|
|
$mailer->Subject = $subject;
|
|
$mailer->Body = $contents;
|
|
$mailer->ContentType = "text/html";
|
|
|
|
$totalsize = 0;
|
|
foreach($attachments as $attachmentName => $path) {
|
|
if (is_file($path)){
|
|
$size = filesize($path);
|
|
$totalsize += intval($size)/1024/1024;
|
|
}
|
|
}
|
|
|
|
if ($totalsize >= $this->zipThreshold){
|
|
//zip files
|
|
$baseFileName = $this->generateFileName($config['reportid'], $config);
|
|
$zippath = "storage/".$baseFileName.".zip";
|
|
$zipname = $baseFileName.".zip";
|
|
$zip = new Vtecrm_Zip($zippath);
|
|
foreach($attachments as $attachmentName => $path) {
|
|
$zip->addFile($path,$attachmentName);
|
|
}
|
|
$zip->save();
|
|
$mailer->AddAttachment($zippath, $zipname);
|
|
$attachments[$zipname] = $zippath;
|
|
} else {
|
|
foreach($attachments as $attachmentName => $path) {
|
|
$mailer->AddAttachment($path, $attachmentName);
|
|
}
|
|
}
|
|
|
|
$this->log("Sending email to ".count($recipientEmails)." recipients...");
|
|
//$mailer->SMTPDebug = true;
|
|
// crmv@203907
|
|
$mailer->SMTPOptions=array(
|
|
//Some men just want to watch the world burn...
|
|
'ssl' => array(
|
|
'verify_peer' => false,
|
|
'allow_self_signed' => true,
|
|
'verify_peer_name' => false,
|
|
)
|
|
);
|
|
// crmv@203907e
|
|
|
|
$result = $mailer->Send(true);
|
|
|
|
if ($result != 1) {
|
|
$this->log("Unable to send email");
|
|
}
|
|
|
|
return ($result == 1);
|
|
}
|
|
|
|
public function getRecipientEmails($scheduling) {
|
|
global $adb, $table_prefix; // crmv@205967
|
|
|
|
$recipientsInfo = $scheduling['recipients'];
|
|
|
|
$recipientsList = array();
|
|
if(!empty($recipientsInfo)) {
|
|
if(!empty($recipientsInfo['users'])) {
|
|
//crmv@203476
|
|
foreach($recipientsInfo['users'] as $oneuser) {
|
|
$result = $adb->pquery("SELECT id FROM {$table_prefix}_users WHERE id=? AND status=?", array($oneuser, 'Active'));
|
|
if ($result && $adb->num_rows($result) > 0) {
|
|
$recipientsList[] = $oneuser;
|
|
}
|
|
}
|
|
//crmv@203476e
|
|
}
|
|
|
|
if(!empty($recipientsInfo['roles'])) {
|
|
foreach($recipientsInfo['roles'] as $roleId) {
|
|
$roleUsers = getRoleUsers($roleId, true);//crmv@203476
|
|
foreach($roleUsers as $userId => $userName) {
|
|
array_push($recipientsList, $userId);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!empty($recipientsInfo['rs'])) {
|
|
foreach($recipientsInfo['rs'] as $roleId) {
|
|
$users = getRoleAndSubordinateUsers($roleId, true);//crmv@203476
|
|
foreach($users as $userId => $userName) {
|
|
array_push($recipientsList, $userId);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!empty($recipientsInfo['groups'])) {
|
|
require_once 'include/utils/GetGroupUsers.php';
|
|
foreach($recipientsInfo['groups'] as $groupId) {
|
|
$userGroups = new GetGroupUsers();
|
|
$userGroups->getAllUsersInGroup($groupId, true); //crmv@203476
|
|
$recipientsList = array_merge($recipientsList, $userGroups->group_users);
|
|
}
|
|
}
|
|
}
|
|
$recipientsEmails = array();
|
|
if(!empty($recipientsList) && count($recipientsList) > 0) {
|
|
foreach($recipientsList as $userId) {
|
|
$userName = getUserFullName($userId);
|
|
$userEmail = getUserEmail($userId);
|
|
if(!in_array($userEmail, $recipientsEmails)) {
|
|
$recipientsEmails[$userName] = $userEmail;
|
|
}
|
|
}
|
|
}
|
|
return $recipientsEmails;
|
|
}
|
|
|
|
} |