Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
bulkmail_manager.inc
1 <?php
17 require_once SQ_PACKAGES_PATH.'/bulkmail/bulkmail_post_office/bulkmail_post_office.inc';
18 require_once SQ_PACKAGES_PATH.'/bulkmail/bulkmail_constant.inc';
19 require_once SQ_FUDGE_PATH.'/general/www.inc';
20 
21 
36 {
37 
38 
45  function __construct($assetid=0)
46  {
47  $this->_ser_attrs = TRUE;
48  parent::__construct($assetid);
49 
50  }//end constructor
51 
52 
61  function _getName($short_name=FALSE)
62  {
63  return $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name');
64 
65  }//end _getName()
66 
67 
77  function create(&$link)
78  {
79  require_once SQ_CORE_PACKAGE_PATH.'/system/system_asset_fns.inc';
80  if (!system_asset_fns_create_pre_check($this)) {
81  return FALSE;
82  }
83  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
84  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
85 
86  if ($linkid = parent::create($link)) {
87  if (!system_asset_fns_create_cleanup($this)) {
88  $linkid = FALSE;
89  }
90  }
91 
92  if ($linkid) {
93  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
94  } else {
95  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
96  }
97 
98  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
99  return $linkid;
100 
101  }//end create()
102 
103 
111  function _getAllowedLinks()
112  {
113  return Array(
114  SQ_LINK_TYPE_1 => Array(),
115  SQ_LINK_TYPE_2 => Array(),
116  SQ_LINK_TYPE_3 => Array(),
117  SQ_LINK_NOTICE => Array(
118  'image' => Array('card' => 1, 'exclusive' => FALSE),
119  'design' => Array('card' => 'M', 'exclusive' => FALSE),
120  ),
121  );
122 
123  }//end _getAllowedLinks()
124 
125 
136  function canMoveLink(&$minor, &$old_major, $link_type)
137  {
138  return FALSE;
139 
140  }//end canMoveLink()
141 
142 
151  function deleteLink($linkid)
152  {
153  trigger_localised_error('CORE0118', E_USER_WARNING, $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name'));
154  return FALSE;
155 
156  }//end deleteLink()
157 
158 
165  function canDelete()
166  {
167  return FALSE;
168 
169  }//end canDelete()
170 
171 
178  function canClone()
179  {
180  return FALSE;
181 
182  }//end canClone()
183 
184 
185 //-- JOB --//
186 
187 
225  function createAdHocJob($from, $recipients, $subject, $content_id, $content_gen_as, $headers=Array(), $post_office_id=0, $server_details=Array(), $design_id=0, $layout_id=0, $subscriptions=Array())
226  {
227  $am = $GLOBALS['SQ_SYSTEM']->am;
228 
229  if (!is_array($recipients)) {
230  $recipients = Array($recipients);
231  }
232 
233  $recipients = $am->assetExists($recipients);
234  if (empty($recipients)) {
235  trigger_localised_error('BML0010', E_USER_ERROR);
236  return FALSE;
237  }
238 
239  $am->includeAsset('bulkmail_job');
240  $bulkmail_job = new Bulkmail_Job();
241 
242  $bulkmail_job->setAttrValue('from', $from);
243  $bulkmail_job->setAttrValue('recipients', $recipients);
244  $bulkmail_job->setAttrValue('subject', $subject);
245  $bulkmail_job->setAttrValue('content_id', $content_id);
246  $bulkmail_job->setAttrValue('content_gen_as', $content_gen_as);
247  $bulkmail_job->setAttrValue('header_details', $headers);
248 
249  $bulkmail_job->setAttrValue('content_design', $design_id);
250  $bulkmail_job->setAttrValue('content_layout', $layout_id);
251 
252  $subscription_status = empty($subscriptions) ? FALSE : TRUE;
253  $bulkmail_job->setAttrValue('user_subscriptions_status', $subscription_status);
254  $bulkmail_job->setAttrValue('subscriptions', $subscriptions);
255 
256  $post_office = NULL;
257  if (empty($post_office_id) && !empty($server_details)) {
258  $post_office = new Bulkmail_Post_Office();
259  $post_office->setAttrValue('server_details', $server_details);
260  } else if ($post_office_id) {
261  $post_office = $am->getAsset($post_office_id, 'bulkmail_post_office');
262  if (is_null($post_office)) return FALSE;
263  }
264 
265  return $this->addJob($bulkmail_job, $post_office);
266 
267  }//end createAdHocJob()
268 
269 
281  function addJob($job, $post_office=NULL)
282  {
283  // if no post office is supplied, work out which one to use
284  if (is_null($post_office)) {
285  if ($job->id == 0) {
286  $post_office = $this;
287  } else {
288  $post_office = $job->getPostOffice();
289  }
290  }
291 
292  // validate normal/ad-hoc jobs before adding entry to db
293  $details_info = $this->generateJobDetails($job, $post_office);
294  $errors = $this->isValidJob($details_info, TRUE);
295  if (!empty($errors)) {
296  trigger_localised_error('BML0001', E_USER_WARNING, $errors[0]);
297  return FALSE;
298  }
299 
300  // work out the unique id for this job in the db table
301  if ($job->id == 0) {
302  // keep generating an ID until we find a new one (probably straight away)
303  do {
304  $unique_id = $post_office->id.':'.md5(uniqid(''));
305  $data_dir = $this->getJobDataPath($unique_id);
306  } while (is_dir($data_dir));
307  } else {
308  $unique_id = $job->id;
309  $data_dir = $this->getJobDataPath($unique_id);
310 
311  // purge and refresh dir
312  if (is_dir($data_dir)) delete_directory($data_dir);
313  }
314 
315  // store progress and details information in files
316  create_directory($data_dir);
317  $this->initProgressFile($data_dir);
318  $this->initDetailsFile($details_info, $data_dir);
319 
320  // add a new entry to the database table
321  try {
322  $bind_vars = Array(
323  'id' => $unique_id,
324  'po_id' => $post_office->id,
325  'status' => BML_JOB_STATE_NOT_RUNNING,
326  );
327  $result = MatrixDAL::executeQuery('bulkmail_package', 'addJob', $bind_vars);
328  } catch (Exception $e) {
329  throw new Exception("Unable to add bulkmail job (#$unique_id) due to database error: ".$e->getMessage());
330  }
331 
332  return TRUE;
333 
334  }//end addJob()
335 
336 
347  function deleteJob($job_id)
348  {
349  try {
350  $bind_vars['job_id'] = $job_id;
351  $result = MatrixDAL::executeQuery('bulkmail_package', 'deleteJob', $bind_vars);
352  } catch (Exception $e) {
353  throw new Exception("Unable to delete bulkmail job (#$job_id) due to database error: ".$e->getMessage());
354  }
355 
356  // purge the data dir if it is an ad-hoc job
357  $job_id_parts = explode(':', $job_id);
358  if (isset($job_id_parts[1])) {
359  $post_office = $GLOBALS['SQ_SYSTEM']->am->getAsset($job_id_parts[0]);
360  $path = $post_office->data_path.'/.data/'.$job_id_parts[1];
361  if (is_dir($path)) delete_directory($path);
362  }
363 
364  return TRUE;
365 
366  }//end deleteJob()
367 
368 
378  function updateJob($job_id, $state)
379  {
380 
381  // set last_updated to zero to skip timeout checking when we resume the job
382  if ($state == BML_JOB_STATE_NOT_RUNNING ) {
383  $progress_info = $this->getJobProgress($job_id);
384  $progress_info['last_updated'] = 0;
385  $data_dir = $this->getJobDataPath($job_id);
386  if (!array_to_file($progress_info, 'progress_info', $data_dir.'/progress_info')) {
387  trigger_localised_error('BML0008', E_USER_WARNING, $data_dir.'/progress_info');
388  return FALSE;
389  }
390  }
391 
392  try {
393  $bind_vars = Array(
394  'status' => $state,
395  'job_id' => $job_id,
396  );
397  $result = MatrixDAL::executeQuery('bulkmail_package', 'updateJob', $bind_vars);
398  } catch (Exception $e) {
399  throw new Exception("Unable to update bulkmail job (#$job_id) due to database error: ".$e->getMessage());
400  }
401  return TRUE;
402 
403  }//end updateJob()
404 
405 
415  function getQueuedJobs($job_id=NULL, $post_office_id=NULL)
416  {
417  if (is_null($job_id) && is_null($post_office_id)) {
418  try {
419  $results = MatrixDAL::executeGrouped('bulkmail_package', 'getAllQueuedJobs');
420  } catch (Exception $e) {
421  throw new Exception('Unable to get queued bulkmail jobs: '.$e->getMessage());
422  }
423  } else if (!is_null($job_id)) {
424  try {
425  $bind_vars['job_id'] = $job_id;
426  $results = MatrixDAL::executeGrouped('bulkmail_package', 'getQueuedJobsByJob', $bind_vars);
427  } catch (Exception $e) {
428  throw new Exception('Unable to get queued bulkmail jobs: '.$e->getMessage());
429  }
430  } else if (!is_null($post_office_id)) {
431  try {
432  $bind_vars['post_office_id'] = $post_office_id;
433  $results = MatrixDAL::executeGrouped('bulkmail_package', 'getQueuedJobsByPostOffice', $bind_vars);
434  } catch (Exception $e) {
435  throw new Exception('Unable to get queued bulkmail jobs: '.$e->getMessage());
436  }
437  }
438 
439  // add progress/details info to each job result
440  foreach ($results as $job_id => $result) {
441  $job_id_actual = strtok($job_id, ':');
442  if ($GLOBALS['SQ_SYSTEM']->am->assetExists($job_id_actual)) {
443  $progress_info = $this->getJobProgress($job_id);
444  $results[$job_id]['progress'] = $progress_info;
445  $details_info = $this->getJobDetails($job_id);
446  $results[$job_id]['details'] = $details_info;
447  // do this since we cannot use executeGroupAssoc
448  $results[$job_id]['po_id'] = $result[0][0];
449  $results[$job_id]['status'] = $result[0][1];
450  } else {
451  // if this job has been removed, then delete it from the bmail queue too
452  $this->deleteJob($job_id);
453  }
454  }
455 
456  return $results;
457 
458  }//end getQueuedJobs()
459 
460 
469  function getJobDataPath($job_id)
470  {
471  $job_id_parts = explode(':', $job_id);
472 
473  if (isset($job_id_parts[1])) {
474  $post_office = $GLOBALS['SQ_SYSTEM']->am->getAsset($job_id_parts[0]);
475  $path = $post_office->data_path.'/.data/'.$job_id_parts[1];
476  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($job);
477  } else {
478  $job = $GLOBALS['SQ_SYSTEM']->am->getAsset($job_id);
479  $post_office = $job->getPostOffice();
480  $path = $post_office->data_path.'/.data/'.$job_id;
481  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($job);
482  }
483  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($post_office);
484 
485  return $path;
486 
487  }//end getJobDataPath()
488 
489 
498  function &getPostOffice($job_id)
499  {
500  $job_id_parts = explode(':', $job_id);
501  if (isset($job_id_parts[1])) {
502  $post_office = $GLOBALS['SQ_SYSTEM']->am->getAsset($job_id_parts[0]);
503  } else {
504  $job = $GLOBALS['SQ_SYSTEM']->am->getAsset($job_id, 'bulkmail_job');
505  $post_office = $job->getPostOffice();
506  }
507 
508  return $post_office;
509 
510  }//end getPostOffice()
511 
512 
513 //-- PROGRESS --//
514 
515 
526  function initProgressFile($data_dir)
527  {
528  /*
529  available fields:
530  current_count processed this number of mails
531  current_recip_id assetid of the current recipient/user
532  keep track of this as recipients are not in numerical order
533  current_chunk currently up to this chunk of recipient
534  last_updated timestamp of when we last added a mail to the queue
535  total_count total number of mails to send (to be updated when chunking recipient)
536  time_per_chunk to be used for thresholding the send rate rule
537  problematic an array of problematic recipient ids
538  */
539  $progress_info = Array(
540  'current_count' => 0,
541  'current_recip_id' => 0,
542  'current_chunk' => 0,
543  'last_updated' => 0,
544  'total_count' => 0,
545  'time_per_chunk' => 0,
546  'problematic' => Array(),
547  );
548 
549  if (!array_to_file($progress_info, 'progress_info', $data_dir.'/progress_info')) {
550  trigger_localised_error('BML0007', E_USER_WARNING, $data_dir.'/progress_info');
551  return FALSE;
552  } else {
553  return TRUE;
554  }
555 
556  }//end initProgressFile()
557 
558 
567  function getJobProgress($job_id)
568  {
569  $job_path = $this->getJobDataPath($job_id);
570  $progress_path = $job_path.'/progress_info';
571  $progress_info = Array();
572  if (file_exists($progress_path)) {
573  include $progress_path;
574  }
575 
576  return $progress_info;
577 
578  }//end getJobProgress()
579 
580 
590  function generateJobDetails($job, $post_office)
591  {
592  $details_info = Array();
593 
594  // Pass the ID of the Job, so we can load the job back up to generate the content
595  $details_info['job_id'] = $job->id;
596 
597  foreach ($job->vars as $var_name => $var_info) {
598  switch ($var_name) {
599  case 'from' :
600  case 'subject' :
601  case 'header_details':
602  if ($job->attr('use_post_office_header')) {
603  $details_info[$var_name] = $post_office->attr($var_name);
604  } else {
605  $details_info[$var_name] = $job->attr($var_name);
606  }
607  break;
608  case 'server_details':
609  $details_info[$var_name] = $post_office->attr($var_name);
610  break;
611  case 'threshold':
612  // whether default post office overrides the threshold settings
613  if ($this->attr('use_bm_threshold')) {
614  $details_info[$var_name] = $this->attr($var_name);
615  } else {
616  $details_info[$var_name] = $post_office->attr($var_name);
617  }
618  break;
619  case 'bulkmail_mode':
620  // Select the bulkmail mode
621  $mode = $post_office->attr('bulkmail_mode');
622  if (!empty($mode)) {
623  $details_info[$var_name] = $mode;
624  } else {
625  $details_info[$var_name] = $this->attr($var_name);
626  }
627  break;
628  default:
629  $details_info[$var_name] = $job->attr($var_name);
630  break;
631  }
632  }
633 
634  return $details_info;
635 
636  }//end generateJobDetails()
637 
638 
648  function initDetailsFile($details_info, $data_dir)
649  {
650  // mail queue options
651  $details_info['queue_details'] = Array(
652  'type' => 'flatfile',
653  'dir' => $data_dir.'/queue',
654  );
655 
656  if (!array_to_file($details_info, 'details_info', $data_dir.'/details_info')) {
657  trigger_localised_error('BML0007', E_USER_WARNING, $data_dir.'/details_info');
658  return FALSE;
659  } else {
660  return TRUE;
661  }
662 
663  }//end initDetailsFile()
664 
665 
674  function getJobDetails($job_id)
675  {
676  $job_path = $this->getJobDataPath($job_id);
677  $details_path = $job_path.'/details_info';
678  $details_info = Array();
679  if (file_exists($details_path)) include $details_path;
680 
681  return $details_info;
682 
683  }//end getJobDetails()
684 
685 
697  function isValidJob($details_info, $report_error=FALSE)
698  {
699  $errors = Array();
700 
701  // cannot create a PEAR mail instance
702  $driver = array_get_index($details_info['server_details'], 'driver', '');
703  $mf = new Mail();
704  $mail_object = $mf->factory($driver, $details_info['server_details']);
705  if ($mail_object instanceof PEAR_Error) {
706  $errors[] = translate('bm_warning_server_details_error', $mail_object->getMessage());
707  }
708  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($mail_object);
709 
710  // no recipient
711  $recipients = $details_info['recipients'];
712  if (empty($recipients)) {
713  $errors[] = translate('bm_warning_no_recipient');
714  }
715 
716  // no content page
717  $public_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('public_user');
718  if (empty($details_info['content_id'])) {
719  $errors[] = translate('bm_warning_no_asset_to_send');
720  } else {
721  // page is not live
722  $content_page = $GLOBALS['SQ_SYSTEM']->am->getAsset($details_info['content_id']);
723  if ($content_page->status != SQ_STATUS_LIVE) {
724  $errors[] = translate('bm_warning_asset_to_send_not_live', $content_page->name, $content_page->id);
725  } else {
726  // no pre-selected user
727  if (empty($details_info['content_gen_as'])) {
728  $errors[] = translate('bm_warning_no_generate_as');
729  } else {
730  // cannot login as pre-selected user, is in trash or not live
731  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($details_info['content_gen_as']);
732  if (!($user instanceof Public_User) && !$user->canLogin()) {
733  $errors[] = translate('bm_warning_generate_as_login_error', $user->name, $user->id);
734  }
735  // pre-selected user does not have (effective) read permission for content page
736  $ids = $GLOBALS['SQ_SYSTEM']->am->getParents($user->id, 'user_group', FALSE);
737  $ids[$user->id] = 'content_generate_as';
738  $content_gen_as_read_access = $content_page->readAccess(array_keys($ids));
739  $public_read_access = $content_page->readAccess(Array($public_user->id));
740  if (!$content_gen_as_read_access && !$public_read_access) {
741  $errors[] = translate('bm_warning_generate_as_no_read_access', $user->name, $user->id, $content_page->name, $content_page->id);
742  }
743  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($user);
744  }
745  }
746 
747  // check recipient read access (not expanding groups)
748  $problematic_list = Array();
749  $permission_denied = $GLOBALS['SQ_SYSTEM']->am->getPermission($details_info['content_id'], SQ_PERMISSION_READ, FALSE, FALSE);
750  $public_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('public_user');
751  $public_user_id = $public_user->id;
752  foreach ($recipients as $recipient_key => $recipient_id) {
753  if (isset($recipient_id['email']) && preg_match('/(.*)\@(.*)/is', $recipient_id['email'])) {
754  // If the recipient is an email address, check for public permission
755  if (!$content_page->readAccess(Array($public_user_id))) {
756  $problematic_list[] = $recipients[$recipient_key];
757  }//end if
758  } else {
759  // get parent user group and get the effective permission on the asset to send
760  $ids = $GLOBALS['SQ_SYSTEM']->am->getParents($recipient_id, 'user_group', FALSE);
761  $ids[$recipient_id] = 'recipient_id';
762  if (!$content_page->readAccess(array_keys($ids))) {
763  $problematic_list[] = $recipient_id;
764  }
765  if (in_array($recipient_id, $permission_denied)) {
766  $problematic_list[] = $recipient_id;
767  }
768  }//end if
769  }//end foreach
770  if (!empty($problematic_list)) {
771  asort($problematic_list);
772  $errors[] = translate('bm_warning_recipient_no_read_access', $content_page->name, $content_page->id, implode(', ', $problematic_list));
773  }
774 
775  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($content_page);
776  }//end else
777 
778  // design does not have public read access
779  if (!empty($details_info['content_design'])) {
780  $design = $GLOBALS['SQ_SYSTEM']->am->getAsset($details_info['content_design']);
781  $public_read_access = $design->readAccess(Array($public_user->id));
782  if (!$public_read_access) {
783  $errors[] = translate('bm_warning_no_public_read_access', $design->name, $design->id);
784  }
785  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($design);
786  }
787 
788  // layout does not have public read access
789  if (!empty($details_info['content_layout'])) {
790  $layout = $GLOBALS['SQ_SYSTEM']->am->getAsset($details_info['content_layout']);
791  $public_read_access = $layout->readAccess(Array($public_user->id));
792  if (!$public_read_access) {
793  $errors[] = translate('bm_warning_no_public_read_access', $layout->name, $layout->id);
794  }
795  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($layout);
796  }
797 
798  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($public_user);
799 
800  // no 'from' field
801  if (empty($details_info['from'])) {
802  $errors[] = translate('bm_warning_no_from_field');
803  // 'from' field is not a valid email address
804  } else {
805  if (!valid_email($details_info['from'])) {
806  $errors[] = translate('bm_warning_from_field_invalid_email');
807  }
808  }
809 
810  // no 'subject' field
811  if (empty($details_info['subject'])) {
812  $errors[] = translate('bm_warning_no_subject_field');
813  }
814 
815  if ($report_error) {
816  return $errors;
817  } else {
818  return (empty($errors) ? TRUE : FALSE);
819  }
820 
821  }//end isValidJob()
822 
823 
824 }//end class
825 ?>