Additional files for PDF/Latex
authorLisa Marie Maginnis <lisam@fsf.org>
Thu, 1 May 2014 21:29:27 +0000 (17:29 -0400)
committerMichael McMahon <michael@fsf.org>
Tue, 27 Sep 2022 21:05:21 +0000 (17:05 -0400)
14 files changed:
CRM/Contact/Form/Task/LatexPDF.php [new file with mode: 0644]
CRM/Contact/Form/Task/PDFLatexCommon.php [new file with mode: 0644]
CRM/Contribute/Form/Task/PDFLatex.php [new file with mode: 0644]
CRM/Contribute/Form/Task/PDFLatexCommon.php [new file with mode: 0644]
CRM/Core/Payment/TrustCommerce.php [new file with mode: 0644]
CRM/Core/Payment/insert_tc.sql [new file with mode: 0644]
i/mini_cvv2.gif [new file with mode: 0644]
latex.patch [new file with mode: 0644]
sigjohns.pdf [new file with mode: 0644]
sigjohns.png [new file with mode: 0644]
templates/CRM/Contact/Form/Task/LatexPDF.tpl [new file with mode: 0644]
templates/CRM/Contact/Form/Task/LatexPDFLatexCommon.tpl [new file with mode: 0644]
templates/CRM/Contribute/Form/Task/PDFLatex.tpl [new file with mode: 0644]
tmp_table_myisam.patch [new file with mode: 0644]

diff --git a/CRM/Contact/Form/Task/LatexPDF.php b/CRM/Contact/Form/Task/LatexPDF.php
new file mode 100644 (file)
index 0000000..3fccdd8
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.3                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2013                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2013
+ * $Id$
+ *
+ */
+
+/**
+ * This class provides the functionality to create PDF letter for a group of
+ * contacts or a single contact.
+ */
+class CRM_Contact_Form_Task_LatexPDF extends CRM_Contact_Form_Task {
+
+  /**
+   * all the existing templates in the system
+   *
+   * @var array
+   */
+  public $_templates = NULL;
+
+  public $_single = NULL;
+
+  public $_cid = NULL;
+
+  public $_activityId = NULL;
+
+  /**
+   * build all the data structures needed to build the form
+   *
+   * @return void
+   * @access public
+   */
+  function preProcess() {
+
+    $this->skipOnHold = $this->skipDeceased = FALSE;
+    CRM_Contact_Form_Task_PDFLatexCommon::preProcess($this);
+
+    // store case id if present
+    $this->_caseId = CRM_Utils_Request::retrieve('caseid', 'Positive', $this, FALSE);
+
+    // retrieve contact ID if this is 'single' mode
+    $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE);
+
+    if ($cid) {
+      // this is true in non-search context / single mode
+      // in search context 'id' is the default profile id for search display
+      // CRM-11227
+      $this->_activityId = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
+    }
+
+    if ($cid) {
+      CRM_Contact_Form_Task_PDFLatexCommon::preProcessSingle($this, $cid);
+      $this->_single = TRUE;
+      $this->_cid = $cid;
+    }
+    else {
+      parent::preProcess();
+    }
+    $this->assign('single', $this->_single);
+  }
+
+  function setDefaultValues() {
+    $defaults = array();
+    if (isset($this->_activityId)) {
+      $params = array('id' => $this->_activityId);
+      CRM_Activity_BAO_Activity::retrieve($params, $defaults);
+      $defaults['html_message'] = CRM_Utils_Array::value('details', $defaults);
+    }
+    $defaults = $defaults + CRM_Contact_Form_Task_PDFLatexCommon::setDefaultValues();
+    return $defaults;
+  }
+
+  /**
+   * Build the form
+   *
+   * @access public
+   *
+   * @return void
+   */
+  public function buildQuickForm() {
+    //enable form element
+    $this->assign('suppressForm', FALSE);
+    CRM_Contact_Form_Task_PDFLatexCommon::buildQuickForm($this);
+  }
+
+  /**
+   * process the form after the input has been submitted and validated
+   *
+   * @access public
+   *
+   * @return None
+   */
+  public function postProcess() {
+    CRM_Contact_Form_Task_PDFLatexCommon::postProcess($this);
+  }
+}
+
diff --git a/CRM/Contact/Form/Task/PDFLatexCommon.php b/CRM/Contact/Form/Task/PDFLatexCommon.php
new file mode 100644 (file)
index 0000000..a0ca453
--- /dev/null
@@ -0,0 +1,434 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.3                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2013                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2013
+ * $Id$
+ *
+ */
+
+/**
+ * This class provides the common functionality for creating PDF letter for
+ * one or a group of contact ids.
+ */
+class CRM_Contact_Form_Task_PDFLatexCommon {
+
+  /**
+   * build all the data structures needed to build the form
+   *
+   * @return void
+   * @access public
+   */
+  static function preProcess(&$form) {
+    $messageText    = array();
+    $messageSubject = array();
+    $dao            = new CRM_Core_BAO_MessageTemplates();
+    $dao->is_active = 1;
+    $dao->find();
+    while ($dao->fetch()) {
+      $messageText[$dao->id] = $dao->msg_text;
+      $messageSubject[$dao->id] = $dao->msg_subject;
+    }
+
+    $form->assign('message', $messageText);
+    $form->assign('messageSubject', $messageSubject);
+  }
+
+  static function preProcessSingle(&$form, $cid) {
+    $form->_contactIds = array($cid);
+    // put contact display name in title for single contact mode
+    CRM_Contact_Page_View::setTitle($cid);
+  }
+
+  /**
+   * Build the form
+   *
+   * @access public
+   *
+   * @return void
+   */
+  static function buildQuickForm(&$form) {
+    $form->add('static', 'pdf_format_header', NULL, ts('Page Format'));
+    $form->add(
+      'select',
+      'format_id',
+      ts('Select Format'),
+      array(0 => ts('- default -')) + CRM_Core_BAO_PdfFormat::getList(TRUE),
+      FALSE,
+      array('onChange' => "selectFormat( this.value, false );")
+    );;
+    $form->add(
+      'select',
+      'paper_size',
+      ts('Paper Size'),
+      array(0 => ts('- default -')) + CRM_Core_BAO_PaperSize::getList(TRUE),
+      FALSE,
+      array('onChange' => "selectPaper( this.value ); showUpdateFormatChkBox();")
+    );
+    $form->add('static', 'paper_dimensions', NULL, ts('Width x Height'));
+    $form->add(
+      'select',
+      'orientation',
+      ts('Orientation'),
+      CRM_Core_BAO_PdfFormat::getPageOrientations(),
+      FALSE,
+      array('onChange' => "updatePaperDimensions(); showUpdateFormatChkBox();")
+    );
+    $form->add(
+      'select',
+      'metric',
+      ts('Unit of Measure'),
+      CRM_Core_BAO_PdfFormat::getUnits(),
+      FALSE,
+      array('onChange' => "selectMetric( this.value );")
+    );
+    $form->add(
+      'text',
+      'margin_left',
+      ts('Left Margin'),
+      array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"),
+      TRUE
+    );
+    $form->add(
+      'text',
+      'margin_right',
+      ts('Right Margin'),
+      array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"),
+      TRUE
+    );
+    $form->add(
+      'text',
+      'margin_top',
+      ts('Top Margin'),
+      array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"),
+      TRUE
+    );
+    $form->add(
+      'text',
+      'margin_bottom',
+      ts('Bottom Margin'),
+      array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"),
+      TRUE
+    );
+    $form->add('checkbox', 'bind_format', ts('Always use this Page Format with the selected Template'));
+    $form->add('checkbox', 'update_format', ts('Update Page Format (this will affect all templates that use this format)'));
+
+    $form->assign('useThisPageFormat', ts('Always use this Page Format with the new template?'));
+    $form->assign('useSelectedPageFormat', ts('Should the new template always use the selected Page Format?'));
+    $form->assign('totalSelectedContacts', count($form->_contactIds));
+
+    CRM_Mailing_BAO_Mailing::commonLetterCompose($form);
+
+    if ($form->_single) {
+      $cancelURL = CRM_Utils_System::url(
+        'civicrm/contact/view',
+        "reset=1&cid={$form->_cid}&selectedChild=activity",
+        FALSE,
+        NULL,
+        FALSE
+      );
+      if ($form->get('action') == CRM_Core_Action::VIEW) {
+        $form->addButtons(array(
+            array(
+              'type' => 'cancel',
+              'name' => ts('Done'),
+              'js' => array('onclick' => "location.href='{$cancelURL}'; return false;"),
+            ),
+          )
+        );
+      }
+      else {
+        $form->addButtons(array(
+            array(
+              'type' => 'submit',
+              'name' => ts('Make PDF Letter'),
+              'isDefault' => TRUE,
+            ),
+            array(
+              'type' => 'cancel',
+              'name' => ts('Done'),
+              'js' => array('onclick' => "location.href='{$cancelURL}'; return false;"),
+            ),
+          )
+        );
+      }
+    }
+    else {
+      $form->addDefaultButtons(ts('Make PDF Letters'));
+    }
+
+    $form->addFormRule(array('CRM_Contact_Form_Task_PDFLatexCommon', 'formRule'), $form);
+  }
+
+  /**
+   * Set default values
+   */
+  static function setDefaultValues() {
+    $defaultFormat = CRM_Core_BAO_PdfFormat::getDefaultValues();
+    $defaultFormat['format_id'] = $defaultFormat['id'];
+    return $defaultFormat;
+  }
+
+  /**
+   * form rule
+   *
+   * @param array $fields    the input form values
+   * @param array $dontCare
+   * @param array $self      additional values form 'this'
+   *
+   * @return true if no errors, else array of errors
+   * @access public
+   *
+   */
+  static function formRule($fields, $dontCare, $self) {
+    $errors = array();
+    $template = CRM_Core_Smarty::singleton();
+
+    //Added for CRM-1393
+    if (CRM_Utils_Array::value('saveTemplate', $fields) && empty($fields['saveTemplateName'])) {
+      $errors['saveTemplateName'] = ts("Enter name to save message template");
+    }
+    if (!is_numeric($fields['margin_left'])) {
+      $errors['margin_left'] = 'Margin must be numeric';
+    }
+    if (!is_numeric($fields['margin_right'])) {
+      $errors['margin_right'] = 'Margin must be numeric';
+    }
+    if (!is_numeric($fields['margin_top'])) {
+      $errors['margin_top'] = 'Margin must be numeric';
+    }
+    if (!is_numeric($fields['margin_bottom'])) {
+      $errors['margin_bottom'] = 'Margin must be numeric';
+    }
+    return empty($errors) ? TRUE : $errors;
+  }
+
+  /**
+   * part of the post process which prepare and extract information from the template
+   *
+   * @access protected
+   *
+   * @return array( $categories, $html_message, $messageToken, $returnProperties )
+   */
+  static protected function processMessageTemplate(&$form) {
+    $formValues = $form->controller->exportValues($form->getName());
+
+    // process message template
+    if (CRM_Utils_Array::value('saveTemplate', $formValues) || CRM_Utils_Array::value('updateTemplate', $formValues)) {
+      $messageTemplate = array(
+        'msg_text' => NULL,
+        'msg_html' => $formValues['html_message'],
+        'msg_subject' => NULL,
+        'is_active' => TRUE,
+      );
+
+      $messageTemplate['pdf_format_id'] = 'null';
+      if (CRM_Utils_Array::value('bind_format', $formValues) && $formValues['format_id'] > 0) {
+        $messageTemplate['pdf_format_id'] = $formValues['format_id'];
+      }
+      if (CRM_Utils_Array::value('saveTemplate', $formValues) && $formValues['saveTemplate']) {
+        $messageTemplate['msg_title'] = $formValues['saveTemplateName'];
+        CRM_Core_BAO_MessageTemplates::add($messageTemplate);
+      }
+
+      if (CRM_Utils_Array::value('updateTemplate', $formValues) && $formValues['template'] && $formValues['updateTemplate']) {
+        $messageTemplate['id'] = $formValues['template'];
+
+        unset($messageTemplate['msg_title']);
+        CRM_Core_BAO_MessageTemplates::add($messageTemplate);
+      }
+    }
+    elseif (CRM_Utils_Array::value('template', $formValues) > 0) {
+      if (CRM_Utils_Array::value('bind_format', $formValues) && $formValues['format_id'] > 0) {
+        $query = "UPDATE civicrm_msg_template SET pdf_format_id = {$formValues['format_id']} WHERE id = {$formValues['template']}";
+      }
+      else {
+        $query = "UPDATE civicrm_msg_template SET pdf_format_id = NULL WHERE id = {$formValues['template']}";
+      }
+      CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
+    }
+    if (CRM_Utils_Array::value('update_format', $formValues)) {
+      $bao = new CRM_Core_BAO_PdfFormat();
+      $bao->savePdfFormat($formValues, $formValues['format_id']);
+    }
+
+    $html = array();
+
+    $tokens = array();
+    CRM_Utils_Hook::tokens($tokens);
+    $categories = array_keys($tokens);
+
+    $html_message = $formValues['html_message'];
+
+    //time being hack to strip '&nbsp;'
+    //from particular letter line, CRM-6798
+    self::formatMessage($html_message);
+
+    $messageToken = CRM_Utils_Token::getTokens($html_message);
+
+    $returnProperties = array();
+    if (isset($messageToken['contact'])) {
+      foreach ($messageToken['contact'] as $key => $value) {
+        $returnProperties[$value] = 1;
+      }
+    }
+
+    return array($formValues, $categories, $html_message, $messageToken, $returnProperties);
+  }
+
+  /**
+   * process the form after the input has been submitted and validated
+   *
+   * @access public
+   *
+   * @return None
+   */
+  static function postProcess(&$form) {
+    list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($form);
+
+    $skipOnHold = isset($form->skipOnHold) ? $form->skipOnHold : FALSE;
+    $skipDeceased = isset($form->skipDeceased) ? $form->skipDeceased : TRUE;
+
+    foreach ($form->_contactIds as $item => $contactId) {
+      $params = array('contact_id' => $contactId);
+
+      list($contact) = CRM_Utils_Token::getTokenDetails($params,
+        $returnProperties,
+        $skipOnHold,
+        $skipDeceased,
+        NULL,
+        $messageToken,
+        'CRM_Contact_Form_Task_PDFLatexCommon'
+      );
+      if (civicrm_error($contact)) {
+        $notSent[] = $contactId;
+        continue;
+      }
+
+      $tokenHtml = CRM_Utils_Token::replaceContactTokens($html_message, $contact[$contactId], TRUE, $messageToken);
+      $tokenHtml = CRM_Utils_Token::replaceHookTokens($tokenHtml, $contact[$contactId], $categories, TRUE);
+
+      if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
+        $smarty = CRM_Core_Smarty::singleton();
+        // also add the contact tokens to the template
+        $smarty->assign_by_ref('contact', $contact);
+        $tokenHtml = $smarty->fetch("string:$tokenHtml");
+      }
+
+      $html[] = $tokenHtml;
+    }
+
+    self::createActivities($form, $html_message, $form->_contactIds);
+
+    CRM_Utils_PDF_Utils::latex2pdf($html, "CiviLatex.pdf", FALSE, $formValues);
+
+    $form->postProcessHook();
+
+    CRM_Utils_System::civiExit(1);
+  }
+
+  function createActivities($form, $html_message, $contactIds) {
+
+    $session        = CRM_Core_Session::singleton();
+    $userID         = $session->get('userID');
+    $activityTypeID = CRM_Core_OptionGroup::getValue('activity_type',
+      'Print PDF LaTeX Letter',
+      'name'
+    );
+    $activityParams = array(
+      'source_contact_id' => $userID,
+      'activity_type_id' => $activityTypeID,
+      'activity_date_time' => date('YmdHis'),
+      'details' => $html_message,
+    );
+    if (!empty($form->_activityId)) {
+      $activityParams += array('id' => $form->_activityId);
+    }
+    if ($form->_cid) {
+      $activity = CRM_Activity_BAO_Activity::create($activityParams);
+    }
+    else {
+      // create  Print PDF activity for each selected contact. CRM-6886
+      $activityIds = array();
+      foreach ($contactIds as $contactId) {
+        $activityID = CRM_Activity_BAO_Activity::create($activityParams);
+        $activityIds[$contactId] = $activityID->id;
+      }
+    }
+
+    foreach ($form->_contactIds as $contactId) {
+      $activityTargetParams = array(
+        'activity_id' => empty($activity->id) ? $activityIds[$contactId] : $activity->id,
+        'target_contact_id' => $contactId,
+      );
+      CRM_Activity_BAO_Activity::createActivityTarget($activityTargetParams);
+    }
+  }
+
+  function formatMessage(&$message) {
+    $newLineOperators = array(
+      'p' => array(
+        'oper' => '<p>',
+        'pattern' => '/<(\s+)?p(\s+)?>/m',
+      ),
+      'br' => array(
+        'oper' => '<br />',
+        'pattern' => '/<(\s+)?br(\s+)?\/>/m',
+      ),
+    );
+
+    $htmlMsg = preg_split($newLineOperators['p']['pattern'], $message);
+    foreach ($htmlMsg as $k => & $m) {
+      $messages = preg_split($newLineOperators['br']['pattern'], $m);
+      foreach ($messages as $key => & $msg) {
+        $msg = trim($msg);
+        $matches = array();
+        if (preg_match('/^(&nbsp;)+/', $msg, $matches)) {
+          $spaceLen = strlen($matches[0]) / 6;
+          $trimMsg  = ltrim($msg, '&nbsp; ');
+          $charLen  = strlen($trimMsg);
+          $totalLen = $charLen + $spaceLen;
+          if ($totalLen > 100) {
+            $spacesCount = 10;
+            if ($spaceLen > 50) {
+              $spacesCount = 20;
+            }
+            if ($charLen > 100) {
+              $spacesCount = 1;
+            }
+            $msg = str_repeat('&nbsp;', $spacesCount) . $trimMsg;
+          }
+        }
+      }
+      $m = implode($newLineOperators['br']['oper'], $messages);
+    }
+    $message = implode($newLineOperators['p']['oper'], $htmlMsg);
+  }
+}
+
diff --git a/CRM/Contribute/Form/Task/PDFLatex.php b/CRM/Contribute/Form/Task/PDFLatex.php
new file mode 100644 (file)
index 0000000..f353e00
--- /dev/null
@@ -0,0 +1,147 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.4                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2013                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2013
+ * $Id$
+ *
+ */
+
+/**
+ * This class provides the functionality to create PDF letter for a group of
+ * contacts or a single contact.
+ */
+class CRM_Contribute_Form_Task_PDFLatex extends CRM_Contribute_Form_Task {
+
+  /**
+   * all the existing templates in the system
+   *
+   * @var array
+   */
+  public $_templates = NULL;
+
+  public $_single = NULL;
+
+  public $_cid = NULL;
+
+  /**
+   * build all the data structures needed to build the form
+   *
+   * @return void
+   * @access public
+   */
+  function preProcess() {
+    $this->skipOnHold = $this->skipDeceased = FALSE;
+    CRM_Contact_Form_Task_PDFLetterCommon::preProcess($this);
+
+    // store case id if present
+    $this->_caseId = CRM_Utils_Request::retrieve('caseid', 'Positive', $this, FALSE);
+
+    // retrieve contact ID if this is 'single' mode
+    $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE);
+
+    $this->_activityId = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
+
+    if ($cid) {
+      CRM_Contact_Form_Task_PDFLetterCommon::preProcessSingle($this, $cid);
+      $this->_single = TRUE;
+      $this->_cid = $cid;
+    }
+    else {
+      parent::preProcess();
+    }
+    $this->assign('single', $this->_single);
+  }
+
+  function setDefaultValues() {
+    $defaults = array();
+    if (isset($this->_activityId)) {
+      $params = array('id' => $this->_activityId);
+      CRM_Activity_BAO_Activity::retrieve($params, $defaults);
+      $defaults['html_message'] = $defaults['details'];
+    }
+    $defaults = $defaults + CRM_Contact_Form_Task_PDFLetterCommon::setDefaultValues();
+    return $defaults;
+  }
+
+  /**
+   * Build the form
+   *
+   * @access public
+   *
+   * @return void
+   */
+  public function buildQuickForm() {
+    //enable form element
+    $this->assign('suppressForm', FALSE);
+
+    // use contact form as a base
+    CRM_Contact_Form_Task_PDFLetterCommon::buildQuickForm($this);
+
+    // specific need for contributions
+    $this->add('static', 'more_options_header', NULL, ts('Record Update Options'));
+    $this->add('checkbox', 'receipt_update', ts('Update receipt dates for these contributions'), FALSE);
+    $this->add('checkbox', 'thankyou_update', ts('Update thank-you dates for these contributions'), FALSE);
+
+    // Group options for tokens are not yet implemented. dgg
+    $options = array(ts('Contact'), ts('Recurring'));
+    $this->addRadio('is_group_by', ts('Grouping contributions in one letter based on'), $options, array(), "<br/>", FALSE);
+
+    $this->addButtons(array(
+        array(
+          'type' => 'submit',
+          'name' => ts('Make Thank-you Letters'),
+          'isDefault' => TRUE,
+        ),
+        array(
+          'type' => 'cancel',
+          'name' => ts('Done'),
+        ),
+      )
+    );
+
+  }
+
+  /**
+   * process the form after the input has been submitted and validated
+   *
+   * @access public
+   *
+   * @return None
+   */
+  public function postProcess() {
+    // TODO: rewrite using contribution token and one letter by contribution
+    $this->setContactIDs();
+
+               CRM_Contribute_Form_Task_PDFLatexCommon::postProcess($this);
+  }
+
+
+}
+
diff --git a/CRM/Contribute/Form/Task/PDFLatexCommon.php b/CRM/Contribute/Form/Task/PDFLatexCommon.php
new file mode 100644 (file)
index 0000000..862b03f
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * This class provides the common functionality for creating PDF letter for
+ * one or a group of contact ids.
+ */
+class CRM_Contribute_Form_Task_PDFLatexCommon extends CRM_Contact_Form_Task_PDFLetterCommon {
+
+  /**
+   * process the form after the input has been submitted and validated
+   *
+   * @access public
+   *
+   * @return None
+   */
+  static function postProcess(&$form) {
+
+    list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($form);
+         //CRM_Core_Error::debug_log_message(":2 $html_message"); //OK
+
+    // update dates ?
+    $receipt_update  = isset($formValues['receipt_update']) ? $formValues['receipt_update'] : FALSE;
+    $thankyou_update = isset($formValues['thankyou_update']) ? $formValues['thankyou_update'] : FALSE;
+    $nowDate         = date('YmdHis');
+    $receipts        = 0;
+    $thanks          = 0;
+    $updateStatus    = '';
+
+    // skip some contacts ?
+    $skipOnHold = isset($form->skipOnHold) ? $form->skipOnHold : FALSE;
+    $skipDeceased = isset($form->skipDeceased) ? $form->skipDeceased : TRUE;
+
+    foreach ($form->getVar('_contributionIds') as $item => $contributionId) {
+
+      // get contact information
+      $contactId = civicrm_api("Contribution", "getvalue", array('version' => '3', 'id' => $contributionId, 'return' => 'contact_id'));
+      $params = array('contact_id' => $contactId);
+
+      list($contact) = CRM_Utils_Token::getTokenDetails($params,
+        $returnProperties,
+        $skipOnHold,
+        $skipDeceased,
+        NULL,
+        $messageToken,
+        'CRM_Contribution_Form_Task_PDFLetterCommon'
+      );
+      if (civicrm_error($contact)) {
+        $notSent[] = $contributionId;
+        continue;
+      }
+
+      // get contribution information
+      $params = array('contribution_id' => $contributionId);
+      $contribution = CRM_Utils_Token::getContributionTokenDetails($params,
+        $returnProperties,
+        NULL,
+        $messageToken,
+        'CRM_Contribution_Form_Task_PDFLetterCommon'
+      );
+      if (civicrm_error($contribution)) {
+        $notSent[] = $contributionId;
+        continue;
+      }
+         
+      $tokenHtml = CRM_Utils_Token::replaceContactTokens($html_message, $contact[$contactId], FALSE, $messageToken);
+      $tokenHtml = CRM_Utils_Token::replaceContributionTokens($tokenHtml, $contribution[$contributionId], TRUE, $messageToken);
+      $tokenHtml = CRM_Utils_Token::replaceHookTokens($tokenHtml, $contact[$contactId], $categories, TRUE);
+
+      if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
+        $smarty = CRM_Core_Smarty::singleton();
+        // also add the tokens to the template
+        $smarty->assign_by_ref('contact', $contact);
+        $tokenHtml = $smarty->fetch("string:$tokenHtml");
+      }
+
+      $html[] = $tokenHtml;
+
+      // update dates (do it for each contribution including grouped recurring contribution)
+      if ($receipt_update) {
+        $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'receipt_date', $nowDate);
+        // We can't use CRM_Core_Error::fatal here because the api error elevates the exception level. FIXME. dgg
+        if ($result) {
+          $receipts++;
+      }
+      }
+      if ($thankyou_update) {
+        $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'thankyou_date', $nowDate);
+        // We can't use CRM_Core_Error::fatal here because the api error elevates the exception level. FIXME. dgg
+        if ($result) {
+          $thanks++;
+      }
+    }
+    }
+
+    self::createActivities($form, $html_message, $form->_contactIds);
+
+    CRM_Utils_PDF_Utils::latex2pdf($html, "CiviLatex.pdf", FALSE, $formValues);
+
+    $form->postProcessHook();
+
+    if ($receipts) {
+      $updateStatus = ts('Receipt date has been updated for %1 contributions.', array(1 => $receipts));
+    }
+    if ($thanks) {
+      $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', array(1 => $thanks));
+    }
+
+    if ($updateStatus) {
+      CRM_Core_Session::setStatus($updateStatus);
+    }
+    
+    CRM_Utils_System::civiExit(1);
+  }
+  //end of function
+}
+
diff --git a/CRM/Core/Payment/TrustCommerce.php b/CRM/Core/Payment/TrustCommerce.php
new file mode 100644 (file)
index 0000000..534bc8f
--- /dev/null
@@ -0,0 +1,467 @@
+<?php
+/*
+ * Copyright (C) 2012
+ * Licensed to CiviCRM under the GPL v3 or higher
+ *
+ * Written and contributed by Ward Vandewege <ward@fsf.org> (http://www.fsf.org)
+ *
+ */
+
+require_once 'CRM/Core/Payment.php';
+class CRM_Core_Payment_TrustCommerce extends CRM_Core_Payment {
+  CONST CHARSET = 'iso-8859-1';
+  CONST AUTH_APPROVED = 'approve';
+  CONST AUTH_DECLINED = 'decline';
+  CONST AUTH_BADDATA = 'baddata';
+  CONST AUTH_ERROR = 'error';
+
+  static protected $_mode = NULL;
+
+  static protected $_params = array();
+
+  /**
+   * We only need one instance of this object. So we use the singleton
+   * pattern and cache the instance in this variable
+   *
+   * @var object
+   * @static
+   */
+  static private $_singleton = NULL;
+
+  /**
+   * Constructor
+   *
+   * @param string $mode the mode of operation: live or test
+   *
+   * @return void
+   */ function __construct($mode, &$paymentProcessor) {
+    $this->_mode = $mode;
+
+    $this->_paymentProcessor = $paymentProcessor;
+
+    $this->_processorName = ts('TrustCommerce');
+
+    $config = CRM_Core_Config::singleton();
+    $this->_setParam('user_name', $paymentProcessor['user_name']);
+    $this->_setParam('password', $paymentProcessor['password']);
+
+    $this->_setParam('timestamp', time());
+    srand(time());
+    $this->_setParam('sequence', rand(1, 1000));
+  }
+
+  /**
+   * singleton function used to manage this object
+   *
+   * @param string $mode the mode of operation: live or test
+   *
+   * @return object
+   * @static
+   *
+   */
+  static
+  function &singleton($mode, &$paymentProcessor) {
+    $processorName = $paymentProcessor['name'];
+    if (self::$_singleton[$processorName] === NULL) {
+      self::$_singleton[$processorName] = new CRM_Core_Payment_TrustCommerce($mode, $paymentProcessor);
+    }
+    return self::$_singleton[$processorName];
+  }
+
+  /**
+   * Submit a payment using Advanced Integration Method
+   *
+   * @param  array $params assoc array of input parameters for this transaction
+   *
+   * @return array the result in a nice formatted array (or an error object)
+   * @public
+   */
+  function doDirectPayment(&$params) {
+    if (!extension_loaded("tclink")) {
+      return self::error(9001, 'TrustCommerce requires that the tclink module is loaded');
+    }
+
+    /*
+         * recurpayment function does not compile an array & then proces it -
+         * - the tpl does the transformation so adding call to hook here
+         * & giving it a change to act on the params array
+         */
+
+    $newParams = $params;
+    if (CRM_Utils_Array::value('is_recur', $params) &&
+      $params['contributionRecurID']
+    ) {
+      CRM_Utils_Hook::alterPaymentProcessorParams($this,
+        $params,
+        $newParams
+      );
+    }
+    foreach ($newParams as $field => $value) {
+      $this->_setParam($field, $value);
+    }
+
+    if (CRM_Utils_Array::value('is_recur', $params) &&
+      $params['contributionRecurID']
+    ) {
+      return $this->doRecurPayment($params);
+    }
+
+    $postFields = array();
+    $tclink = $this->_getTrustCommerceFields();
+
+    // Set up our call for hook_civicrm_paymentProcessor,
+    // since we now have our parameters as assigned for the AIM back end.
+    CRM_Utils_Hook::alterPaymentProcessorParams($this,
+      $params,
+      $tclink
+    );
+
+    // TrustCommerce will not refuse duplicates, so we should check if the user already submitted this transaction
+    if ($this->_checkDupe($tclink['ticket'])) {
+      return self::error(9004, 'It appears that this transaction is a duplicate.  Have you already submitted the form once?  If so there may have been a connection problem. You can try your transaction again.  If you continue to have problems please contact the site administrator.');
+    }
+
+               $result = tclink_send($tclink);
+
+    if (!$result) {
+      return self::error(9002, 'Could not initiate connection to payment gateway');
+    }
+
+    foreach ($result as $field => $value) {
+      error_log("result: $field => $value");
+    }
+
+               switch($result['status']) {
+                       case self::AUTH_APPROVED:
+        // It's all good
+        break;
+                       case self::AUTH_DECLINED:
+        // TODO FIXME be more or less specific? 
+        // declinetype can be: decline, avs, cvv, call, expiredcard, carderror, authexpired, fraud, blacklist, velocity
+        // See TC documentation for more info
+        return self::error(9009, "Your transaction was declined: {$result['declinetype']}");
+        break;
+                       case self::AUTH_BADDATA:
+        // TODO FIXME do something with $result['error'] and $result['offender']
+        return self::error(9011, "Invalid credit card information. Please re-enter.");
+        break;
+                       case self::AUTH_ERROR:
+       return self::error(9002, 'Could not initiate connection to payment gateway');
+        break;
+    }
+
+    // Success
+
+    $params['trxn_id'] = $result['transid'];
+    $params['gross_amount'] = $tclink['amount'] / 100;
+
+    return $params;
+  }
+
+  /**
+   * Submit an Automated Recurring Billing subscription
+   *
+   * @param  array $params assoc array of input parameters for this transaction
+   *
+   * @return array the result in a nice formatted array (or an error object)
+   * @public
+   */
+  function doRecurPayment(&$params) {
+    $template = CRM_Core_Smarty::singleton();
+
+    $intervalLength = $this->_getParam('frequency_interval');
+    $intervalUnit = $this->_getParam('frequency_unit');
+    if ($intervalUnit == 'week') {
+      $intervalLength *= 7;
+      $intervalUnit = 'days';
+    }
+    elseif ($intervalUnit == 'year') {
+      $intervalLength *= 12;
+      $intervalUnit = 'months';
+    }
+    elseif ($intervalUnit == 'day') {
+      $intervalUnit = 'days';
+    }
+    elseif ($intervalUnit == 'month') {
+      $intervalUnit = 'months';
+    }
+
+    // interval cannot be less than 7 days or more than 1 year
+    if ($intervalUnit == 'days') {
+      if ($intervalLength < 7) {
+        return self::error(9001, 'Payment interval must be at least one week');
+      }
+      elseif ($intervalLength > 365) {
+        return self::error(9001, 'Payment interval may not be longer than one year');
+      }
+    }
+    elseif ($intervalUnit == 'months') {
+      if ($intervalLength < 1) {
+        return self::error(9001, 'Payment interval must be at least one week');
+      }
+      elseif ($intervalLength > 12) {
+        return self::error(9001, 'Payment interval may not be longer than one year');
+      }
+    }
+
+    $template->assign('intervalLength', $intervalLength);
+    $template->assign('intervalUnit', $intervalUnit);
+
+    $template->assign('apiLogin', $this->_getParam('apiLogin'));
+    $template->assign('paymentKey', $this->_getParam('paymentKey'));
+    $template->assign('refId', substr($this->_getParam('invoiceID'), 0, 20));
+
+    //for recurring, carry first contribution id
+    $template->assign('invoiceNumber', $this->_getParam('contributionID'));
+    $firstPaymentDate = $this->_getParam('receive_date');
+    if (!empty($firstPaymentDate)) {
+      //allow for post dated payment if set in form
+      $template->assign('startDate', date('Y-m-d', strtotime($firstPaymentDate)));
+    }
+    else {
+      $template->assign('startDate', date('Y-m-d'));
+    }
+    // for open ended subscription totalOccurrences has to be 9999
+    $installments = $this->_getParam('installments');
+    $template->assign('totalOccurrences', $installments ? $installments : 9999);
+
+    $template->assign('amount', $this->_getParam('amount'));
+
+    $template->assign('cardNumber', $this->_getParam('credit_card_number'));
+    $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT);
+    $exp_year = $this->_getParam('year');
+    $template->assign('expirationDate', $exp_year . '-' . $exp_month);
+    // name rather than description is used in the tpl - see http://www.authorize.net/support/ARB_guide.pdf
+    $template->assign('name', $this->_getParam('description'));
+
+    $template->assign('email', $this->_getParam('email'));
+    $template->assign('contactID', $this->_getParam('contactID'));
+    $template->assign('billingFirstName', $this->_getParam('billing_first_name'));
+    $template->assign('billingLastName', $this->_getParam('billing_last_name'));
+    $template->assign('billingAddress', $this->_getParam('street_address'));
+    $template->assign('billingCity', $this->_getParam('city'));
+    $template->assign('billingState', $this->_getParam('state_province'));
+    $template->assign('billingZip', $this->_getParam('postal_code'));
+    $template->assign('billingCountry', $this->_getParam('country'));
+
+    $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
+    // submit to authorize.net
+
+    $submit = curl_init($this->_paymentProcessor['url_recur']);
+    if (!$submit) {
+      return self::error(9002, 'Could not initiate connection to payment gateway');
+    }
+    curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1);
+    curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
+    curl_setopt($submit, CURLOPT_HEADER, 1);
+    curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML);
+    curl_setopt($submit, CURLOPT_POST, 1);
+    curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, 0);
+
+    $response = curl_exec($submit);
+
+    if (!$response) {
+      return self::error(curl_errno($submit), curl_error($submit));
+    }
+
+    curl_close($submit);
+    $responseFields = $this->_ParseArbReturn($response);
+
+    if ($responseFields['resultCode'] == 'Error') {
+      return self::error($responseFields['code'], $responseFields['text']);
+    }
+
+    // update recur processor_id with subscriptionId
+    CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_ContributionRecur', $params['contributionRecurID'],
+      'processor_id', $responseFields['subscriptionId']
+    );
+    //only impact of assigning this here is is can be used to cancel the subscription in an automated test
+    // if it isn't cancelled a duplicate transaction error occurs
+    if (CRM_Utils_Array::value('subscriptionId', $responseFields)) {
+      $this->_setParam('subscriptionId', $responseFields['subscriptionId']);
+    }
+    return $params;
+  }
+
+  function _getTrustCommerceFields() {
+    // Total amount is from the form contribution field
+    $amount = $this->_getParam('total_amount');
+    // CRM-9894 would this ever be the case??
+    if (empty($amount)) {
+      $amount = $this->_getParam('amount');
+    }
+    $fields = array();
+    $fields['custid'] = $this->_getParam('user_name');
+    $fields['password'] = $this->_getParam('password');
+    $fields['action'] = 'sale';
+
+               // Enable address verification
+    $fields['avs'] = 'y';
+
+    $fields['address1'] = $this->_getParam('street_address');
+    $fields['zip'] = $this->_getParam('postal_code');
+
+    $fields['name'] = $this->_getParam('billing_first_name') . ' ' . $this->_getParam('billing_last_name');
+
+    // We need to pass the amount to TrustCommerce in dollar cents
+    $fields['amount'] = $amount * 100;
+
+               // Unique identifier
+    $fields['ticket'] = substr($this->_getParam('invoiceID'), 0, 20);
+
+    // cc info
+    $fields['cc'] = $this->_getParam('credit_card_number');
+    $fields['cvv'] = $this->_getParam('cvv2');
+    $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT);
+    $exp_year = substr($this->_getParam('year'),-2);
+    $fields['exp'] = "$exp_month$exp_year";
+
+    if ($this->_mode != 'live') {
+      $fields['demo'] = 'y';
+    }
+    // TODO FIXME remove
+    foreach ($fields as $field => $value) {
+      if ($field == 'custid') $value = '********';
+      if ($field == 'password') $value = '********';
+      error_log("fields: $field => $value");
+    }
+
+    return $fields;
+  }
+
+  /**
+   * Checks to see if invoice_id already exists in db
+   *
+   * @param  int     $invoiceId   The ID to check
+   *
+   * @return bool                 True if ID exists, else false
+   */
+  function _checkDupe($invoiceId) {
+    require_once 'CRM/Contribute/DAO/Contribution.php';
+    $contribution = new CRM_Contribute_DAO_Contribution();
+    $contribution->invoice_id = $invoiceId;
+    return $contribution->find();
+  }
+
+  /**
+   * Get the value of a field if set
+   *
+   * @param string $field the field
+   *
+   * @return mixed value of the field, or empty string if the field is
+   * not set
+   */
+  function _getParam($field) {
+    return CRM_Utils_Array::value($field, $this->_params, '');
+  }
+
+  function &error($errorCode = NULL, $errorMessage = NULL) {
+    $e = CRM_Core_Error::singleton();
+    if ($errorCode) {
+      $e->push($errorCode, 0, NULL, $errorMessage);
+    }
+    else {
+      $e->push(9001, 0, NULL, 'Unknown System Error.');
+    }
+    return $e;
+  }
+
+  /**
+   * Set a field to the specified value.  Value must be a scalar (int,
+   * float, string, or boolean)
+   *
+   * @param string $field
+   * @param mixed $value
+   *
+   * @return bool false if value is not a scalar, true if successful
+   */
+  function _setParam($field, $value) {
+    if (!is_scalar($value)) {
+      return FALSE;
+    }
+    else {
+      $this->_params[$field] = $value;
+    }
+  }
+
+  /**
+   * This function checks to see if we have the right config values
+   *
+   * @return string the error message if any
+   * @public
+   */
+  function checkConfig() {
+    $error = array();
+    if (empty($this->_paymentProcessor['user_name'])) {
+      $error[] = ts('Customer ID is not set for this payment processor');
+    }
+
+    if (empty($this->_paymentProcessor['password'])) {
+      $error[] = ts('Password is not set for this payment processor');
+    }
+
+    if (!empty($error)) {
+      return implode('<p>', $error);
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  function cancelSubscriptionURL($entityID = NULL, $entity = NULL) {
+    if ($entityID && $entity == 'membership') {
+      require_once 'CRM/Contact/BAO/Contact/Utils.php';
+      $contactID = CRM_Core_DAO::getFieldValue("CRM_Member_DAO_Membership", $entityID, "contact_id");
+      $checksumValue = CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID, NULL, 'inf');
+
+      return CRM_Utils_System::url('civicrm/contribute/unsubscribe',
+        "reset=1&mid={$entityID}&cs={$checksumValue}", TRUE, NULL, FALSE, FALSE
+      );
+    }
+
+    return ($this->_mode == 'test') ? 'https://test.authorize.net' : 'https://authorize.net';
+  }
+
+  function cancelSubscription() {
+    $template = CRM_Core_Smarty::singleton();
+
+    $template->assign('subscriptionType', 'cancel');
+
+    $template->assign('apiLogin', $this->_getParam('apiLogin'));
+    $template->assign('paymentKey', $this->_getParam('paymentKey'));
+    $template->assign('subscriptionId', $this->_getParam('subscriptionId'));
+
+    $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
+
+    // submit to authorize.net
+    $submit = curl_init($this->_paymentProcessor['url_recur']);
+    if (!$submit) {
+      return self::error(9002, 'Could not initiate connection to payment gateway');
+    }
+
+    curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1);
+    curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
+    curl_setopt($submit, CURLOPT_HEADER, 1);
+    curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML);
+    curl_setopt($submit, CURLOPT_POST, 1);
+    curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, 0);
+
+    $response = curl_exec($submit);
+
+    if (!$response) {
+      return self::error(curl_errno($submit), curl_error($submit));
+    }
+
+    curl_close($submit);
+
+    $responseFields = $this->_ParseArbReturn($response);
+
+    if ($responseFields['resultCode'] == 'Error') {
+      return self::error($responseFields['code'], $responseFields['text']);
+    }
+
+    // carry on cancelation procedure
+    return TRUE;
+  }
+}
+
diff --git a/CRM/Core/Payment/insert_tc.sql b/CRM/Core/Payment/insert_tc.sql
new file mode 100644 (file)
index 0000000..3bbd1e5
--- /dev/null
@@ -0,0 +1,7 @@
+       INSERT INTO civicrm_payment_processor_type 
+           (name, title, description, is_active, is_default, user_name_label, password_label, signature_label, subject_label, class_name, url_site_default, url_recur_default, url_button_default, url_site_test_default, url_recur_test_default, url_button_test_default, billing_mode, is_recur )
+       VALUES 
+           ('TrustCommerce','TrustCommerce',NULL,1,0,'Customer ID','Password',NULL,NULL,'Payment_TrustCommerce',NULL,NULL,NULL,NULL,NULL,NULL,1,NULL);
+
+
+
diff --git a/i/mini_cvv2.gif b/i/mini_cvv2.gif
new file mode 100644 (file)
index 0000000..c3d6a8d
Binary files /dev/null and b/i/mini_cvv2.gif differ
diff --git a/latex.patch b/latex.patch
new file mode 100644 (file)
index 0000000..4a59aa2
--- /dev/null
@@ -0,0 +1,764 @@
+diff --git a/CRM/Contribute/Form/Task/PDFLatex.php b/CRM/Contribute/Form/Task/PDFLatex.php
+new file mode 100644
+index 0000000..f353e00
+--- /dev/null
++++ b/CRM/Contribute/Form/Task/PDFLatex.php
+@@ -0,0 +1,147 @@
++<?php
++/*
++ +--------------------------------------------------------------------+
++ | CiviCRM version 4.4                                                |
++ +--------------------------------------------------------------------+
++ | Copyright CiviCRM LLC (c) 2004-2013                                |
++ +--------------------------------------------------------------------+
++ | This file is a part of CiviCRM.                                    |
++ |                                                                    |
++ | CiviCRM is free software; you can copy, modify, and distribute it  |
++ | under the terms of the GNU Affero General Public License           |
++ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
++ |                                                                    |
++ | CiviCRM is distributed in the hope that it will be useful, but     |
++ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
++ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
++ | See the GNU Affero General Public License for more details.        |
++ |                                                                    |
++ | You should have received a copy of the GNU Affero General Public   |
++ | License and the CiviCRM Licensing Exception along                  |
++ | with this program; if not, contact CiviCRM LLC                     |
++ | at info[AT]civicrm[DOT]org. If you have questions about the        |
++ | GNU Affero General Public License or the licensing of CiviCRM,     |
++ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
++ +--------------------------------------------------------------------+
++*/
++
++/**
++ *
++ * @package CRM
++ * @copyright CiviCRM LLC (c) 2004-2013
++ * $Id$
++ *
++ */
++
++/**
++ * This class provides the functionality to create PDF letter for a group of
++ * contacts or a single contact.
++ */
++class CRM_Contribute_Form_Task_PDFLatex extends CRM_Contribute_Form_Task {
++
++  /**
++   * all the existing templates in the system
++   *
++   * @var array
++   */
++  public $_templates = NULL;
++
++  public $_single = NULL;
++
++  public $_cid = NULL;
++
++  /**
++   * build all the data structures needed to build the form
++   *
++   * @return void
++   * @access public
++   */
++  function preProcess() {
++    $this->skipOnHold = $this->skipDeceased = FALSE;
++    CRM_Contact_Form_Task_PDFLetterCommon::preProcess($this);
++
++    // store case id if present
++    $this->_caseId = CRM_Utils_Request::retrieve('caseid', 'Positive', $this, FALSE);
++
++    // retrieve contact ID if this is 'single' mode
++    $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE);
++
++    $this->_activityId = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
++
++    if ($cid) {
++      CRM_Contact_Form_Task_PDFLetterCommon::preProcessSingle($this, $cid);
++      $this->_single = TRUE;
++      $this->_cid = $cid;
++    }
++    else {
++      parent::preProcess();
++    }
++    $this->assign('single', $this->_single);
++  }
++
++  function setDefaultValues() {
++    $defaults = array();
++    if (isset($this->_activityId)) {
++      $params = array('id' => $this->_activityId);
++      CRM_Activity_BAO_Activity::retrieve($params, $defaults);
++      $defaults['html_message'] = $defaults['details'];
++    }
++    $defaults = $defaults + CRM_Contact_Form_Task_PDFLetterCommon::setDefaultValues();
++    return $defaults;
++  }
++
++  /**
++   * Build the form
++   *
++   * @access public
++   *
++   * @return void
++   */
++  public function buildQuickForm() {
++    //enable form element
++    $this->assign('suppressForm', FALSE);
++
++    // use contact form as a base
++    CRM_Contact_Form_Task_PDFLetterCommon::buildQuickForm($this);
++
++    // specific need for contributions
++    $this->add('static', 'more_options_header', NULL, ts('Record Update Options'));
++    $this->add('checkbox', 'receipt_update', ts('Update receipt dates for these contributions'), FALSE);
++    $this->add('checkbox', 'thankyou_update', ts('Update thank-you dates for these contributions'), FALSE);
++
++    // Group options for tokens are not yet implemented. dgg
++    $options = array(ts('Contact'), ts('Recurring'));
++    $this->addRadio('is_group_by', ts('Grouping contributions in one letter based on'), $options, array(), "<br/>", FALSE);
++
++    $this->addButtons(array(
++        array(
++          'type' => 'submit',
++          'name' => ts('Make Thank-you Letters'),
++          'isDefault' => TRUE,
++        ),
++        array(
++          'type' => 'cancel',
++          'name' => ts('Done'),
++        ),
++      )
++    );
++
++  }
++
++  /**
++   * process the form after the input has been submitted and validated
++   *
++   * @access public
++   *
++   * @return None
++   */
++  public function postProcess() {
++    // TODO: rewrite using contribution token and one letter by contribution
++    $this->setContactIDs();
++
++              CRM_Contribute_Form_Task_PDFLatexCommon::postProcess($this);
++  }
++
++
++}
++
+diff --git a/CRM/Contribute/Form/Task/PDFLatexCommon.php b/CRM/Contribute/Form/Task/PDFLatexCommon.php
+new file mode 100644
+index 0000000..862b03f
+--- /dev/null
++++ b/CRM/Contribute/Form/Task/PDFLatexCommon.php
+@@ -0,0 +1,116 @@
++<?php
++
++/**
++ * This class provides the common functionality for creating PDF letter for
++ * one or a group of contact ids.
++ */
++class CRM_Contribute_Form_Task_PDFLatexCommon extends CRM_Contact_Form_Task_PDFLetterCommon {
++
++  /**
++   * process the form after the input has been submitted and validated
++   *
++   * @access public
++   *
++   * @return None
++   */
++  static function postProcess(&$form) {
++
++    list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($form);
++        //CRM_Core_Error::debug_log_message(":2 $html_message"); //OK
++
++    // update dates ?
++    $receipt_update  = isset($formValues['receipt_update']) ? $formValues['receipt_update'] : FALSE;
++    $thankyou_update = isset($formValues['thankyou_update']) ? $formValues['thankyou_update'] : FALSE;
++    $nowDate         = date('YmdHis');
++    $receipts        = 0;
++    $thanks          = 0;
++    $updateStatus    = '';
++
++    // skip some contacts ?
++    $skipOnHold = isset($form->skipOnHold) ? $form->skipOnHold : FALSE;
++    $skipDeceased = isset($form->skipDeceased) ? $form->skipDeceased : TRUE;
++
++    foreach ($form->getVar('_contributionIds') as $item => $contributionId) {
++
++      // get contact information
++      $contactId = civicrm_api("Contribution", "getvalue", array('version' => '3', 'id' => $contributionId, 'return' => 'contact_id'));
++      $params = array('contact_id' => $contactId);
++
++      list($contact) = CRM_Utils_Token::getTokenDetails($params,
++        $returnProperties,
++        $skipOnHold,
++        $skipDeceased,
++        NULL,
++        $messageToken,
++        'CRM_Contribution_Form_Task_PDFLetterCommon'
++      );
++      if (civicrm_error($contact)) {
++        $notSent[] = $contributionId;
++        continue;
++      }
++
++      // get contribution information
++      $params = array('contribution_id' => $contributionId);
++      $contribution = CRM_Utils_Token::getContributionTokenDetails($params,
++        $returnProperties,
++        NULL,
++        $messageToken,
++        'CRM_Contribution_Form_Task_PDFLetterCommon'
++      );
++      if (civicrm_error($contribution)) {
++        $notSent[] = $contributionId;
++        continue;
++      }
++        
++      $tokenHtml = CRM_Utils_Token::replaceContactTokens($html_message, $contact[$contactId], FALSE, $messageToken);
++      $tokenHtml = CRM_Utils_Token::replaceContributionTokens($tokenHtml, $contribution[$contributionId], TRUE, $messageToken);
++      $tokenHtml = CRM_Utils_Token::replaceHookTokens($tokenHtml, $contact[$contactId], $categories, TRUE);
++
++      if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
++        $smarty = CRM_Core_Smarty::singleton();
++        // also add the tokens to the template
++        $smarty->assign_by_ref('contact', $contact);
++        $tokenHtml = $smarty->fetch("string:$tokenHtml");
++      }
++
++      $html[] = $tokenHtml;
++
++      // update dates (do it for each contribution including grouped recurring contribution)
++      if ($receipt_update) {
++        $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'receipt_date', $nowDate);
++        // We can't use CRM_Core_Error::fatal here because the api error elevates the exception level. FIXME. dgg
++        if ($result) {
++          $receipts++;
++      }
++      }
++      if ($thankyou_update) {
++        $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'thankyou_date', $nowDate);
++        // We can't use CRM_Core_Error::fatal here because the api error elevates the exception level. FIXME. dgg
++        if ($result) {
++          $thanks++;
++      }
++    }
++    }
++
++    self::createActivities($form, $html_message, $form->_contactIds);
++
++    CRM_Utils_PDF_Utils::latex2pdf($html, "CiviLatex.pdf", FALSE, $formValues);
++
++    $form->postProcessHook();
++
++    if ($receipts) {
++      $updateStatus = ts('Receipt date has been updated for %1 contributions.', array(1 => $receipts));
++    }
++    if ($thanks) {
++      $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', array(1 => $thanks));
++    }
++
++    if ($updateStatus) {
++      CRM_Core_Session::setStatus($updateStatus);
++    }
++    
++    CRM_Utils_System::civiExit(1);
++  }
++  //end of function
++}
++
+diff --git a/CRM/Contribute/Task.php b/CRM/Contribute/Task.php
+index 987b280..129cb45 100644
+--- a/CRM/Contribute/Task.php
++++ b/CRM/Contribute/Task.php
+@@ -39,7 +39,7 @@
+  *
+  */
+ class CRM_Contribute_Task {
+-  CONST DELETE_CONTRIBUTIONS = 1, PRINT_CONTRIBUTIONS = 2, EXPORT_CONTRIBUTIONS = 3, BATCH_CONTRIBUTIONS = 4, EMAIL_CONTACTS = 5, UPDATE_STATUS = 6, PDF_RECEIPT = 7;
++  CONST DELETE_CONTRIBUTIONS = 1, PRINT_CONTRIBUTIONS = 2, EXPORT_CONTRIBUTIONS = 3, BATCH_CONTRIBUTIONS = 4, EMAIL_CONTACTS = 5, UPDATE_STATUS = 6, PDF_RECEIPT = 7, LATEX_TEST = 9;
+   /**
+    * the task array
+@@ -106,6 +106,10 @@ static function &tasks() {
+           'class' => 'CRM_Contribute_Form_Task_PDFLetter',
+           'result' => FALSE,
+         ),
++        9 => array('title' => ts('Latex Letters for Contributions'),
++          'class' => 'CRM_Contribute_Form_Task_PDFLatex',
++          'result' => FALSE,
++        ),
+       );
+       //CRM-4418, check for delete
+diff --git a/CRM/Utils/PDF/Utils.php b/CRM/Utils/PDF/Utils.php
+index 72d4880..e9d59b9 100644
+--- a/CRM/Utils/PDF/Utils.php
++++ b/CRM/Utils/PDF/Utils.php
+@@ -34,6 +34,69 @@
+  */
+ class CRM_Utils_PDF_Utils {
++  static function latex2pdf(&$text, $fileName = 'civicrm.pdf', $output = FALSE, $pdfFormat = NULL) {
++        /* FIXME: get $paper_size, $orientation, $margins */
++
++    if (is_array($text)) {
++      $pages = &$text;
++    }
++    else {
++      $pages = array($text);
++    }
++
++
++    $head='\documentclass[12pt]{letter}
++\oddsidemargin 0in
++\evensidemargin 0in
++\textwidth 6.5in
++
++\topmargin -.25in
++\textheight 8.15in
++
++\address{\vspace{0.354in}}
++
++\begin{document}
++
++';
++               $footer='
++\end{document}';
++
++    $latex = $head;
++    foreach ($pages as $page) {
++                      $latex.=$page;
++              }
++    $latex.=$footer;
++
++              $descriptorspec = array(
++       0 => array("pipe", "r"),
++       1 => array("pipe", "w")
++              );
++
++
++
++              $process = proc_open("/usr/local/bin/pdflatex_wrapper.sh", $descriptorspec, $pipes);
++
++
++        if (is_resource($process)) {
++                      fwrite($pipes[0], $latex);
++                      fclose($pipes[0]);
++
++      $pdf = stream_get_contents($pipes[1]);
++                      fclose($pipes[1]);
++        } else {
++                      CRM_Core_Error::debug_log_message("ERROR creating PDF. Check /tmp/pdflatex_*");
++              }
++
++    if ($output) {
++      return $pdf;
++    }
++    else {
++      header('Content-Type: application/pdf');
++      header('Content-Disposition: attachment; filename="' . $fileName . '"');
++      echo $pdf;
++    }
++      }
++
+   static function html2pdf(&$text, $fileName = 'civicrm.pdf', $output = FALSE, $pdfFormat = NULL) {
+     if (is_array($text)) {
+       $pages = &$text;
+diff --git a/CRM/Utils/Token.php b/CRM/Utils/Token.php
+index 28ca941..e8df124 100644
+--- a/CRM/Utils/Token.php
++++ b/CRM/Utils/Token.php
+@@ -619,7 +619,10 @@ function ($matches) use(&$contact, $html, $returnBlankToken, $escapeSmarty) {
+       $str
+     );
+-    $str = preg_replace('/\\\\|\{(\s*)?\}/', ' ', $str);
++              // 2013-08-31 nico@fsf.org this replacement is stripping all the '{' and '}'  from the LaTeX files
++              // maybe it got solved at some point. maybe it has undesired collateral effects. I'm bugging the
++              // CiviCRM community to take a look at this pull request.
++    //$str = preg_replace('/\\\\|\{(\s*)?\}/', ' ', $str);
+     return $str;
+   }
+diff --git a/templates/CRM/Contact/Form/Task/PDFLatexCommon.tpl b/templates/CRM/Contact/Form/Task/PDFLatexCommon.tpl
+new file mode 100644
+index 0000000..1f75f97
+--- /dev/null
++++ b/templates/CRM/Contact/Form/Task/PDFLatexCommon.tpl
+@@ -0,0 +1,312 @@
++{*
++ +--------------------------------------------------------------------+
++ | CiviCRM version 4.4                                                |
++ +--------------------------------------------------------------------+
++ | Copyright CiviCRM LLC (c) 2004-2013                                |
++ +--------------------------------------------------------------------+
++ | This file is a part of CiviCRM.                                    |
++ |                                                                    |
++ | CiviCRM is free software; you can copy, modify, and distribute it  |
++ | under the terms of the GNU Affero General Public License           |
++ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
++ |                                                                    |
++ | CiviCRM is distributed in the hope that it will be useful, but     |
++ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
++ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
++ | See the GNU Affero General Public License for more details.        |
++ |                                                                    |
++ | You should have received a copy of the GNU Affero General Public   |
++ | License and the CiviCRM Licensing Exception along                  |
++ | with this program; if not, contact CiviCRM LLC                     |
++ | at info[AT]civicrm[DOT]org. If you have questions about the        |
++ | GNU Affero General Public License or the licensing of CiviCRM,     |
++ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
++ +--------------------------------------------------------------------+
++*}
++{*common template for compose PDF letters*}
++{if $form.template.html}
++<table class="form-layout-compressed">
++    <tr>
++        <td class="label-left">{$form.template.label}</td>
++      <td>{$form.template.html}</td>
++    </tr>
++</table>
++{/if}
++
++<div class="crm-accordion-wrapper collapsed">
++    <div class="crm-accordion-header">
++        {$form.pdf_format_header.html}
++    </div>
++    <div class="crm-accordion-body">
++      <div class="crm-block crm-form-block crm-pdf-format-form-block">
++    <table class="form-layout-compressed">
++      <tr>
++        <td class="label-left">{$form.format_id.label}</td><td>{$form.format_id.html}{help id="id-pdf-format" file="CRM/Contact/Form/Task/PDFLetterCommon.hlp"}</td>
++        <td colspan="2">&nbsp;</td>
++            </tr>
++      <tr>
++        <td class="label-left">{$form.paper_size.label}</td><td>{$form.paper_size.html}</td>
++        <td class="label-left">{$form.orientation.label}</td><td>{$form.orientation.html}</td>
++      </tr>
++      <tr>
++        <td class="label-left">{$form.metric.label}</td><td>{$form.metric.html}</td>
++        <td colspan="2">&nbsp;</td>
++      </tr>
++      <tr>
++        <td>{$form.paper_dimensions.html}</td><td id="paper_dimensions">&nbsp;</td>
++        <td colspan="2">&nbsp;</td>
++      </tr>
++      <tr>
++        <td class="label-left">{$form.margin_top.label}</td><td>{$form.margin_top.html}</td>
++        <td class="label-left">{$form.margin_bottom.label}</td><td>{$form.margin_bottom.html}</td>
++      </tr>
++      <tr>
++        <td class="label-left">{$form.margin_left.label}</td><td>{$form.margin_left.html}</td>
++        <td class="label-left">{$form.margin_right.label}</td><td>{$form.margin_right.html}</td>
++      </tr>
++    </table>
++        <div id="bindFormat">{$form.bind_format.html}&nbsp;{$form.bind_format.label}</div>
++        <div id="updateFormat" style="display: none">{$form.update_format.html}&nbsp;{$form.update_format.label}</div>
++      </div>
++  </div>
++</div>
++
++<div class="crm-accordion-wrapper crm-html_email-accordion ">
++<div class="crm-accordion-header">
++    {$form.html_message.label}
++</div><!-- /.crm-accordion-header -->
++ <div class="crm-accordion-body">
++  {if $action neq 4}
++  <span class="helpIcon" id="helphtml">
++  <a href="#" onClick="return showToken('Html', 1);">{$form.token1.label}</a>
++  {help id="id-token-html" file="CRM/Contact/Form/Task/Email.hlp" tplFile=$tplFile isAdmin=$isAdmin editor=$editor}
++  <div id="tokenHtml" style="display:none;">
++      <input style="border:1px solid #999999;" type="text" id="filter1" size="20" name="filter1" onkeyup="filter(this, 1)"/><br />
++      <span class="description">{ts}Begin typing to filter list of tokens{/ts}</span><br/>
++      {$form.token1.html}
++  </div>
++  </span>
++  {/if}
++    <div class="clear"></div>
++    <div class='html'>
++  {if $editor EQ 'textarea'}
++      <div class="help description">{ts}The content of this page should be LaTeX.{/ts}</div>
++  {/if}
++  {$form.html_message.html}<br />
++    </div>
++
++<div id="editMessageDetails">
++    <div id="updateDetails" >
++        {$form.updateTemplate.html}&nbsp;{$form.updateTemplate.label}
++    </div>
++    <div>
++        {$form.saveTemplate.html}&nbsp;{$form.saveTemplate.label}
++    </div>
++</div>
++
++<div id="saveDetails" class="section">
++    <div class="label">{$form.saveTemplateName.label}</div>
++    <div class="content">{$form.saveTemplateName.html|crmAddClass:huge}</div>
++</div>
++
++  </div><!-- /.crm-accordion-body -->
++</div><!-- /.crm-accordion-wrapper -->
++
++{include file="CRM/Mailing/Form/InsertTokens.tpl"}
++
++{literal}
++<script type="text/javascript">
++cj(function() {
++    cj().crmAccordions();
++});
++
++var currentWidth;
++var currentHeight;
++var currentMetric = document.getElementById('metric').value;
++showBindFormatChkBox();
++selectPaper( document.getElementById('paper_size').value );
++
++function tokenReplHtml ( )
++{
++    var token1 = cj("#token1").val( )[0];
++    var editor = {/literal}"{$editor}"{literal};
++    if ( editor == "tinymce" ) {
++        var content= tinyMCE.get('html_message').getContent() +token1;
++        tinyMCE.get('html_message').setContent(content);
++    } else if ( editor == "joomlaeditor" ) {
++        tinyMCE.execCommand('mceInsertContent',false, token1);
++        var msg       = document.getElementById(html_message).value;
++        var cursorlen = document.getElementById(html_message).selectionStart;
++        var textlen   = msg.length;
++        document.getElementById(html_message).value = msg.substring(0, cursorlen) + token1 + msg.substring(cursorlen, textlen);
++        var cursorPos = (cursorlen + token1.length);
++        document.getElementById(html_message).selectionStart = cursorPos;
++        document.getElementById(html_message).selectionEnd   = cursorPos;
++        document.getElementById(html_message).focus();
++  } else if ( editor == "ckeditor" ) {
++        oEditor = CKEDITOR.instances[html_message];
++        oEditor.insertHtml(token1.toString() );
++    } else if ( editor == "drupalwysiwyg" ) {
++        Drupal.wysiwyg.instances[html_message].insert(token1.toString());
++    } else {
++    var msg       = document.getElementById(html_message).value;
++        var cursorlen = document.getElementById(html_message).selectionStart;
++        var textlen   = msg.length;
++        document.getElementById(html_message).value = msg.substring(0, cursorlen) + token1 + msg.substring(cursorlen, textlen);
++        var cursorPos = (cursorlen + token1.length);
++        document.getElementById(html_message).selectionStart = cursorPos;
++        document.getElementById(html_message).selectionEnd   = cursorPos;
++        document.getElementById(html_message).focus();
++    }
++    verify();
++}
++
++function showBindFormatChkBox()
++{
++    var templateExists = true;
++    if ( document.getElementById('template') == null || document.getElementById('template').value == '' ) {
++        templateExists = false;
++    }
++    var formatExists = true;
++    if ( document.getElementById('format_id').value == 0 ) {
++        formatExists = false;
++    }
++    if ( templateExists && formatExists ) {
++        document.getElementById("bindFormat").style.display = "block";
++    } else if ( formatExists && document.getElementById("saveTemplate") != null && document.getElementById("saveTemplate").checked ) {
++        document.getElementById("bindFormat").style.display = "block";
++        var yes = confirm( '{/literal}{$useThisPageFormat}{literal}' );
++        if ( yes ) {
++            document.getElementById("bind_format").checked = true;
++        }
++    } else {
++        document.getElementById("bindFormat").style.display = "none";
++        document.getElementById("bind_format").checked = false;
++    }
++}
++
++function showUpdateFormatChkBox()
++{
++    if ( document.getElementById('format_id').value != 0 ) {
++        document.getElementById("updateFormat").style.display = "block";
++    }
++}
++
++function hideUpdateFormatChkBox()
++{
++    document.getElementById("update_format").checked = false;
++    document.getElementById("updateFormat").style.display = "none";
++}
++
++function selectFormat( val, bind )
++{
++    if ( val == null || val == 0 ) {
++        val = 0;
++        bind = false;
++    }
++    var dataUrl = {/literal}"{crmURL p='civicrm/ajax/pdfFormat' h=0 }"{literal};
++    cj.post( dataUrl, {formatId: val}, function( data ) {
++        cj("#format_id").val( data.id );
++        cj("#paper_size").val( data.paper_size );
++        cj("#orientation").val( data.orientation );
++        cj("#metric").val( data.metric );
++        cj("#margin_top").val( data.margin_top );
++        cj("#margin_bottom").val( data.margin_bottom );
++        cj("#margin_left").val( data.margin_left );
++        cj("#margin_right").val( data.margin_right );
++        selectPaper( data.paper_size );
++        hideUpdateFormatChkBox();
++        document.getElementById('bind_format').checked = bind;
++        showBindFormatChkBox();
++    }, 'json');
++}
++
++function selectPaper( val )
++{
++    dataUrl = {/literal}"{crmURL p='civicrm/ajax/paperSize' h=0 }"{literal};
++    cj.post( dataUrl, {paperSizeName: val}, function( data ) {
++        cj("#paper_size").val( data.name );
++        metric = document.getElementById('metric').value;
++        currentWidth = convertMetric( data.width, data.metric, metric );
++        currentHeight = convertMetric( data.height, data.metric, metric );
++        updatePaperDimensions( );
++    }, 'json');
++}
++
++function selectMetric( metric )
++{
++    convertField( 'margin_top', currentMetric, metric );
++    convertField( 'margin_bottom', currentMetric, metric );
++    convertField( 'margin_left', currentMetric, metric );
++    convertField( 'margin_right', currentMetric, metric );
++    currentWidth = convertMetric( currentWidth, currentMetric, metric );
++    currentHeight = convertMetric( currentHeight, currentMetric, metric );
++    updatePaperDimensions( );
++}
++
++function updatePaperDimensions( )
++{
++    metric = document.getElementById('metric').value;
++    width = new String( currentWidth.toFixed( 2 ) );
++    height = new String( currentHeight.toFixed( 2 ) );
++    if ( document.getElementById('orientation').value == 'landscape' ) {
++        width = new String( currentHeight.toFixed( 2 ) );
++        height = new String( currentWidth.toFixed( 2 ) );
++    }
++    document.getElementById('paper_dimensions').innerHTML = parseFloat( width ) + ' ' + metric + ' x ' + parseFloat( height ) + ' ' + metric;
++    currentMetric = metric;
++}
++
++function convertField( id, from, to )
++{
++    val = document.getElementById( id ).value;
++    if ( val == '' || isNaN( val ) ) return;
++    val = convertMetric( val, from, to );
++    val = new String( val.toFixed( 3 ) );
++    document.getElementById( id ).value = parseFloat( val );
++}
++
++function convertMetric( value, from, to ) {
++    switch( from + to ) {
++        case 'incm': return value * 2.54;
++        case 'inmm': return value * 25.4;
++        case 'inpt': return value * 72;
++        case 'cmin': return value / 2.54;
++        case 'cmmm': return value * 10;
++        case 'cmpt': return value * 72 / 2.54;
++        case 'mmin': return value / 25.4;
++        case 'mmcm': return value / 10;
++        case 'mmpt': return value * 72 / 25.4;
++        case 'ptin': return value / 72;
++        case 'ptcm': return value * 2.54 / 72;
++        case 'ptmm': return value * 25.4 / 72;
++    }
++    return value;
++}
++
++function showSaveDetails(chkbox)  {
++    var formatSelected = ( document.getElementById('format_id').value > 0 );
++    var templateSelected = ( document.getElementById('template') != null && document.getElementById('template').value > 0 );
++    if (chkbox.checked) {
++        document.getElementById("saveDetails").style.display = "block";
++        document.getElementById("saveTemplateName").disabled = false;
++        if ( formatSelected && ! templateSelected ) {
++            document.getElementById("bindFormat").style.display = "block";
++            var yes = confirm( '{/literal}{$useSelectedPageFormat}{literal}' );
++            if ( yes ) {
++                document.getElementById("bind_format").checked = true;
++            }
++        }
++    } else {
++        document.getElementById("saveDetails").style.display = "none";
++        document.getElementById("saveTemplateName").disabled = true;
++        if ( ! templateSelected ) {
++            document.getElementById("bindFormat").style.display = "none";
++            document.getElementById("bind_format").checked = false;
++        }
++    }
++}
++
++</script>
++{/literal}
++
+diff --git a/templates/CRM/Contribute/Form/Task/PDFLatex.tpl b/templates/CRM/Contribute/Form/Task/PDFLatex.tpl
+new file mode 100644
+index 0000000..a7fb259
+--- /dev/null
++++ b/templates/CRM/Contribute/Form/Task/PDFLatex.tpl
+@@ -0,0 +1,48 @@
++{*
++ +--------------------------------------------------------------------+
++ | CiviCRM version 4.4                                                |
++ +--------------------------------------------------------------------+
++ | Copyright CiviCRM LLC (c) 2004-2013                                |
++ +--------------------------------------------------------------------+
++ | This file is a part of CiviCRM.                                    |
++ |                                                                    |
++ | CiviCRM is free software; you can copy, modify, and distribute it  |
++ | under the terms of the GNU Affero General Public License           |
++ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
++ |                                                                    |
++ | CiviCRM is distributed in the hope that it will be useful, but     |
++ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
++ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
++ | See the GNU Affero General Public License for more details.        |
++ |                                                                    |
++ | You should have received a copy of the GNU Affero General Public   |
++ | License and the CiviCRM Licensing Exception along                  |
++ | with this program; if not, contact CiviCRM LLC                     |
++ | at info[AT]civicrm[DOT]org. If you have questions about the        |
++ | GNU Affero General Public License or the licensing of CiviCRM,     |
++ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
++ +--------------------------------------------------------------------+
++*}
++<div class="crm-form-block crm-block crm-contact-task-pdflatex-form-block">
++<h3>{ts}Thank-you Letter In Latex for Contributions (PDF){/ts}</h3>
++{if $single eq false}
++    <div class="messages status no-popup">{include file="CRM/Contribute/Form/Task.tpl"}</div>
++{/if}
++
++<div class="crm-accordion-wrapper crm-html_email-accordion ">
++  <div class="crm-accordion-header">
++    {$form.more_options_header.html}
++  </div><!-- /.crm-accordion-header -->
++  <div class="crm-accordion-body">
++    <table class="form-layout-compressed">
++      <tr><td class="label-left">{$form.thankyou_update.html} {$form.thankyou_update.label}</td><td></td></tr>
++      <tr><td class="label-left">{$form.receipt_update.html} {$form.receipt_update.label}</td><td></td></tr>
++      <!--tr><td class="label-left">{$form.is_group_by.label} (<a href="#" title="unselect" onclick="unselectRadio('is_group_by'); return false;" >{ts}clear{/ts}</a>)</td><td>{$form.is_group_by.html}</td></tr-->
++    </table>
++  </div><!-- /.crm-accordion-body -->
++</div><!-- /.crm-accordion-wrapper -->
++
++{include file="CRM/Contact/Form/Task/PDFLatexCommon.tpl"}
++
++<div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
++</div>
+-- 
+1.8.4
+
diff --git a/sigjohns.pdf b/sigjohns.pdf
new file mode 100644 (file)
index 0000000..1965aa8
Binary files /dev/null and b/sigjohns.pdf differ
diff --git a/sigjohns.png b/sigjohns.png
new file mode 100644 (file)
index 0000000..733c18c
Binary files /dev/null and b/sigjohns.png differ
diff --git a/templates/CRM/Contact/Form/Task/LatexPDF.tpl b/templates/CRM/Contact/Form/Task/LatexPDF.tpl
new file mode 100644 (file)
index 0000000..d260d56
--- /dev/null
@@ -0,0 +1,33 @@
+{*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.3                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2013                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*}
+<div class="crm-form-block crm-block crm-contact-task-pdf-form-block">
+<h3>{ts}Create Printable Latex Letters (PDF){/ts}</h3>
+{if $single eq false}
+    <div class="messages status no-popup">{include file="CRM/Contact/Form/Task.tpl"}</div>
+{/if}
+{include file="CRM/Contact/Form/Task/LatexPDFLatexCommon.tpl"}
+<div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
+</div>
diff --git a/templates/CRM/Contact/Form/Task/LatexPDFLatexCommon.tpl b/templates/CRM/Contact/Form/Task/LatexPDFLatexCommon.tpl
new file mode 100644 (file)
index 0000000..1f75f97
--- /dev/null
@@ -0,0 +1,312 @@
+{*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.4                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2013                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*}
+{*common template for compose PDF letters*}
+{if $form.template.html}
+<table class="form-layout-compressed">
+    <tr>
+        <td class="label-left">{$form.template.label}</td>
+      <td>{$form.template.html}</td>
+    </tr>
+</table>
+{/if}
+
+<div class="crm-accordion-wrapper collapsed">
+    <div class="crm-accordion-header">
+        {$form.pdf_format_header.html}
+    </div>
+    <div class="crm-accordion-body">
+      <div class="crm-block crm-form-block crm-pdf-format-form-block">
+    <table class="form-layout-compressed">
+      <tr>
+        <td class="label-left">{$form.format_id.label}</td><td>{$form.format_id.html}{help id="id-pdf-format" file="CRM/Contact/Form/Task/PDFLetterCommon.hlp"}</td>
+        <td colspan="2">&nbsp;</td>
+            </tr>
+      <tr>
+        <td class="label-left">{$form.paper_size.label}</td><td>{$form.paper_size.html}</td>
+        <td class="label-left">{$form.orientation.label}</td><td>{$form.orientation.html}</td>
+      </tr>
+      <tr>
+        <td class="label-left">{$form.metric.label}</td><td>{$form.metric.html}</td>
+        <td colspan="2">&nbsp;</td>
+      </tr>
+      <tr>
+        <td>{$form.paper_dimensions.html}</td><td id="paper_dimensions">&nbsp;</td>
+        <td colspan="2">&nbsp;</td>
+      </tr>
+      <tr>
+        <td class="label-left">{$form.margin_top.label}</td><td>{$form.margin_top.html}</td>
+        <td class="label-left">{$form.margin_bottom.label}</td><td>{$form.margin_bottom.html}</td>
+      </tr>
+      <tr>
+        <td class="label-left">{$form.margin_left.label}</td><td>{$form.margin_left.html}</td>
+        <td class="label-left">{$form.margin_right.label}</td><td>{$form.margin_right.html}</td>
+      </tr>
+    </table>
+        <div id="bindFormat">{$form.bind_format.html}&nbsp;{$form.bind_format.label}</div>
+        <div id="updateFormat" style="display: none">{$form.update_format.html}&nbsp;{$form.update_format.label}</div>
+      </div>
+  </div>
+</div>
+
+<div class="crm-accordion-wrapper crm-html_email-accordion ">
+<div class="crm-accordion-header">
+    {$form.html_message.label}
+</div><!-- /.crm-accordion-header -->
+ <div class="crm-accordion-body">
+  {if $action neq 4}
+  <span class="helpIcon" id="helphtml">
+  <a href="#" onClick="return showToken('Html', 1);">{$form.token1.label}</a>
+  {help id="id-token-html" file="CRM/Contact/Form/Task/Email.hlp" tplFile=$tplFile isAdmin=$isAdmin editor=$editor}
+  <div id="tokenHtml" style="display:none;">
+      <input style="border:1px solid #999999;" type="text" id="filter1" size="20" name="filter1" onkeyup="filter(this, 1)"/><br />
+      <span class="description">{ts}Begin typing to filter list of tokens{/ts}</span><br/>
+      {$form.token1.html}
+  </div>
+  </span>
+  {/if}
+    <div class="clear"></div>
+    <div class='html'>
+  {if $editor EQ 'textarea'}
+      <div class="help description">{ts}The content of this page should be LaTeX.{/ts}</div>
+  {/if}
+  {$form.html_message.html}<br />
+    </div>
+
+<div id="editMessageDetails">
+    <div id="updateDetails" >
+        {$form.updateTemplate.html}&nbsp;{$form.updateTemplate.label}
+    </div>
+    <div>
+        {$form.saveTemplate.html}&nbsp;{$form.saveTemplate.label}
+    </div>
+</div>
+
+<div id="saveDetails" class="section">
+    <div class="label">{$form.saveTemplateName.label}</div>
+    <div class="content">{$form.saveTemplateName.html|crmAddClass:huge}</div>
+</div>
+
+  </div><!-- /.crm-accordion-body -->
+</div><!-- /.crm-accordion-wrapper -->
+
+{include file="CRM/Mailing/Form/InsertTokens.tpl"}
+
+{literal}
+<script type="text/javascript">
+cj(function() {
+    cj().crmAccordions();
+});
+
+var currentWidth;
+var currentHeight;
+var currentMetric = document.getElementById('metric').value;
+showBindFormatChkBox();
+selectPaper( document.getElementById('paper_size').value );
+
+function tokenReplHtml ( )
+{
+    var token1 = cj("#token1").val( )[0];
+    var editor = {/literal}"{$editor}"{literal};
+    if ( editor == "tinymce" ) {
+        var content= tinyMCE.get('html_message').getContent() +token1;
+        tinyMCE.get('html_message').setContent(content);
+    } else if ( editor == "joomlaeditor" ) {
+        tinyMCE.execCommand('mceInsertContent',false, token1);
+        var msg       = document.getElementById(html_message).value;
+        var cursorlen = document.getElementById(html_message).selectionStart;
+        var textlen   = msg.length;
+        document.getElementById(html_message).value = msg.substring(0, cursorlen) + token1 + msg.substring(cursorlen, textlen);
+        var cursorPos = (cursorlen + token1.length);
+        document.getElementById(html_message).selectionStart = cursorPos;
+        document.getElementById(html_message).selectionEnd   = cursorPos;
+        document.getElementById(html_message).focus();
+  } else if ( editor == "ckeditor" ) {
+        oEditor = CKEDITOR.instances[html_message];
+        oEditor.insertHtml(token1.toString() );
+    } else if ( editor == "drupalwysiwyg" ) {
+        Drupal.wysiwyg.instances[html_message].insert(token1.toString());
+    } else {
+    var msg       = document.getElementById(html_message).value;
+        var cursorlen = document.getElementById(html_message).selectionStart;
+        var textlen   = msg.length;
+        document.getElementById(html_message).value = msg.substring(0, cursorlen) + token1 + msg.substring(cursorlen, textlen);
+        var cursorPos = (cursorlen + token1.length);
+        document.getElementById(html_message).selectionStart = cursorPos;
+        document.getElementById(html_message).selectionEnd   = cursorPos;
+        document.getElementById(html_message).focus();
+    }
+    verify();
+}
+
+function showBindFormatChkBox()
+{
+    var templateExists = true;
+    if ( document.getElementById('template') == null || document.getElementById('template').value == '' ) {
+        templateExists = false;
+    }
+    var formatExists = true;
+    if ( document.getElementById('format_id').value == 0 ) {
+        formatExists = false;
+    }
+    if ( templateExists && formatExists ) {
+        document.getElementById("bindFormat").style.display = "block";
+    } else if ( formatExists && document.getElementById("saveTemplate") != null && document.getElementById("saveTemplate").checked ) {
+        document.getElementById("bindFormat").style.display = "block";
+        var yes = confirm( '{/literal}{$useThisPageFormat}{literal}' );
+        if ( yes ) {
+            document.getElementById("bind_format").checked = true;
+        }
+    } else {
+        document.getElementById("bindFormat").style.display = "none";
+        document.getElementById("bind_format").checked = false;
+    }
+}
+
+function showUpdateFormatChkBox()
+{
+    if ( document.getElementById('format_id').value != 0 ) {
+        document.getElementById("updateFormat").style.display = "block";
+    }
+}
+
+function hideUpdateFormatChkBox()
+{
+    document.getElementById("update_format").checked = false;
+    document.getElementById("updateFormat").style.display = "none";
+}
+
+function selectFormat( val, bind )
+{
+    if ( val == null || val == 0 ) {
+        val = 0;
+        bind = false;
+    }
+    var dataUrl = {/literal}"{crmURL p='civicrm/ajax/pdfFormat' h=0 }"{literal};
+    cj.post( dataUrl, {formatId: val}, function( data ) {
+        cj("#format_id").val( data.id );
+        cj("#paper_size").val( data.paper_size );
+        cj("#orientation").val( data.orientation );
+        cj("#metric").val( data.metric );
+        cj("#margin_top").val( data.margin_top );
+        cj("#margin_bottom").val( data.margin_bottom );
+        cj("#margin_left").val( data.margin_left );
+        cj("#margin_right").val( data.margin_right );
+        selectPaper( data.paper_size );
+        hideUpdateFormatChkBox();
+        document.getElementById('bind_format').checked = bind;
+        showBindFormatChkBox();
+    }, 'json');
+}
+
+function selectPaper( val )
+{
+    dataUrl = {/literal}"{crmURL p='civicrm/ajax/paperSize' h=0 }"{literal};
+    cj.post( dataUrl, {paperSizeName: val}, function( data ) {
+        cj("#paper_size").val( data.name );
+        metric = document.getElementById('metric').value;
+        currentWidth = convertMetric( data.width, data.metric, metric );
+        currentHeight = convertMetric( data.height, data.metric, metric );
+        updatePaperDimensions( );
+    }, 'json');
+}
+
+function selectMetric( metric )
+{
+    convertField( 'margin_top', currentMetric, metric );
+    convertField( 'margin_bottom', currentMetric, metric );
+    convertField( 'margin_left', currentMetric, metric );
+    convertField( 'margin_right', currentMetric, metric );
+    currentWidth = convertMetric( currentWidth, currentMetric, metric );
+    currentHeight = convertMetric( currentHeight, currentMetric, metric );
+    updatePaperDimensions( );
+}
+
+function updatePaperDimensions( )
+{
+    metric = document.getElementById('metric').value;
+    width = new String( currentWidth.toFixed( 2 ) );
+    height = new String( currentHeight.toFixed( 2 ) );
+    if ( document.getElementById('orientation').value == 'landscape' ) {
+        width = new String( currentHeight.toFixed( 2 ) );
+        height = new String( currentWidth.toFixed( 2 ) );
+    }
+    document.getElementById('paper_dimensions').innerHTML = parseFloat( width ) + ' ' + metric + ' x ' + parseFloat( height ) + ' ' + metric;
+    currentMetric = metric;
+}
+
+function convertField( id, from, to )
+{
+    val = document.getElementById( id ).value;
+    if ( val == '' || isNaN( val ) ) return;
+    val = convertMetric( val, from, to );
+    val = new String( val.toFixed( 3 ) );
+    document.getElementById( id ).value = parseFloat( val );
+}
+
+function convertMetric( value, from, to ) {
+    switch( from + to ) {
+        case 'incm': return value * 2.54;
+        case 'inmm': return value * 25.4;
+        case 'inpt': return value * 72;
+        case 'cmin': return value / 2.54;
+        case 'cmmm': return value * 10;
+        case 'cmpt': return value * 72 / 2.54;
+        case 'mmin': return value / 25.4;
+        case 'mmcm': return value / 10;
+        case 'mmpt': return value * 72 / 25.4;
+        case 'ptin': return value / 72;
+        case 'ptcm': return value * 2.54 / 72;
+        case 'ptmm': return value * 25.4 / 72;
+    }
+    return value;
+}
+
+function showSaveDetails(chkbox)  {
+    var formatSelected = ( document.getElementById('format_id').value > 0 );
+    var templateSelected = ( document.getElementById('template') != null && document.getElementById('template').value > 0 );
+    if (chkbox.checked) {
+        document.getElementById("saveDetails").style.display = "block";
+        document.getElementById("saveTemplateName").disabled = false;
+        if ( formatSelected && ! templateSelected ) {
+            document.getElementById("bindFormat").style.display = "block";
+            var yes = confirm( '{/literal}{$useSelectedPageFormat}{literal}' );
+            if ( yes ) {
+                document.getElementById("bind_format").checked = true;
+            }
+        }
+    } else {
+        document.getElementById("saveDetails").style.display = "none";
+        document.getElementById("saveTemplateName").disabled = true;
+        if ( ! templateSelected ) {
+            document.getElementById("bindFormat").style.display = "none";
+            document.getElementById("bind_format").checked = false;
+        }
+    }
+}
+
+</script>
+{/literal}
+
diff --git a/templates/CRM/Contribute/Form/Task/PDFLatex.tpl b/templates/CRM/Contribute/Form/Task/PDFLatex.tpl
new file mode 100644 (file)
index 0000000..a7fb259
--- /dev/null
@@ -0,0 +1,48 @@
+{*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.4                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2013                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*}
+<div class="crm-form-block crm-block crm-contact-task-pdflatex-form-block">
+<h3>{ts}Thank-you Letter In Latex for Contributions (PDF){/ts}</h3>
+{if $single eq false}
+    <div class="messages status no-popup">{include file="CRM/Contribute/Form/Task.tpl"}</div>
+{/if}
+
+<div class="crm-accordion-wrapper crm-html_email-accordion ">
+  <div class="crm-accordion-header">
+    {$form.more_options_header.html}
+  </div><!-- /.crm-accordion-header -->
+  <div class="crm-accordion-body">
+    <table class="form-layout-compressed">
+      <tr><td class="label-left">{$form.thankyou_update.html} {$form.thankyou_update.label}</td><td></td></tr>
+      <tr><td class="label-left">{$form.receipt_update.html} {$form.receipt_update.label}</td><td></td></tr>
+      <!--tr><td class="label-left">{$form.is_group_by.label} (<a href="#" title="unselect" onclick="unselectRadio('is_group_by'); return false;" >{ts}clear{/ts}</a>)</td><td>{$form.is_group_by.html}</td></tr-->
+    </table>
+  </div><!-- /.crm-accordion-body -->
+</div><!-- /.crm-accordion-wrapper -->
+
+{include file="CRM/Contact/Form/Task/PDFLatexCommon.tpl"}
+
+<div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
+</div>
diff --git a/tmp_table_myisam.patch b/tmp_table_myisam.patch
new file mode 100644 (file)
index 0000000..a531b5f
--- /dev/null
@@ -0,0 +1,51 @@
+diff --git a/CRM/Contact/Form/Search/Custom/Group.php b/CRM/Contact/Form/Search/Custom/Group.php
+index 999a733..b40d55f 100644
+--- a/CRM/Contact/Form/Search/Custom/Group.php
++++ b/CRM/Contact/Form/Search/Custom/Group.php
+@@ -32,7 +32,9 @@
+  * $Id$
+  *
+  */
+-class CRM_Contact_Form_Search_Custom_Group extends CRM_Contact_Form_Search_Custom_Base implements CRM_Contact_Form_Search_Interface {
++class CRM_Contact_Form_Search_Custom_Group
++  extends CRM_Contact_Form_Search_Custom_Base
++  implements CRM_Contact_Form_Search_Interface {
+   protected $_formValues;
+@@ -283,7 +285,7 @@ function from() {
+         $xGroups = 0;
+       }
+-      $sql = "CREATE TEMPORARY TABLE Xg_{$this->_tableName} ( contact_id int primary key) ENGINE=HEAP";
++      $sql = "CREATE TEMPORARY TABLE Xg_{$this->_tableName} ( contact_id int primary key) ENGINE=MyISAM";
+       CRM_Core_DAO::executeQuery($sql);
+       //used only when exclude group is selected
+@@ -318,7 +320,7 @@ function from() {
+       $sql = "CREATE TEMPORARY TABLE Ig_{$this->_tableName} ( id int PRIMARY KEY AUTO_INCREMENT,
+                                                                    contact_id int,
+-                                                                   group_names varchar(64)) ENGINE=HEAP";
++                                                                   group_names varchar(64)) ENGINE=MyISAM";
+       CRM_Core_DAO::executeQuery($sql);
+@@ -418,7 +420,7 @@ function from() {
+         $xTags = 0;
+       }
+-      $sql = "CREATE TEMPORARY TABLE Xt_{$this->_tableName} ( contact_id int primary key) ENGINE=HEAP";
++      $sql = "CREATE TEMPORARY TABLE Xt_{$this->_tableName} ( contact_id int primary key) ENGINE=MyISAM";
+       CRM_Core_DAO::executeQuery($sql);
+       //used only when exclude tag is selected
+@@ -436,7 +438,7 @@ function from() {
+       $sql = "CREATE TEMPORARY TABLE It_{$this->_tableName} ( id int PRIMARY KEY AUTO_INCREMENT,
+                                                                contact_id int,
+-                                                               tag_names varchar(64)) ENGINE=HEAP";
++                                                               tag_names varchar(64)) ENGINE=MyISAM";
+       CRM_Core_DAO::executeQuery($sql);