2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
40 * Set up an array of Jobs with the appropriate metadata
41 * 'jobName' => array (
44 * 'X' should be an increment of 1
45 * 'name' should be the EXACT name of your function
47 * Your function should not be passed any parameters
48 * Always return a Boolean. If it does not the Job will not terminate itself
49 * after completion, and the webserver will be forced to time-out that Job instance.
50 * DO NOT USE sugar_cleanup(); in your function flow or includes. this will
51 * break Schedulers. That function is called at the foot of cron.php
55 * This array provides the Schedulers admin interface with values for its "Job"
58 $job_strings = array (
60 1 => 'pollMonitoredInboxes',
61 2 => 'runMassEmailCampaign',
62 5 => 'pollMonitoredInboxesForBouncedCampaignEmails',
65 /*4 => 'securityAudit()',*/
66 12 => 'sendEmailReminders',
67 14 => 'cleanJobQueue',
68 15 => 'removeDocumentsFromFS',
69 16 => 'trimSugarFeeds',
74 * Job 0 refreshes all job schedulers at midnight
77 function refreshJobs() {
85 function pollMonitoredInboxes() {
87 $_bck_up = array('team_id' => $GLOBALS['current_user']->team_id, 'team_set_id' => $GLOBALS['current_user']->team_set_id);
88 $GLOBALS['log']->info('----->Scheduler fired job of type pollMonitoredInboxes()');
93 require_once('modules/Emails/EmailUI.php');
95 $ie = new InboundEmail();
96 $emailUI = new EmailUI();
97 $r = $ie->db->query('SELECT id, name FROM inbound_email WHERE is_personal = 0 AND deleted=0 AND status=\'Active\' AND mailbox_type != \'bounce\'');
98 $GLOBALS['log']->debug('Just got Result from get all Inbounds of Inbound Emails');
100 while($a = $ie->db->fetchByAssoc($r)) {
101 $GLOBALS['log']->debug('In while loop of Inbound Emails');
102 $ieX = new InboundEmail();
103 $ieX->retrieve($a['id']);
104 $GLOBALS['current_user']->team_id = $ieX->team_id;
105 $GLOBALS['current_user']->team_set_id = $ieX->team_set_id;
106 $mailboxes = $ieX->mailboxarray;
107 foreach($mailboxes as $mbox) {
108 $ieX->mailbox = $mbox;
110 $msgNoToUIDL = array();
111 $connectToMailServer = false;
112 if ($ieX->isPop3Protocol()) {
113 $msgNoToUIDL = $ieX->getPop3NewMessagesToDownloadForCron();
114 // get all the keys which are msgnos;
115 $newMsgs = array_keys($msgNoToUIDL);
117 if($ieX->connectMailserver() == 'true') {
118 $connectToMailServer = true;
121 $GLOBALS['log']->debug('Trying to connect to mailserver for [ '.$a['name'].' ]');
122 if($connectToMailServer) {
123 $GLOBALS['log']->debug('Connected to mailserver');
124 if (!$ieX->isPop3Protocol()) {
125 $newMsgs = $ieX->getNewMessageIds();
127 if(is_array($newMsgs)) {
129 $total = count($newMsgs);
130 require_once("include/SugarFolders/SugarFolders.php");
131 $sugarFolder = new SugarFolder();
132 $groupFolderId = $ieX->groupfolder_id;
133 $isGroupFolderExists = false;
135 if ($groupFolderId != null && $groupFolderId != "") {
136 $sugarFolder->retrieve($groupFolderId);
137 $isGroupFolderExists = true;
139 $messagesToDelete = array();
140 if ($ieX->isMailBoxTypeCreateCase()) {
141 $users[] = $sugarFolder->assign_to_id;
142 $distributionMethod = $ieX->get_stored_options("distrib_method", "");
143 if ($distributionMethod != 'roundRobin') {
144 $counts = $emailUI->getAssignedEmailsCountForUsers($users);
146 $lastRobin = $emailUI->getLastRobin($ieX);
148 $GLOBALS['log']->debug('distribution method id [ '.$distributionMethod.' ]');
150 foreach($newMsgs as $k => $msgNo) {
152 if ($ieX->isPop3Protocol()) {
153 $uid = $msgNoToUIDL[$msgNo];
155 $uid = imap_uid($ieX->conn, $msgNo);
157 if ($isGroupFolderExists) {
158 if ($ieX->importOneEmail($msgNo, $uid)) {
160 $sugarFolder->addBean($ieX->email);
161 if ($ieX->isPop3Protocol()) {
162 $messagesToDelete[] = $msgNo;
164 $messagesToDelete[] = $uid;
166 if ($ieX->isMailBoxTypeCreateCase()) {
168 if ($distributionMethod == 'roundRobin') {
169 if (sizeof($users) == 1) {
171 $lastRobin = $users[0];
173 $userIdsKeys = array_flip($users); // now keys are values
174 $thisRobinKey = $userIdsKeys[$lastRobin] + 1;
175 if(!empty($users[$thisRobinKey])) {
176 $userId = $users[$thisRobinKey];
177 $lastRobin = $users[$thisRobinKey];
180 $lastRobin = $users[0];
184 if (sizeof($users) == 1) {
185 foreach($users as $k => $value) {
189 asort($counts); // lowest to highest
190 $countsKeys = array_flip($counts); // keys now the 'count of items'
191 $leastBusy = array_shift($countsKeys); // user id of lowest item count
192 $userId = $leastBusy;
193 $counts[$leastBusy] = $counts[$leastBusy] + 1;
196 $GLOBALS['log']->debug('userId [ '.$userId.' ]');
197 $ieX->handleCreateCase($ieX->email, $userId);
201 if($ieX->isAutoImport()) {
202 $ieX->importOneEmail($msgNo, $uid);
204 /*If the group folder doesn't exist then download only those messages
205 which has caseid in message*/
206 $ieX->getMessagesInEmailCache($msgNo, $uid);
207 $email = new Email();
208 $header = imap_headerinfo($ieX->conn, $msgNo);
209 $email->name = $ieX->handleMimeHeaderDecode($header->subject);
210 $email->from_addr = $ieX->convertImapToSugarEmailAddress($header->from);
211 $email->reply_to_email = $ieX->convertImapToSugarEmailAddress($header->reply_to);
212 if(!empty($email->reply_to_email)) {
213 $contactAddr = $email->reply_to_email;
215 $contactAddr = $email->from_addr;
217 $mailBoxType = $ieX->mailbox_type;
218 $ieX->handleAutoresponse($email, $contactAddr);
221 $GLOBALS['log']->debug('***** On message [ '.$current.' of '.$total.' ] *****');
224 // update Inbound Account with last robin
225 if ($ieX->isMailBoxTypeCreateCase() && $distributionMethod == 'roundRobin') {
226 $emailUI->setLastRobin($ieX, $lastRobin);
230 if ($isGroupFolderExists) {
231 $leaveMessagesOnMailServer = $ieX->get_stored_options("leaveMessagesOnMailServer", 0);
232 if (!$leaveMessagesOnMailServer) {
233 if ($ieX->isPop3Protocol()) {
234 $ieX->deleteMessageOnMailServerForPop3(implode(",", $messagesToDelete));
236 $ieX->deleteMessageOnMailServer(implode($app_strings['LBL_EMAIL_DELIMITER'], $messagesToDelete));
241 $GLOBALS['log']->fatal("SCHEDULERS: could not get an IMAP connection resource for ID [ {$a['id']} ]. Skipping mailbox [ {$a['name']} ].");
242 // cn: bug 9171 - continue while
245 imap_expunge($ieX->conn);
246 imap_close($ieX->conn, CL_EXPUNGE);
248 $GLOBALS['current_user']->team_id = $_bck_up['team_id'];
249 $GLOBALS['current_user']->team_set_id = $_bck_up['team_set_id'];
256 function runMassEmailCampaign() {
257 if (!class_exists('LoggerManager')){
260 $GLOBALS['log'] = LoggerManager::getLogger('emailmandelivery');
261 $GLOBALS['log']->debug('Called:runMassEmailCampaign');
263 if (!class_exists('DBManagerFactory')){
264 require('include/database/DBManagerFactory.php');
269 require("config.php");
270 require('include/modules.php');
271 if(!class_exists('AclController')) {
272 require('modules/ACL/ACLController.php');
275 require('modules/EmailMan/EmailManDelivery.php');
282 function pruneDatabase() {
283 $GLOBALS['log']->info('----->Scheduler fired job of type pruneDatabase()');
284 $backupDir = sugar_cached('backups');
285 $backupFile = 'backup-pruneDatabase-GMT0_'.gmdate('Y_m_d-H_i_s', strtotime('now')).'.php';
287 $db = DBManagerFactory::getInstance();
288 $tables = $db->getTablesArray();
289 $queryString = array();
291 if(!empty($tables)) {
292 foreach($tables as $kTable => $table) {
293 // find tables with deleted=1
294 $columns = $db->get_columns($table);
295 // no deleted - won't delete
296 if(empty($columns['deleted'])) continue;
298 $custom_columns = array();
299 if(array_search($table.'_cstm', $tables)) {
300 $custom_columns = $db->get_columns($table.'_cstm');
301 if(empty($custom_columns['id_c'])) {
302 $custom_columns = array();
306 $qDel = "SELECT * FROM $table WHERE deleted = 1";
307 $rDel = $db->query($qDel);
309 // make a backup INSERT query if we are deleting.
310 while($aDel = $db->fetchByAssoc($rDel, false)) {
311 // build column names
313 $queryString[] = $db->insertParams($table, $columns, $aDel, null, false);
315 if(!empty($custom_columns) && !empty($aDel['id'])) {
316 $qDelCstm = 'SELECT * FROM '.$table.'_cstm WHERE id_c = '.$db->quoted($aDel['id']);
317 $rDelCstm = $db->query($qDelCstm);
319 // make a backup INSERT query if we are deleting.
320 while($aDelCstm = $db->fetchByAssoc($rDelCstm)) {
321 $queryString[] = $db->insertParams($table, $custom_columns, $aDelCstm, null, false);
322 } // end aDel while()
324 $db->query('DELETE FROM '.$table.'_cstm WHERE id_c = '.$db->quoted($aDel['id']));
326 } // end aDel while()
327 // now do the actual delete
328 $db->query('DELETE FROM '.$table.' WHERE deleted = 1');
329 } // foreach() tables
331 if(!file_exists($backupDir) || !file_exists($backupDir.'/'.$backupFile)) {
332 // create directory if not existent
333 mkdir_recursive($backupDir, false);
337 write_array_to_file('pruneDatabase', $queryString, $backupDir.'/'.$backupFile);
348 //function securityAudit() {
353 function trimTracker()
355 global $sugar_config, $timedate;
356 $GLOBALS['log']->info('----->Scheduler fired job of type trimTracker()');
357 $db = DBManagerFactory::getInstance();
359 $admin = new Administration();
360 $admin->retrieveSettings('tracker');
361 require('modules/Trackers/config.php');
362 $trackerConfig = $tracker_config;
364 require_once('include/utils/db_utils.php');
365 $prune_interval = !empty($admin->settings['tracker_prune_interval']) ? $admin->settings['tracker_prune_interval'] : 30;
366 foreach($trackerConfig as $tableName=>$tableConfig) {
368 //Skip if table does not exist
369 if(!$db->tableExists($tableName)) {
373 $timeStamp = db_convert("'". $timedate->asDb($timedate->getNow()->get("-".$prune_interval." days")) ."'" ,"datetime");
374 if($tableName == 'tracker_sessions') {
375 $query = "DELETE FROM $tableName WHERE date_end < $timeStamp";
377 $query = "DELETE FROM $tableName WHERE date_modified < $timeStamp";
380 $GLOBALS['log']->info("----->Scheduler is about to trim the $tableName table by running the query $query");
389 function pollMonitoredInboxesForBouncedCampaignEmails() {
390 $GLOBALS['log']->info('----->Scheduler job of type pollMonitoredInboxesForBouncedCampaignEmails()');
394 $ie = new InboundEmail();
395 $r = $ie->db->query('SELECT id FROM inbound_email WHERE deleted=0 AND status=\'Active\' AND mailbox_type=\'bounce\'');
397 while($a = $ie->db->fetchByAssoc($r)) {
398 $ieX = new InboundEmail();
399 $ieX->retrieve($a['id']);
400 $ieX->connectMailserver();
401 $ieX->importMessages();
413 function sendEmailReminders(){
414 $GLOBALS['log']->info('----->Scheduler fired job of type sendEmailReminders()');
415 require_once("modules/Activities/EmailReminder.php");
416 $reminder = new EmailReminder();
417 return $reminder->process();
420 function removeDocumentsFromFS()
422 $GLOBALS['log']->info('Starting removal of documents if they are not present in DB');
426 * @var SugarBean $bean
430 // temp table to store id of files without memory leak
431 $tableName = 'cron_remove_documents';
433 $resource = $db->limitQuery("SELECT * FROM cron_remove_documents WHERE 1=1 ORDER BY date_modified ASC", 0, 100);
435 while ($row = $db->fetchByAssoc($resource)) {
436 $bean = BeanFactory::getBean($row['module']);
437 $bean->retrieve($row['bean_id'], true, false);
438 if (empty($bean->id)) {
440 $bean->id = $row['bean_id'];
441 $directory = $bean->deleteFileDirectory();
442 if (!empty($directory) && is_dir('upload://deleted/' . $directory)) {
443 if ($isSuccess = rmdir_recursive('upload://deleted/' . $directory)) {
444 $directory = explode('/', $directory);
445 while (!empty($directory)) {
446 $path = 'upload://deleted/' . implode('/', $directory);
448 $directoryIterator = new DirectoryIterator($path);
450 foreach ($directoryIterator as $item) {
451 if ($item->getFilename() == '.' || $item->getFilename() == '..') {
461 array_pop($directory);
466 $db->query('DELETE FROM ' . $tableName . ' WHERE id=' . $db->quoted($row['id']));
471 $db->query('UPDATE ' . $tableName . ' SET date_modified=' . $db->convert($db->quoted(TimeDate::getInstance()->nowDb()), 'datetime') . ' WHERE id=' . $db->quoted($row['id']));
481 + * this will trim all records in sugarfeeds table that are older than 30 days or specified interval
484 function trimSugarFeeds()
486 global $sugar_config, $timedate;
487 $GLOBALS['log']->info('----->Scheduler fired job of type trimSugarFeeds()');
488 $db = DBManagerFactory::getInstance();
490 //get the pruning interval from globals if it's specified
491 $prune_interval = !empty($GLOBALS['sugar_config']['sugarfeed_prune_interval']) && is_numeric($GLOBALS['sugar_config']['sugarfeed_prune_interval']) ? $GLOBALS['sugar_config']['sugarfeed_prune_interval'] : 30;
494 //create and run the query to delete the records
495 $timeStamp = $db->convert("'". $timedate->asDb($timedate->getNow()->get("-".$prune_interval." days")) ."'" ,"datetime");
496 $query = "DELETE FROM sugarfeed WHERE date_modified < $timeStamp";
499 $GLOBALS['log']->info("----->Scheduler is about to trim the sugarfeed table by running the query $query");
507 function cleanJobQueue($job)
509 $td = TimeDate::getInstance();
510 // soft delete all jobs that are older than cutoff
512 if(isset($GLOBALS['sugar_config']['jobs']['soft_lifetime'])) {
513 $soft_cutoff = $GLOBALS['sugar_config']['jobs']['soft_lifetime'];
515 $soft_cutoff_date = $job->db->quoted($td->getNow()->modify("- $soft_cutoff days")->asDb());
516 $job->db->query("UPDATE {$job->table_name} SET deleted=1 WHERE status='done' AND date_modified < ".$job->db->convert($soft_cutoff_date, 'datetime'));
517 // hard delete all jobs that are older than hard cutoff
519 if(isset($GLOBALS['sugar_config']['jobs']['hard_lifetime'])) {
520 $hard_cutoff = $GLOBALS['sugar_config']['jobs']['hard_lifetime'];
522 $hard_cutoff_date = $job->db->quoted($td->getNow()->modify("- $hard_cutoff days")->asDb());
523 $job->db->query("DELETE FROM {$job->table_name} WHERE status='done' AND date_modified < ".$job->db->convert($hard_cutoff_date, 'datetime'));
527 if (file_exists('custom/modules/Schedulers/_AddJobsHere.php')) {
528 require('custom/modules/Schedulers/_AddJobsHere.php');
531 if (file_exists('custom/modules/Schedulers/Ext/ScheduledTasks/scheduledtasks.ext.php'))
533 require('custom/modules/Schedulers/Ext/ScheduledTasks/scheduledtasks.ext.php');