/**
  * generate a transaction message for the given mandate/creditor
  *
  * @return a SEPA compliant transaction message
  */
 static function getTransactionMessage($mandate, $creditor)
 {
     // get tx message from settings
     $transaction_message = self::getSetting('custom_txmsg', $creditor['id']);
     // run hook for further customisation
     CRM_Utils_SepaCustomisationHooks::modify_txmessage($transaction_message, $mandate, $creditor);
     // fallback is "Thanks."
     if (empty($transaction_message)) {
         $transaction_message = ts("Thank you");
     }
     // make sure that it doesn't contain any special characters
     $transaction_message = preg_replace("#[^a-zA-Z0-9\\/\\-\\:\\(\\)\\'\\+ \\.\\*]#", '?', $transaction_message);
     return $transaction_message;
 }
 /**
  * @param array  $params         (reference ) an assoc array of name/value pairs
  *
  * @return object       CRM_Core_BAO_SEPAMandate object on success, null otherwise
  * @access public
  * @static (I do apologize, I don't want to)
  */
 static function add(&$params)
 {
     // handle 'normal' creation process inlcuding hooks
     $hook = empty($params['id']) ? 'create' : 'edit';
     CRM_Utils_Hook::pre($hook, 'SepaMandate', CRM_Utils_Array::value('id', $params), $params);
     // set default date to today
     if (!array_key_exists("date", $params)) {
         $params["date"] = date("YmdHis");
     }
     if (empty($params['id'])) {
         CRM_Utils_SepaCustomisationHooks::create_mandate($params);
         if (empty($params['reference'])) {
             // If no mandate reference was supplied by the caller nor the customisation hook, create a nice default one.
             $creditor = civicrm_api3('SepaCreditor', 'getsingle', array('id' => $params['creditor_id'], 'return' => 'mandate_prefix'));
             $dao = new CRM_Core_DAO();
             $database = $dao->database();
             $next_id = CRM_Core_DAO::singleValueQuery("SELECT auto_increment FROM information_schema.tables WHERE table_schema='{$database}' and table_name='civicrm_sdd_mandate';");
             $params['reference'] = $creditor['mandate_prefix'] . '-' . $params['creditor_id'] . '-' . $params['type'] . '-' . date("Y") . '-' . $next_id;
         }
     }
     // validate IBAN / BIC
     if (!empty($params['iban'])) {
         $params['iban'] = strtoupper($params['iban']);
         // create uppercase string
         $params['iban'] = str_replace(' ', '', $params['iban']);
         // strip spaces
         $iban_error = CRM_Sepa_Logic_Verification::verifyIBAN($params['iban']);
         if ($iban_error) {
             throw new CRM_Exception($iban_error . ':' . $params['iban']);
         }
     }
     if (!empty($params['bic'])) {
         $bic_error = CRM_Sepa_Logic_Verification::verifyBIC($params['bic']);
         if ($bic_error) {
             throw new CRM_Exception($bic_error . ':' . $params['bic']);
         }
     }
     // create the DAO object
     $dao = new CRM_Sepa_DAO_SEPAMandate();
     $dao->copyValues($params);
     if (self::is_active(CRM_Utils_Array::value('status', $params))) {
         if ($dao->validation_date == NULL) {
             $dao->validation_date = date("YmdHis");
         }
     }
     $dao->save();
     CRM_Utils_Hook::post($hook, 'SepaMandate', $dao->id, $dao);
     return $dao;
 }
 /**
  * subroutine to create the group/contribution structure as calculated
  */
 protected static function syncGroups($calculated_groups, $existing_groups, $mode, $type, $notice, $creditor_id)
 {
     $group_status_id_open = (int) CRM_Core_OptionGroup::getValue('batch_status', 'Open', 'name');
     foreach ($calculated_groups as $collection_date => $mandates) {
         // check if we need to defer the collection date (e.g. due to bank holidays)
         $exclude_weekends = CRM_Core_BAO_Setting::getItem('SEPA Direct Debit Preferences', 'exclude_weekends');
         if ($exclude_weekends) {
             // skip (western) week ends, if the option is activated.
             $day_of_week = date('N', strtotime($collection_date));
             if ($day_of_week > 5) {
                 // this is a weekend -> skip to Monday
                 $defer_days = 8 - $day_of_week;
                 $collection_date = date('Y-m-d', strtotime("+{$defer_days} day", strtotime($collection_date)));
             }
         }
         // also run the hook, in case somebody has a
         CRM_Utils_SepaCustomisationHooks::defer_collection_date($collection_date, $creditor_id);
         if (!isset($existing_groups[$collection_date])) {
             // this group does not yet exist -> create
             // find unused reference
             $reference = "TXG-{$creditor_id}-{$mode}-{$collection_date}";
             $counter = 0;
             while (self::referenceExists($reference)) {
                 $counter += 1;
                 $reference = "TXG-{$creditor_id}-{$mode}-{$collection_date}--" . $counter;
             }
             $group = civicrm_api('SepaTransactionGroup', 'create', array('version' => 3, 'reference' => $reference, 'type' => $mode, 'collection_date' => $collection_date, 'latest_submission_date' => date('Y-m-d', strtotime("-{$notice} days", strtotime($collection_date))), 'created_date' => date('Y-m-d'), 'status_id' => $group_status_id_open, 'sdd_creditor_id' => $creditor_id));
             if (!empty($group['is_error'])) {
                 // TODO: Error handling
                 error_log("org.project60.sepa: batching:syncGroups/createGroup " . $group['error_message']);
             }
         } else {
             $group = civicrm_api('SepaTransactionGroup', 'getsingle', array('version' => 3, 'id' => $existing_groups[$collection_date], 'status_id' => $group_status_id_open));
             if (!empty($group['is_error'])) {
                 // TODO: Error handling
                 error_log("org.project60.sepa: batching:syncGroups/getGroup " . $group['error_message']);
             }
             unset($existing_groups[$collection_date]);
         }
         // now we have the right group. Prepare some parameters...
         $group_id = $group['id'];
         $entity_ids = array();
         foreach ($mandates as $mandate) {
             // remark: "mandate_entity_id" in this case means the contribution ID
             if (empty($mandate['mandate_entity_id'])) {
                 // this shouldn't happen
                 error_log("org.project60.sepa: batching:syncGroups mandate with bad mandate_entity_id ignored:" . $mandate['mandate_id']);
             } else {
                 array_push($entity_ids, $mandate['mandate_entity_id']);
             }
         }
         if (count($entity_ids) <= 0) {
             continue;
         }
         // now, filter out the entity_ids that are are already in a non-open group
         //   (DO NOT CHANGE CLOSED GROUPS!)
         $entity_ids_list = implode(',', $entity_ids);
         $already_sent_contributions = CRM_Core_DAO::executeQuery("\n        SELECT contribution_id \n        FROM civicrm_sdd_contribution_txgroup \n        LEFT JOIN civicrm_sdd_txgroup ON civicrm_sdd_contribution_txgroup.txgroup_id = civicrm_sdd_txgroup.id\n        WHERE contribution_id IN ({$entity_ids_list})\n         AND  civicrm_sdd_txgroup.status_id <> {$group_status_id_open};");
         while ($already_sent_contributions->fetch()) {
             $index = array_search($already_sent_contributions->contribution_id, $entity_ids);
             if ($index !== false) {
                 unset($entity_ids[$index]);
             }
         }
         if (count($entity_ids) <= 0) {
             continue;
         }
         // remove all the unwanted entries from our group
         $entity_ids_list = implode(',', $entity_ids);
         CRM_Core_DAO::executeQuery("DELETE FROM civicrm_sdd_contribution_txgroup WHERE txgroup_id={$group_id} AND contribution_id NOT IN ({$entity_ids_list});");
         // remove all our entries from other groups, if necessary
         CRM_Core_DAO::executeQuery("DELETE FROM civicrm_sdd_contribution_txgroup WHERE txgroup_id!={$group_id} AND contribution_id IN ({$entity_ids_list});");
         // now check which ones are already in our group...
         $existing = CRM_Core_DAO::executeQuery("SELECT * FROM civicrm_sdd_contribution_txgroup WHERE txgroup_id={$group_id} AND contribution_id IN ({$entity_ids_list});");
         while ($existing->fetch()) {
             // remove from entity ids, if in there:
             if (($key = array_search($existing->contribution_id, $entity_ids)) !== false) {
                 unset($entity_ids[$key]);
             }
         }
         // the remaining must be added
         foreach ($entity_ids as $entity_id) {
             CRM_Core_DAO::executeQuery("INSERT INTO civicrm_sdd_contribution_txgroup (txgroup_id, contribution_id) VALUES ({$group_id}, {$entity_id});");
         }
     }
     // CLEANUP: remove nonexisting contributions from groups
     CRM_Core_DAO::executeQuery("\n      DELETE FROM civicrm_sdd_contribution_txgroup \n      WHERE contribution_id NOT IN (SELECT id FROM civicrm_contribution);");
     // CLEANUP: delete empty groups
     $empty_group_query = CRM_Core_DAO::executeQuery("\n      SELECT id, sdd_file_id \n      FROM civicrm_sdd_txgroup \n      WHERE type = '{$mode}'\n        AND status_id = {$group_status_id_open}\n        AND id NOT IN (SELECT txgroup_id FROM civicrm_sdd_contribution_txgroup);");
     while ($empty_group_query->fetch()) {
         if (!in_array($empty_group_query->id, $existing_groups)) {
             array_push($existing_groups, $empty_group_query->id);
         }
         if ($empty_group_query->sdd_file_id) {
             error_log("org.project60.sepa: WARNING: txgroup " . $empty_group_query->id . " should be deleted, but already has a file. Trouble ahead.");
         }
     }
     // now, use the API to delete all these groups
     foreach ($existing_groups as $group_id) {
         CRM_Core_DAO::executeQuery("DELETE FROM civicrm_sdd_contribution_txgroup WHERE txgroup_id={$group_id};");
         $result = civicrm_api('SepaTransactionGroup', 'delete', array('version' => 3, 'id' => $group_id));
         if (isset($result['is_error']) && $result['is_error']) {
             error_log("org.project60.sepa: Cannot delete txgroup " . $group_id . ". Error was " . $result['error_message']);
         }
     }
 }