Mwalker has submitted this change and it was merged.
Change subject: Generalized mailing job queue
......................................................................
Generalized mailing job queue
These components can still be used independently (see the current state of
the Thank You and WMF End of Year Receipt modules), but this commit introduces
a generalized implementation of mailing jobs.
The design of this job queue is significantly different than what we're
currently doing. Most importantly, the template parameter variables should
be calculated before inserting your recipients into the queue, so the mailing
process can remain agnostic of the business logic. The impact on thank_you,
for example, if we were to migrate to this Job class, would be that the
contact and contribution information would be saved to the database during q2c,
and the TY job would simply pull data out of its queue and render using letter
templates.
TODO: do something to actually test the generated email contents, by replacing
the templates with simple, known quantities.
Change-Id: I36cd21a1bff531931565e1248e8c39edd0a066c4
---
M sites/all/modules/thank_you/thank_you.module
A sites/all/modules/wmf_common/wmf_communication/Job.php
M sites/all/modules/wmf_common/wmf_communication/Mailer.php
A sites/all/modules/wmf_common/wmf_communication/MailingTemplate.php
A sites/all/modules/wmf_common/wmf_communication/Recipient.php
M sites/all/modules/wmf_common/wmf_communication/Templating.php
M sites/all/modules/wmf_common/wmf_communication/Translation.php
A sites/all/modules/wmf_common/wmf_communication/tests/Job.test
A sites/all/modules/wmf_common/wmf_communication/tests/TestThankyouTemplate.php
A
sites/all/modules/wmf_common/wmf_communication/tests/templates/html/thank_you.it.html
A
sites/all/modules/wmf_common/wmf_communication/tests/templates/txt/thank_you.it.txt
A
sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.info
A
sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.module
A sites/all/modules/wmf_common/wmf_communication/wmf_communication.drush.inc
M sites/all/modules/wmf_common/wmf_communication/wmf_communication.info
A sites/all/modules/wmf_common/wmf_communication/wmf_communication.install
M sites/all/modules/wmf_common/wmf_communication/wmf_communication.module
M sites/all/modules/wmf_eoy_receipt/EoySummary.php
18 files changed, 790 insertions(+), 25 deletions(-)
Approvals:
Mwalker: Verified; Looks good to me, approved
diff --git a/sites/all/modules/thank_you/thank_you.module
b/sites/all/modules/thank_you/thank_you.module
index d629327..3f8372b 100644
--- a/sites/all/modules/thank_you/thank_you.module
+++ b/sites/all/modules/thank_you/thank_you.module
@@ -317,7 +317,7 @@
$subj_msg = "donate_interface-email-subject";
$email['subject'] = Translation::get_translated_message( $subj_msg,
$language );
- $mailer = new Mailer();
+ $mailer = Mailer::getDefault();
$email['reply_to'] = 'bounce-' . str_replace( '@', '=',
$email['to_address'] ) . '@donate.wikimedia.org';
@@ -332,6 +332,8 @@
$email_success = $mailer->send( $email );
} catch (phpmailerException $e) {
+ //TODO: don't assume phpmailer
+
$debug = array_merge( $email, array( "html" => '', "plaintext"
=> '' ) );
watchdog('thank_you', 'Sending thank you message failed in
phpmailer for contribution: ' .
$contribution_id . '<pre>' .
check_plain(print_r($debug, TRUE)) . "\n\n" .
diff --git a/sites/all/modules/wmf_common/wmf_communication/Job.php
b/sites/all/modules/wmf_common/wmf_communication/Job.php
new file mode 100644
index 0000000..3dd677a
--- /dev/null
+++ b/sites/all/modules/wmf_common/wmf_communication/Job.php
@@ -0,0 +1,86 @@
+<?php namespace wmf_communication;
+
+use \Exception;
+
+class Job {
+ protected $id;
+ protected $template;
+
+ /**
+ * Pull a job from the database.
+ *
+ * @param integer $id The job's database ID
+ */
+ static function getJob( $id ) {
+ $job = new Job();
+ $job->id = $id;
+
+ watchdog( 'wmf_communication', t( "Retrieving mailing job :id from the
database.", array( ':id' => $id ) ), WATCHDOG_INFO );
+ $row = db_select( 'wmf_communication_job' )
+ ->fields( 'wmf_communication_job' )
+ ->condition( 'id', $id )
+ ->execute()
+ ->fetchAssoc();
+
+ if ( !$row ) {
+ throw new Exception( 'No such job found: ' . $id );
+ }
+
+ $templateClass = $row['template_class'];
+ if ( !class_exists( $templateClass ) ) {
+ throw new Exception( 'Could not find mailing template class: ' .
$templateClass );
+ }
+ $job->template = new $templateClass;
+ if ( !( $job->template instanceof IMailingTemplate ) ) {
+ throw new Exception( 'Mailing template class must implement
IMailingTemplate' );
+ }
+
+ return $job;
+ }
+
+ /**
+ * Find all queued recipients and send letters.
+ */
+ function run() {
+ watchdog( 'wmf_communication', t( "Running mailing job ID :id...",
array( ':id' => $this->id ) ), WATCHDOG_INFO );
+
+ $mailer = Mailer::getDefault();
+ $successful = 0;
+ $failed = 0;
+
+ while ( $recipients = Recipient::getQueuedBatch( $this->id ) ) {
+ foreach ( $recipients as $recipient ) {
+ $bodyTemplate = $this->template->getBodyTemplate( $recipient );
+
+ $email = array(
+ 'from_name' => $this->template->getFromName(),
+ 'from_address' => $this->template->getFromAddress(),
+ 'reply_to' => $this->template->getFromAddress(),
+ 'to_name' => $recipient->getName(),
+ 'to_address' => $recipient->getEmail(),
+ 'subject' => $this->template->getSubject( $recipient ),
+ 'plaintext' => $bodyTemplate->render( 'txt' ),
+ 'html' => $bodyTemplate->render( 'html' ),
+ );
+
+ $success = $mailer->send( $email );
+
+ if ( $success ) {
+ $recipient->setSuccessful();
+ } else {
+ $recipient->setFailed();
+ }
+ }
+ }
+
+ if ( ( $successful + $failed ) === 0 ) {
+ watchdog( 'wmf_communication', t( "The mailing job (ID :id) was
empty, or already completed.", array( ':id' => $this->id ) ), WATCHDOG_WARNING
);
+ } else {
+ watchdog( 'wmf_communication',
+ t( "Completed mailing job (ID :id), :successful letters
successfully sent, and :failed failed.",
+ array( ':id' => $this->id, ':successful' => $successful,
':failed' => $failed )
+ ),
+ WATCHDOG_INFO );
+ }
+ }
+}
diff --git a/sites/all/modules/wmf_common/wmf_communication/Mailer.php
b/sites/all/modules/wmf_common/wmf_communication/Mailer.php
index b437190..6294ae8 100644
--- a/sites/all/modules/wmf_common/wmf_communication/Mailer.php
+++ b/sites/all/modules/wmf_common/wmf_communication/Mailer.php
@@ -1,15 +1,6 @@
-<?php
-/**
- * TODO: really decouple from implementation
- */
+<?php namespace wmf_communication;
-namespace wmf_communication;
-
-class Mailer {
- function __construct() {
- require_once implode(DIRECTORY_SEPARATOR,
array(variable_get('wmf_common_phpmailer_location', ''),
'class.phpmailer.php'));
- }
-
+interface IMailer {
/**
* @param array $email All keys are required:
* from_address
@@ -21,23 +12,100 @@
* to_address
* to_name
*/
+ function send( $email );
+}
+
+class Mailer {
+ static public $defaultSystem = 'phpmailer';
+
+ static public function getDefault() {
+ switch ( self::$defaultSystem ) {
+ case 'phpmailer':
+ return new MailerPHPMailer();
+ case 'drupal':
+ return new MailerDrupal();
+ default:
+ throw new Exception( "Unknown mailer requested: " .
self::$defaultSystem );
+ }
+ }
+}
+
+class MailerPHPMailer implements IMailer {
+ function __construct() {
+ $path = implode(DIRECTORY_SEPARATOR,
array(variable_get('wmf_common_phpmailer_location', ''),
'class.phpmailer.php'));
+ watchdog( 'wmf_communication', t( "Loading PHPMailer class from
:path", array( ':path' => $path ) ), WATCHDOG_INFO );
+ require_once( $path );
+ }
+
function send( $email ) {
+ watchdog( 'wmf_communication', t( "Sending an email to :to_address,
using PHPMailer", array( ':to_address' => $email['to_address'] ) ),
WATCHDOG_DEBUG );
+
$mailer = new \PHPMailer( true );
$mailer->set( 'Charset', 'utf-8' );
- $mailer->AddReplyTo( $email[ 'from_address' ], $email[ 'from_name' ] );
- $mailer->SetFrom( $email[ 'from_address' ], $email[ 'from_name' ] );
- $mailer->set( 'Sender', $email[ 'reply_to' ] );
+ $mailer->AddReplyTo( $email['from_address'], $email['from_name'] );
+ $mailer->SetFrom( $email['from_address'], $email['from_name'] );
+ $mailer->set( 'Sender', $email['reply_to'] );
- $mailer->AddAddress( $email[ 'to_address' ], $email[ 'to_name' ] );
+ $mailer->AddAddress( $email['to_address'], $email['to_name'] );
- $mailer->Subject = $email[ 'subject' ];
- $mailer->AltBody = $email[ 'plaintext' ];
- $mailer->MsgHTML( $email[ 'html' ] );
+ $mailer->Subject = $email['subject'];
+ $mailer->AltBody = $email['plaintext'];
+ $mailer->MsgHTML( $email['html'] );
$success = $mailer->Send();
return $success;
}
}
+
+class MailerDrupal implements IMailer {
+ function send( $email ) {
+ $from = "{$email['from_name']} <{$email['from_address']}>";
+
+ $headers = array(
+ 'From' => $from,
+ 'Sender' => $email['reply_to'],
+ 'Return-Path' => $from,
+ );
+
+ $body = $this->formatTwoPart( $email['html'], $email['plaintext'],
$headers );
+
+ $message = array(
+ 'id' => 'wmf_communication_generic',
+ 'to' => "{$email['to_name']} <{$email['to_address']}>",
+ 'subject' => $email['subject'],
+ 'body' => $body,
+ 'headers' => $headers,
+ );
+
+ $mailsys = drupal_mail_system( 'wmf_communication', 'generic' );
+ $success = $mailsys->mail( $message );
+
+ return $success;
+ }
+
+ protected function formatTwoPart( $html, $txt, &$headers ) {
+ $boundary = uniqid('wmf');
+
+ $headers['MIME-Version'] = '1.0';
+ $headers['Content-Type'] =
"multipart/alternative;boundary={$boundary}";
+
+ $body = "
+This is a MIME-encoded message.
+--{$boundary}
+Content-type: text/plain;charset=utf-8
+
+{$txt}
+
+--{$boundary}
+Content-type: text/html;charset=utf-8
+
+$html
+
+--{$boundary}--";
+
+ return $body;
+ }
+}
diff --git a/sites/all/modules/wmf_common/wmf_communication/MailingTemplate.php
b/sites/all/modules/wmf_common/wmf_communication/MailingTemplate.php
new file mode 100644
index 0000000..e4c157e
--- /dev/null
+++ b/sites/all/modules/wmf_common/wmf_communication/MailingTemplate.php
@@ -0,0 +1,60 @@
+<?php namespace wmf_communication;
+
+/**
+ * An overridable template provider, which returns the subject string and body
+ * template for a given recipient's preferred language.
+ */
+interface IMailingTemplate {
+ /**
+ * @return string Subject header for the letter
+ */
+ function getSubject( $recipient );
+
+ /**
+ * @return Templating template for generating the letter body
+ */
+ function getBodyTemplate( $recipient );
+}
+
+/**
+ * Most mailings will extend this class and simply define a subject key and
template path.
+ * Anything which will be customized on a per-recipient basis should be
controlled by this
+ * class or in your derivative.
+ */
+abstract class AbstractMailingTemplate implements IMailingTemplate {
+ /**
+ * @return string key into the DonationInterface i18n messages
+ */
+ abstract function getSubjectKey();
+
+ abstract function getTemplateDir();
+
+ abstract function getTemplateName();
+
+ function getFromAddress() {
+ return variable_get( 'thank_you_from_address', null );
+ }
+
+ function getFromName() {
+ return variable_get( 'thank_you_from_name', null );
+ }
+
+ function getSubject( $recipient ) {
+ return Translation::get_translated_message( $this->getSubjectKey(),
$recipient->getLanguage() );
+ }
+
+ function getBodyTemplate( $recipient ) {
+ $templateParams = array(
+ 'name' => $recipient->getName(),
+ 'email' => $recipient->getEmail(),
+ );
+ $templateParams = array_merge( $templateParams, $recipient->getVars()
);
+
+ return new Templating(
+ $this->getTemplateDir(),
+ $this->getTemplateName(),
+ $recipient->getLanguage(),
+ $templateParams
+ );
+ }
+}
diff --git a/sites/all/modules/wmf_common/wmf_communication/Recipient.php
b/sites/all/modules/wmf_common/wmf_communication/Recipient.php
new file mode 100644
index 0000000..359f2af
--- /dev/null
+++ b/sites/all/modules/wmf_common/wmf_communication/Recipient.php
@@ -0,0 +1,123 @@
+<?php namespace wmf_communication;
+
+use \Exception;
+
+/**
+ * Record linking a job and CiviCRM contact, contains all information needed to
+ * generate and send a letter, and current delivery status.
+ */
+class Recipient {
+ protected $contactId;
+ protected $jobId;
+ protected $status;
+
+ /**
+ * @var array additional parameters passed to the letter body template
+ */
+ protected $vars = array();
+
+ /**
+ * Get recipients for this job, in the "queued" status, up to $batchSize.
+ *
+ * Call this repeatedly until empty set is returned.
+ */
+ static function getQueuedBatch( $jobId, $batchSize = 100 ) {
+ $result = db_select( 'wmf_communication_recipient' )
+ ->fields( 'wmf_communication_recipient' )
+ ->condition( 'job_id', $jobId )
+ ->condition( 'status', 'queued' )
+ ->range( 0, $batchSize )
+ ->execute();
+
+ $recipients = array();
+ while ( $row = $result->fetchAssoc() ) {
+ $recipients[] = Recipient::loadFromRow( $row );
+ }
+ return $recipients;
+ }
+
+ static function create( $jobId, $contactId, $vars ) {
+ db_insert( 'wmf_communication_recipient' )
+ ->fields( array(
+ 'job_id' => $jobId,
+ 'contact_id' => $contactId,
+ 'status' => 'queued',
+ 'vars' => json_encode( $vars ),
+ ) )
+ ->execute();
+ }
+
+ /**
+ * @param array $dbRow associative form of the db record for a single
recipient
+ */
+ static protected function loadFromRow( $dbRow ) {
+ $recipient = new Recipient();
+
+ $recipient->contactId = $dbRow['contact_id'];
+ $recipient->jobId = $dbRow['job_id'];
+ $recipient->status = $dbRow['status'];
+
+ if ( $dbRow['vars'] ) {
+ $recipient->vars = json_decode( $dbRow['vars'], true );
+ if ( $recipient->vars === null ) {
+ throw new Exception( 'Could not decode serialized vars, error
code: ' . json_last_error() );
+ }
+ }
+
+ // FIXME: Get contact details en masse.
+ // TODO: Maybe we want to decouple from the civi db, and keep all
necessary contact
+ // info in the recipient table.
+ $api = civicrm_api_classapi();
+ $success = $api->Contact->get( array(
+ 'id' => $recipient->contactId,
+ 'return' => 'email,display_name,preferred_language',
+ 'version' => 3,
+ ) );
+ if ( !$success ) {
+ throw new Exception( $api->errorMsg() );
+ }
+ $values = $api->values();
+ if ( $values ) {
+ $recipient->contact = array_pop( $values );
+ } else {
+ throw new Exception( 'Tried to email a non-existent contact, ' .
$recipient->contactId );
+ }
+
+ return $recipient;
+ }
+
+ function getEmail() {
+ return $this->contact->email;
+ }
+
+ function getName() {
+ return $this->contact->display_name;
+ }
+
+ function getLanguage() {
+ return Translation::normalize_language_code(
$this->contact->preferred_language );
+ }
+
+ function getVars() {
+ return $this->vars;
+ }
+
+ function setFailed() {
+ $this->setStatus( 'failed' );
+ }
+
+ function setSuccessful() {
+ $this->setStatus( 'successful' );
+ }
+
+ /**
+ * Usually, you will want to use a specific accessor like setFailed, above.
+ */
+ function setStatus( $status ) {
+ db_update( 'wmf_communication_recipient' )
+ ->fields( array(
+ 'status' => $status
+ ) )
+ ->execute();
+ }
+}
diff --git a/sites/all/modules/wmf_common/wmf_communication/Templating.php
b/sites/all/modules/wmf_common/wmf_communication/Templating.php
index df60c35..b429001 100644
--- a/sites/all/modules/wmf_common/wmf_communication/Templating.php
+++ b/sites/all/modules/wmf_common/wmf_communication/Templating.php
@@ -1,10 +1,16 @@
-<?php
+<?php namespace wmf_communication;
-namespace wmf_communication;
+use \Exception;
+/**
+ * Single-use template, currently wraps the Twig implementation.
+ * Once this object has been instantiated, you can render in multiple formats,
+ * but template parameters are constant and can no longer be modified.
+ */
class Templating {
protected $twig;
+ protected $templates_dir;
protected $template_name;
protected $language;
protected $template_params;
@@ -15,24 +21,47 @@
}
$this->twig = wmf_common_get_twig( $templates_dir );
+ $this->templates_dir = $templates_dir;
$this->template_name = $template_name;
$this->language = $language;
$this->template_params = $template_params;
}
+ /**
+ * Construct a path from the given parameters, load the appropriate
+ * template, and render.
+ *
+ * For the parameters {template_name: thank_you, language: it, format:
txt}, we will look in the path
+ * ${templates_dir}/txt/thank_you.it.txt
+ *
+ * @param string $format
+ *
+ * @return string
+ */
function render( $format )
{
+ $template = null;
$language = $this->language;
do {
try {
- $template = $this->twig->loadTemplate(
"{$format}/{$this->template_name}.{$language}.{$format}" );
+ $path =
"{$format}/{$this->template_name}.{$language}.{$format}";
+ watchdog( 'wmf_communication',
+ t( "Attempting to load template from path :path",
+ array( ':path' => $this->templates_dir . "/" . $path )
+ ), WATCHDOG_DEBUG );
+ $template = $this->twig->loadTemplate( $path );
break;
} catch ( \Twig_Error_Loader $ex ) {
// pass
}
+ watchdog( 'wmf_communication', t( "Template not found for language
':language', attempting next fallback...", array( ':language' => $language ) ),
WATCHDOG_INFO );
$language = Translation::next_fallback( $language );
} while ( $language );
+ if ( !$template ) {
+ throw new Exception( "Cannot load template {$this->template_name}
/ {$this->language} . {$format}" );
+ }
+
return $template->render( $this->template_params );
}
}
diff --git a/sites/all/modules/wmf_common/wmf_communication/Translation.php
b/sites/all/modules/wmf_common/wmf_communication/Translation.php
index 64d1792..e3ed7b1 100644
--- a/sites/all/modules/wmf_common/wmf_communication/Translation.php
+++ b/sites/all/modules/wmf_common/wmf_communication/Translation.php
@@ -1,6 +1,6 @@
-<?php
+<?php namespace wmf_communication;
-namespace wmf_communication;
+use \Exception;
class Translation {
//TODO: get from MediaWiki
diff --git a/sites/all/modules/wmf_common/wmf_communication/tests/Job.test
b/sites/all/modules/wmf_common/wmf_communication/tests/Job.test
new file mode 100644
index 0000000..3b4e918
--- /dev/null
+++ b/sites/all/modules/wmf_common/wmf_communication/tests/Job.test
@@ -0,0 +1,84 @@
+<?php
+
+use wmf_communication\Job;
+use wmf_communication\Mailer;
+use wmf_communication\Recipient;
+
+require_once 'TestThankyouTemplate.php';
+
+class JobTest extends DrupalWebTestCase {
+ protected $profile = 'minimal';
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Mailing Job',
+ 'group' => 'Wikimedia',
+ 'description' => 'Create and run a mailing job',
+ );
+ }
+
+ public function setUp() {
+ // FIXME: cheat by taking some variables from the live db
+ $phpmailerDir = variable_get( 'wmf_common_phpmailer_location', '' );
+ $twigDir = variable_get( 'wmf_common_twig_location', '' );
+ $diDir = variable_get( 'wmf_common_di_location', '' );
+
+ parent::setUp( 'wmf_communication', 'wmf_common' );
+
+ variable_set( 'wmf_common_phpmailer_location', $phpmailerDir );
+ variable_set( 'wmf_common_twig_location', $twigDir );
+ variable_set( 'wmf_common_di_location', $diDir );
+
+ variable_set( 'thank_you_from_address', '[email protected]' );
+ variable_set( 'thank_you_from_name', 'Testus' );
+
+ Mailer::$defaultSystem = 'drupal';
+
+ $api = civicrm_api_classapi();
+ $success = $api->Contact->create( array(
+ 'contact_type' => 'Individual',
+ 'email' => '[email protected]',
+ 'first_name' => 'Foo',
+ 'last_name' => 'Beer',
+ 'preferred_language' => 'it',
+ 'version' => 3,
+ ) );
+ if ( !$success ) {
+ $this->fail( $api->errorMsg() );
+ }
+ $result = $api->values();
+ $contact = array_pop( $result );
+ $this->contactId = $contact->id;
+
+ $this->jobId = db_insert( 'wmf_communication_job' )
+ ->fields( array(
+ 'template_class' => 'TestThankyouTemplate',
+ ) )
+ ->execute();
+
+ Recipient::create(
+ $this->jobId,
+ $this->contactId,
+ array(
+ 'contribution' => array(
+ 'receive_date' => date( DateTime::ISO8601 ),
+ 'contribution_source' => 'EUR 22.11',
+ ),
+ )
+ );
+ }
+
+ public function testRun() {
+ $job = Job::getJob( $this->jobId );
+ $this->assertNotNull( $job, "Got the job" );
+ $job->run();
+
+ $mails = $this->drupalGetMails();
+ $this->verbose(json_encode($mails));
+ //...
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+ }
+}
diff --git
a/sites/all/modules/wmf_common/wmf_communication/tests/TestThankyouTemplate.php
b/sites/all/modules/wmf_common/wmf_communication/tests/TestThankyouTemplate.php
new file mode 100644
index 0000000..d2613c3
--- /dev/null
+++
b/sites/all/modules/wmf_common/wmf_communication/tests/TestThankyouTemplate.php
@@ -0,0 +1,17 @@
+<?php
+
+use wmf_communication\AbstractMailingTemplate;
+
+class TestThankyouTemplate extends AbstractMailingTemplate {
+ function getSubjectKey() {
+ return 'donate_interface-email-subject';
+ }
+
+ function getTemplateName() {
+ return 'thank_you';
+ }
+
+ function getTemplateDir() {
+ return __DIR__ . "/templates";
+ }
+}
diff --git
a/sites/all/modules/wmf_common/wmf_communication/tests/templates/html/thank_you.it.html
b/sites/all/modules/wmf_common/wmf_communication/tests/templates/html/thank_you.it.html
new file mode 100644
index 0000000..a2e0ea5
--- /dev/null
+++
b/sites/all/modules/wmf_common/wmf_communication/tests/templates/html/thank_you.it.html
@@ -0,0 +1,83 @@
+<p>Caro {{contact.first_name}},</p>
+
+<p>Grazie per la tua donazione alla Wikimedia Foundation. È stata davvero
apprezzata di
+cuore!</p>
+
+<p>È facile ignorare i nostri banner, perciò sono davvero felice che tu non
l'abbia
+fatto. È così che Wikipedia paga le proprie bollette: persone come te che
donano un po'
+del loro denaro per permetterci di mantenere questo sito disponibile
gratuitamente in
+tutto il mondo.</p>
+
+<p>Molte persone mi hanno detto che donano a Wikipedia perché lo considerano
un atto
+utile, e si fidano del progetto anche se non è perfetto, perché è scritto per
loro.
+Wikipedia non è fatta per supportare una qualche agenzia di pubbliche
relazioni o una
+particolare ideologia, né cerca di persuadere le persone a credere in qualcosa
che non
+sia vero. Il nostro obiettivo è dire la verità, e lo possiamo fare grazie a
te. Il tuo
+supporto economico al sito ci permette di restare indipendenti e di fornirti
ciò di cui
+hai bisogno e ciò che vuoi da Wikipedia. Esattamente come è giusto che sia.</p>
+
+<p>È necessario che tu sappia che la tua donazione non copre solamente i costi
correlati
+al tuo uso del progetto. Il donatore medio copre i costi del proprio utilizzo
ma anche per
+quello di migliaia di altre persone. La tua donazione rende Wikipedia
utilizzabile da
+tutti: un'ambiziosa bambina di Bangalore che sta imparando da autodidatta a
programmare al
+computer; una casalinga di mezza età di Vienna alla quale è stato appena
diagnosticato
+il morbo di Parkinson; un romanziere che sta studiando la letteratura
britannica di metà
+Ottocento; un bambino salvadoregno di dieci anni che ha appena scoperto Carl
Sagan.</p>
+
+<p>A nome di queste persone e del mezzo miliardo di altri lettori di Wikipedia
e i suoi
+progetti fratelli, ti ringrazio per il tuo supporto al nostro impegno nel
rendere la
+conoscenza umana disponibile per tutti. La tua donazione rende il mondo un
posto migliore.
+Grazie.</p>
+
+<p>La maggior parte delle persone non sa che Wikipedia funziona come
un'associazione no
+profit. Magari potresti occuparti di diffondere questa e-mail a qualche tuo
amico per
+incoraggiare anche loro ad effettuare una donazione. E se sei interessato,
puoi provare tu
+stesso ad aggiungere qualche nuova informazione a Wikipedia. Se trovi un
refuso o qualche
+piccolo errore, correggilo; se trovi qualche informazione mancante,
aggiungila.</p>
+
+<p>Apprezzo molto la fiducia che ci hai accordato, e prometto che utilizzeremo
il tuo
+denaro al meglio.</p>
+
+<p>Grazie,<br />
+Sue</p>
+
+<br />
+<p>Sue Gardner<br />
+Direttore esecutivo,<br />
+Fondazione Wikimedia</p>
+
+<p>Per le tue registrazioni: La tua donazione il {{contribution.receive_date}}
è stata
+{{contribution.contribution_source|l10n_currency(locale)}}.{% if recurring
%}{% include
+'recurring/it.html' ignore missing %}{% endif %}</p>
+
+<p>Questa lettera può servire come prova della tua donazione. Nessun bene o
servizio è
+stato fornito, in tutto o in parte, per questo contributo. La Wikimedia
Foundation, Inc.
+è un'organizzazione no profit di beneficenza con esenzione fiscale negli Stati
Uniti. Il
+nostro indirizzo è 149 New Montgomery, 3rd Floor, San Francisco, CA, 94105.
Numero di
+esenzione fiscale (Stati Uniti): 20-0049703</p>
+
+<p>Puoi seguirci su <a href="https://twitter.com/Wikipedia">Twitter</a>, <a
+href="https://identi.ca/wikipedia">identi.ca</a> o <a
+href="https://plus.google.com/+Wikipedia/posts">Google+</a>, mettere un "mi
piace" su <a
+href="https://www.facebook.com/wikipedia">Facebook</a> e leggere <a
+href="https://blog.wikimedia.org">il nostro blog</a>. Di seguito puoi trovare:
il <a
+href="https://wikimediafoundation.org/wiki/Annual_Report">Rapporto annuale
2010/2011 della
+Wikimedia Foundation</a>, il <a
+href="http://upload.wikimedia.org/wikipedia/foundation/4/4f/2012-13_Wikimedia_Foundation_Plan_FINAL_FOR_WEBSITE.pdf">Piano
+annuale 2012/2013 della Wikimedia Foundation</a> e il <a
+href="https://wikimediafoundation.org/wiki/Wikimedia_Movement_Strategic_Plan_Summary">Piano
+strategico quinquennale della Wikimedia Foundation</a>. Puoi anche comprare
merchandise di
+Wikipedia su <a href="https://shop.wikimedia.org">shop.wikimedia.org</a>.</p>
+
+<div style="padding:0 10px 5px 10px; border:1px solid black;">
+
+<p><i>Disiscrizione</i></p>
+
+<p>In quanto donatore, saremmo felici di aggiornarti sulle attività della
comunità e le
+raccolte fondi. Se però preferisci non ricevere questo genere di email, clicca
sul link
+sottostante e il tuo nominativo sarà prontamente cancellato dalla lista.</p>
+
+<a style="padding-left: 25px;" href="{{unsubscribe_link|raw}}">Unsubscribe |
Non sottoscrivere</a>
+
+</div>
+
diff --git
a/sites/all/modules/wmf_common/wmf_communication/tests/templates/txt/thank_you.it.txt
b/sites/all/modules/wmf_common/wmf_communication/tests/templates/txt/thank_you.it.txt
new file mode 100644
index 0000000..a87b2aa
--- /dev/null
+++
b/sites/all/modules/wmf_common/wmf_communication/tests/templates/txt/thank_you.it.txt
@@ -0,0 +1,91 @@
+
+
+ Caro {{contact.first_name}},
+
+ Grazie per la tua donazione alla Wikimedia Foundation. È stata
+davvero apprezzata di cuore!
+
+ È facile ignorare i nostri banner, perciò sono davvero felice che tu
+non l'abbia fatto. È così che Wikipedia paga le proprie bollette: persone
+come te che donano un po' del loro denaro per permetterci di mantenere
+questo sito disponibile gratuitamente in tutto il mondo.
+
+ Molte persone mi hanno detto che donano a Wikipedia perché lo
+considerano un atto utile, e si fidano del progetto anche se non è perfetto,
+perché è scritto per loro. Wikipedia non è fatta per supportare una qualche
+agenzia di pubbliche relazioni o una particolare ideologia, né cerca di
+persuadere le persone a credere in qualcosa che non sia vero. Il nostro
+obiettivo è dire la verità, e lo possiamo fare grazie a te. Il tuo supporto
+economico al sito ci permette di restare indipendenti e di fornirti ciò di cui
+hai bisogno e ciò che vuoi da Wikipedia. Esattamente come è giusto che
+sia.
+
+ È necessario che tu sappia che la tua donazione non copre solamente i
+costi correlati al tuo uso del progetto. Il donatore medio copre i costi
+del proprio utilizzo ma anche per quello di migliaia di altre persone. La
+tua donazione rende Wikipedia utilizzabile da tutti: un'ambiziosa bambina
+di Bangalore che sta imparando da autodidatta a programmare al computer;
+una casalinga di mezza età di Vienna alla quale è stato appena
+diagnosticato il morbo di Parkinson; un romanziere che sta studiando la
letteratura
+britannica di metà Ottocento; un bambino salvadoregno di dieci anni che ha
+appena scoperto Carl Sagan.
+
+ A nome di queste persone e del mezzo miliardo di altri lettori di
+Wikipedia e i suoi progetti fratelli, ti ringrazio per il tuo supporto al
+nostro impegno nel rendere la conoscenza umana disponibile per tutti. La tua
+donazione rende il mondo un posto migliore. Grazie.
+
+ La maggior parte delle persone non sa che Wikipedia funziona come
+un'associazione no profit. Magari potresti occuparti di diffondere questa
e-mail a
+qualche tuo amico per incoraggiare anche loro ad effettuare una donazione. E
+se sei interessato, puoi provare tu stesso ad aggiungere qualche nuova
+informazione a Wikipedia. Se trovi un refuso o qualche piccolo errore,
correggilo;
+se trovi qualche informazione mancante, aggiungila.
+
+ Apprezzo molto la fiducia che ci hai accordato, e prometto che
+utilizzeremo il tuo denaro al meglio.
+
+ Grazie,
+ Sue
+
+ Sue Gardner
+ Direttore esecutivo,
+ Fondazione Wikimedia
+
+ Per le tue registrazioni: La tua donazione il
+{{contribution.receive_date}} è stata
{{contribution.contribution_source|l10n_currency(locale)}}.{%
+if recurring %}{% include 'recurring/it.html' ignore missing %}{% endif
+%}
+
+ Questa lettera può servire come prova della tua donazione. Nessun
+bene o servizio è stato fornito, in tutto o in parte, per questo
+contributo. La Wikimedia Foundation, Inc. è un'organizzazione no profit di
+beneficenza con esenzione fiscale negli Stati Uniti. Il nostro indirizzo è 149
+New Montgomery, 3rd Floor, San Francisco, CA, 94105. Numero di esenzione
+fiscale (Stati Uniti): 20-0049703
+
+ Puoi seguirci su Twitter [1], identi.ca [2] o Google+ [3], mettere un
+"mi piace" su Facebook [4] e leggere il nostro blog [5]. Di seguito puoi
+trovare: il Rapporto annuale 2010/2011 della Wikimedia Foundation [6], il
+Piano annuale 2012/2013 della Wikimedia Foundation [7] e il Piano
+strategico quinquennale della Wikimedia Foundation [8]. Puoi anche comprare
+merchandise di Wikipedia su shop.wikimedia.org [9].
+
+ _Disiscrizione_
+
+ In quanto donatore, saremmo felici di aggiornarti sulle attività
+della comunità e le raccolte fondi. Se però preferisci non ricevere questo
+genere di email, clicca sul link sottostante e il tuo nominativo sarà
+prontamente cancellato dalla lista. Unsubscribe | Non sottoscrivere [10]
+
+
+[1] https://twitter.com/Wikipedia
+[2] https://identi.ca/wikipedia
+[3] https://plus.google.com/+Wikipedia/posts
+[4] https://www.facebook.com/wikipedia
+[5] https://blog.wikimedia.org
+[6] https://wikimediafoundation.org/wiki/Annual_Report
+[7]
http://upload.wikimedia.org/wikipedia/foundation/4/4f/2012-13_Wikimedia_Foundation_Plan_FINAL_FOR_WEBSITE.pdf
+[8]
https://wikimediafoundation.org/wiki/Wikimedia_Movement_Strategic_Plan_Summary
+[9] https://shop.wikimedia.org
+[10] {{unsubscribe_link|raw}}
diff --git
a/sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.info
b/sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.info
new file mode 100644
index 0000000..ba832e7
--- /dev/null
+++
b/sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.info
@@ -0,0 +1,6 @@
+name = WMF Communication Test Cases
+description = Simpletests
+package = Wikimedia
+core = 7.x
+dependencies[] = wmf_communication
+files[] = Job.test
diff --git
a/sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.module
b/sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.module
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++
b/sites/all/modules/wmf_common/wmf_communication/tests/wmf_communication_tests.module
@@ -0,0 +1 @@
+<?php
diff --git
a/sites/all/modules/wmf_common/wmf_communication/wmf_communication.drush.inc
b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.drush.inc
new file mode 100644
index 0000000..7fb2403
--- /dev/null
+++ b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.drush.inc
@@ -0,0 +1,30 @@
+<?php
+
+function wmf_communication_drush_command()
+{
+ $items = array(
+ 'wmf-send-letters' => array(
+ 'description' => 'Run a prepared mailing job',
+ 'arguments' => array(
+ 'job' => 'Job id',
+ ),
+ 'required-arguments' => true,
+ ),
+ );
+
+ return $items;
+}
+
+function wmf_communication_drush_help($section)
+{
+ switch ($section) {
+ case 'drush:wmf-send-letters':
+ return dt('Run a prepared mailing job.');
+ }
+}
+
+function drush_wmf_communication_wmf_send_letters()
+{
+ $options = drush_get_arguments();
+ module_invoke('wmf_communication', 'send_letters', $options[1] );
+}
diff --git
a/sites/all/modules/wmf_common/wmf_communication/wmf_communication.info
b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.info
index 727c368..921affd 100644
--- a/sites/all/modules/wmf_common/wmf_communication/wmf_communication.info
+++ b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.info
@@ -3,3 +3,10 @@
core = 7.x
package = Wikimedia
dependencies[] = wmf_common
+dependencies[] = wmf_civicrm
+files[] = Job.php
+files[] = Mailer.php
+files[] = MailingTemplate.php
+files[] = Recipient.php
+files[] = Templating.php
+files[] = Translation.php
diff --git
a/sites/all/modules/wmf_common/wmf_communication/wmf_communication.install
b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.install
new file mode 100644
index 0000000..7373452
--- /dev/null
+++ b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.install
@@ -0,0 +1,68 @@
+<?php
+
+function wmf_communication_schema() {
+ $schema['wmf_communication_job'] = array(
+ 'description' => 'Specification of mail job',
+ 'fields' => array(
+ 'id' => array(
+ 'type' => 'serial',
+ 'unsigned' => true,
+ 'not null' => true,
+ ),
+ 'template_class' => array(
+ 'description' => 'Template provider, instance of
AbstractMailingClass',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => true,
+ ),
+ ),
+ 'primary key' => array('id'),
+ 'indexes' => array(
+ 'job_template' => array('template_class'),
+ ),
+ );
+
+ $schema['wmf_communication_recipient'] = array(
+ 'description' => 'Record linking contact and mail job',
+ 'fields' => array(
+ 'contact_id' => array(
+ 'description' => 'Foreign key to civicrm.civicrm_contact',
+ 'type' => 'int',
+ 'unsigned' => true,
+ 'not null' => true,
+ ),
+ 'job_id' => array(
+ 'type' => 'int',
+ 'unsigned' => true,
+ 'not null' => true,
+ ),
+ 'status' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ ),
+ 'vars' => array(
+ 'description' => 'Extra parameters to pass during template
rendering, specific to this contact. Stored as JSON.',
+ 'type' => 'text',
+ ),
+ ),
+ 'indexes' => array(
+ 'recipient_status' => array('status'),
+ 'recipient_job' => array('job_id'),
+ ),
+ 'unique keys' => array(
+ 'recipient_contact_job' => array('contact_id', 'job_id'),
+ ),
+ 'foreign keys' => array(
+ 'recipient_job' => array(
+ 'table' => 'wmf_communication_job',
+ 'columns' => array('job_id' => 'id'),
+ ),
+ 'recipient_contact' => array(
+ 'table' => 'civicrm.civicrm_contact',
+ 'columns' => array('contact_id' => 'id'),
+ ),
+ ),
+ );
+
+ return $schema;
+}
diff --git
a/sites/all/modules/wmf_common/wmf_communication/wmf_communication.module
b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.module
index 918dec1..8876c00 100644
--- a/sites/all/modules/wmf_common/wmf_communication/wmf_communication.module
+++ b/sites/all/modules/wmf_common/wmf_communication/wmf_communication.module
@@ -1,5 +1,15 @@
<?php
+# oh well, Drupal's registry won't do namespaces until d8.
+require_once 'Job.php';
require_once 'Mailer.php';
+require_once 'MailingTemplate.php';
+require_once 'Recipient.php';
require_once 'Templating.php';
require_once 'Translation.php';
+
+use wmf_communication\Job;
+
+function wmf_communication_send_letters( $jobId ) {
+ Job::getJob( $jobId )->run();
+}
diff --git a/sites/all/modules/wmf_eoy_receipt/EoySummary.php
b/sites/all/modules/wmf_eoy_receipt/EoySummary.php
index 3fb24af..844ed4c 100644
--- a/sites/all/modules/wmf_eoy_receipt/EoySummary.php
+++ b/sites/all/modules/wmf_eoy_receipt/EoySummary.php
@@ -114,7 +114,7 @@
function send_letters()
{
- $mailer = new Mailer();
+ $mailer = Mailer::getDefault();
$sql = <<<EOS
SELECT *
--
To view, visit https://gerrit.wikimedia.org/r/74305
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I36cd21a1bff531931565e1248e8c39edd0a066c4
Gerrit-PatchSet: 5
Gerrit-Project: wikimedia/fundraising/crm
Gerrit-Branch: master
Gerrit-Owner: Adamw <[email protected]>
Gerrit-Reviewer: Katie Horn <[email protected]>
Gerrit-Reviewer: Mwalker <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits