petitionemail.php 44.4 KB
Newer Older
1 2 3 4
<?php

require_once 'petitionemail.civix.php';

5 6 7 8 9
// You can define multiple pairs of target groups to
// matching field. This constant defines how many are 
// presented in the user interface.
define('PETITIONEMAIL_ALLOWED_GROUP_FIELD_COMBINATIONS_COUNT', 3);

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/**
 * Implementation of hook_civicrm_config
 */
function petitionemail_civicrm_config(&$config) {
  _petitionemail_civix_civicrm_config($config);
}

/**
 * Implementation of hook_civicrm_xmlMenu
 *
 * @param $files array(string)
 */
function petitionemail_civicrm_xmlMenu(&$files) {
  _petitionemail_civix_civicrm_xmlMenu($files);
}

/**
 * Implementation of hook_civicrm_install
 */
function petitionemail_civicrm_install() {
  return _petitionemail_civix_civicrm_install();
}

/**
 * Implementation of hook_civicrm_uninstall
 */
function petitionemail_civicrm_uninstall() {
37
  // Clear out our variables.
38
  petitionemail_remove_profiles();
39
  petitionemail_remove_variables();
40 41 42 43 44 45 46
  return _petitionemail_civix_civicrm_uninstall();
}

/**
 * Implementation of hook_civicrm_enable
 */
function petitionemail_civicrm_enable() {
47
  // Ensure the profile id is created.
48
  petitionemail_get_matching_fields_profile_id();
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
  return _petitionemail_civix_civicrm_enable();
}

/**
 * Implementation of hook_civicrm_disable
 */
function petitionemail_civicrm_disable() {
  return _petitionemail_civix_civicrm_disable();
}

/**
 * Implementation of hook_civicrm_upgrade
 *
 * @param $op string, the type of operation being performed; 'check' or 'enqueue'
 * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks
 *
 * @return mixed  based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending)
 *                for 'enqueue', returns void
 */
function petitionemail_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
  return _petitionemail_civix_civicrm_upgrade($op, $queue);
}

/**
 * Implementation of hook_civicrm_managed
 *
 * Generate a list of entities to create/deactivate/delete when this module
 * is installed, disabled, uninstalled.
 */
function petitionemail_civicrm_managed(&$entities) {
  return _petitionemail_civix_civicrm_managed($entities);
}
81

82 83 84
/**
 * Implemention of hook_civicrm_buildForm
 */
85 86 87 88
function petitionemail_civicrm_buildForm( $formName, &$form ) {
  if ($formName == 'CRM_Campaign_Form_Petition_Signature') {  
    $survey_id = $form->getVar('_surveyId');
    if ($survey_id) {
89 90
      $sql = "SELECT petition_id, 
                  default_message, 
91
                  subject, 
92
                  message_field, 
93
                  subject_field,
94 95 96 97 98
                  subject 
             FROM civicrm_petition_email 
             WHERE petition_id = %1";
      $params = array( 1 => array( $survey_id, 'Integer' ) );
      $dao = CRM_Core_DAO::executeQuery( $sql, $params );
99 100
      $defaults = array();
      $dao->fetch();
101 102 103 104
      if($dao->N == 0) {
        // Not a email enabled petition
        return;
      }
105
      $message_field = $dao->message_field;
106
      $subject_field = $dao->subject_field;
107
      $defaults[$message_field] = $dao->default_message;
108
      $defaults[$subject_field] = $dao->subject;
109
      $form->setDefaults($defaults);
110 111 112
    }
  }

113
  if ($formName == 'CRM_Campaign_Form_Petition') {
114
    CRM_Core_Resources::singleton()->addScriptFile('cc.tadpole.petitionemail', 'petitionemail.js');
115 116
    $survey_id = $form->getVar('_surveyId');
    if ($survey_id) {
117
      // Set default values for saved petitions.
118 119 120
      $sql = "SELECT petition_id, 
                default_message, 
                message_field, 
121
                subject_field,
122 123
                subject,
                recipients,
124
                location_type_id
125 126 127 128
              FROM civicrm_petition_email 
              WHERE petition_id = %1";
      $params = array( 1 => array( $survey_id, 'Integer' ) );
      $dao = CRM_Core_DAO::executeQuery( $sql, $params );
129
      $dao->fetch();
130 131 132 133 134
      if($dao->N > 0) {
        // Base table values.
        $defaults['email_petition'] = 1;
        $defaults['recipients'] = $dao->recipients;
        $defaults['default_message'] = $dao->default_message;
135
        $defaults['message_field'] = $dao->message_field;
136
        $defaults['subject_field'] = $dao->subject_field;
137 138 139 140
        $defaults['subject'] = $dao->subject;
        $defaults['location_type_id'] = $dao->location_type_id;
        
        // Now get matching fields.
141 142
        $sql = "SELECT matching_field, matching_group_id FROM
          civicrm_petition_email_matching_field WHERE petition_id = %1";
143 144
        $dao = CRM_Core_DAO::executeQuery($sql, $params);
        $matching_fields = array();
145
        $i = 1;
146
        while($dao->fetch()) {
147 148 149
          $defaults['matching_field' . $i] = $dao->matching_field;
          $defaults['matching_group_id' . $i] = $dao->matching_group_id;
          $i++;
150
        }
151 152 153 154
        // We have to build this URL by hand to avoid having the curly 
        // braces get escaped.
        $base_url = CIVICRM_UF_BASEURL . "civicrm/petition/sign?sid=$survey_id&reset=1";
        $personal_url = $base_url . '&{contact.checksum}&cid={contact.contact_id}';
155
        $defaults['links'] = ts("Personal link (use this link if you are sending it via CiviMail, it will auto fill with the user's address): ") . "\n" . 
156
          $personal_url . "\n\n" .  ts("General link: ") . $base_url;
157
        $form->setDefaults($defaults);
158
      }
159
    }
160 161 162 163 164 165 166
    else {
      $form->setDefaults(
        array(
          'links' => ts("Please save the petition first, then you can copy and paste the link to sign the petition.")
        )
      );
    }
167
    // Now add our extra fields to the form.
168
    $form->add('checkbox', 'email_petition', ts('Send an email to a target'));
169

170 171 172
    // Get the Profiles in use by this petition so we can find out
    // if there are any potential fields for an extra message to the
    // petition target.
173
    $params = array('module' => 'CiviCampaign', 
174
                    'entity_table' => 'civicrm_survey', 
175
                    'entity_id' => $survey_id,
176
                    'rowCount' => 0);
177
    $join_results = civicrm_api3('UFJoin','get', $params);
178
    $custom_fields = array();
179
    $profile_ids = array();
180 181
    if ($join_results['is_error'] == 0) {
      foreach ($join_results['values'] as $join_value) {
182
        $profile_ids[] = $join_value['uf_group_id'];
183 184
      }
    }
185
    $custom_fields = petitionemail_get_text_fields($profile_ids);
186
    
187
    $custom_field_options = array();
188
    if(count($custom_fields) == 0) {
189
      $custom_field_options = array(
190 191
        '' => t('- No Text or TextArea fields defined in your profiles -')
      );
192 193
    }
    else {
194 195
      $custom_field_options = array('' => t('- Select -'));
      $custom_field_options = $custom_field_options + $custom_fields;
196 197 198
    }
    $choose_one = array('0' => ts('--choose one--'));
    $group_options = $choose_one + CRM_Core_PseudoConstant::group('Mailing');
199 200
    $location_options = $choose_one + 
      CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
201

202
    $field_options = petitionemail_get_matching_field_options();
203
    $field_options_count = count($field_options);
204 205 206 207 208 209 210
    if($field_options_count == 0) {
      // No matching fields!
      $field_options[''] = ts("No fields are configured");
    }
    else {
      array_unshift($field_options, ts("--Choose one--"));
    }
211 212
    $form->assign('petitionemail_matching_fields_count', $field_options_count);
    $url_params = array(
213
      'gid' => petitionemail_get_matching_fields_profile_id(),
214 215 216 217
      'action' => 'browse'
    );
    $url = CRM_Utils_System::url("civicrm/admin/uf/group/field", $url_params);
    $form->assign('petitionemail_profile_edit_link', $url);
218 219 220 221 222 223 224

    $i = 1;
    while($i <= PETITIONEMAIL_ALLOWED_GROUP_FIELD_COMBINATIONS_COUNT) {
      $form->add('select', 'matching_group_id' . $i, ts('Matching Target Group'), $group_options);
      $form->add('select', 'matching_field' . $i, ts('Matching field(s)'), $field_options); 
      $i++;
    }
225 226
    $form->add('select', 'location_type_id', ts('Email'), $location_options);
    $form->add('textarea', 'recipients', ts("Send petitions to"));
227
    $form->add('select', 'message_field', ts('Custom Message Field'),
228 229 230
      $custom_field_options);
    $form->add('select', 'subject_field', ts('Custom Subject Field'),
      $custom_field_options);
231
    $form->add('textarea', 'default_message', ts('Default Message'), 'rows=20');
232
    $form->add('text', 'subject', ts('Default Email Subject Line'), array('size' => 70));
233
    $form->add('textarea', 'links', ts('Links to sign the petition'), 'rows=5')->freeze();
234 235
    
 
236 237 238
  }
}

239 240 241 242 243
/**
 * Get fields from the special petition email profile.
 *
 * Filter out un-supported fields.
 */
244
function petitionemail_get_matching_field_options() {
245 246
  $session = CRM_Core_Session::singleton();
  $ret = array();
247
  $uf_group_id = petitionemail_get_matching_fields_profile_id();
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
  $fields = CRM_Core_BAO_UFGroup::getFields($uf_group_id); 
  $allowed = petitionemail_get_allowed_matching_fields();
  if(is_array($fields)) {
    reset($fields);
    while(list($id, $value) = each($fields)) {
      $include = FALSE;
      // Check to see if it's a custom field
      if(preg_match('/^custom_/', $id)) {
        $ret[$id] = $value['title'];
        continue;
      }
      else {
        // Check to see if it's an address field
        $field_pieces = petitionemail_split_address_field($id);
        if($field_pieces) {
          if($field_pieces['location_name'] != 'Primary') {
            $session->setStatus(ts("Only primary address fields are support at this time."));
            continue;
          }
          if(array_key_exists($field_pieces['field_name'], $allowed)) {
            $ret[$id] = $value['title'];
            continue;
          }
        }
      }
      // Warn the user about a field that is not allowed
      $session->setStatus(ts("The field $id is not supported as a matching field at this time."));
    }
  }
277
  
278 279 280
  return $ret;
}

281 282 283 284 285 286
/**
 * Validate the petition form
 *
 * Ensure our values are consistent to avoid broken petitions.
 */
function petitionemail_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) {
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
  if ($formName == 'CRM_Campaign_Form_Petition_Signature') {  
    // Do some basic sanity checking to prevent spammers
    if(empty($form->_surveyId)) {
      // Can't do much without the survey_id
      return;
    }
    $survey_id = $form->_surveyId;

    // Check to see if it's an email petition
    $sql = "SELECT message_field FROM civicrm_petition_email WHERE
      petition_id = %0";
    $dao = CRM_Core_DAO::executeQuery($sql, array(0 => array($survey_id, 'Integer')));
    $dao->fetch();
    if($dao->N == 0) {
      // Nothing to do
      return;
    }

    if(!empty($dao->message_field)) {
      $field_name = 'custom_' . $dao->message_field;
      // If we are allowing a user-supplied message field, ensure it doesn't
      // have any URLs or HTML in it.
      if(array_key_exists($field_name, $fields)) {
        if(preg_match('#https?://#i', $fields[$field_name])) {
          $errors[$field_name] = ts("To avoid spammers, you are not allowed to put web addresses in your message. Please revise your message and try again.");
        }
        // Now ensure we have no html tag
        if (preg_match('/([\<])([^\>]{1,})*([\>])/i', $fields[$field_name] )) {
          $errors[$field_name] = ts("To avoid spammers, you are not allowed to put HTML code in your message. Please revise your message and try again.");
        }
      }
    }
  }

321 322
  if($formName == 'CRM_Campaign_Form_Petition') {
    if(CRM_Utils_Array::value('email_petition', $fields)) {
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
      // Make sure we have a subject field and a default message.
      if(!CRM_Utils_Array::value('subject', $fields)) {
        $msg = ts("You must enter an email subject line.");
        $errors['subject'] = $msg;
      }
      if(!CRM_Utils_Array::value('default_message', $fields)) {
        $msg = ts("You must enter a default message.");
        $errors['default_message'] = $msg;
      }
      // For each matching_group_id, make sure we have a corresponding
      // matching field. 
      $i = 1;
      $using_dynamic_method = FALSE;
      while($i <= PETITIONEMAIL_ALLOWED_GROUP_FIELD_COMBINATIONS_COUNT) {
        $matching_group_id = CRM_Utils_Array::value('matching_group_id' . $i, $fields);
        $matching_field = CRM_Utils_Array::value('matching_field' . $i, $fields);

        if(!empty($matching_group_id) && empty($matching_field)) {
341
          $msg = ts("If you select a matching target group you must select
342 343 344 345 346 347 348 349 350 351 352 353
            a corresponding matching field.");
          $errors['matching_field' . $i] = $msg; 
        }
        if(empty($matching_group_id) && !empty($matching_field)) {
          $msg = ts("If you select a matching field you must select a 
            corresponding matching target group.");
          $errors['matching_group_id' . $i] = $msg; 
        }

        // Keep track to see if there are using the dynamic method
        if(!empty($matching_group_id)) {
          $using_dynamic_method = TRUE;
354
        }
355
        $i++;
356
      }
357

358 359 360 361 362 363 364 365 366 367 368 369
      // If additional email targets have been provided, make sure they are
      // all syntactically correct.
      $recipients = CRM_Utils_Array::value('recipients', $fields);
      if(!empty($recipients)) {
        $recipient_array = explode("\n", $recipients);
        while(list(,$line) = each($recipient_array)) {
          if(FALSE === petitionemail_parse_email_line($line)) {
            $errors['recipients'] = ts("Invalid email address listed: %1.", array(1 => $line));
          }
        }
      }

370 371
      if(!$using_dynamic_method && empty($recipients)) {
        $msg = ts("You must select either one target matching group/field or list
372 373 374 375 376 377 378
          at least one address to send all petitions to.");
        $errors['recipients'] = $msg;
      }
    }
  }
}

379 380 381
/**
 * Given an array of profile ids, list all text area fields
 */
382
function petitionemail_get_text_fields($profile_ids) {
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
  // Now get all fields in this profile
  $custom_fields = array();
  while(list(,$uf_group_id) = each($profile_ids)) {
    $params = array('uf_group_id' => $uf_group_id, 'rowCount' => 0);
    $field_results = civicrm_api3('UFField', 'get', $params);
    if ($field_results['is_error'] == 0) {
      foreach ($field_results['values'] as $field_value) {
        $field_name = $field_value['field_name'];
        if(!preg_match('/^custom_[0-9]+/', $field_name)) {
          // We only know how to lookup field types for custom
          // fields. Skip core fields.
          continue;
        }

        $id = substr(strrchr($field_name, '_'), 1);
        // Finally, see if this is a text or textarea field.
        $params = array('id' => $id);
        $custom_results = civicrm_api3('CustomField', 'get', $params);
        if ($custom_results['is_error'] == 0) {
          $field_value = array_pop($custom_results['values']);
          $html_type = $field_value['html_type'];
          $label = $field_value['label'];
          $id = $field_value['id'];
          if($html_type == 'Text' || $html_type == 'TextArea') {
407
            $custom_fields['custom_' . $id] = $label;
408 409 410 411 412 413 414 415 416
          }
        }
      }
    }
  }
  return $custom_fields;
}


417
function petitionemail_civicrm_postProcess( $formName, &$form ) {
418 419 420
  if ($formName != 'CRM_Campaign_Form_Petition') { 
    return; 
  }
421 422
  $email_petition = CRM_Utils_Array::value('email_petition', $form->_submitValues);
  if($email_petition && $email_petition  == 1 ) {
423 424 425
    $survey_id = $form->getVar('_surveyId');
    $lastmoddate = 0;
    if (!$survey_id) {  // Ugly hack because the form doesn't return the id
426 427
      $params = array('title' =>$form->_submitValues['title']);
      $surveys = civicrm_api3("Survey", "get", $params);
428 429
      if (is_array($surveys['values'])) {
        foreach($surveys['values'] as $survey) {
430 431 432
          if ($lastmoddate > strtotime($survey['last_modified_date'])) { 
            continue; 
          }
433 434 435 436 437 438
          $lastmoddate = strtotime($survey['last_modified_date']);
          $survey_id = $survey['id'];
        }
      }
    }
    if (!$survey_id) {
439 440
      $msg = ts('Cannot find the petition for saving email delivery fields.');
      CRM_Core_Session::setStatus($msg);
441 442 443 444
      return;
    }

    $default_message =  $form->_submitValues['default_message'];
445
    $message_field = $form->_submitValues['message_field'];
446
    $subject_field = $form->_submitValues['subject_field'];
447 448 449
    $subject = $form->_submitValues['subject'];
    $recipients = $form->_submitValues['recipients'];
    $location_type_id = $form->_submitValues['location_type_id'];
450

451
    $sql = "REPLACE INTO civicrm_petition_email (
452 453 454
             petition_id,
             default_message, 
             message_field, 
455
             subject_field, 
456 457 458 459 460 461 462 463 464
             subject,
             recipients,
             location_type_id
           ) VALUES ( 
             %1, 
             %2, 
             %3, 
             %4,
             %5,
465 466
             %6,
             %7
467
    )";
468 469 470
    $params = array( 
      1 => array( $survey_id, 'Integer' ),
      2 => array( $default_message, 'String' ),
471
      3 => array( $message_field, 'String' ),
472 473 474 475
      4 => array( $subject_field, 'String' ),
      5 => array( $subject, 'String' ),
      6 => array( $recipients, 'String' ),
      7 => array( $location_type_id, 'Integer' ),
476 477
    );
    $petitionemail = CRM_Core_DAO::executeQuery( $sql, $params );
478
    
479
    // delete any existing ones
480 481
    $sql = "DELETE FROM civicrm_petition_email_matching_field WHERE
      petition_id = %0";
482 483
    $params = array(0 => array($survey_id, 'Integer'));
    CRM_Core_DAO::executeQuery($sql, $params);
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499

    $i = 1;
    while($i <= PETITIONEMAIL_ALLOWED_GROUP_FIELD_COMBINATIONS_COUNT) {
      $matching_group_id = CRM_Utils_Array::value('matching_group_id' . $i, $form->_submitValues);
      $matching_field = CRM_Utils_Array::value('matching_field' . $i, $form->_submitValues);
      if(!empty($matching_group_id) && !empty($matching_field)) {
        $sql = "INSERT INTO civicrm_petition_email_matching_field SET
          petition_id = %0, matching_field = %1, matching_group_id = %2";
        $params = array(
          0 => array($survey_id, 'Integer'),
          1 => array($matching_field, 'String'),
          2 => array($matching_group_id, 'Integer')
        );
        CRM_Core_DAO::executeQuery($sql, $params);
      }
      $i++;
500
    }
501 502 503
  }
}

504 505 506 507
/**
 * Implementation of hook_civicrm_post
 *
 * Run everytime a post is made to see if it's a new profile/activity
508 509
 * that should trigger a petition email to be sent. Also clean up 
 * our tables if a petition is deleted.
510
 */
511
function petitionemail_civicrm_post( $op, $objectName, $objectId, &$objectRef ) {
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
  static $profile_fields = NULL;
  if($objectName == 'Profile' && is_array($objectRef)) {
    // This is hacky but seems to be unavoidable. We really want to run 
    // on the activity post create hook. However, the activity post create
    // hook is called *before* the custom fields are saved for the activity
    // record. That means that none of the custom fields are available when
    // it is called, so we can't provide a custom subject or custom message
    // field to the petitionemail_process_signature function.
    //
    // However, the profile post hook is called before the activity post
    // hook is called. So, we set a static variable when the profile post
    // hook is called to save all the fields being submitted and then make
    // that available when the activity post hook is called.
    $profile_fields = $objectRef;
  }
527 528 529
  if ($objectName == 'Activity') {
    $activity_id = $objectId;

530
    // Only run on creation. For petitions that require a confirmation,
531 532 533
    // after the petition has been created, see petitionemail_civicrm_pageRun().
    if($op == 'create') {
      if(petitionemail_is_actionable_activity($activity_id)) {
534
        petitionemail_process_signature($activity_id, $profile_fields);
535 536
      }
    }
537
  }
538
}
539

540 541 542 543 544 545 546 547 548 549 550 551 552 553
/**
 * Implementation of hook_civicrm_pageRun
 */
function petitionemail_civicrm_pageRun(&$page) {
  // This should be fired after most of the parent run()
  // code is done, which means the activity status should
  // be converted to "complete" if it has been properly
  // verified.
  $pageName = $page->getVar('_name');
  if ($pageName == 'CRM_Campaign_Page_Petition_Confirm') { 
    // Get the activity id from the URL
    $activity_id  = CRM_Utils_Request::retrieve('a', 'String', CRM_Core_DAO::$_nullObject);
    if(petitionemail_is_actionable_activity($activity_id)) {
      petitionemail_process_signature($activity_id);
554 555 556
    }
  }
}
557

558 559
function petitionemail_get_petition_details($petition_id) {
  $ret = array();
Jamie McClelland's avatar
Jamie McClelland committed
560
  $sql = "SELECT default_message, 
561
               message_field, 
562
               subject_field,
563 564 565 566 567
               subject,
               location_type_id,
               recipients
         FROM civicrm_petition_email
         WHERE petition_id = %1 GROUP BY petition_id";
568
  $params = array( 1 => array( $petition_id, 'Integer' ) );
569 570
  $petition_email = CRM_Core_DAO::executeQuery( $sql, $params );
  $petition_email->fetch();
Jamie McClelland's avatar
Jamie McClelland committed
571
  if($petition_email->N == 0) {
572
    // Must not be a petition with a target.
573
    return FALSE;;
574
  }
575

576
  // Store variables we need
577 578 579 580
  $ret['default_message'] = $petition_email->default_message;
  $ret['subject'] = $petition_email->subject;
  $ret['location_type_id'] = $petition_email->location_type_id;
  $ret['message_field'] = $petition_email->message_field;
581
  $ret['subject_field'] = $petition_email->subject_field;
582
  $ret['recipients'] = $petition_email->recipients;
583

584
  // Now retrieve the matching fields, if any
585 586
  $sql = "SELECT matching_field, matching_group_id FROM
    civicrm_petition_email_matching_field WHERE petition_id = %1";
587
  $params = array( 1 => array( $petition_id, 'Integer' ) );
588
  $dao = CRM_Core_DAO::executeQuery($sql, $params);
589
  $ret['matching'] = array();
590
  while($dao->fetch()) {
591
    $ret['matching'][$dao->matching_field] = $dao->matching_group_id;
592
  }
593 594 595
  return $ret;
}

596 597 598 599 600 601 602 603
/**
 * This function handles all petition signature processing.
 *
 * @activity_id integer The activity id of the signature activity
 * @profile_fields array An array of fields submitted by the user, which
 *   may include the custom subject and custom message values.
 */
function petitionemail_process_signature($activity_id, $profile_fields = NULL) {
604 605 606 607 608 609 610
  $petition_id = petitionemail_get_petition_id_for_activity($activity_id);
  if(empty($petition_id)) {
    $log = "Failed to find petition id for activity id: $activity_id";
    CRM_Core_Error::debug_log_message($log);
    return FALSE;
  }
  $petition_vars = petitionemail_get_petition_details($petition_id);
611 612 613 614
  if(!$petition_vars) {
    // Nothing to process, this isn't an email target enabled petition
    return;
  }
615
  $default_message = $petition_vars['default_message'];
616
  $default_subject = $petition_vars['subject'];
617
  $message_field = $petition_vars['message_field'];
618
  $subject_field = $petition_vars['subject_field'];
619

620 621
  // Figure out whether to use the user-supplied message/subject or the default
  // message/subject.
622
  $petition_message = NULL;
623
  $subject = NULL;
624 625
  // If the petition has specified a message field
  if(!empty($message_field)) {
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
    // Check for a custom message field value in the passed in profile fields.
    // This field will be populated if we are operating on a new activity via
    // the post hook.
    if(is_array($profile_fields) && !empty($profile_fields[$message_field])) {
      $petition_message = $profile_fields[$message_field];
    }
    else {
      // Retrieve the value of the field for this activity (this may happen
      // if we are operating on a confirmation click from pageRun hook).
      $params = array(
        'id' => $activity_id, 
        'return' => $message_field
      );
      $result = civicrm_api3('Activity', 'getsingle', $params);
      if(!empty($result[$message_field])) {
        $petition_message = $result[$message_field];
      }
643 644
    }
  } 
645 646 647 648
  if(is_null($petition_message)) {
    $petition_message = $default_message;
  }

649 650
  // If the petition has specified a subject field
  if(!empty($subject_field)) {
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
    // Check for a custom subject field value in the passed in profile fields.
    // This field will be populated if we are operating on a new activity via
    // the post hook.
    if(is_array($profile_fields) && !empty($profile_fields[$subject_field])) {
      $subject = $profile_fields[$subject_field];
    }
    else {
      // Retrieve the value of the field for this activity (this may happen
      // if we are operating on a confirmation click from pageRun hook).
      $params = array(
        'id' => $activity_id, 
        'return' => $subject_field
      );
      $result = civicrm_api3('Activity', 'getsingle', $params);
      if(!empty($result[$subject_field])) {
        $subject = $result[$subject_field];
      }
668 669 670
    }
  }
  // No user supplied message/subject, use the default
671
  
672 673 674 675
  if(is_null($subject)) {
    $subject = $default_subject;
  }

676 677
  $activity = civicrm_api3("Activity", "getsingle", array ('id' => $activity_id));
  $contact_id = $activity['source_contact_id'];
678
  $contact = civicrm_api3("Contact", "getsingle", array ('id' => $contact_id));
679

680 681 682
  $from = NULL;
  if (array_key_exists('email', $contact) && !empty($contact['email'])) {
    $from = $contact['display_name'] . ' <' . $contact['email'] . '>';
683 684 685 686
  } else {
    $domain = civicrm_api3("Domain", "get", array ());
    if ($domain['is_error'] != 0 || !is_array($domain['values'])) { 
      // Can't send email without a from address.
687 688
      $msg = "petition_email: Failed to send petition email because from
        address not sent.";
689 690 691
      CRM_Core_Error::debug_log_message($msg);
      return; 
    }
692
    $from = '"' . $contact['display_name'] . '"' . ' <' .
693 694 695 696 697 698 699 700
      $domain['values']['from_email'] . '>';
  }

  // Setup email message (except to address)
  $email_params = array( 
    'from'    => $from,
    'toName'  => NULL,
    'toEmail' => NULL,
701
    'subject' => $subject,
702
    'text'    => $petition_message, 
703
    'html'    => NULL, 
704 705 706
  );

  // Get array of recipients
707
  $recipients = petitionemail_get_recipients($contact_id, $petition_id);
708 709
  while(list(, $recipient) = each($recipients)) {
    if(!empty($recipient['email'])) {
710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
      $log = "petition email: contact id ($contact_id) sending to email (" .
        $recipient['email'] . ")";
      CRM_Core_Error::debug_log_message($log);
      if(!empty($recipient['contact_id'])) {
        // Since we're sending to a recipient in the database, create this
        // as an email activity so we record it properly.

        $log = "petition email: recording email as activity against ".
          "target contact id: " . $recipient['contact_id'];
        CRM_Core_Error::debug_log_message($log);

        $contactDetails = array(0 => $recipient);
        // We are sending a text message, so ensure it's the preferred one
        $contactDetails[0]['preferred_mail_format'] = 'Text';
        $subject = $email_params['subject'];
        $text = $email_params['text'];
        $html = $email_params['html'];
        $emailAddress = $recipient['email'];
        $userID = $contact_id;
        $from = NULL; // This will be pulled from $contact_id,
        $attachments = NULL;
        $cc = NULL;
        $bcc = NULL;
        $contactIds = array($recipient['contact_id']);

        // Create/Send away.
        $ret = CRM_Activity_BAO_Activity::sendEmail(
          $contactDetails, $subject, $text, $html, $emailAddress, $userID,
          $from, $attachments, $cc, $bcc, $contactIds
        );
740

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784
        $activity_id = array_pop($ret);
        $status = array_pop($ret);
        if($status === TRUE) {
          $log = "petition email: email sent successfully";
          CRM_Core_Error::debug_log_message($log);

          // Update the activity with the petition id so we can properly
          // report on the email messages sent as a result of this petition.
          $params = array(
            'activity_id' => $activity_id,
            'source_record_id' => $petition_id
          );
          $result = civicrm_api3('Activity', 'update', $params);
          if($result['is_error'] != 0) {
            $log = "civicrm petition: failed to update activity with ".
              "source_record_id";
            CRM_Core_Error::debug_log_message($log);
          }
        }
        else {
          $log = "petition email: failed to send email as activity.";
          CRM_Core_Error::debug_log_message($log);
          CRM_Core_Error::debug_log_message(print_r($ret, TRUE));
        }
      }
      else {
        // Handle targets not in the database.
        $email_params['toName'] = $recipient['name'];
        $email_params['toEmail'] = $recipient['email'];
        $to = $email_params['toName'] . ' ' . $email_params['toEmail'];

        $log = "petition_email: sending petition to '$to' via mail function.";
        CRM_Core_Error::debug_log_message($log);

        $success = CRM_Utils_Mail::send($email_params);

        if($success == 1) {
          CRM_Core_Session::setStatus( ts('Message sent successfully to') . "$to", '', 'success' );
          $log = "petition_email: message sent.";
        } else {
          $log = "petition_email: message was not sent.";
          CRM_Core_Session::setStatus( ts('Error sending message to') . "$to" );
        }
        CRM_Core_Error::debug_log_message($log);
785 786 787 788 789
      }
    }
  }
}
 
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
/**
 * Non custom data fields allowed to be a matching field.
 *
 * All custom fields can be used as matching fields, but only
 * a subset of non-custom fields (so we can be sure to build
 * a working query to retrieve them).
 */
function petitionemail_get_allowed_matching_fields() {
  $ret = array(
    'street_name' => 'civicrm_address',
    'street_number' => 'civicrm_address',
    'street_name' => 'civicrm_address',
    'city' => 'civicrm_address',
    'county_id' => 'civicrm_address',
    'state_province_id' => 'civicrm_address',
    'postal_code' => 'civicrm_address',
    'postal_code_suffix' => 'civicrm_address',
    'country_id' => 'civicrm_address',
  );
  return $ret;
}

812 813
function petitionemail_get_recipients($contact_id, $petition_id) {
  $petition_vars = petitionemail_get_petition_details($petition_id);
814 815 816 817
  if(!$petition_vars) {
    // Not an email target enabled petition
    return;
  }
818 819 820 821 822 823 824 825 826
  $ret = array();
  // First, parse the additional recipients, if any. These get the email
  // regarldess of who signs it.
  if(!empty($petition_vars['recipients'])) {
    $recipients = explode("\n", $petition_vars['recipients']);
    while(list(,$recipient) = each($recipients)) {
      $email_parts = petitionemail_parse_email_line($recipient); 
      if(FALSE !== $email_parts) {
        $ret[] = array(
827
          'contact_id' => NULL,
828 829 830 831 832 833
          'name' => $email_parts['name'],
          'email' => $email_parts['email']
        );
      }
    }
  }
834 835 836
  // If there are any matching criteria (for a dynamic lookup) we do a
  // complex query to figure out which members of the group should be
  // included as recipients.
837
  if(count($petition_vars['matching']) > 0) {
838 839 840 841
    // This comes as an array with the key being the matching field and
    // the value being the matching_group_id.
    $matching_fields = $petition_vars['matching'];

842 843 844
    // Get the values of the matching fields for the contact. These values
    // are used to match the contact who signed the petition with the 
    // contact or contacts in the target group.
845 846 847 848 849 850

    // Given the matching fields, we're going to do an API call against
    // the contact to get the values that we will be matching on.

    // Build a return_fields array that we will pass to the api call to 
    // specify the fields we want returned with this query.
851
    $field_names = array_keys($matching_fields);
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
    $return_fields = array();
    reset($field_names);
    while(list(, $field_name) = each($field_names)) {
      // If the field_name starts with custom_ we can add it straight 
      // away.
      if(preg_match('/^custom_/', $field_name)) {
        $return_fields[] = $field_name;
        continue;
      }

      // Look for field names with a - in them - that's an indication 
      // that it's an address field which will have the location part
      // stuck into the name.
      $field_pieces = petitionemail_split_address_field($field_name);
      if($field_pieces) {
        if($field_pieces['location_name'] == 'Primary') {
          // Primary will be included via the api call, so we just need
          // the field name. If it's not primary, we'll have to do a 
          // manual SQL call below to get the value.
          $return_fields[] = $field_pieces['field_name'];
          continue;
        }
      }
875
      // FIXME If we get here, this is an error
876 877
    }
    $contact_params = array('return' => $return_fields, 'id' => $contact_id);
878 879
    $contact = civicrm_api3('Contact', 'getsingle', $contact_params);
    while(list($matching_field) = each($matching_fields)) {
880 881 882 883 884 885 886 887
      // Check if the field was returned. If not, it's probably an address field
      if(array_key_exists($matching_field, $contact)) {
        $matching_fields[$matching_field] = $contact[$matching_field];
        continue;
      }
      // This means it's probably an address field.
      $field_pieces = petitionemail_split_address_field($matching_field);
      if(!$field_pieces) {
888
        // FIXME This is an error
889 890 891 892 893 894 895
        continue;
      }
      $location_name = $field_pieces['location_name'];
      $field_name = $field_pieces['field_name'];
      if($location_name == 'Primary' && array_key_exists($field_name, $contact)) {
        // We have to unset the field that was saved as fieldname-locatiname
        unset($matching_fields[$matching_field]);
896

897 898 899 900 901 902
        // And now set the proper key
        $matching_field = $field_name;
        $matching_fields[$matching_field] = $contact[$matching_field];
        continue;
      }
      else {
903
        // FIXME This is an error
904 905 906
        continue;
      }
    } 
907 908

    // Initialize variables to build the SQL statement
909
    $from = array();
910 911
    // The master $where clause will be put together using AND
    $where = array();
912
    $params = array();
913
    $added_tables = array();
914

915 916
    // Initialize the from clause
    $from[] = 'civicrm_contact c';
917

918 919 920 921 922
    // We build a sub where clause that limits results based on the 
    // matching group and matching field that will be put together using
    // OR since we match any any of the matching field => group
    // combinations.
    $sub_where = array();
923
    reset($matching_fields);
924
    $id = 0;
925
    while(list($matching_field, $value) = each($matching_fields)) {
926 927 928 929 930 931 932 933 934 935 936 937 938 939
      // The $where_fragment will be put together using AND because
      // you have to match both the group and the field.
      $where_fragment = array();

      // Gather information about the group that is paired with this
      // matching field.
      $group_id = $petition_vars['matching'][$matching_field];
      // Retrieve details (specifically, find out if it's a smart group)
      $results = civicrm_api3('Group', 'getsingle', array('id' => $group_id));
      if(!empty($results['id'])) {
        if(!empty($results['saved_search_id'])) {
          // Populate the cache
          CRM_Contact_BAO_GroupContactCache::check($group_id);
          if(!in_array('civicrm_group_contact_cache', $added_tables)) {
940
            $from[] = 'LEFT JOIN civicrm_group_contact_cache cc ON
941 942 943 944 945 946
              c.id = cc.contact_id';
            $added_tables[] = 'civicrm_group_contact_cache';
          }
          $where_fragment[] = 'cc.group_id = %' . $id;
          $params[$id] = array($group_id, 'Integer');
          $id++;
947 948
        }
        else {
949
          if(!in_array('civicrm_group_contact', $added_tables)) {
950
            $from[] = 'LEFT JOIN civicrm_group_contact gc ON
951 952 953 954 955 956 957
              c.id = gc.contact_id';
            $added_tables[] = 'civicrm_group_contact';
          }
          $where_fragment[] = 'gc.group_id = %' . $id;
          $where_fragment[] = 'gc.status = "Added"';
          $params[$id] = array($group_id, 'Integer');
          $id++;
958
        }
959 960 961 962 963
      
        // Now add in the matching field
        if(empty($value)) {
          // We should never match in this case
          $where_fragment[] = "(0)";
964 965
        }
        else {
966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
          if(preg_match('/^custom_/', $matching_field)) {
            $sql = "SELECT column_name, table_name FROM civicrm_custom_group g 
              JOIN civicrm_custom_field f ON g.id = f.custom_group_id WHERE 
              f.id = %0";
            $custom_field_id = str_replace('custom_', '', $matching_field);
            $dao = CRM_Core_DAO::executeQuery($sql, array(0 => array($custom_field_id, 'Integer')));
            $dao->fetch();
            if(!in_array($dao->table_name, $added_tables)) {
              $from[] = "LEFT JOIN " . $dao->table_name . " ON " . $dao->table_name . ".entity_id = 
                c.id";
              $added_tables[] = $dao->table_name;
            }
            $where_fragment[] = $dao->column_name . ' = %' . $id;
            // Fixme - we should use the proper data type for each custom field
            $params[$id] = array($value, 'String');
            $id++;
          }
          else {
            // Handle non-custom fields (address fields)
            if(!in_array('civicrm_address', $added_tables)) {
              $from[] = "LEFT JOIN civicrm_address a ON a.contact_id = c.id";
              $added_tables[] = 'civicrm_address';
            }
            $field_where[] = '(' . $matching_field . ' = %' . $id . ')';
            $params[$id] = array($value, 'String');
            $id++;
          }
993
        }
994
        $sub_where[] = '(' . implode(' AND ', $where_fragment) . ')';
995
      }
996 997 998 999 1000 1001 1002
      else {
        // This is an error
      }
    }

    if(count($sub_where) > 0) {
      $where[] = '(' . implode(' OR ', $sub_where) . ')';
1003 1004 1005
    }

    // put it all together
1006
    $sql = "SELECT DISTINCT c.id, c.display_name ";
1007 1008
    $sql .= "FROM " . implode("\n", $from) . " ";
    $sql .= "WHERE " . implode(" AND\n", $where);
1009
    $dao = CRM_Core_DAO::executeQuery($sql, $params);
1010
    $location_type_id = $petition_vars['location_type_id'];
1011
    while($dao->fetch()) {
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
      // Lookup the best email address. 
      // ORDER BY FIELD allows us to arbitrarily set the location type id
      // we want to be set the highest.
      $sql = "SELECT e.email FROM civicrm_email e WHERE contact_id = %0 ".
        "AND (location_type_id = %1 OR is_primary = 1) ".
        "ORDER BY FIELD(e.location_type_id, %2) DESC, e.is_primary LIMIT 1";
      
      $email_params = array(
        0 => array($dao->id, 'Integer'),
        1 => array($petition_vars['location_type_id'], 'Integer'),
        2 => array($petition_vars['location_type_id'], 'Integer')
      );
      
      $email_dao = CRM_Core_DAO::executeQuery($sql, $email_params);
      $email_dao->fetch();
1027
      $ret[] = array(
1028
        'contact_id' => $dao->id,
1029
        'name' => $dao->display_name,
1030
        'email' => $email_dao->email
1031 1032 1033 1034 1035 1036
      );
    }
  }
  return $ret; 
}

1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058
/**
 * Split address field name
 * 
 * Field names in profiles are stored in the format
 * fieldname-locationame (e.g. postal_code-Primary).
 * This function breaks that string into the field name
 * and location name or returns FALSE if it's not a
 * location field.
 */
function petitionemail_split_address_field($field_name) {
  $ret = FALSE;
  if(preg_match('/([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)/', $field_name, $matches)) {
    if(!empty($matches[1]) && !empty($matches[2])) {
      $ret = array(
        'field_name' => $matches[1],
        'location_name' => $matches[2],
      );
    }
  }
  return $ret;
}

1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
/**
 * Convert name + email line into name and email parts
 *
 * Thanks: http://www.regular-expressions.info/email.html
 */
function petitionemail_parse_email_line($line) {
  $ret = array();
  $recipient = trim($line);
  // First attempt to extract a valid email address
  if(preg_match('/([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6})/i', $recipient, $matches)) {
    $email = $matches[1];
    // Now remove the matching email from the string, along with any <> characters
    $remainder = trim(str_replace(array($email, '>', '<'), '', $recipient)); 
    // Trim off any opening/closing quotes
    $name = trim($remainder, '"');
    $ret['name'] = $name;
    $ret['email'] = $email;
  }
  else {
    // Could not find an email address in there any where.
    $ret = FALSE;
  }
  return $ret;
}

1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
/**
 * Given an activity id, return the related petition id 
 *
 * Return FALSE if this is not an activity that is a petition
 * signature. 
 */
function petitionemail_get_petition_id_for_activity($activity_id) {
  // If there is a related civicrm_petition_email record, we are good to go.
  // NOTE: source_record_id stores the survey_id which is the same thing
  // as the petition_id for our purposes.
  $sql = "SELECT a.source_record_id FROM civicrm_activity a JOIN
    civicrm_petition_email pe ON a.source_record_id = pe.petition_id
    WHERE a.id = %0";
  $params = array(0 => array($activity_id, 'Integer'));
  $dao = CRM_Core_DAO::executeQuery($sql, $params);
  $dao->fetch();
  if($dao->N == 0) return FALSE;
  return $dao->source_record_id; 
}

/**
 * Ensure activity_id should generate an email 
 *
 * Should have a related petition_email record and should have
 * a status of complete and should have a date.
 */
function petitionemail_is_actionable_activity($activity_id) {
  if(!petitionemail_get_petition_id_for_activity($activity_id)) {
    return FALSE;
1113
  }
1114 1115 1116 1117 1118 1119 1120
  $completed = CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name');
  $sql = "SELECT id FROM civicrm_activity WHERE id = %0 AND status_id = %1";
  $params = array(0 => array($activity_id, 'Integer'), 1 => array($completed, 'Integer'));
  $dao = CRM_Core_DAO::executeQuery($sql, $params);
  $dao->fetch();
  if($dao->N == 0) return FALSE;
  return TRUE;
1121
}
1122

1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
/**
 * Helper function to get or create required profile
 *
 * This profile controls which fields can be used to match a petition 
 * signer with a petition target. We use a profile to avoid having a
 * giant list of fields presented to the user.
 *
 * This function ensures that the profile is created and if not, it
 * creates it. 
 *
 * @return integer profile id 
 */
1135
function petitionemail_get_matching_fields_profile_id() {
1136
  $group = 'petitionemail';
1137
  $key = 'petitionemail_matching_fields';
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
  $ret = CRM_Core_BAO_Setting::getItem($group, $key);
  if(!empty($ret)) {
    // Ensure it exists
    $sql = "SELECT id FROM civicrm_uf_group WHERE id = %0";
    $dao = CRM_Core_DAO::executeQuery($sql, array(0 => array($ret, 'Integer')));
    $dao->fetch();
    if($dao->N == 1) {
      return $ret;
    }
    // Delete this variable - probably the user deleted the profile not knowing
    // what it was used for.
    $sql = "DELETE FROM civicrm_setting WHERE group_name = %0 AND name = %1";
    $params = array(
      0 => array($group, 'String'),
      1 => array($key, 'String')
    );
    CRM_Core_DAO::executeQuery($sql, $params);
  }

  // Create the profile
  // We have to manually set created_id if the current user is not set
  $session = CRM_Core_Session::singleton();
  $contact_id = $session->get('userID');
  if(empty($contact_id)) {
    // Maybe we are running via drush?
    // Try the contact associated with uid 1
    $contact_id = CRM_Core_BAO_UFMatch::getContactId(1);
    if(empty($contact_id)) {
      // Last ditch effort
      $sql = "SELECT MIN(id) FROM civicrm_contact WHERE is_active = 1 AND is_deleted = 0";
      $dao = CRM_Core_DAO::executeQuery($sql);
      $dao->fetch();
      $contact_id = $dao->id;
    }
  }

  $description = ts('This profile controls which fields are available as
    matching fields when using the petition email extension. Please do
    not delete this profile.');
  $params = array(
1178
    'name' => $key,
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
    'title' => ts('Petition Email Available Matching fields'),
    'description' => $description,
    'created_id' => $contact_id
  );
  $results = civicrm_api3('UFGroup', 'create', $params);
  if($results['is_error'] != 0) {
    $session->setStatus(ts("Error creating the petition email profile group."));
    return FALSE;
  }
  $value = array_pop($results['values']);
  $id = $value['id'];

  CRM_Core_BAO_Setting::setItem($id, $group, $key);
  return $id;
}

1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
/**
 * Remove any profiles we automatically created.
 */
function petitionemail_remove_profiles() {
  $profiles_to_remove = array('petitionemail_matching_fields');
  while(list(,$key) = each($profiles_to_remove)) {
    $group = 'petitionemail';
    $ret = CRM_Core_BAO_Setting::getItem($group, $key);
    if($ret) {
      // Get a list of existing profile fields and remove those 
      // first.
      $params = array(
        'uf_group_id' => $ret,
        'return' => array('id')
      );
      $results = civicrm_api3('UFField', 'get', $params);
      if(is_array($results['values'])) {
        while(list($id) = each($results['values'])) {
          $params = array('id' => $id);
          civicrm_api3('UFField', 'delete', $params);
        }
      }
      $params = array('id' => $ret);
      civicrm_api3('UFGroup', 'delete', $params);
    }
  }
}

1223 1224 1225 1226
/**
 * Helper to remove any extension created variables
 */
function petitionemail_remove_variables() {
1227
  $group = 'petitionemail';
1228 1229 1230 1231 1232 1233
  $sql = "DELETE FROM civicrm_setting WHERE group_name = %0";
  $params = array(
    0 => array($group, 'String')
  );
  CRM_Core_DAO::executeQuery($sql, $params);
}