Eileen has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/394219 )
Change subject: Apply drupal formatting to file prior to editing
......................................................................
Apply drupal formatting to file prior to editing
Change-Id: I1d22b440bf5e4dc4b66fc2cd7ef91a9c38905c6c
---
M sites/all/modules/offline2civicrm/ChecksFile.php
1 file changed, 499 insertions(+), 468 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/wikimedia/fundraising/crm
refs/changes/19/394219/1
diff --git a/sites/all/modules/offline2civicrm/ChecksFile.php
b/sites/all/modules/offline2civicrm/ChecksFile.php
index 618d960..74fa25c 100644
--- a/sites/all/modules/offline2civicrm/ChecksFile.php
+++ b/sites/all/modules/offline2civicrm/ChecksFile.php
@@ -5,52 +5,60 @@
/**
* CSV batch format for manually-keyed donation checks
*
- * FIXME: This currently includes stuff specific to Wikimedia Foundation
fundraising.
+ * FIXME: This currently includes stuff specific to Wikimedia Foundation
+ * fundraising.
*/
abstract class ChecksFile {
- protected $numSkippedRows = 0;
- protected $messages = array();
- protected $file_uri = '';
- protected $error_file_uri = '';
- protected $skipped_file_uri = '';
- protected $ignored_file_uri = '';
- protected $all_missed_file_uri = '';
- /**
- * @var resource
- */
- protected $ignoredFileResource = NULL;
+ protected $numSkippedRows = 0;
- /**
- * @var resource
- */
- protected $skippedFileResource = NULL;
+ protected $messages = array();
- /**
- * @var resource
- */
- protected $errorFileResource = NULL;
+ protected $file_uri = '';
- /**
- * @var resource
- */
- protected $allMissedFileResource = NULL;
+ protected $error_file_uri = '';
- /**
- * @param string $file_uri path to the file
- */
- function __construct( $file_uri ) {
- $this->file_uri = $file_uri;
- global $user;
- $suffix = $user->uid . '.csv';
- $this->error_file_uri = str_replace('.csv', '_errors.' . $suffix,
$file_uri);
- $this->skipped_file_uri = str_replace('.csv', '_skipped.' . $suffix,
$file_uri);
- $this->ignored_file_uri = str_replace('.csv', '_ignored.' . $suffix,
$file_uri);
- $this->all_missed_file_uri = str_replace('.csv', '_all_missed.' .
$suffix, $file_uri);
- wmf_common_set_smashpig_message_source(
- 'direct', 'Offline importer: ' . get_class( $this )
- );
- }
+ protected $skipped_file_uri = '';
+
+ protected $ignored_file_uri = '';
+
+ protected $all_missed_file_uri = '';
+
+ /**
+ * @var resource
+ */
+ protected $ignoredFileResource = NULL;
+
+ /**
+ * @var resource
+ */
+ protected $skippedFileResource = NULL;
+
+ /**
+ * @var resource
+ */
+ protected $errorFileResource = NULL;
+
+ /**
+ * @var resource
+ */
+ protected $allMissedFileResource = NULL;
+
+ /**
+ * @param string $file_uri path to the file
+ */
+ function __construct($file_uri) {
+ $this->file_uri = $file_uri;
+ global $user;
+ $suffix = $user->uid . '.csv';
+ $this->error_file_uri = str_replace('.csv', '_errors.' . $suffix,
$file_uri);
+ $this->skipped_file_uri = str_replace('.csv', '_skipped.' . $suffix,
$file_uri);
+ $this->ignored_file_uri = str_replace('.csv', '_ignored.' . $suffix,
$file_uri);
+ $this->all_missed_file_uri = str_replace('.csv', '_all_missed.' . $suffix,
$file_uri);
+ wmf_common_set_smashpig_message_source(
+ 'direct', 'Offline importer: ' . get_class($this)
+ );
+ }
/**
* Getter for messages array.
@@ -69,456 +77,470 @@
*
* @throws \Exception
*/
- function import() {
- ChecksImportLog::record( "Beginning import of checks file
{$this->file_uri}..." );
- //TODO: $db->begin();
+ function import() {
+ ChecksImportLog::record("Beginning import of checks file
{$this->file_uri}...");
+ //TODO: $db->begin();
- ini_set( 'auto_detect_line_endings', true );
- if( ( $file = fopen( $this->file_uri, 'r' )) === FALSE ){
- throw new WmfException( 'FILE_NOT_FOUND', 'Import checks: Could
not open file for reading: ' . $this->file_uri );
- }
+ ini_set('auto_detect_line_endings', TRUE);
+ if (($file = fopen($this->file_uri, 'r')) === FALSE) {
+ throw new WmfException('FILE_NOT_FOUND', 'Import checks: Could not open
file for reading: ' . $this->file_uri);
+ }
- if ( $this->numSkippedRows ) {
- foreach ( range( 1, $this->numSkippedRows ) as $i ) {
- fgets( $file );
- }
- }
-
- $headers = _load_headers( fgetcsv( $file, 0, ',', '"', '\\') );
-
- $this->validateColumns( $headers );
-
- $num_errors = 0;
- $num_ignored = 0;
- $num_successful = 0;
- $num_duplicates = 0;
- $this->row_index = -1 + $this->numSkippedRows;
- $error_streak_start = 0;
- $error_streak_count = 0;
- $error_streak_threshold = 10;
- $this->allMissedFileResource =
$this->createOutputFile($this->all_missed_file_uri, 'Not Imported', $headers);
- $lastError = '';
- $lastErrorRow = 0;
-
- while( ( $row = fgetcsv( $file, 0, ',', '"', '\\')) !== FALSE) {
- // Reset the PHP timeout for each row.
- set_time_limit( 10 );
-
- $this->row_index++;
- // FIXME: This is odd. Can't we just keep track of one index?
- $rowNum = $this->row_index + 2;
-
- // Zip headers and row into a dict
- $data = array_combine( array_keys( $headers ), array_slice( $row,
0, count( $headers ) ) );
-
- // Strip whitespaces
- foreach ( $data as $key => &$value ) {
- $value = trim( $value );
- }
-
- try {
- if ( $error_streak_count >= $error_streak_threshold ) {
- throw new IgnoredRowException('IMPORT_CONTRIB', 'Error limit
reached');
- }
- $msg = $this->parseRow( $data );
-
- // check to see if we have already processed this check
- if ( $existing =
wmf_civicrm_get_contributions_from_gateway_id( $msg['gateway'],
$msg['gateway_txn_id'] ) ){
- $skipped = $this->handleDuplicate( $existing );
- if ( $skipped ) {
- if ($num_duplicates === 0) {
- $this->skippedFileResource =
$this->createOutputFile($this->skipped_file_uri, 'Skipped', $headers);
- }
- $num_duplicates++;
- fputcsv($this->skippedFileResource,
array_merge(array('Skipped' => 'Duplicate'), $data));
- fputcsv($this->allMissedFileResource,
array_merge(array('Not Imported' => 'Duplicate'), $data));
-
- } else {
- $num_successful++;
- }
- continue;
- }
- // tha business.
- $contribution = WmfDatabase::transactionalCall(array($this,
'doImport'), array($msg));
-
- watchdog( 'offline2civicrm',
- 'Import checks: Contribution imported successfully (@id):
!msg', array(
- '@id' => $contribution['id'],
- '!msg' => print_r( $msg, true ),
- ), WATCHDOG_INFO
- );
- $num_successful++;
- } catch ( EmptyRowException $ex ) {
- continue;
- } catch ( IgnoredRowException $ex ) {
- if ($num_ignored === 0) {
- $this->ignoredFileResource =
$this->createOutputFile($this->ignored_file_uri, 'Ignored', $headers);
- }
- fputcsv($this->ignoredFileResource,
array_merge(array('Ignored' => $ex->getUserErrorMessage()), $data));
- fputcsv($this->allMissedFileResource, array_merge(array('Not
Imported' => 'Ignored: ' . $ex->getUserErrorMessage()), $data));
- $num_ignored++;
- continue;
- } catch ( WmfException $ex ) {
- if ($num_errors === 0) {
- $this->errorFileResource =
$this->createOutputFile($this->error_file_uri, 'Error', $headers);
- }
-
- $num_errors++;
- fputcsv($this->errorFileResource, array_merge(array('error' =>
$ex->getUserErrorMessage()), $data));
- fputcsv($this->allMissedFileResource, array_merge(array('Not
Imported' => 'Error: ' . $ex->getUserErrorMessage()), $data));
-
-
- ChecksImportLog::record( t( "Error in line @rownum: (@exception)
@row", array(
- '@rownum' => $rowNum,
- '@row' => implode( ', ', $row ),
- '@exception' => $ex->getUserErrorMessage(),
- ) ) );
-
- if ( $error_streak_start + $error_streak_count < $rowNum ) {
- // The last result must have been a success. Restart
streak counts.
- $error_streak_start = $rowNum;
- $error_streak_count = 0;
- }
- $error_streak_count++;
- $lastError = $ex->getUserErrorMessage();
- $lastErrorRow = $rowNum;
- }
- }
- $totalRows = $rowNum -1;
-
- if ( $error_streak_count >= $error_streak_threshold ) {
- $this->closeFilesAndSetMessage($totalRows, $num_successful,
$num_errors, $num_ignored, $num_duplicates);
- throw new Exception("Import aborted due to {$error_streak_count}
consecutive errors, last error was at row {$lastErrorRow}: {$lastError}. " .
implode(' ', $this->messages)
- );
+ if ($this->numSkippedRows) {
+ foreach (range(1, $this->numSkippedRows) as $i) {
+ fgets($file);
}
- array_unshift($this->messages, "Successful import!");
-
- // Unset time limit.
- set_time_limit( 0 );
-
- ChecksImportLog::record(implode(' ', $this->messages));
- watchdog( 'offline2civicrm', implode(' ', $this->messages), array(),
WATCHDOG_INFO );
- $this->closeFilesAndSetMessage($totalRows, $num_successful,
$num_errors, $num_ignored, $num_duplicates);
- return $this->messages;
-
}
- /**
- * Read a row and transform into normalized queue message form
- *
- * @param array $row native format for this upload file, usually a dict
- *
- * @return array queue message format
- */
- protected function parseRow( $data ) {
- $msg = array();
+ $headers = _load_headers(fgetcsv($file, 0, ',', '"', '\\'));
- foreach ( $this->getFieldMapping() as $header => $normal ) {
- if ( !empty( $data[$header] ) ) {
- $msg[$normal] = $data[$header];
+ $this->validateColumns($headers);
+
+ $num_errors = 0;
+ $num_ignored = 0;
+ $num_successful = 0;
+ $num_duplicates = 0;
+ $this->row_index = -1 + $this->numSkippedRows;
+ $error_streak_start = 0;
+ $error_streak_count = 0;
+ $error_streak_threshold = 10;
+ $this->allMissedFileResource =
$this->createOutputFile($this->all_missed_file_uri, 'Not Imported', $headers);
+ $lastError = '';
+ $lastErrorRow = 0;
+
+ while (($row = fgetcsv($file, 0, ',', '"', '\\')) !== FALSE) {
+ // Reset the PHP timeout for each row.
+ set_time_limit(10);
+
+ $this->row_index++;
+ // FIXME: This is odd. Can't we just keep track of one index?
+ $rowNum = $this->row_index + 2;
+
+ // Zip headers and row into a dict
+ $data = array_combine(array_keys($headers), array_slice($row, 0,
count($headers)));
+
+ // Strip whitespaces
+ foreach ($data as $key => &$value) {
+ $value = trim($value);
+ }
+
+ try {
+ if ($error_streak_count >= $error_streak_threshold) {
+ throw new IgnoredRowException('IMPORT_CONTRIB', 'Error limit
reached');
+ }
+ $msg = $this->parseRow($data);
+
+ // check to see if we have already processed this check
+ if ($existing =
wmf_civicrm_get_contributions_from_gateway_id($msg['gateway'],
$msg['gateway_txn_id'])) {
+ $skipped = $this->handleDuplicate($existing);
+ if ($skipped) {
+ if ($num_duplicates === 0) {
+ $this->skippedFileResource =
$this->createOutputFile($this->skipped_file_uri, 'Skipped', $headers);
}
+ $num_duplicates++;
+ fputcsv($this->skippedFileResource, array_merge(array('Skipped' =>
'Duplicate'), $data));
+ fputcsv($this->allMissedFileResource, array_merge(array('Not
Imported' => 'Duplicate'), $data));
+
+ }
+ else {
+ $num_successful++;
+ }
+ continue;
}
+ // tha business.
+ $contribution = WmfDatabase::transactionalCall(array(
+ $this,
+ 'doImport',
+ ), array($msg));
- if ( !$msg ) {
- throw new EmptyRowException();
- }
-
- $this->setDefaults( $msg );
-
- $this->mungeMessage( $msg );
-
- $this->validateRequiredFields( $msg );
-
- SourceFields::addToMessage( $msg );
- return $msg;
- }
-
- protected function handleDuplicate ( $duplicate ) {
- watchdog( 'offline2civicrm', 'Contribution matches existing
contribution (id: @id), skipping it.', array( '@id' => $duplicate[0]['id'] ),
WATCHDOG_INFO );
- return true; // true means this was a duplicate and i skipped it
- }
-
- protected function setDefaults( &$msg ) {
- foreach ( $this->getDefaultValues() as $key => $defaultValue ) {
- if ( empty( $msg[$key] ) ) {
- $msg[$key] = $defaultValue;
- }
- }
- }
-
- /**
- * Do any final transformation on a normalized and default-laden queue
- * message. Overrides are specific to each upload source.
- */
- protected function mungeMessage( &$msg ) {
- if ( isset( $msg['raw_contribution_type'] ) ) {
- $contype = $msg['raw_contribution_type'];
- switch ( $contype ) {
- case "Merkle":
- $msg['gateway'] = "merkle";
- break;
-
- case "Engage":
- case "Engage Direct Mail":
- $msg['gateway'] = "engage";
- $msg['contribution_type'] = "engage";
- break;
-
- case "Cash":
- $msg['contribution_type'] = "cash";
- break;
-
- default:
- $msg['contribution_type'] = $msg['raw_contribution_type'];
- }
- }
-
- if ( isset( $msg['organization_name'] ) ) {
- $msg['contact_type'] = "Organization";
- } else {
- // If this is not an Organization contact, freak out if Name or
Title are filled.
- if ( !empty( $msg['org_contact_name'] )
- || !empty( $msg['org_contact_title'] )
- ) {
- throw new WmfException( 'INVALID_MESSAGE', "Don't give a Name
or Title unless this is an Organization contact." );
- }
- }
-
- $msg['gross'] = str_replace(',','', trim( $msg['gross'], '$' ));
-
- if ( isset( $msg['contribution_source'] ) ) {
- // Check that the message amounts match
- list($currency, $source_amount) = explode( ' ',
$msg['contribution_source'] );
-
- if ( abs( $source_amount - $msg['gross'] ) > .01 ) {
- $pretty_msg = json_encode( $msg );
- watchdog( 'offline2civicrm', "Amount mismatch in row: " .
$pretty_msg, NULL, WATCHDOG_ERROR );
- throw new WmfException( 'INVALID_MESSAGE', "Amount mismatch
during checks import" );
- }
-
- $msg['currency'] = $currency;
- }
-
- // left-pad the zipcode
- // Unclear whether US needs to be handled. United States is valid from
a csv &
- // gets this far. United States covered by a unit test.
- if ( ($msg['country'] === 'US' || $msg['country'] === 'United States')
&& !empty( $msg['postal_code'] ) ) {
- if ( preg_match( '/^(\d{1,4})(-\d+)?$/', $msg['postal_code'],
$matches ) ) {
- $msg['postal_code'] = str_pad( $matches[1], 5, "0",
STR_PAD_LEFT );
- if ( !empty( $matches[2] ) ) {
- $msg['postal_code'] .= $matches[2];
- }
- }
- }
-
- // Generate a transaction ID so that we don't import the same rows
multiple times
- if ( empty( $msg['gateway_txn_id'] ) ) {
- if ( $msg['contact_type'] === 'Individual' ) {
- $name_salt = $msg['first_name'] . $msg['last_name'];
- } else {
- $name_salt = $msg['organization_name'];
- }
-
- if ( !empty( $msg['check_number'] ) ) {
- $msg['gateway_txn_id'] = md5( $msg['check_number'] .
$name_salt );
- } else {
- // The scenario where this would happen is anonymous cash
gifts.
- // the name would be 'Anonymous Anonymous' and there might be
several on the same
- // day. Hence we rely on them all being carefully arranged in
a spreadsheet and
- // no-one messing with the order. I was worried this was
fragile but there
- // is no obvious better way.
- $msg['gateway_txn_id'] = md5( $msg['date'] . $name_salt .
$this->row_index );
- }
- }
-
- // Expand soft credit short names.
- if ( !empty( $msg['soft_credit_to'] ) ) {
- $nickname_mapping = array(
- 'Fidelity' => 'Fidelity Charitable Gift Fund',
- 'Vanguard' => 'Vanguard Charitable Endowment Program',
- 'Schwab' => 'Schwab Charitable Fund',
- );
- if ( array_key_exists( $msg['soft_credit_to'], $nickname_mapping )
) {
- $msg['soft_credit_to'] =
$nickname_mapping[$msg['soft_credit_to']];
- }
- }
-
- if ( empty( $msg['gateway'] ) ) {
- $msg['gateway'] = 'generic_import';
- }
-
- foreach ( $this->getDatetimeFields() as $field ) {
- if ( !empty( $msg[$field] ) && !is_numeric( $msg[$field] ) ) {
- $msg[$field] = wmf_common_date_parse_string( $msg[$field] );
- }
- }
-
- // Allow yes or true as inputs for opt-out fields
- $optOutFields = array(
- 'do_not_email',
- 'do_not_mail',
- 'do_not_phone',
- 'do_not_sms',
- 'do_not_solicit',
- 'is_opt_out',
- );
-
- $trueValues = array(
- 'yes',
- 'y',
- 'true',
- 't',
- '1',
+ watchdog('offline2civicrm',
+ 'Import checks: Contribution imported successfully (@id): !msg',
array(
+ '@id' => $contribution['id'],
+ '!msg' => print_r($msg, TRUE),
+ ), WATCHDOG_INFO
);
-
- foreach( $optOutFields as $field ) {
- if ( isset( $msg[$field] ) ) {
- if ( in_array( strtolower( $msg[$field] ), $trueValues ) ) {
- $msg[$field] = 1;
- }
- else {
- $msg[$field] = 0;
- }
- }
+ $num_successful++;
+ }
+ catch (EmptyRowException $ex) {
+ continue;
+ }
+ catch (IgnoredRowException $ex) {
+ if ($num_ignored === 0) {
+ $this->ignoredFileResource =
$this->createOutputFile($this->ignored_file_uri, 'Ignored', $headers);
+ }
+ fputcsv($this->ignoredFileResource, array_merge(array('Ignored' =>
$ex->getUserErrorMessage()), $data));
+ fputcsv($this->allMissedFileResource, array_merge(array('Not Imported'
=> 'Ignored: ' . $ex->getUserErrorMessage()), $data));
+ $num_ignored++;
+ continue;
+ }
+ catch (WmfException $ex) {
+ if ($num_errors === 0) {
+ $this->errorFileResource =
$this->createOutputFile($this->error_file_uri, 'Error', $headers);
}
+ $num_errors++;
+ fputcsv($this->errorFileResource, array_merge(array('error' =>
$ex->getUserErrorMessage()), $data));
+ fputcsv($this->allMissedFileResource, array_merge(array('Not Imported'
=> 'Error: ' . $ex->getUserErrorMessage()), $data));
+
+
+ ChecksImportLog::record(t("Error in line @rownum: (@exception) @row",
array(
+ '@rownum' => $rowNum,
+ '@row' => implode(', ', $row),
+ '@exception' => $ex->getUserErrorMessage(),
+ )));
+
+ if ($error_streak_start + $error_streak_count < $rowNum) {
+ // The last result must have been a success. Restart streak counts.
+ $error_streak_start = $rowNum;
+ $error_streak_count = 0;
+ }
+ $error_streak_count++;
+ $lastError = $ex->getUserErrorMessage();
+ $lastErrorRow = $rowNum;
+ }
+ }
+ $totalRows = $rowNum - 1;
+
+ if ($error_streak_count >= $error_streak_threshold) {
+ $this->closeFilesAndSetMessage($totalRows, $num_successful, $num_errors,
$num_ignored, $num_duplicates);
+ throw new Exception("Import aborted due to {$error_streak_count}
consecutive errors, last error was at row {$lastErrorRow}: {$lastError}. " .
implode(' ', $this->messages)
+ );
+ }
+ array_unshift($this->messages, "Successful import!");
+
+ // Unset time limit.
+ set_time_limit(0);
+
+ ChecksImportLog::record(implode(' ', $this->messages));
+ watchdog('offline2civicrm', implode(' ', $this->messages), array(),
WATCHDOG_INFO);
+ $this->closeFilesAndSetMessage($totalRows, $num_successful, $num_errors,
$num_ignored, $num_duplicates);
+ return $this->messages;
+
+ }
+
+ /**
+ * Read a row and transform into normalized queue message form
+ *
+ * @param array $row native format for this upload file, usually a dict
+ *
+ * @return array queue message format
+ */
+ protected function parseRow($data) {
+ $msg = array();
+
+ foreach ($this->getFieldMapping() as $header => $normal) {
+ if (!empty($data[$header])) {
+ $msg[$normal] = $data[$header];
+ }
}
- /**
- * Do fancy stuff with the contribution we just created
- *
- * FIXME: We need to wrap each loop iteration in a transaction to
- * make this safe. Otherwise we can easily die before adding the
- * second message, and skip it when resuming the import.
- *
- * @param array $contribution
- */
- protected function mungeContribution( $contribution ) {
+ if (!$msg) {
+ throw new EmptyRowException();
}
- protected function getDefaultValues() {
- return array(
- 'contact_source' => 'check',
- 'contact_type' => 'Individual',
- 'country' => 'US',
- );
+ $this->setDefaults($msg);
+
+ $this->mungeMessage($msg);
+
+ $this->validateRequiredFields($msg);
+
+ SourceFields::addToMessage($msg);
+ return $msg;
+ }
+
+ protected function handleDuplicate($duplicate) {
+ watchdog('offline2civicrm', 'Contribution matches existing contribution
(id: @id), skipping it.', array('@id' => $duplicate[0]['id']), WATCHDOG_INFO);
+ return TRUE; // true means this was a duplicate and i skipped it
+ }
+
+ protected function setDefaults(&$msg) {
+ foreach ($this->getDefaultValues() as $key => $defaultValue) {
+ if (empty($msg[$key])) {
+ $msg[$key] = $defaultValue;
+ }
+ }
+ }
+
+ /**
+ * Do any final transformation on a normalized and default-laden queue
+ * message. Overrides are specific to each upload source.
+ */
+ protected function mungeMessage(&$msg) {
+ if (isset($msg['raw_contribution_type'])) {
+ $contype = $msg['raw_contribution_type'];
+ switch ($contype) {
+ case "Merkle":
+ $msg['gateway'] = "merkle";
+ break;
+
+ case "Engage":
+ case "Engage Direct Mail":
+ $msg['gateway'] = "engage";
+ $msg['contribution_type'] = "engage";
+ break;
+
+ case "Cash":
+ $msg['contribution_type'] = "cash";
+ break;
+
+ default:
+ $msg['contribution_type'] = $msg['raw_contribution_type'];
+ }
}
- /**
- * Return column mappings
- *
- * @return array of {spreadsheet column title} => {normalized field name}
- */
- protected function getFieldMapping() {
- return array(
- 'Additional Address 1' => 'supplemental_address_1',
- 'Additional Address 2' => 'supplemental_address_2',
- 'Batch' => 'import_batch_number', # deprecated, use External Batch
Number instead.
- 'Banner' => 'utm_source',
- 'Campaign' => 'utm_campaign',
- 'Check Number' => 'check_number',
- 'City' => 'city',
- 'Contribution Type' => 'raw_contribution_type',
- 'Contribution Tracking ID' => 'contribution_tracking_id',
- 'Country' => 'country',
- 'Description of Stock' => 'stock_description',
- 'Direct Mail Appeal' => 'direct_mail_appeal',
- 'Do Not Email' => 'do_not_email',
- 'Do Not Mail' => 'do_not_mail',
- 'Do Not Phone' => 'do_not_phone',
- 'Do Not SMS' => 'do_not_sms',
- 'Do Not Solicit' => 'do_not_solicit',
- 'Email' => 'email',
- 'External Batch Number' => 'import_batch_number',
- 'First Name' => 'first_name',
- 'Gift Source' => 'gift_source',
- 'Groups' => 'contact_groups',
- 'Is Opt Out' => 'is_opt_out',
- 'Last Name' => 'last_name',
- 'Letter Code' => 'letter_code',
- 'Medium' => 'utm_medium',
- 'Middle Name' => 'middle_name',
- 'Name' => 'org_contact_name',
- 'No Thank You' => 'no_thank_you',
- 'Notes' => 'notes',
- 'Organization Name' => 'organization_name',
- 'Original Amount' => 'gross',
- 'Original Currency' => 'currency',
- 'Payment Instrument' => 'payment_method',
- 'Payment Gateway' => 'gateway',
- 'Postal Code' => 'postal_code',
- 'Postmark Date' => 'postmark_date',
- 'Prefix' => 'name_prefix',
- 'Raw Payment Instrument' => 'raw_payment_instrument',
- 'Received Date' => 'date',
- 'Relationship Type' => 'relationship_type',
- 'Restrictions' => 'restrictions',
- 'Soft Credit To' => 'soft_credit_to',
- 'Source' => 'contribution_source',
- 'State' => 'state_province',
- 'Street Address' => 'street_address',
- 'Suffix' => 'name_suffix',
- 'Tags' => 'contact_tags',
- 'Target Contact ID' => 'relationship_target_contact_id',
- 'Thank You Letter Date' => 'thankyou_date',
- 'Title' => 'org_contact_title',
- 'Total Amount' => 'gross', # deprecated, use Original Amount
- 'Transaction ID' => 'gateway_txn_id',
- );
+ if (isset($msg['organization_name'])) {
+ $msg['contact_type'] = "Organization";
+ }
+ else {
+ // If this is not an Organization contact, freak out if Name or Title
are filled.
+ if (!empty($msg['org_contact_name'])
+ || !empty($msg['org_contact_title'])
+ ) {
+ throw new WmfException('INVALID_MESSAGE', "Don't give a Name or Title
unless this is an Organization contact.");
+ }
}
- /**
- * Date fields which must be converted to unix timestamps
- *
- * @return array of field names
- */
- protected function getDatetimeFields() {
- return array(
- 'date',
- 'thankyou_date',
- 'postmark_date',
- );
+ $msg['gross'] = str_replace(',', '', trim($msg['gross'], '$'));
+
+ if (isset($msg['contribution_source'])) {
+ // Check that the message amounts match
+ list($currency, $source_amount) = explode(' ',
$msg['contribution_source']);
+
+ if (abs($source_amount - $msg['gross']) > .01) {
+ $pretty_msg = json_encode($msg);
+ watchdog('offline2civicrm', "Amount mismatch in row: " . $pretty_msg,
NULL, WATCHDOG_ERROR);
+ throw new WmfException('INVALID_MESSAGE', "Amount mismatch during
checks import");
+ }
+
+ $msg['currency'] = $currency;
}
- /**
- * Columns which must exist in the spreadsheet
- *
- * This is just a "schema" check. We don't require that the fields
contain data.
- *
- * @return array of column header titles
- */
- abstract protected function getRequiredColumns();
+ // left-pad the zipcode
+ // Unclear whether US needs to be handled. United States is valid from a
csv &
+ // gets this far. United States covered by a unit test.
+ if (($msg['country'] === 'US' || $msg['country'] === 'United States') &&
!empty($msg['postal_code'])) {
+ if (preg_match('/^(\d{1,4})(-\d+)?$/', $msg['postal_code'], $matches)) {
+ $msg['postal_code'] = str_pad($matches[1], 5, "0", STR_PAD_LEFT);
+ if (!empty($matches[2])) {
+ $msg['postal_code'] .= $matches[2];
+ }
+ }
+ }
- /**
- * Fields that must not be empty in the normalized message
- *
- * @return array of normalized message field names
- */
- protected function getRequiredData() {
- return array(
- 'currency',
- 'date',
- 'gross',
- );
- }
+ // Generate a transaction ID so that we don't import the same rows
multiple times
+ if (empty($msg['gateway_txn_id'])) {
+ if ($msg['contact_type'] === 'Individual') {
+ $name_salt = $msg['first_name'] . $msg['last_name'];
+ }
+ else {
+ $name_salt = $msg['organization_name'];
+ }
- /**
- * Ensure the file contains all the data we need.
- *
- * @param array $headers Column names
- * @throws WmfException if required columns are missing
- */
- protected function validateColumns( $headers ) {
- $failed = array();
- foreach ( $this->getRequiredColumns() as $name ) {
- if ( !array_key_exists( $name, $headers ) ) {
- $failed[] = $name;
- }
- }
- if ( $failed ) {
- throw new WmfException( 'INVALID_FILE_FORMAT', "This
file is missing column headers: " . implode( ", ", $failed ) );
- }
- }
+ if (!empty($msg['check_number'])) {
+ $msg['gateway_txn_id'] = md5($msg['check_number'] . $name_salt);
+ }
+ else {
+ // The scenario where this would happen is anonymous cash gifts.
+ // the name would be 'Anonymous Anonymous' and there might be several
on the same
+ // day. Hence we rely on them all being carefully arranged in a
spreadsheet and
+ // no-one messing with the order. I was worried this was fragile but
there
+ // is no obvious better way.
+ $msg['gateway_txn_id'] = md5($msg['date'] . $name_salt .
$this->row_index);
+ }
+ }
+
+ // Expand soft credit short names.
+ if (!empty($msg['soft_credit_to'])) {
+ $nickname_mapping = array(
+ 'Fidelity' => 'Fidelity Charitable Gift Fund',
+ 'Vanguard' => 'Vanguard Charitable Endowment Program',
+ 'Schwab' => 'Schwab Charitable Fund',
+ );
+ if (array_key_exists($msg['soft_credit_to'], $nickname_mapping)) {
+ $msg['soft_credit_to'] = $nickname_mapping[$msg['soft_credit_to']];
+ }
+ }
+
+ if (empty($msg['gateway'])) {
+ $msg['gateway'] = 'generic_import';
+ }
+
+ foreach ($this->getDatetimeFields() as $field) {
+ if (!empty($msg[$field]) && !is_numeric($msg[$field])) {
+ $msg[$field] = wmf_common_date_parse_string($msg[$field]);
+ }
+ }
+
+ // Allow yes or true as inputs for opt-out fields
+ $optOutFields = array(
+ 'do_not_email',
+ 'do_not_mail',
+ 'do_not_phone',
+ 'do_not_sms',
+ 'do_not_solicit',
+ 'is_opt_out',
+ );
+
+ $trueValues = array(
+ 'yes',
+ 'y',
+ 'true',
+ 't',
+ '1',
+ );
+
+ foreach ($optOutFields as $field) {
+ if (isset($msg[$field])) {
+ if (in_array(strtolower($msg[$field]), $trueValues)) {
+ $msg[$field] = 1;
+ }
+ else {
+ $msg[$field] = 0;
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Do fancy stuff with the contribution we just created
+ *
+ * FIXME: We need to wrap each loop iteration in a transaction to
+ * make this safe. Otherwise we can easily die before adding the
+ * second message, and skip it when resuming the import.
+ *
+ * @param array $contribution
+ */
+ protected function mungeContribution($contribution) {
+ }
+
+ protected function getDefaultValues() {
+ return array(
+ 'contact_source' => 'check',
+ 'contact_type' => 'Individual',
+ 'country' => 'US',
+ );
+ }
+
+ /**
+ * Return column mappings
+ *
+ * @return array of {spreadsheet column title} => {normalized field name}
+ */
+ protected function getFieldMapping() {
+ return array(
+ 'Additional Address 1' => 'supplemental_address_1',
+ 'Additional Address 2' => 'supplemental_address_2',
+ 'Batch' => 'import_batch_number',
+ # deprecated, use External Batch Number instead.
+ 'Banner' => 'utm_source',
+ 'Campaign' => 'utm_campaign',
+ 'Check Number' => 'check_number',
+ 'City' => 'city',
+ 'Contribution Type' => 'raw_contribution_type',
+ 'Contribution Tracking ID' => 'contribution_tracking_id',
+ 'Country' => 'country',
+ 'Description of Stock' => 'stock_description',
+ 'Direct Mail Appeal' => 'direct_mail_appeal',
+ 'Do Not Email' => 'do_not_email',
+ 'Do Not Mail' => 'do_not_mail',
+ 'Do Not Phone' => 'do_not_phone',
+ 'Do Not SMS' => 'do_not_sms',
+ 'Do Not Solicit' => 'do_not_solicit',
+ 'Email' => 'email',
+ 'External Batch Number' => 'import_batch_number',
+ 'First Name' => 'first_name',
+ 'Gift Source' => 'gift_source',
+ 'Groups' => 'contact_groups',
+ 'Is Opt Out' => 'is_opt_out',
+ 'Last Name' => 'last_name',
+ 'Letter Code' => 'letter_code',
+ 'Medium' => 'utm_medium',
+ 'Middle Name' => 'middle_name',
+ 'Name' => 'org_contact_name',
+ 'No Thank You' => 'no_thank_you',
+ 'Notes' => 'notes',
+ 'Organization Name' => 'organization_name',
+ 'Original Amount' => 'gross',
+ 'Original Currency' => 'currency',
+ 'Payment Instrument' => 'payment_method',
+ 'Payment Gateway' => 'gateway',
+ 'Postal Code' => 'postal_code',
+ 'Postmark Date' => 'postmark_date',
+ 'Prefix' => 'name_prefix',
+ 'Raw Payment Instrument' => 'raw_payment_instrument',
+ 'Received Date' => 'date',
+ 'Relationship Type' => 'relationship_type',
+ 'Restrictions' => 'restrictions',
+ 'Soft Credit To' => 'soft_credit_to',
+ 'Source' => 'contribution_source',
+ 'State' => 'state_province',
+ 'Street Address' => 'street_address',
+ 'Suffix' => 'name_suffix',
+ 'Tags' => 'contact_tags',
+ 'Target Contact ID' => 'relationship_target_contact_id',
+ 'Thank You Letter Date' => 'thankyou_date',
+ 'Title' => 'org_contact_title',
+ 'Total Amount' => 'gross',
+ # deprecated, use Original Amount
+ 'Transaction ID' => 'gateway_txn_id',
+ );
+ }
+
+ /**
+ * Date fields which must be converted to unix timestamps
+ *
+ * @return array of field names
+ */
+ protected function getDatetimeFields() {
+ return array(
+ 'date',
+ 'thankyou_date',
+ 'postmark_date',
+ );
+ }
+
+ /**
+ * Columns which must exist in the spreadsheet
+ *
+ * This is just a "schema" check. We don't require that the fields contain
+ * data.
+ *
+ * @return array of column header titles
+ */
+ abstract protected function getRequiredColumns();
+
+ /**
+ * Fields that must not be empty in the normalized message
+ *
+ * @return array of normalized message field names
+ */
+ protected function getRequiredData() {
+ return array(
+ 'currency',
+ 'date',
+ 'gross',
+ );
+ }
+
+ /**
+ * Ensure the file contains all the data we need.
+ *
+ * @param array $headers Column names
+ *
+ * @throws WmfException if required columns are missing
+ */
+ protected function validateColumns($headers) {
+ $failed = array();
+ foreach ($this->getRequiredColumns() as $name) {
+ if (!array_key_exists($name, $headers)) {
+ $failed[] = $name;
+ }
+ }
+ if ($failed) {
+ throw new WmfException('INVALID_FILE_FORMAT', "This file is missing
column headers: " . implode(", ", $failed));
+ }
+ }
/**
* Create a file for output.
@@ -544,7 +566,12 @@
* @param int $num_duplicates
*/
public function closeFilesAndSetMessage($totalRows, $num_successful,
$num_errors, $num_ignored, $num_duplicates) {
- foreach (array($this->skippedFileResource, $this->errorFileResource,
$this->ignoredFileResource, $this->allMissedFileResource) as $fileResource) {
+ foreach (array(
+ $this->skippedFileResource,
+ $this->errorFileResource,
+ $this->ignoredFileResource,
+ $this->allMissedFileResource,
+ ) as $fileResource) {
if ($fileResource) {
fclose($fileResource);
}
@@ -555,9 +582,12 @@
$this->messages['Result'] = ts("All rows were imported");
}
else {
- $this->messages['Result'] = ts("%1 out of %2 rows were imported.",
array('1' => $num_successful, 2 => $totalRows));
+ $this->messages['Result'] = ts("%1 out of %2 rows were imported.", array(
+ '1' => $num_successful,
+ 2 => $totalRows,
+ ));
- if($num_duplicates !== $notImported && $num_errors !== $notImported &&
$num_ignored !== $notImported) {
+ if ($num_duplicates !== $notImported && $num_errors !== $notImported &&
$num_ignored !== $notImported) {
// If the number of rows not imported is the same as the number
skipped, or the number of errors etc
// then the Not Imported csv will duplicate that & it is confusing to
provide a link to it.
$this->setMessage($this->all_missed_file_uri, 'not imported',
$notImported);
@@ -594,6 +624,7 @@
* Do the actual import.
*
* @param array $msg
+ *
* @return array
*/
public function doImport($msg) {
--
To view, visit https://gerrit.wikimedia.org/r/394219
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I1d22b440bf5e4dc4b66fc2cd7ef91a9c38905c6c
Gerrit-PatchSet: 1
Gerrit-Project: wikimedia/fundraising/crm
Gerrit-Branch: deployment
Gerrit-Owner: Eileen <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits