Change eWAY processor to use guzzle, fix style issues and update info.xml and add...
authorSeamus Lee <seamuslee001@gmail.com>
Fri, 4 Sep 2020 22:43:32 +0000 (08:43 +1000)
committerSeamus Lee <seamuslee001@gmail.com>
Sat, 5 Sep 2020 01:40:21 +0000 (11:40 +1000)
Update README

CRM/Upgrade/Incremental/php/FiveThirtyOne.php
ext/ewaysingle/CRM/Core/Payment/eWAY.php
ext/ewaysingle/README.md
ext/ewaysingle/info.xml
ext/ewaysingle/lib/XML/Util.php
ext/ewaysingle/lib/eWAY/eWAY_GatewayRequest.php
ext/ewaysingle/lib/eWAY/eWAY_GatewayResponse.php
ext/ewaysingle/phpunit.xml.dist [new file with mode: 0644]
ext/ewaysingle/tests/phpunit/CRM/Core/Payment/EwayTest.php [new file with mode: 0644]
ext/ewaysingle/tests/phpunit/bootstrap.php [new file with mode: 0644]

index b85942cd3fced2db45572e475dbc042931837066..efc87e6e14e0162b2111c6b2c0c64f626265fb01 100644 (file)
@@ -71,8 +71,8 @@ class CRM_Upgrade_Incremental_php_FiveThirtyOne extends CRM_Upgrade_Incremental_
    * @param string $rev
    */
   public function upgrade_5_31_alpha1($rev) {
-   $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
-   $this->addTask('enableeWAYSingleCurrencyExtension', 'enableEwaySingleExtension');
+    $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
+    $this->addTask('enableeWAYSingleCurrencyExtension', 'enableEwaySingleExtension');
   }
 
   public static function enableEwaySingleExtension(CRM_Queue_TaskContext $ctx) {
index 59411cd5d0cfd4845555967c3dd2f302c852624e..a50389ffd33bbb7bb8d5232d441e41d487540d68 100644 (file)
@@ -88,6 +88,11 @@ require_once E::path('lib/eWAY/eWAY_GatewayResponse.php');
  */
 class CRM_Core_Payment_eWAY extends CRM_Core_Payment {
 
+  /**
+   * @var GuzzleHttp\Client
+   */
+  protected $guzzleClient;
+
   /**
    * *******************************************************
    * Constructor
@@ -106,6 +111,20 @@ class CRM_Core_Payment_eWAY extends CRM_Core_Payment {
     $this->_paymentProcessor = $paymentProcessor;
   }
 
+  /**
+   * @return \GuzzleHttp\Client
+   */
+  public function getGuzzleClient(): \GuzzleHttp\Client {
+    return $this->guzzleClient ?? new \GuzzleHttp\Client();
+  }
+
+  /**
+   * @param \GuzzleHttp\Client $guzzleClient
+   */
+  public function setGuzzleClient(\GuzzleHttp\Client $guzzleClient) {
+    $this->guzzleClient = $guzzleClient;
+  }
+
   /**
    * Sends request and receive response from eWAY payment process.
    *
@@ -246,50 +265,13 @@ class CRM_Core_Payment_eWAY extends CRM_Core_Payment {
     //----------------------------------------------------------------------------------------------------
     $requestxml = $eWAYRequest->ToXML();
 
-    $submit = curl_init($gateway_URL);
-
-    if (!$submit) {
-      throw new PaymentProcessorException('Could not initiate connection to payment gateway', 9004);
-    }
-
-    curl_setopt($submit, CURLOPT_POST, TRUE);
-    // return the result on success, FALSE on failure
-    curl_setopt($submit, CURLOPT_RETURNTRANSFER, TRUE);
-    curl_setopt($submit, CURLOPT_POSTFIELDS, $requestxml);
-    curl_setopt($submit, CURLOPT_TIMEOUT, 36000);
-    // if open_basedir or safe_mode are enabled in PHP settings CURLOPT_FOLLOWLOCATION won't work so don't apply it
-    // it's not really required CRM-5841
-    if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
-      // ensures any Location headers are followed
-      curl_setopt($submit, CURLOPT_FOLLOWLOCATION, 1);
-    }
-
-    // Send the data out over the wire
-    //--------------------------------
-    $responseData = curl_exec($submit);
-
-    //----------------------------------------------------------------------------------------------------
-    // See if we had a curl error - if so tell 'em and bail out
-    //
-    // NOTE: curl_error does not return a logical value (see its documentation), but
-    //       a string, which is empty when there was no error.
-    //----------------------------------------------------------------------------------------------------
-    if ((curl_errno($submit) > 0) || (strlen(curl_error($submit)) > 0)) {
-      $errorNum = curl_errno($submit);
-      $errorDesc = curl_error($submit);
-
-      // Paranoia - in the unlikley event that 'curl' errno fails
-      if ($errorNum == 0) {
-        $errorNum = 9005;
-      }
-
-      // Paranoia - in the unlikley event that 'curl' error fails
-      if (strlen($errorDesc) == 0) {
-        $errorDesc = 'Connection to eWAY payment gateway failed';
-      }
-
-      throw new PaymentProcessorException($errorDesc, $errorNum);
-    }
+    $responseData = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_site'], [
+      'body' => $requestxml,
+      'curl' => [
+        CURLOPT_RETURNTRANSFER => TRUE,
+        CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
+      ],
+    ])->getBody();
 
     //----------------------------------------------------------------------------------------------------
     // If null data returned - tell 'em and bail out
@@ -308,11 +290,6 @@ class CRM_Core_Payment_eWAY extends CRM_Core_Payment {
       throw new PaymentProcessorException('Error: No data returned from payment gateway.', 9007);
     }
 
-    //----------------------------------------------------------------------------------------------------
-    // Success so far - close the curl and check the data
-    //----------------------------------------------------------------------------------------------------
-    curl_close($submit);
-
     //----------------------------------------------------------------------------------------------------
     // Payment successfully sent to gateway - process the response now
     //----------------------------------------------------------------------------------------------------
index 52c94dafc11640f1d8e31266c2a8d0a277f9a784..81ddd1e7a6bac0212d0d66b6e4af6dfa6ed410fa 100644 (file)
@@ -2,43 +2,31 @@
 
 ![Screenshot](/images/screenshot.png)
 
-(*FIXME: In one or two paragraphs, describe what the extension does and why one would download it. *)
+This extension is aimed at containing the original Core eWAY (Single Currency) Payment Processor Type that is legacy. See known issues below
 
 The extension is licensed under [AGPL-3.0](LICENSE.txt).
 
 ## Requirements
 
-* PHP v7.0+
-* CiviCRM (*FIXME: Version number*)
+* PHP v7.1+
+* CiviCRM 5.31
 
 ## Installation (Web UI)
 
-This extension has not yet been published for installation via the web UI.
+Navigate to the Extensions Page and install the extension.
 
-## Installation (CLI, Zip)
+## Installation (CLI)
 
-Sysadmins and developers may download the `.zip` file for this extension and
-install it with the command-line tool [cv](https://github.com/civicrm/cv).
+To enable this extension in the CLI do the following
 
 ```bash
-cd <extension-dir>
-cv dl ewaysingle@https://github.com/FIXME/ewaysingle/archive/master.zip
-```
-
-## Installation (CLI, Git)
-
-Sysadmins and developers may clone the [Git](https://en.wikipedia.org/wiki/Git) repo for this extension and
-install it with the command-line tool [cv](https://github.com/civicrm/cv).
-
-```bash
-git clone https://github.com/FIXME/ewaysingle.git
 cv en ewaysingle
 ```
 
 ## Usage
 
-(* FIXME: Where would a new user navigate to get started? What changes would they see? *)
+The eWAY (Single Currency) Payment Processor Type will show up as one of the options when your adding in a PaymentProcessor.
 
 ## Known Issues
 
-(* FIXME *)
+This Payment Processor does not do any kind of recurring payments at all for that you would need another extension e.g. [Agileware Eway Recurring](https://github.com/agileware/au.com.agileware.ewayrecurring)
index bd99f5c4bd29abb8708f905bb3624cba6def56d8..d69c7cd3627d969a255800d6cca7f05c923a2696 100644 (file)
@@ -9,18 +9,18 @@
     <email>seamuslee001@gmail.com</email>
   </maintainer>
   <urls>
-    <url desc="Main Extension Page">http://FIXME</url>
-    <url desc="Documentation">http://FIXME</url>
-    <url desc="Support">http://FIXME</url>
+    <url desc="Main Extension Page">https://github.com/civicrm/civicrm-core/blob/master/ext/ewayrecurring</url>
+    <url desc="Documentation">https://github.com/civicrm/civicrm-core/blob/master/ext/ewayrecurring</url>
+    <url desc="Support">https://github.com/civicrm/civicrm-core/blob/master/ext/ewayrecurring</url>
     <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url>
   </urls>
-  <releaseDate>2020-09-04</releaseDate>
+  <releaseDate>2020-10-07</releaseDate>
   <version>1.0</version>
-  <develStage>alpha</develStage>
+  <develStage>stable</develStage>
   <compatibility>
-    <ver>5.0</ver>
+    <ver>5.31</ver>
   </compatibility>
-  <comments>This is a new, undeveloped module</comments>
+  <comments>This is an extension to contain the eWAY Single Currency Payment Processor</comments>
   <classloader>
     <psr4 prefix="Civi\" path="Civi"/>
   </classloader>
index f5927b16cfd45bac188d11ac0a68a6fdaeeeac45..cbef96ac831c6d1b95135b095995366a0541c1fb 100644 (file)
@@ -6,7 +6,7 @@
  * XML_Util
  *
  * XML Utilities package
- * 
+ *
  * PHP versions 4 and 5
  *
  * LICENSE:
@@ -113,7 +113,7 @@ define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2);
 /**
  * utility class for working with XML documents
  *
-
+ *
  * @category  XML
  * @package   XML_Util
  * @author    Stephan Schmidt <schst@php.net>
@@ -122,790 +122,800 @@ define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2);
  * @version   Release: 1.2.1
  * @link      http://pear.php.net/package/XML_Util
  */
-class XML_Util
-{
-    /**
-     * return API version
-     *
-     * @return string $version API version
-     * @access public
-     * @static
-     */
-    function apiVersion()
-    {
-        return '1.1';
-    }
-
-    /**
-     * replace XML entities
-     *
-     * With the optional second parameter, you may select, which
-     * entities should be replaced.
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // replace XML entites:
-     * $string = XML_Util::replaceEntities('This string contains < & >.');
-     * </code>
-     *
-     * With the optional third parameter, you may pass the character encoding
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // replace XML entites in UTF-8:
-     * $string = XML_Util::replaceEntities(
-     *     'This string contains < & > as well as ä, ö, ß, à and ê',
-     *     XML_UTIL_ENTITIES_HTML,
-     *     'UTF-8'
-     * );
-     * </code>
-     *
-     * @param string $string          string where XML special chars 
-     *                                should be replaced
-     * @param int    $replaceEntities setting for entities in attribute values 
-     *                                (one of XML_UTIL_ENTITIES_XML, 
-     *                                XML_UTIL_ENTITIES_XML_REQUIRED, 
-     *                                XML_UTIL_ENTITIES_HTML)
-     * @param string $encoding        encoding value (if any)...
-     *                                must be a valid encoding as determined
-     *                                by the htmlentities() function
-     *
-     * @return string string with replaced chars
-     * @access public
-     * @static
-     * @see reverseEntities()
-     */
-    function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
-        $encoding = 'ISO-8859-1')
-    {
-        switch ($replaceEntities) {
-        case XML_UTIL_ENTITIES_XML:
-            return strtr($string, array(
-                '&'  => '&amp;',
-                '>'  => '&gt;',
-                '<'  => '&lt;',
-                '"'  => '&quot;',
-                '\'' => '&apos;' ));
-            break;
-        case XML_UTIL_ENTITIES_XML_REQUIRED:
-            return strtr($string, array(
-                '&' => '&amp;',
-                '<' => '&lt;',
-                '"' => '&quot;' ));
-            break;
-        case XML_UTIL_ENTITIES_HTML:
-            return htmlentities($string, ENT_COMPAT, $encoding);
-            break;
-        }
-        return $string;
-    }
-
-    /**
-     * reverse XML entities
-     *
-     * With the optional second parameter, you may select, which
-     * entities should be reversed.
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // reverse XML entites:
-     * $string = XML_Util::reverseEntities('This string contains &lt; &amp; &gt;.');
-     * </code>
-     *
-     * With the optional third parameter, you may pass the character encoding
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // reverse XML entites in UTF-8:
-     * $string = XML_Util::reverseEntities(
-     *     'This string contains &lt; &amp; &gt; as well as'
-     *     . ' &auml;, &ouml;, &szlig;, &agrave; and &ecirc;',
-     *     XML_UTIL_ENTITIES_HTML,
-     *     'UTF-8'
-     * );
-     * </code>
-     *
-     * @param string $string          string where XML special chars 
-     *                                should be replaced
-     * @param int    $replaceEntities setting for entities in attribute values 
-     *                                (one of XML_UTIL_ENTITIES_XML, 
-     *                                XML_UTIL_ENTITIES_XML_REQUIRED, 
-     *                                XML_UTIL_ENTITIES_HTML)
-     * @param string $encoding        encoding value (if any)...
-     *                                must be a valid encoding as determined
-     *                                by the html_entity_decode() function
-     *
-     * @return string string with replaced chars
-     * @access public
-     * @static
-     * @see replaceEntities()
-     */
-    function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
-        $encoding = 'ISO-8859-1')
-    {
-        switch ($replaceEntities) {
-        case XML_UTIL_ENTITIES_XML:
-            return strtr($string, array(
-                '&amp;'  => '&',
-                '&gt;'   => '>',
-                '&lt;'   => '<',
-                '&quot;' => '"',
-                '&apos;' => '\'' ));
-            break;
-        case XML_UTIL_ENTITIES_XML_REQUIRED:
-            return strtr($string, array(
-                '&amp;'  => '&',
-                '&lt;'   => '<',
-                '&quot;' => '"' ));
-            break;
-        case XML_UTIL_ENTITIES_HTML:
-            return html_entity_decode($string, ENT_COMPAT, $encoding);
-            break;
-        }
-        return $string;
-    }
-
-    /**
-     * build an xml declaration
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // get an XML declaration:
-     * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true);
-     * </code>
-     *
-     * @param string $version    xml version
-     * @param string $encoding   character encoding
-     * @param bool   $standalone document is standalone (or not)
-     *
-     * @return string xml declaration
-     * @access public
-     * @static
-     * @uses attributesToString() to serialize the attributes of the XML declaration
-     */
-    function getXMLDeclaration($version = '1.0', $encoding = null, 
-        $standalone = null)
-    {
-        $attributes = array(
-            'version' => $version,
-        );
-        // add encoding
-        if ($encoding !== null) {
-            $attributes['encoding'] = $encoding;
-        }
-        // add standalone, if specified
-        if ($standalone !== null) {
-            $attributes['standalone'] = $standalone ? 'yes' : 'no';
-        }
-
-        return sprintf('<?xml%s?>', 
-            XML_Util::attributesToString($attributes, false));
-    }
-
-    /**
-     * build a document type declaration
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // get a doctype declaration:
-     * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd');
-     * </code>
-     *
-     * @param string $root        name of the root tag
-     * @param string $uri         uri of the doctype definition 
-     *                            (or array with uri and public id)
-     * @param string $internalDtd internal dtd entries
-     *
-     * @return string doctype declaration
-     * @access public
-     * @static
-     * @since 0.2
-     */
-    function getDocTypeDeclaration($root, $uri = null, $internalDtd = null)
-    {
-        if (is_array($uri)) {
-            $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']);
-        } elseif (!empty($uri)) {
-            $ref = sprintf(' SYSTEM "%s"', $uri);
-        } else {
-            $ref = '';
-        }
+class XML_Util {
+
+  /**
+   * return API version
+   *
+   * @return string $version API version
+   * @access public
+   * @static
+   */
+  public function apiVersion() {
+    return '1.1';
+  }
+
+  /**
+   * replace XML entities
+   *
+   * With the optional second parameter, you may select, which
+   * entities should be replaced.
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // replace XML entites:
+   * $string = XML_Util::replaceEntities('This string contains < & >.');
+   * </code>
+   *
+   * With the optional third parameter, you may pass the character encoding
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // replace XML entites in UTF-8:
+   * $string = XML_Util::replaceEntities(
+   *     'This string contains < & > as well as ä, ö, ß, à and ê',
+   *     XML_UTIL_ENTITIES_HTML,
+   *     'UTF-8'
+   * );
+   * </code>
+   *
+   * @param string $string          string where XML special chars
+   *                                should be replaced
+   * @param int $replaceEntities
+   *                                (one of XML_UTIL_ENTITIES_XML,
+   *                             XML_UTIL_ENTITIES_XML_REQUIRED,
+   *                             XML_UTIL_ENTITIES_HTML)
+   * @param string $encoding        encoding value (if any)...
+   *                                must be a valid encoding as determined
+   *                                by the htmlentities() function
+   *
+   * @return string string with replaced chars
+   * @access public
+   * @static
+   * @see reverseEntities()
+   */
+  public function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
+        $encoding = 'ISO-8859-1') {
+    switch ($replaceEntities) {
+      case XML_UTIL_ENTITIES_XML:
+        return strtr($string, array(
+          '&'  => '&amp;',
+          '>'  => '&gt;',
+          '<'  => '&lt;',
+          '"'  => '&quot;',
+          '\'' => '&apos;',
+        ));
+
+      break;
+      case XML_UTIL_ENTITIES_XML_REQUIRED:
+        return strtr($string, array(
+          '&' => '&amp;',
+          '<' => '&lt;',
+          '"' => '&quot;',
+        ));
+
+      break;
+      case XML_UTIL_ENTITIES_HTML:
+        return htmlentities($string, ENT_COMPAT, $encoding);
+
+      break;
+    }
+    return $string;
+  }
+
+  /**
+   * reverse XML entities
+   *
+   * With the optional second parameter, you may select, which
+   * entities should be reversed.
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // reverse XML entites:
+   * $string = XML_Util::reverseEntities('This string contains &lt; &amp; &gt;.');
+   * </code>
+   *
+   * With the optional third parameter, you may pass the character encoding
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // reverse XML entites in UTF-8:
+   * $string = XML_Util::reverseEntities(
+   *     'This string contains &lt; &amp; &gt; as well as'
+   *     . ' &auml;, &ouml;, &szlig;, &agrave; and &ecirc;',
+   *     XML_UTIL_ENTITIES_HTML,
+   *     'UTF-8'
+   * );
+   * </code>
+   *
+   * @param string $string          string where XML special chars
+   *                                should be replaced
+   * @param int $replaceEntities
+   *                                (one of XML_UTIL_ENTITIES_XML,
+   *                             XML_UTIL_ENTITIES_XML_REQUIRED,
+   *                             XML_UTIL_ENTITIES_HTML)
+   * @param string $encoding        encoding value (if any)...
+   *                                must be a valid encoding as determined
+   *                                by the html_entity_decode() function
+   *
+   * @return string string with replaced chars
+   * @access public
+   * @static
+   * @see replaceEntities()
+   */
+  public function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
+        $encoding = 'ISO-8859-1') {
+    switch ($replaceEntities) {
+      case XML_UTIL_ENTITIES_XML:
+        return strtr($string, array(
+          '&amp;'  => '&',
+          '&gt;'   => '>',
+          '&lt;'   => '<',
+          '&quot;' => '"',
+          '&apos;' => '\'',
+        ));
+
+      break;
+      case XML_UTIL_ENTITIES_XML_REQUIRED:
+        return strtr($string, array(
+          '&amp;'  => '&',
+          '&lt;'   => '<',
+          '&quot;' => '"',
+        ));
+
+      break;
+      case XML_UTIL_ENTITIES_HTML:
+        return html_entity_decode($string, ENT_COMPAT, $encoding);
+
+      break;
+    }
+    return $string;
+  }
+
+  /**
+   * build an xml declaration
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // get an XML declaration:
+   * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true);
+   * </code>
+   *
+   * @param string $version    xml version
+   * @param string $encoding   character encoding
+   * @param bool $standalone
+   *
+   * @return string xml declaration
+   * @access public
+   * @static
+   * @uses attributesToString() to serialize the attributes of the XML declaration
+   */
+  public function getXMLDeclaration($version = '1.0', $encoding = NULL,
+        $standalone = NULL) {
+    $attributes = array(
+      'version' => $version,
+    );
+    // add encoding
+    if ($encoding !== NULL) {
+      $attributes['encoding'] = $encoding;
+    }
+    // add standalone, if specified
+    if ($standalone !== NULL) {
+      $attributes['standalone'] = $standalone ? 'yes' : 'no';
+    }
 
-        if (empty($internalDtd)) {
-            return sprintf('<!DOCTYPE %s%s>', $root, $ref);
-        } else {
-            return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
-        }
+    return sprintf('<?xml%s?>',
+        XML_Util::attributesToString($attributes, FALSE));
+  }
+
+  /**
+   * build a document type declaration
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // get a doctype declaration:
+   * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd');
+   * </code>
+   *
+   * @param string $root        name of the root tag
+   * @param string $uri         uri of the doctype definition
+   *                            (or array with uri and public id)
+   * @param string $internalDtd internal dtd entries
+   *
+   * @return string doctype declaration
+   * @access public
+   * @static
+   * @since 0.2
+   */
+  public function getDocTypeDeclaration($root, $uri = NULL, $internalDtd = NULL) {
+    if (is_array($uri)) {
+      $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']);
+    }
+    elseif (!empty($uri)) {
+      $ref = sprintf(' SYSTEM "%s"', $uri);
+    }
+    else {
+      $ref = '';
     }
 
-    /**
-     * create string representation of an attribute list
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // build an attribute string
-     * $att = array(
-     *              'foo'   =>  'bar',
-     *              'argh'  =>  'tomato'
-     *            );
-     *
-     * $attList = XML_Util::attributesToString($att);
-     * </code>
-     *
-     * @param array      $attributes attribute array
-     * @param bool|array $sort       sort attribute list alphabetically, 
-     *                               may also be an assoc array containing 
-     *                               the keys 'sort', 'multiline', 'indent', 
-     *                               'linebreak' and 'entities'
-     * @param bool       $multiline  use linebreaks, if more than 
-     *                               one attribute is given
-     * @param string     $indent     string used for indentation of 
-     *                               multiline attributes
-     * @param string     $linebreak  string used for linebreaks of 
-     *                               multiline attributes
-     * @param int        $entities   setting for entities in attribute values 
-     *                               (one of XML_UTIL_ENTITIES_NONE, 
-     *                               XML_UTIL_ENTITIES_XML, 
-     *                               XML_UTIL_ENTITIES_XML_REQUIRED, 
-     *                               XML_UTIL_ENTITIES_HTML)
-     *
-     * @return string string representation of the attributes
-     * @access public
-     * @static
-     * @uses replaceEntities() to replace XML entities in attribute values
-     * @todo allow sort also to be an options array
+    if (empty($internalDtd)) {
+      return sprintf('<!DOCTYPE %s%s>', $root, $ref);
+    }
+    else {
+      return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
+    }
+  }
+
+  /**
+   * create string representation of an attribute list
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // build an attribute string
+   * $att = array(
+   *              'foo'   =>  'bar',
+   *              'argh'  =>  'tomato'
+   *            );
+   *
+   * $attList = XML_Util::attributesToString($att);
+   * </code>
+   *
+   * @param array $attributes
+   * @param bool|array $sort       sort attribute list alphabetically,
+   *                               may also be an assoc array containing
+   *                               the keys 'sort', 'multiline', 'indent',
+   *                               'linebreak' and 'entities'
+   * @param bool $multiline
+   *                               one attribute is given
+   * @param string $indent
+   *                               multiline attributes
+   * @param string $linebreak
+   *                               multiline attributes
+   * @param int $entities
+   *                               (one of XML_UTIL_ENTITIES_NONE,
+   *                        XML_UTIL_ENTITIES_XML,
+   *                        XML_UTIL_ENTITIES_XML_REQUIRED,
+   *                        XML_UTIL_ENTITIES_HTML)
+   *
+   * @return string string representation of the attributes
+   * @access public
+   * @static
+   * @uses replaceEntities() to replace XML entities in attribute values
+   * @todo allow sort also to be an options array
+   */
+  public function attributesToString($attributes, $sort = TRUE, $multiline = FALSE,
+        $indent = '    ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML) {
+    /*
+     * second parameter may be an array
      */
-    function attributesToString($attributes, $sort = true, $multiline = false, 
-        $indent = '    ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML)
-    {
-        /*
-         * second parameter may be an array
-         */
-        if (is_array($sort)) {
-            if (isset($sort['multiline'])) {
-                $multiline = $sort['multiline'];
-            }
-            if (isset($sort['indent'])) {
-                $indent = $sort['indent'];
-            }
-            if (isset($sort['linebreak'])) {
-                $multiline = $sort['linebreak'];
-            }
-            if (isset($sort['entities'])) {
-                $entities = $sort['entities'];
-            }
-            if (isset($sort['sort'])) {
-                $sort = $sort['sort'];
-            } else {
-                $sort = true;
-            }
-        }
-        $string = '';
-        if (is_array($attributes) && !empty($attributes)) {
-            if ($sort) {
-                ksort($attributes);
-            }
-            if ( !$multiline || count($attributes) == 1) {
-                foreach ($attributes as $key => $value) {
-                    if ($entities != XML_UTIL_ENTITIES_NONE) {
-                        if ($entities === XML_UTIL_CDATA_SECTION) {
-                            $entities = XML_UTIL_ENTITIES_XML;
-                        }
-                        $value = XML_Util::replaceEntities($value, $entities);
-                    }
-                    $string .= ' ' . $key . '="' . $value . '"';
-                }
-            } else {
-                $first = true;
-                foreach ($attributes as $key => $value) {
-                    if ($entities != XML_UTIL_ENTITIES_NONE) {
-                        $value = XML_Util::replaceEntities($value, $entities);
-                    }
-                    if ($first) {
-                        $string .= ' ' . $key . '="' . $value . '"';
-                        $first   = false;
-                    } else {
-                        $string .= $linebreak . $indent . $key . '="' . $value . '"';
-                    }
-                }
+    if (is_array($sort)) {
+      if (isset($sort['multiline'])) {
+        $multiline = $sort['multiline'];
+      }
+      if (isset($sort['indent'])) {
+        $indent = $sort['indent'];
+      }
+      if (isset($sort['linebreak'])) {
+        $multiline = $sort['linebreak'];
+      }
+      if (isset($sort['entities'])) {
+        $entities = $sort['entities'];
+      }
+      if (isset($sort['sort'])) {
+        $sort = $sort['sort'];
+      }
+      else {
+        $sort = TRUE;
+      }
+    }
+    $string = '';
+    if (is_array($attributes) && !empty($attributes)) {
+      if ($sort) {
+        ksort($attributes);
+      }
+      if (!$multiline || count($attributes) == 1) {
+        foreach ($attributes as $key => $value) {
+          if ($entities != XML_UTIL_ENTITIES_NONE) {
+            if ($entities === XML_UTIL_CDATA_SECTION) {
+              $entities = XML_UTIL_ENTITIES_XML;
             }
+            $value = XML_Util::replaceEntities($value, $entities);
+          }
+          $string .= ' ' . $key . '="' . $value . '"';
         }
-        return $string;
-    }
-
-    /**
-     * Collapses empty tags.
-     *
-     * @param string $xml  XML
-     * @param int    $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL)
-     *                      or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones.
-     *
-     * @return string XML
-     * @access public
-     * @static
-     * @todo PEAR CS - unable to avoid "space after open parens" error
-     *       in the IF branch
-     */
-    function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) 
-    {
-        if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) {
-            return preg_replace(
-                '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|'
-                . 'param)([^>]*)><\/\\1>/s',
-                '<\\1\\2 />',
-                $xml);
-        } else {
-            return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml);
+      }
+      else {
+        $first = TRUE;
+        foreach ($attributes as $key => $value) {
+          if ($entities != XML_UTIL_ENTITIES_NONE) {
+            $value = XML_Util::replaceEntities($value, $entities);
+          }
+          if ($first) {
+            $string .= ' ' . $key . '="' . $value . '"';
+            $first   = FALSE;
+          }
+          else {
+            $string .= $linebreak . $indent . $key . '="' . $value . '"';
+          }
         }
+      }
+    }
+    return $string;
+  }
+
+  /**
+   * Collapses empty tags.
+   *
+   * @param string $xml  XML
+   * @param int $mode
+   *                      or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones.
+   *
+   * @return string XML
+   * @access public
+   * @static
+   * @todo PEAR CS - unable to avoid "space after open parens" error
+   *       in the IF branch
+   */
+  public function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) {
+    if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) {
+      return preg_replace(
+        '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|'
+        . 'param)([^>]*)><\/\\1>/s',
+        '<\\1\\2 />',
+        $xml);
+    }
+    else {
+      return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml);
+    }
+  }
+
+  /**
+   * create a tag
+   *
+   * This method will call XML_Util::createTagFromArray(), which
+   * is more flexible.
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // create an XML tag:
+   * $tag = XML_Util::createTag('myNs:myTag',
+   *     array('foo' => 'bar'),
+   *     'This is inside the tag',
+   *     'http://www.w3c.org/myNs#');
+   * </code>
+   *
+   * @param string $qname           qualified tagname (including namespace)
+   * @param array $attributes
+   * @param mixed $content
+   * @param string $namespaceUri    URI of the namespace
+   * @param int $replaceEntities
+   *                                content, embedd it in a CData section
+   *                             or none of both
+   * @param bool $multiline
+   *                                each attribute gets written to a single line
+   * @param string $indent          string used to indent attributes
+   *                                (_auto indents attributes so they start
+   *                                at the same column)
+   * @param string $linebreak       string used for linebreaks
+   * @param bool $sortAttributes
+   *
+   * @return string XML tag
+   * @access public
+   * @static
+   * @see createTagFromArray()
+   * @uses createTagFromArray() to create the tag
+   */
+  public function createTag($qname, $attributes = array(), $content = NULL,
+        $namespaceUri = NULL, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
+        $multiline = FALSE, $indent = '_auto', $linebreak = "\n",
+        $sortAttributes = TRUE) {
+    $tag = array(
+      'qname'      => $qname,
+      'attributes' => $attributes,
+    );
+
+    // add tag content
+    if ($content !== NULL) {
+      $tag['content'] = $content;
     }
 
-    /**
-     * create a tag
-     *
-     * This method will call XML_Util::createTagFromArray(), which
-     * is more flexible.
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // create an XML tag:
-     * $tag = XML_Util::createTag('myNs:myTag', 
-     *     array('foo' => 'bar'), 
-     *     'This is inside the tag', 
-     *     'http://www.w3c.org/myNs#');
-     * </code>
-     *
-     * @param string $qname           qualified tagname (including namespace)
-     * @param array  $attributes      array containg attributes
-     * @param mixed  $content         the content
-     * @param string $namespaceUri    URI of the namespace
-     * @param int    $replaceEntities whether to replace XML special chars in 
-     *                                content, embedd it in a CData section 
-     *                                or none of both
-     * @param bool   $multiline       whether to create a multiline tag where 
-     *                                each attribute gets written to a single line
-     * @param string $indent          string used to indent attributes 
-     *                                (_auto indents attributes so they start 
-     *                                at the same column)
-     * @param string $linebreak       string used for linebreaks
-     * @param bool   $sortAttributes  Whether to sort the attributes or not
-     *
-     * @return string XML tag
-     * @access public
-     * @static
-     * @see createTagFromArray()
-     * @uses createTagFromArray() to create the tag
-     */
-    function createTag($qname, $attributes = array(), $content = null, 
-        $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, 
-        $multiline = false, $indent = '_auto', $linebreak = "\n", 
-        $sortAttributes = true)
-    {
-        $tag = array(
-            'qname'      => $qname,
-            'attributes' => $attributes
-        );
-
-        // add tag content
-        if ($content !== null) {
-            $tag['content'] = $content;
-        }
-
-        // add namespace Uri
-        if ($namespaceUri !== null) {
-            $tag['namespaceUri'] = $namespaceUri;
-        }
+    // add namespace Uri
+    if ($namespaceUri !== NULL) {
+      $tag['namespaceUri'] = $namespaceUri;
+    }
 
-        return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, 
-            $indent, $linebreak, $sortAttributes);
-    }
-
-    /**
-     * create a tag from an array
-     * this method awaits an array in the following format
-     * <pre>
-     * array(
-     *     // qualified name of the tag
-     *     'qname' => $qname        
-     *
-     *     // namespace prefix (optional, if qname is specified or no namespace)
-     *     'namespace' => $namespace    
-     *
-     *     // local part of the tagname (optional, if qname is specified)
-     *     'localpart' => $localpart,   
-     *
-     *     // array containing all attributes (optional)
-     *     'attributes' => array(),      
-     *
-     *     // tag content (optional)
-     *     'content' => $content,     
-     *
-     *     // namespaceUri for the given namespace (optional)
-     *     'namespaceUri' => $namespaceUri 
-     * )
-     * </pre>
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * $tag = array(
-     *     'qname'        => 'foo:bar',
-     *     'namespaceUri' => 'http://foo.com',
-     *     'attributes'   => array('key' => 'value', 'argh' => 'fruit&vegetable'),
-     *     'content'      => 'I\'m inside the tag',
-     * );
-     * // creating a tag with qualified name and namespaceUri
-     * $string = XML_Util::createTagFromArray($tag);
-     * </code>
-     *
-     * @param array  $tag             tag definition
-     * @param int    $replaceEntities whether to replace XML special chars in 
-     *                                content, embedd it in a CData section 
-     *                                or none of both
-     * @param bool   $multiline       whether to create a multiline tag where each 
-     *                                attribute gets written to a single line
-     * @param string $indent          string used to indent attributes 
-     *                                (_auto indents attributes so they start 
-     *                                at the same column)
-     * @param string $linebreak       string used for linebreaks
-     * @param bool   $sortAttributes  Whether to sort the attributes or not
-     *
-     * @return string XML tag
-     * @access public
-     * @static
-     * @see createTag()
-     * @uses attributesToString() to serialize the attributes of the tag
-     * @uses splitQualifiedName() to get local part and namespace of a qualified name
-     * @uses createCDataSection()
-     * @uses raiseError()
-     */
-    function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
-        $multiline = false, $indent = '_auto', $linebreak = "\n", 
-        $sortAttributes = true)
-    {
-        if (isset($tag['content']) && !is_scalar($tag['content'])) {
-            return XML_Util::raiseError('Supplied non-scalar value as tag content',
-            XML_UTIL_ERROR_NON_SCALAR_CONTENT);
-        }
+    return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline,
+        $indent, $linebreak, $sortAttributes);
+  }
+
+  /**
+   * create a tag from an array
+   * this method awaits an array in the following format
+   * <pre>
+   * array(
+   *     // qualified name of the tag
+   *     'qname' => $qname
+   *
+   *     // namespace prefix (optional, if qname is specified or no namespace)
+   *     'namespace' => $namespace
+   *
+   *     // local part of the tagname (optional, if qname is specified)
+   *     'localpart' => $localpart,
+   *
+   *     // array containing all attributes (optional)
+   *     'attributes' => array(),
+   *
+   *     // tag content (optional)
+   *     'content' => $content,
+   *
+   *     // namespaceUri for the given namespace (optional)
+   *     'namespaceUri' => $namespaceUri
+   * )
+   * </pre>
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * $tag = array(
+   *     'qname'        => 'foo:bar',
+   *     'namespaceUri' => 'http://foo.com',
+   *     'attributes'   => array('key' => 'value', 'argh' => 'fruit&vegetable'),
+   *     'content'      => 'I\'m inside the tag',
+   * );
+   * // creating a tag with qualified name and namespaceUri
+   * $string = XML_Util::createTagFromArray($tag);
+   * </code>
+   *
+   * @param array $tag
+   * @param int $replaceEntities
+   *                                content, embedd it in a CData section
+   *                             or none of both
+   * @param bool $multiline
+   *                                attribute gets written to a single line
+   * @param string $indent          string used to indent attributes
+   *                                (_auto indents attributes so they start
+   *                                at the same column)
+   * @param string $linebreak       string used for linebreaks
+   * @param bool $sortAttributes
+   *
+   * @return string XML tag
+   * @access public
+   * @static
+   * @see createTag()
+   * @uses attributesToString() to serialize the attributes of the tag
+   * @uses splitQualifiedName() to get local part and namespace of a qualified name
+   * @uses createCDataSection()
+   * @uses raiseError()
+   */
+  public function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
+        $multiline = FALSE, $indent = '_auto', $linebreak = "\n",
+        $sortAttributes = TRUE) {
+    if (isset($tag['content']) && !is_scalar($tag['content'])) {
+      return XML_Util::raiseError('Supplied non-scalar value as tag content',
+      XML_UTIL_ERROR_NON_SCALAR_CONTENT);
+    }
 
-        if (!isset($tag['qname']) && !isset($tag['localPart'])) {
-            return XML_Util::raiseError('You must either supply a qualified name '
-                . '(qname) or local tag name (localPart).', 
-                XML_UTIL_ERROR_NO_TAG_NAME);
-        }
+    if (!isset($tag['qname']) && !isset($tag['localPart'])) {
+      return XML_Util::raiseError('You must either supply a qualified name '
+        . '(qname) or local tag name (localPart).',
+        XML_UTIL_ERROR_NO_TAG_NAME);
+    }
 
-        // if no attributes hav been set, use empty attributes
-        if (!isset($tag['attributes']) || !is_array($tag['attributes'])) {
-            $tag['attributes'] = array();
-        }
+    // if no attributes hav been set, use empty attributes
+    if (!isset($tag['attributes']) || !is_array($tag['attributes'])) {
+      $tag['attributes'] = array();
+    }
 
-        if (isset($tag['namespaces'])) {
-            foreach ($tag['namespaces'] as $ns => $uri) {
-                $tag['attributes']['xmlns:' . $ns] = $uri;
-            }
-        }
+    if (isset($tag['namespaces'])) {
+      foreach ($tag['namespaces'] as $ns => $uri) {
+        $tag['attributes']['xmlns:' . $ns] = $uri;
+      }
+    }
 
-        if (!isset($tag['qname'])) {
-            // qualified name is not given
+    if (!isset($tag['qname'])) {
+      // qualified name is not given
 
-            // check for namespace
-            if (isset($tag['namespace']) && !empty($tag['namespace'])) {
-                $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart'];
-            } else {
-                $tag['qname'] = $tag['localPart'];
-            }
-        } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) {
-            // namespace URI is set, but no namespace
+      // check for namespace
+      if (isset($tag['namespace']) && !empty($tag['namespace'])) {
+        $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart'];
+      }
+      else {
+        $tag['qname'] = $tag['localPart'];
+      }
+    }
+    elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) {
+      // namespace URI is set, but no namespace
 
-            $parts = XML_Util::splitQualifiedName($tag['qname']);
+      $parts = XML_Util::splitQualifiedName($tag['qname']);
 
-            $tag['localPart'] = $parts['localPart'];
-            if (isset($parts['namespace'])) {
-                $tag['namespace'] = $parts['namespace'];
-            }
-        }
+      $tag['localPart'] = $parts['localPart'];
+      if (isset($parts['namespace'])) {
+        $tag['namespace'] = $parts['namespace'];
+      }
+    }
 
-        if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) {
-            // is a namespace given
-            if (isset($tag['namespace']) && !empty($tag['namespace'])) {
-                $tag['attributes']['xmlns:' . $tag['namespace']] =
+    if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) {
+      // is a namespace given
+      if (isset($tag['namespace']) && !empty($tag['namespace'])) {
+        $tag['attributes']['xmlns:' . $tag['namespace']] =
                     $tag['namespaceUri'];
-            } else {
-                // define this Uri as the default namespace
-                $tag['attributes']['xmlns'] = $tag['namespaceUri'];
-            }
-        }
+      }
+      else {
+        // define this Uri as the default namespace
+        $tag['attributes']['xmlns'] = $tag['namespaceUri'];
+      }
+    }
 
-        // check for multiline attributes
-        if ($multiline === true) {
-            if ($indent === '_auto') {
-                $indent = str_repeat(' ', (strlen($tag['qname'])+2));
-            }
-        }
+    // check for multiline attributes
+    if ($multiline === TRUE) {
+      if ($indent === '_auto') {
+        $indent = str_repeat(' ', (strlen($tag['qname']) + 2));
+      }
+    }
 
-        // create attribute list
-        $attList = XML_Util::attributesToString($tag['attributes'], 
-            $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities);
-        if (!isset($tag['content']) || (string)$tag['content'] == '') {
-            $tag = sprintf('<%s%s />', $tag['qname'], $attList);
-        } else {
-            switch ($replaceEntities) {
-            case XML_UTIL_ENTITIES_NONE:
-                break;
-            case XML_UTIL_CDATA_SECTION:
-                $tag['content'] = XML_Util::createCDataSection($tag['content']);
-                break;
-            default:
-                $tag['content'] = XML_Util::replaceEntities($tag['content'], 
-                    $replaceEntities);
-                break;
-            }
-            $tag = sprintf('<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'],
-                $tag['qname']);
-        }
-        return $tag;
-    }
-
-    /**
-     * create a start element
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // create an XML start element:
-     * $tag = XML_Util::createStartElement('myNs:myTag', 
-     *     array('foo' => 'bar') ,'http://www.w3c.org/myNs#');
-     * </code>
-     *
-     * @param string $qname          qualified tagname (including namespace)
-     * @param array  $attributes     array containg attributes
-     * @param string $namespaceUri   URI of the namespace
-     * @param bool   $multiline      whether to create a multiline tag where each 
-     *                               attribute gets written to a single line
-     * @param string $indent         string used to indent attributes (_auto indents
-     *                               attributes so they start at the same column)
-     * @param string $linebreak      string used for linebreaks
-     * @param bool   $sortAttributes Whether to sort the attributes or not
-     *
-     * @return string XML start element
-     * @access public
-     * @static
-     * @see createEndElement(), createTag()
-     */
-    function createStartElement($qname, $attributes = array(), $namespaceUri = null,
-        $multiline = false, $indent = '_auto', $linebreak = "\n", 
-        $sortAttributes = true)
-    {
-        // if no attributes hav been set, use empty attributes
-        if (!isset($attributes) || !is_array($attributes)) {
-            $attributes = array();
-        }
+    // create attribute list
+    $attList = XML_Util::attributesToString($tag['attributes'],
+        $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities);
+    if (!isset($tag['content']) || (string) $tag['content'] == '') {
+      $tag = sprintf('<%s%s />', $tag['qname'], $attList);
+    }
+    else {
+      switch ($replaceEntities) {
+        case XML_UTIL_ENTITIES_NONE:
+          break;
+
+        case XML_UTIL_CDATA_SECTION:
+          $tag['content'] = XML_Util::createCDataSection($tag['content']);
+          break;
+
+        default:
+          $tag['content'] = XML_Util::replaceEntities($tag['content'],
+          $replaceEntities);
+          break;
+      }
+      $tag = sprintf('<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'],
+        $tag['qname']);
+    }
+    return $tag;
+  }
+
+  /**
+   * create a start element
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // create an XML start element:
+   * $tag = XML_Util::createStartElement('myNs:myTag',
+   *     array('foo' => 'bar') ,'http://www.w3c.org/myNs#');
+   * </code>
+   *
+   * @param string $qname          qualified tagname (including namespace)
+   * @param array $attributes
+   * @param string $namespaceUri   URI of the namespace
+   * @param bool $multiline
+   *                               attribute gets written to a single line
+   * @param string $indent         string used to indent attributes (_auto indents
+   *                               attributes so they start at the same column)
+   * @param string $linebreak      string used for linebreaks
+   * @param bool $sortAttributes
+   *
+   * @return string XML start element
+   * @access public
+   * @static
+   * @see createEndElement()
+   * @see createTag()
+   */
+  public function createStartElement($qname, $attributes = array(), $namespaceUri = NULL,
+        $multiline = FALSE, $indent = '_auto', $linebreak = "\n",
+        $sortAttributes = TRUE) {
+    // if no attributes hav been set, use empty attributes
+    if (!isset($attributes) || !is_array($attributes)) {
+      $attributes = array();
+    }
 
-        if ($namespaceUri != null) {
-            $parts = XML_Util::splitQualifiedName($qname);
-        }
+    if ($namespaceUri != NULL) {
+      $parts = XML_Util::splitQualifiedName($qname);
+    }
 
-        // check for multiline attributes
-        if ($multiline === true) {
-            if ($indent === '_auto') {
-                $indent = str_repeat(' ', (strlen($qname)+2));
-            }
-        }
+    // check for multiline attributes
+    if ($multiline === TRUE) {
+      if ($indent === '_auto') {
+        $indent = str_repeat(' ', (strlen($qname) + 2));
+      }
+    }
 
-        if ($namespaceUri != null) {
-            // is a namespace given
-            if (isset($parts['namespace']) && !empty($parts['namespace'])) {
-                $attributes['xmlns:' . $parts['namespace']] = $namespaceUri;
-            } else {
-                // define this Uri as the default namespace
-                $attributes['xmlns'] = $namespaceUri;
-            }
-        }
+    if ($namespaceUri != NULL) {
+      // is a namespace given
+      if (isset($parts['namespace']) && !empty($parts['namespace'])) {
+        $attributes['xmlns:' . $parts['namespace']] = $namespaceUri;
+      }
+      else {
+        // define this Uri as the default namespace
+        $attributes['xmlns'] = $namespaceUri;
+      }
+    }
 
-        // create attribute list
-        $attList = XML_Util::attributesToString($attributes, $sortAttributes, 
-            $multiline, $indent, $linebreak);
-        $element = sprintf('<%s%s>', $qname, $attList);
-        return  $element;
-    }
-
-    /**
-     * create an end element
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // create an XML start element:
-     * $tag = XML_Util::createEndElement('myNs:myTag');
-     * </code>
-     *
-     * @param string $qname qualified tagname (including namespace)
-     *
-     * @return string XML end element
-     * @access public
-     * @static
-     * @see createStartElement(), createTag()
-     */
-    function createEndElement($qname)
-    {
-        $element = sprintf('</%s>', $qname);
-        return $element;
-    }
-
-    /**
-     * create an XML comment
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // create an XML start element:
-     * $tag = XML_Util::createComment('I am a comment');
-     * </code>
-     *
-     * @param string $content content of the comment
-     *
-     * @return string XML comment
-     * @access public
-     * @static
-     */
-    function createComment($content)
-    {
-        $comment = sprintf('<!-- %s -->', $content);
-        return $comment;
-    }
-
-    /**
-     * create a CData section
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // create a CData section
-     * $tag = XML_Util::createCDataSection('I am content.');
-     * </code>
-     *
-     * @param string $data data of the CData section
-     *
-     * @return string CData section with content
-     * @access public
-     * @static
-     */
-    function createCDataSection($data)
-    {
-        return sprintf('<![CDATA[%s]]>', 
-            preg_replace('/\]\]>/', ']]]]><![CDATA[>', strval($data)));
-
-    }
-
-    /**
-     * split qualified name and return namespace and local part
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // split qualified tag
-     * $parts = XML_Util::splitQualifiedName('xslt:stylesheet');
-     * </code>
-     * the returned array will contain two elements:
-     * <pre>
-     * array(
-     *     'namespace' => 'xslt',
-     *     'localPart' => 'stylesheet'
-     * );
-     * </pre>
-     *
-     * @param string $qname     qualified tag name
-     * @param string $defaultNs default namespace (optional)
-     *
-     * @return array array containing namespace and local part
-     * @access public
-     * @static
-     */
-    function splitQualifiedName($qname, $defaultNs = null)
-    {
-        if (strstr($qname, ':')) {
-            $tmp = explode(':', $qname);
-            return array(
-                'namespace' => $tmp[0],
-                'localPart' => $tmp[1]
-            );
-        }
-        return array(
-            'namespace' => $defaultNs,
-            'localPart' => $qname
-        );
-    }
-
-    /**
-     * check, whether string is valid XML name
-     *
-     * <p>XML names are used for tagname, attribute names and various
-     * other, lesser known entities.</p>
-     * <p>An XML name may only consist of alphanumeric characters,
-     * dashes, undescores and periods, and has to start with a letter
-     * or an underscore.</p>
-     *
-     * <code>
-     * require_once 'XML/Util.php';
-     *
-     * // verify tag name
-     * $result = XML_Util::isValidName('invalidTag?');
-     * if (is_a($result, 'PEAR_Error')) {
-     *    print 'Invalid XML name: ' . $result->getMessage();
-     * }
-     * </code>
-     *
-     * @param string $string string that should be checked
-     *
-     * @return mixed true, if string is a valid XML name, PEAR error otherwise
-     * @access public
-     * @static
-     * @todo support for other charsets
-     * @todo PEAR CS - unable to avoid 85-char limit on second preg_match
-     */
-    function isValidName($string)
-    {
-        // check for invalid chars
-        if (!preg_match('/^[[:alpha:]_]$/', $string{0})) {
-            return XML_Util::raiseError('XML names may only start with letter '
-                . 'or underscore', XML_UTIL_ERROR_INVALID_START);
-        }
+    // create attribute list
+    $attList = XML_Util::attributesToString($attributes, $sortAttributes,
+        $multiline, $indent, $linebreak);
+    $element = sprintf('<%s%s>', $qname, $attList);
+    return $element;
+  }
+
+  /**
+   * create an end element
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // create an XML start element:
+   * $tag = XML_Util::createEndElement('myNs:myTag');
+   * </code>
+   *
+   * @param string $qname qualified tagname (including namespace)
+   *
+   * @return string XML end element
+   * @access public
+   * @static
+   * @see createStartElement()
+   * @see createTag()
+   */
+  public function createEndElement($qname) {
+    $element = sprintf('</%s>', $qname);
+    return $element;
+  }
+
+  /**
+   * create an XML comment
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // create an XML start element:
+   * $tag = XML_Util::createComment('I am a comment');
+   * </code>
+   *
+   * @param string $content content of the comment
+   *
+   * @return string XML comment
+   * @access public
+   * @static
+   */
+  public function createComment($content) {
+    $comment = sprintf('<!-- %s -->', $content);
+    return $comment;
+  }
+
+  /**
+   * create a CData section
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // create a CData section
+   * $tag = XML_Util::createCDataSection('I am content.');
+   * </code>
+   *
+   * @param string $data data of the CData section
+   *
+   * @return string CData section with content
+   * @access public
+   * @static
+   */
+  public function createCDataSection($data) {
+    return sprintf('<![CDATA[%s]]>',
+        preg_replace('/\]\]>/', ']]]]><![CDATA[>', strval($data)));
+
+  }
+
+  /**
+   * split qualified name and return namespace and local part
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // split qualified tag
+   * $parts = XML_Util::splitQualifiedName('xslt:stylesheet');
+   * </code>
+   * the returned array will contain two elements:
+   * <pre>
+   * array(
+   *     'namespace' => 'xslt',
+   *     'localPart' => 'stylesheet'
+   * );
+   * </pre>
+   *
+   * @param string $qname     qualified tag name
+   * @param string $defaultNs default namespace (optional)
+   *
+   * @return array array containing namespace and local part
+   * @access public
+   * @static
+   */
+  public function splitQualifiedName($qname, $defaultNs = NULL) {
+    if (strstr($qname, ':')) {
+      $tmp = explode(':', $qname);
+      return array(
+        'namespace' => $tmp[0],
+        'localPart' => $tmp[1],
+      );
+    }
+    return array(
+      'namespace' => $defaultNs,
+      'localPart' => $qname,
+    );
+  }
+
+  /**
+   * check, whether string is valid XML name
+   *
+   * <p>XML names are used for tagname, attribute names and various
+   * other, lesser known entities.</p>
+   * <p>An XML name may only consist of alphanumeric characters,
+   * dashes, undescores and periods, and has to start with a letter
+   * or an underscore.</p>
+   *
+   * <code>
+   * require_once 'XML/Util.php';
+   *
+   * // verify tag name
+   * $result = XML_Util::isValidName('invalidTag?');
+   * if (is_a($result, 'PEAR_Error')) {
+   *    print 'Invalid XML name: ' . $result->getMessage();
+   * }
+   * </code>
+   *
+   * @param string $string string that should be checked
+   *
+   * @return mixed true, if string is a valid XML name, PEAR error otherwise
+   * @access public
+   * @static
+   * @todo support for other charsets
+   * @todo PEAR CS - unable to avoid 85-char limit on second preg_match
+   */
+  public function isValidName($string) {
+    // check for invalid chars
+    if (!preg_match('/^[[:alpha:]_]$/', $string{0})) {
+      return XML_Util::raiseError('XML names may only start with letter '
+        . 'or underscore', XML_UTIL_ERROR_INVALID_START);
+    }
 
-        // check for invalid chars
-        if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/',
-            $string)
-        ) {
-            return XML_Util::raiseError('XML names may only contain alphanumeric '
-                . 'chars, period, hyphen, colon and underscores', 
-                XML_UTIL_ERROR_INVALID_CHARS);
-        }
-        // XML name is valid
-        return true;
-    }
-
-    /**
-     * replacement for XML_Util::raiseError
-     *
-     * Avoids the necessity to always require
-     * PEAR.php
-     *
-     * @param string $msg  error message
-     * @param int    $code error code
-     *
-     * @return PEAR_Error
-     * @access public
-     * @static
-     * @todo PEAR CS - should this use include_once instead?
-     */
-    function raiseError($msg, $code)
-    {
-        require_once 'PEAR.php';
-        return PEAR::raiseError($msg, $code);
+    // check for invalid chars
+    if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/',
+        $string)
+    ) {
+      return XML_Util::raiseError('XML names may only contain alphanumeric '
+        . 'chars, period, hyphen, colon and underscores',
+        XML_UTIL_ERROR_INVALID_CHARS);
     }
+    // XML name is valid
+    return TRUE;
+  }
+
+  /**
+   * replacement for XML_Util::raiseError
+   *
+   * Avoids the necessity to always require
+   * PEAR.php
+   *
+   * @param string $msg  error message
+   * @param int $code
+   *
+   * @return PEAR_Error
+   * @access public
+   * @static
+   * @todo PEAR CS - should this use include_once instead?
+   */
+  public function raiseError($msg, $code) {
+    require_once 'PEAR.php';
+    return PEAR::raiseError($msg, $code);
+  }
+
 }
-?>
index 2d26e59a3142ab216b3a594940d6d309ed658b11..a3f5984e4b83ada46b4848599853efbe14679c84 100644 (file)
-<?php\r
-\r
-/*\r
- +--------------------------------------------------------------------+\r
- | CiviCRM version 5                                                  |\r
- +--------------------------------------------------------------------+\r
- | This file is a part of CiviCRM.                                    |\r
- |                                                                    |\r
- | CiviCRM is free software; you can copy, modify, and distribute it  |\r
- | under the terms of the GNU Affero General Public License           |\r
- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |\r
- |                                                                    |\r
- | CiviCRM is distributed in the hope that it will be useful, but     |\r
- | WITHOUT ANY WARRANTY; without even the implied warranty of         |\r
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |\r
- | See the GNU Affero General Public License for more details.        |\r
- |                                                                    |\r
- | You should have received a copy of the GNU Affero General Public   |\r
- | License and the CiviCRM Licensing Exception along                  |\r
- | with this program; if not, contact CiviCRM LLC                     |\r
- | at info[AT]civicrm[DOT]org. If you have questions about the        |\r
- | GNU Affero General Public License or the licensing of CiviCRM,     |\r
- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |\r
- +--------------------------------------------------------------------+\r
-*/\r
-\r
-/**************************************************************************************************************************\r
- * Licensed to CiviCRM under the Academic Free License version 3.0\r
- * Written & Contributed by Dolphin Software P/L - March 2008 \r
- *\r
- * 'eWAY_GatewayRequest.php' - Based on the standard supplied eWay sample code 'GatewayResponse.php'\r
- *\r
- * The only significant change from the original is that the 'CVN' field is uncommented,\r
- * unlike the distributed sample code.\r
- *\r
- * ALSO: Added a 'GetTransactionNumber' function.\r
- *\r
- **************************************************************************************************************************/\r
-use CRM_Ewaysingle_ExtensionUtil as E;\r
\r
-class GatewayRequest\r
-{\r
-       var $txCustomerID = "";\r
-\r
-       var $txAmount = 0;\r
-\r
-       var $txCardholderName = "";\r
-\r
-       var $txCardNumber = "";\r
-\r
-       var $txCardExpiryMonth = "01";\r
-\r
-       var $txCardExpiryYear = "00";\r
-\r
-       var $txTransactionNumber = "";\r
-\r
-       var $txCardholderFirstName = "";\r
-\r
-       var $txCardholderLastName = "";\r
-\r
-       var $txCardholderEmailAddress = "";\r
-\r
-       var $txCardholderAddress = "";\r
-\r
-       var $txCardholderPostalCode = "";\r
-\r
-       var $txInvoiceReference = "";\r
-\r
-       var $txInvoiceDescription = "";\r
-\r
-    var $txCVN = "";\r
-\r
-       var $txOption1 = "";\r
-\r
-       var $txOption2 = "";\r
-\r
-       var $txOption3 = "";\r
-    \r
-    var $txCustomerBillingCountry = "";\r
-\r
-    var $txCustomerIPAddress = "";\r
-\r
-   function __construct()\r
-   {\r
-      // Empty Constructor\r
-   }\r
-\r
-   function GetTransactionNumber()\r
-   {\r
-      return $this->txTransactionNumber;\r
-   }\r
-\r
-   function EwayCustomerID($value) \r
-   {\r
-      $this->txCustomerID=$value;\r
-   }\r
-\r
-   function InvoiceAmount($value)\r
-   {\r
-      $this->txAmount=$value;\r
-   }\r
-\r
-   function CardHolderName($value)\r
-   {\r
-      $this->txCardholderName=$value;\r
-   }\r
-\r
-   function CardExpiryMonth($value)  \r
-   {\r
-      $this->txCardExpiryMonth=$value;\r
-   }\r
-\r
-   function CardExpiryYear($value)\r
-   {\r
-      $this->txCardExpiryYear=$value;\r
-   }\r
-\r
-   function TransactionNumber($value)\r
-   {\r
-      $this->txTransactionNumber=$value;\r
-   }\r
-\r
-   function PurchaserFirstName($value)\r
-   {\r
-      $this->txCardholderFirstName=$value;\r
-   }\r
-\r
-   function PurchaserLastName($value)\r
-   {\r
-      $this->txCardholderLastName=$value;\r
-   }\r
-\r
-   function CardNumber($value)\r
-   {\r
-      $this->txCardNumber=$value;\r
-   }\r
-\r
-   function PurchaserAddress($value)\r
-   {\r
-      $this->txCardholderAddress=$value;\r
-   }\r
-\r
-   function PurchaserPostalCode($value)\r
-   {\r
-      $this->txCardholderPostalCode=$value;\r
-   }\r
-\r
-   function PurchaserEmailAddress($value)\r
-   {\r
-      $this->txCardholderEmailAddress=$value;\r
-   }\r
-\r
-   function InvoiceReference($value) \r
-   {\r
-      $this->txInvoiceReference=$value; \r
-   }\r
-\r
-   function InvoiceDescription($value) \r
-   {\r
-      $this->txInvoiceDescription=$value; \r
-   }\r
-\r
-   function CVN($value) \r
-   {\r
-      $this->txCVN=$value; \r
-   }\r
-\r
-   function EwayOption1($value) \r
-   {\r
-      $this->txOption1=$value; \r
-   }\r
-\r
-   function EwayOption2($value) \r
-   {\r
-      $this->txOption2=$value; \r
-   }\r
-\r
-   function EwayOption3($value) \r
-   {\r
-      $this->txOption3=$value; \r
-   }\r
-\r
-   function CustomerBillingCountry($value) \r
-   {\r
-       $this->txCustomerBillingCountry=$value; \r
-   }\r
-\r
-   function CustomerIPAddress($value) \r
-   {\r
-       $this->txCustomerIPAddress=$value; \r
-   }\r
-\r
-   function ToXml()\r
-   {\r
-      // We don't really need the overhead of creating an XML DOM object\r
-      // to really just concatenate a string together.\r
-\r
-      $xml = "<ewaygateway>";\r
-      $xml .= $this->CreateNode("ewayCustomerID",                 $this->txCustomerID);\r
-      $xml .= $this->CreateNode("ewayTotalAmount",                $this->txAmount);\r
-      $xml .= $this->CreateNode("ewayCardHoldersName",            $this->txCardholderName);\r
-      $xml .= $this->CreateNode("ewayCardNumber",                 $this->txCardNumber);\r
-      $xml .= $this->CreateNode("ewayCardExpiryMonth",            $this->txCardExpiryMonth);\r
-      $xml .= $this->CreateNode("ewayCardExpiryYear",             $this->txCardExpiryYear);\r
-      $xml .= $this->CreateNode("ewayTrxnNumber",                 $this->txTransactionNumber);\r
-      $xml .= $this->CreateNode("ewayCustomerInvoiceDescription", $this->txInvoiceDescription);\r
-      $xml .= $this->CreateNode("ewayCustomerFirstName",          $this->txCardholderFirstName);\r
-      $xml .= $this->CreateNode("ewayCustomerLastName",           $this->txCardholderLastName);\r
-      $xml .= $this->CreateNode("ewayCustomerEmail",              $this->txCardholderEmailAddress);\r
-      $xml .= $this->CreateNode("ewayCustomerAddress",            $this->txCardholderAddress);\r
-      $xml .= $this->CreateNode("ewayCustomerPostcode",           $this->txCardholderPostalCode);\r
-      $xml .= $this->CreateNode("ewayCustomerInvoiceRef",         $this->txInvoiceReference);\r
-      $xml .= $this->CreateNode("ewayCVN",                        $this->txCVN);\r
-      $xml .= $this->CreateNode("ewayOption1",                    $this->txOption1);\r
-      $xml .= $this->CreateNode("ewayOption2",                    $this->txOption2);\r
-      $xml .= $this->CreateNode("ewayOption3",                    $this->txOption3);\r
-      $xml .= $this->CreateNode("ewayCustomerIPAddress",          $this->txCustomerIPAddress);\r
-      $xml .= $this->CreateNode("ewayCustomerBillingCountry",     $this->txCustomerBillingCountry);\r
-      $xml .= "</ewaygateway>";\r
-      \r
-      return $xml;\r
-   }\r
-   \r
-   \r
-   /********************************************************\r
-   * Builds a simple XML Node\r
-   *\r
-   * 'NodeName' is the anem of the node being created.\r
-   * 'NodeValue' is its value\r
-   *\r
-   ********************************************************/\r
-   function CreateNode($NodeName, $NodeValue)\r
-   {\r
-    require_once E::path('lib/XML/Util.php');\r
-\r
-    $xml = new XML_Util();\r
-    $node = "<" . $NodeName . ">" . $xml->replaceEntities($NodeValue) . "</" . $NodeName . ">";\r
-    return $node;\r
-   }\r
-   \r
-} // class GatewayRequest\r
-\r
-?>\r
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Licensed to CiviCRM under the Academic Free License version 3.0
+ * Written & Contributed by Dolphin Software P/L - March 2008
+ *
+ * 'eWAY_GatewayRequest.php' - Based on the standard supplied eWay sample code 'GatewayResponse.php'
+ *
+ * The only significant change from the original is that the 'CVN' field is uncommented,
+ * unlike the distributed sample code.
+ *
+ * ALSO: Added a 'GetTransactionNumber' function.
+ *
+ */
+use CRM_Ewaysingle_ExtensionUtil as E;
+
+class GatewayRequest {
+  public $txCustomerID = "";
+
+  public $txAmount = 0;
+
+  public $txCardholderName = "";
+
+  public $txCardNumber = "";
+
+  public $txCardExpiryMonth = "01";
+
+  public $txCardExpiryYear = "00";
+
+  public $txTransactionNumber = "";
+
+  public $txCardholderFirstName = "";
+
+  public $txCardholderLastName = "";
+
+  public $txCardholderEmailAddress = "";
+
+  public $txCardholderAddress = "";
+
+  public $txCardholderPostalCode = "";
+
+  public $txInvoiceReference = "";
+
+  public $txInvoiceDescription = "";
+
+  public $txCVN = "";
+
+  public $txOption1 = "";
+
+  public $txOption2 = "";
+
+  public $txOption3 = "";
+
+  public $txCustomerBillingCountry = "";
+
+  public $txCustomerIPAddress = "";
+
+  public function __construct() {
+    // Empty Constructor
+  }
+
+  public function GetTransactionNumber() {
+    return $this->txTransactionNumber;
+  }
+
+  public function EwayCustomerID($value) {
+    $this->txCustomerID = $value;
+  }
+
+  public function InvoiceAmount($value) {
+    $this->txAmount = $value;
+  }
+
+  public function CardHolderName($value) {
+    $this->txCardholderName = $value;
+  }
+
+  public function CardExpiryMonth($value) {
+    $this->txCardExpiryMonth = $value;
+  }
+
+  public function CardExpiryYear($value) {
+    $this->txCardExpiryYear = $value;
+  }
+
+  public function TransactionNumber($value) {
+    $this->txTransactionNumber = $value;
+  }
+
+  public function PurchaserFirstName($value) {
+    $this->txCardholderFirstName = $value;
+  }
+
+  public function PurchaserLastName($value) {
+    $this->txCardholderLastName = $value;
+  }
+
+  public function CardNumber($value) {
+    $this->txCardNumber = $value;
+  }
+
+  public function PurchaserAddress($value) {
+    $this->txCardholderAddress = $value;
+  }
+
+  public function PurchaserPostalCode($value) {
+    $this->txCardholderPostalCode = $value;
+  }
+
+  public function PurchaserEmailAddress($value) {
+    $this->txCardholderEmailAddress = $value;
+  }
+
+  public function InvoiceReference($value) {
+    $this->txInvoiceReference = $value;
+  }
+
+  public function InvoiceDescription($value) {
+    $this->txInvoiceDescription = $value;
+  }
+
+  public function CVN($value) {
+    $this->txCVN = $value;
+  }
+
+  public function EwayOption1($value) {
+    $this->txOption1 = $value;
+  }
+
+  public function EwayOption2($value) {
+    $this->txOption2 = $value;
+  }
+
+  public function EwayOption3($value) {
+    $this->txOption3 = $value;
+  }
+
+  public function CustomerBillingCountry($value) {
+    $this->txCustomerBillingCountry = $value;
+  }
+
+  public function CustomerIPAddress($value) {
+    $this->txCustomerIPAddress = $value;
+  }
+
+  public function ToXml() {
+    // We don't really need the overhead of creating an XML DOM object
+    // to really just concatenate a string together.
+
+    $xml = "<ewaygateway>";
+    $xml .= $this->CreateNode("ewayCustomerID", $this->txCustomerID);
+    $xml .= $this->CreateNode("ewayTotalAmount", $this->txAmount);
+    $xml .= $this->CreateNode("ewayCardHoldersName", $this->txCardholderName);
+    $xml .= $this->CreateNode("ewayCardNumber", $this->txCardNumber);
+    $xml .= $this->CreateNode("ewayCardExpiryMonth", $this->txCardExpiryMonth);
+    $xml .= $this->CreateNode("ewayCardExpiryYear", $this->txCardExpiryYear);
+    $xml .= $this->CreateNode("ewayTrxnNumber", $this->txTransactionNumber);
+    $xml .= $this->CreateNode("ewayCustomerInvoiceDescription", $this->txInvoiceDescription);
+    $xml .= $this->CreateNode("ewayCustomerFirstName", $this->txCardholderFirstName);
+    $xml .= $this->CreateNode("ewayCustomerLastName", $this->txCardholderLastName);
+    $xml .= $this->CreateNode("ewayCustomerEmail", $this->txCardholderEmailAddress);
+    $xml .= $this->CreateNode("ewayCustomerAddress", $this->txCardholderAddress);
+    $xml .= $this->CreateNode("ewayCustomerPostcode", $this->txCardholderPostalCode);
+    $xml .= $this->CreateNode("ewayCustomerInvoiceRef", $this->txInvoiceReference);
+    $xml .= $this->CreateNode("ewayCVN", $this->txCVN);
+    $xml .= $this->CreateNode("ewayOption1", $this->txOption1);
+    $xml .= $this->CreateNode("ewayOption2", $this->txOption2);
+    $xml .= $this->CreateNode("ewayOption3", $this->txOption3);
+    $xml .= $this->CreateNode("ewayCustomerIPAddress", $this->txCustomerIPAddress);
+    $xml .= $this->CreateNode("ewayCustomerBillingCountry", $this->txCustomerBillingCountry);
+    $xml .= "</ewaygateway>";
+
+    return $xml;
+  }
+
+  /**
+   * Builds a simple XML Node
+   *
+   * 'NodeName' is the anem of the node being created.
+   * 'NodeValue' is its value
+   *
+   */
+  public function CreateNode($NodeName, $NodeValue) {
+    require_once E::path('lib/XML/Util.php');
+
+    $xml = new XML_Util();
+    $node = "<" . $NodeName . ">" . $xml->replaceEntities($NodeValue) . "</" . $NodeName . ">";
+    return $node;
+  }
+
+}
index 100bfb3fdcf434987984f27f3d3b0dd15bcb1cdf..08c16b284dc7efaa32ba3e6cd27bd6035f6dd195 100644 (file)
-<?php\r
-\r
-/*\r
- +--------------------------------------------------------------------+\r
- | CiviCRM version 5                                                  |\r
- +--------------------------------------------------------------------+\r
- | This file is a part of CiviCRM.                                    |\r
- |                                                                    |\r
- | CiviCRM is free software; you can copy, modify, and distribute it  |\r
- | under the terms of the GNU Affero General Public License           |\r
- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |\r
- |                                                                    |\r
- | CiviCRM is distributed in the hope that it will be useful, but     |\r
- | WITHOUT ANY WARRANTY; without even the implied warranty of         |\r
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |\r
- | See the GNU Affero General Public License for more details.        |\r
- |                                                                    |\r
- | You should have received a copy of the GNU Affero General Public   |\r
- | License and the CiviCRM Licensing Exception along                  |\r
- | with this program; if not, contact CiviCRM LLC                     |\r
- | at info[AT]civicrm[DOT]org. If you have questions about the        |\r
- | GNU Affero General Public License or the licensing of CiviCRM,     |\r
- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |\r
- +--------------------------------------------------------------------+\r
-*/\r
-\r
-/**************************************************************************************************************************\r
- * Licensed to CiviCRM under the Academic Free License version 3.0\r
- * Written & Contributed by Dolphin Software P/L - March 2008 \r
- *\r
- * 'eWAY_GatewayResponse.php' - Loosley based on the standard supplied eWay sample code 'GatewayResponse.php'\r
- *\r
- * The 'simplexml_load_string' has been removed as it was causing major issues\r
- * with Drupal V5.7 / CiviCRM 1.9 installtion's Home page. \r
- * Filling the Home page with "Warning: session_start() [function.session-start]: Node no longer exists in ..." messages\r
- *\r
- * Found web reference indicating 'simplexml_load_string' was a probable cause.\r
- * As soon as 'simplexml_load_string' was removed the problem fixed itself.\r
- *\r
- * Additionally the '$txStatus' var has been set as a string rather than a boolean.\r
- * This is because the returned $params['trxn_result_code'] is in fact a string and not a boolean.\r
- **************************************************************************************************************************/\r
\r
-class GatewayResponse\r
-{\r
-       var $txAmount              = 0;\r
-       var $txTransactionNumber   = "";\r
-       var $txInvoiceReference    = "";\r
-       var $txOption1             = "";\r
-       var $txOption2             = "";\r
-       var $txOption3             = "";\r
-       var $txStatus              = "";\r
-       var $txAuthCode            = "";\r
-       var $txError               = "";\r
-    var $txBeagleScore         = "";\r
-\r
-       function __construct()\r
-       {\r
-          // Empty Constructor\r
-    }\r
-   \r
-       function ProcessResponse($Xml)\r
-       {\r
-#####################################################################################\r
-#                                                                                   #\r
-#      $xtr = simplexml_load_string($Xml) or die ("Unable to load XML string!");    #\r
-#                                                                                   #\r
-#      $this->txError             = $xtr->ewayTrxnError;                            #\r
-#      $this->txStatus            = $xtr->ewayTrxnStatus;                           #\r
-#      $this->txTransactionNumber = $xtr->ewayTrxnNumber;                           #\r
-#      $this->txOption1           = $xtr->ewayTrxnOption1;                          #\r
-#      $this->txOption2           = $xtr->ewayTrxnOption2;                          #\r
-#      $this->txOption3           = $xtr->ewayTrxnOption3;                          #\r
-#      $this->txAmount            = $xtr->ewayReturnAmount;                         #\r
-#      $this->txAuthCode          = $xtr->ewayAuthCode;                             #\r
-#      $this->txInvoiceReference  = $xtr->ewayTrxnReference;                        #\r
-#                                                                                   #\r
-#####################################################################################\r
-\r
-      $this->txError             = self::GetNodeValue("ewayTrxnError", $Xml);\r
-      $this->txStatus            = self::GetNodeValue("ewayTrxnStatus", $Xml);\r
-      $this->txTransactionNumber = self::GetNodeValue("ewayTrxnNumber", $Xml);\r
-      $this->txOption1           = self::GetNodeValue("ewayTrxnOption1", $Xml);\r
-      $this->txOption2           = self::GetNodeValue("ewayTrxnOption2", $Xml);\r
-      $this->txOption3           = self::GetNodeValue("ewayTrxnOption3", $Xml);\r
-      $amount                    = self::GetNodeValue("ewayReturnAmount", $Xml);\r
-      $this->txAuthCode          = self::GetNodeValue("ewayAuthCode", $Xml);\r
-      $this->txInvoiceReference  = self::GetNodeValue("ewayTrxnReference", $Xml);\r
-      $this->txBeagleScore       = self::GetNodeValue("ewayBeagleScore", $Xml);\r
-      $this->txAmount = (int) $amount;\r
-   }\r
-   \r
-   \r
-   /************************************************************************\r
-   * Simple function to use in place of the 'simplexml_load_string' call.\r
-   * \r
-   * It returns the NodeValue for a given NodeName\r
-   * or returns and empty string.\r
-   ************************************************************************/\r
-   function GetNodeValue($NodeName, &$strXML)\r
-   {\r
-      $OpeningNodeName = "<" . $NodeName . ">";\r
-      $ClosingNodeName = "</" . $NodeName . ">";\r
-      \r
-      $pos1 = stripos($strXML, $OpeningNodeName);\r
-      $pos2 = stripos($strXML, $ClosingNodeName);\r
-      \r
-      if ( ($pos1 === false) || ($pos2 === false) )\r
-         return "";\r
-         \r
-      $pos1 += strlen($OpeningNodeName);\r
-      $len   = $pos2 - $pos1;\r
-\r
-      $return = substr($strXML, $pos1, $len);                                       \r
-      \r
-      return ($return);\r
-   }\r
-   \r
-\r
-   function TransactionNumber()\r
-   {\r
-      return $this->txTransactionNumber; \r
-   }\r
-\r
-   function InvoiceReference() \r
-   {\r
-      return $this->txInvoiceReference; \r
-   }\r
-\r
-   function Option1() \r
-   {\r
-      return $this->txOption1; \r
-   }\r
-\r
-   function Option2() \r
-   {\r
-      return $this->txOption2; \r
-   }\r
-\r
-   function Option3() \r
-   {\r
-      return $this->txOption3; \r
-   }\r
-\r
-   function AuthorisationCode()\r
-   {\r
-      return $this->txAuthCode; \r
-   }\r
-\r
-   function Error()\r
-   {\r
-      return $this->txError; \r
-   }   \r
-\r
-   function Amount() \r
-   {\r
-      return $this->txAmount; \r
-   }   \r
-\r
-   function Status()\r
-   {\r
-      return $this->txStatus;\r
-   }\r
-\r
-   function BeagleScore ()\r
-   {\r
-       return $this->txBeagleScore ;\r
-   }\r
-}\r
-\r
-?>\r
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Licensed to CiviCRM under the Academic Free License version 3.0
+ * Written & Contributed by Dolphin Software P/L - March 2008
+ *
+ * 'eWAY_GatewayResponse.php' - Loosley based on the standard supplied eWay sample code 'GatewayResponse.php'
+ *
+ * The 'simplexml_load_string' has been removed as it was causing major issues
+ * with Drupal V5.7 / CiviCRM 1.9 installtion's Home page.
+ * Filling the Home page with "Warning: session_start() [function.session-start]: Node no longer exists in ..." messages
+ *
+ * Found web reference indicating 'simplexml_load_string' was a probable cause.
+ * As soon as 'simplexml_load_string' was removed the problem fixed itself.
+ *
+ * Additionally the '$txStatus' var has been set as a string rather than a boolean.
+ * This is because the returned $params['trxn_result_code'] is in fact a string and not a boolean.
+ */
+class GatewayResponse {
+  public $txAmount              = 0;
+  public $txTransactionNumber   = "";
+  public $txInvoiceReference    = "";
+  public $txOption1             = "";
+  public $txOption2             = "";
+  public $txOption3             = "";
+  public $txStatus              = "";
+  public $txAuthCode            = "";
+  public $txError               = "";
+  public $txBeagleScore         = "";
+
+  public function __construct() {
+    // Empty Constructor
+  }
+
+  public function ProcessResponse($Xml) {
+    //$xtr = simplexml_load_string($Xml) or die ("Unable to load XML string!");
+
+    //$this->txError             = $xtr->ewayTrxnError;
+    //$this->txStatus            = $xtr->ewayTrxnStatus;
+    //$this->txTransactionNumber = $xtr->ewayTrxnNumber;
+    //$this->txOption1           = $xtr->ewayTrxnOption1;
+    //$this->txOption2           = $xtr->ewayTrxnOption2;
+    //$this->txOption3           = $xtr->ewayTrxnOption3;
+    //$this->txAmount            = $xtr->ewayReturnAmount;
+    //$this->txAuthCode          = $xtr->ewayAuthCode;
+    //$this->txInvoiceReference  = $xtr->ewayTrxnReference;
+
+    $this->txError             = self::GetNodeValue("ewayTrxnError", $Xml);
+    $this->txStatus            = self::GetNodeValue("ewayTrxnStatus", $Xml);
+    $this->txTransactionNumber = self::GetNodeValue("ewayTrxnNumber", $Xml);
+    $this->txOption1           = self::GetNodeValue("ewayTrxnOption1", $Xml);
+    $this->txOption2           = self::GetNodeValue("ewayTrxnOption2", $Xml);
+    $this->txOption3           = self::GetNodeValue("ewayTrxnOption3", $Xml);
+    $amount                    = self::GetNodeValue("ewayReturnAmount", $Xml);
+    $this->txAuthCode          = self::GetNodeValue("ewayAuthCode", $Xml);
+    $this->txInvoiceReference  = self::GetNodeValue("ewayTrxnReference", $Xml);
+    $this->txBeagleScore       = self::GetNodeValue("ewayBeagleScore", $Xml);
+    $this->txAmount = (int) $amount;
+  }
+
+  /**
+   * Simple function to use in place of the 'simplexml_load_string' call.
+   *
+   * It returns the NodeValue for a given NodeName
+   * or returns and empty string.
+   */
+  public function GetNodeValue($NodeName, &$strXML) {
+    $OpeningNodeName = "<" . $NodeName . ">";
+    $ClosingNodeName = "</" . $NodeName . ">";
+
+    $pos1 = stripos($strXML, $OpeningNodeName);
+    $pos2 = stripos($strXML, $ClosingNodeName);
+
+    if (($pos1 === FALSE) || ($pos2 === FALSE)) {
+      return "";
+    }
+
+    $pos1 += strlen($OpeningNodeName);
+    $len   = $pos2 - $pos1;
+
+    $return = substr($strXML, $pos1, $len);
+
+    return ($return);
+  }
+
+  public function TransactionNumber() {
+    return $this->txTransactionNumber;
+  }
+
+  public function InvoiceReference() {
+    return $this->txInvoiceReference;
+  }
+
+  public function Option1() {
+    return $this->txOption1;
+  }
+
+  public function Option2() {
+    return $this->txOption2;
+  }
+
+  public function Option3() {
+    return $this->txOption3;
+  }
+
+  public function AuthorisationCode() {
+    return $this->txAuthCode;
+  }
+
+  public function Error() {
+    return $this->txError;
+  }
+
+  public function Amount() {
+    return $this->txAmount;
+  }
+
+  public function Status() {
+    return $this->txStatus;
+  }
+
+  public function BeagleScore () {
+    return $this->txBeagleScore;
+  }
+
+}
diff --git a/ext/ewaysingle/phpunit.xml.dist b/ext/ewaysingle/phpunit.xml.dist
new file mode 100644 (file)
index 0000000..fc8f870
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<phpunit backupGlobals="false" backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" bootstrap="tests/phpunit/bootstrap.php">
+  <testsuites>
+    <testsuite name="My Test Suite">
+      <directory>./tests/phpunit</directory>
+    </testsuite>
+  </testsuites>
+  <filter>
+    <whitelist>
+      <directory suffix=".php">./</directory>
+    </whitelist>
+  </filter>
+  <listeners>
+    <listener class="Civi\Test\CiviTestListener">
+      <arguments/>
+    </listener>
+  </listeners>
+</phpunit>
diff --git a/ext/ewaysingle/tests/phpunit/CRM/Core/Payment/EwayTest.php b/ext/ewaysingle/tests/phpunit/CRM/Core/Payment/EwayTest.php
new file mode 100644 (file)
index 0000000..6591c5b
--- /dev/null
@@ -0,0 +1,212 @@
+<?php
+
+use CRM_Ewaysingle_ExtensionUtil as E;
+use Civi\Test\HeadlessInterface;
+use Civi\Test\HookInterface;
+
+/**
+ * FIXME - Add test description.
+ *
+ * Tips:
+ *  - With HookInterface, you may implement CiviCRM hooks directly in the test class.
+ *    Simply create corresponding functions (e.g. "hook_civicrm_post(...)" or similar).
+ *  - With TransactionalInterface, any data changes made by setUp() or test****() functions will
+ *    rollback automatically -- as long as you don't manipulate schema or truncate tables.
+ *    If this test needs to manipulate schema or truncate tables, then either:
+ *       a. Do all that using setupHeadless() and Civi\Test.
+ *       b. Disable TransactionalInterface, and handle all setup/teardown yourself.
+ *
+ * @group headless
+ */
+class CRM_Core_Payment_EwayTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface {
+
+  use \Civi\Test\GuzzleTestTrait;
+  use \Civi\Test\Api3TestTrait;
+
+  public function setUpHeadless() {
+    // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
+    // See: https://docs.civicrm.org/dev/en/latest/testing/phpunit/#civitest
+    return \Civi\Test::headless()
+      ->installMe(__DIR__)
+      ->apply();
+  }
+
+  public function setUp() {
+    $this->setUpEwayProcessor();
+    $this->processor = \Civi\Payment\System::singleton()->getById($this->ids['PaymentProcessor']['eWAY']);
+    parent::setUp();
+  }
+
+  public function tearDown() {
+    $this->callAPISuccess('PaymentProcessor', 'delete', ['id' => $this->ids['PaymentProcessor']['eWAY']]);
+    parent::tearDown();
+  }
+
+  /**
+   * Test making a once off payment
+   */
+  public function testSinglePayment() {
+    $this->setupMockHandler();
+    $params = $this->getBillingParams();
+    $params['amount'] = 10.00;
+    $params['currency'] = 'AUD';
+    $params['description'] = 'Test Contribution';
+    $params['invoiceID'] = 'xyz';
+    $params['email'] = 'unittesteway@civicrm.org';
+    $params['ip_address'] = '127.0.0.1';
+    foreach ($params as $key => $value) {
+      // Paypal is super special and requires this. Leaving out of the more generic
+      // get billing params for now to make it more obvious.
+      // When/if PropertyBag supports all the params paypal needs we can convert & simplify this.
+      $params[str_replace('-5', '', str_replace('billing_', '', $key))] = $value;
+    }
+    $params['state_province'] = 'NSW';
+    $params['country'] = 'AUS';
+    $this->processor->doPayment($params);
+    $this->assertEquals($this->getExpectedSinglePaymentRequests(), $this->getRequestBodies());
+  }
+
+  /**
+   * Test making a failed once off payment
+   */
+  public function testErrorSinglePayment() {
+    $this->setupMockHandler(NULL, TRUE);
+    $params = $this->getBillingParams();
+    $params['amount'] = 5.24;
+    $params['currency'] = 'AUD';
+    $params['description'] = 'Test Contribution';
+    $params['invoiceID'] = 'xyz';
+    $params['email'] = 'unittesteway@civicrm.org';
+    $params['ip_address'] = '127.0.0.1';
+    foreach ($params as $key => $value) {
+      // Paypal is super special and requires this. Leaving out of the more generic
+      // get billing params for now to make it more obvious.
+      // When/if PropertyBag supports all the params paypal needs we can convert & simplify this.
+      $params[str_replace('-5', '', str_replace('billing_', '', $key))] = $value;
+    }
+    $params['state_province'] = 'NSW';
+    $params['country'] = 'AUS';
+    try {
+      $this->processor->doPayment($params);
+      $this->fail('Test was meant to throw an exception');
+    }
+    catch (\Civi\Payment\Exception\PaymentProcessorException $e) {
+      $this->assertEquals('Error: [24] - Do Not Honour(Test Gateway).', $e->getMessage());
+      $this->assertEquals(9008, $e->getErrorCode());
+    }
+  }
+
+  /**
+   * Get some basic billing parameters.
+   *
+   * These are what are entered by the form-filler.
+   *
+   * @return array
+   */
+  protected function getBillingParams(): array {
+    return [
+      'billing_first_name' => 'John',
+      'billing_middle_name' => '',
+      'billing_last_name' => "O'Connor",
+      'billing_street_address-5' => '8 Hobbitton Road',
+      'billing_city-5' => 'The Shire',
+      'billing_state_province_id-5' => 1012,
+      'billing_postal_code-5' => 5010,
+      'billing_country_id-5' => 1228,
+      'credit_card_number' => '4444333322221111',
+      'cvv2' => 123,
+      'credit_card_exp_date' => [
+        'M' => 9,
+        'Y' => 2025,
+      ],
+      'credit_card_type' => 'Visa',
+      'year' => 2022,
+      'month' => 10,
+    ];
+  }
+
+  public function setUpEwayProcessor() {
+    $params = [
+      'name' => 'demo',
+      'domain_id' => CRM_Core_Config::domainID(),
+      'payment_processor_type_id' => 'eWAY',
+      'is_active' => 1,
+      'is_default' => 0,
+      'is_test' => 0,
+      'user_name' => '87654321',
+      'url_site' => 'https://www.eway.com.au/gateway/xmltest/testpage.asp',
+      'class_name' => 'Payment_eWAY',
+      'billing_mode' => 1,
+      'financial_type_id' => 1,
+      'financial_account_id' => 12,
+      // Credit card = 1 so can pass 'by accident'.
+      'payment_instrument_id' => 'Debit Card',
+    ];
+    if (!is_numeric($params['payment_processor_type_id'])) {
+      // really the api should handle this through getoptions but it's not exactly api call so lets just sort it
+      //here
+      $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', [
+        'name' => $params['payment_processor_type_id'],
+        'return' => 'id',
+      ], 'integer');
+    }
+    $result = $this->callAPISuccess('payment_processor', 'create', $params);
+    $processorID = $result['id'];
+    $this->setupMockHandler($processorID);
+    $this->ids['PaymentProcessor']['eWAY'] = $processorID;
+  }
+
+  /**
+   * Add a mock handler to the paypal Pro processor for testing.
+   *
+   * @param int|null $id
+   * @param bool $error
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  protected function setupMockHandler($id = NULL, $error = FALSE) {
+    if ($id) {
+      $this->processor = Civi\Payment\System::singleton()->getById($id);
+    }
+    $responses = $error ? $this->getExpectedSinglePaymentErrorResponses() : $this->getExpectedSinglePaymentResponses();
+    // Comment the next line out when trying to capture the response.
+    // see https://github.com/civicrm/civicrm-core/pull/18350
+    $this->createMockHandler($responses);
+    $this->setUpClientWithHistoryContainer();
+    $this->processor->setGuzzleClient($this->getGuzzleClient());
+  }
+
+  /**
+   * Get the expected response from eWAY for a single payment.
+   *
+   * @return array
+   */
+  public function getExpectedSinglePaymentResponses() {
+    return [
+      '<ewayResponse><ewayTrxnStatus>True</ewayTrxnStatus><ewayTrxnNumber>10002</ewayTrxnNumber><ewayTrxnReference>xyz</ewayTrxnReference><ewayTrxnOption1/><ewayTrxnOption2/><ewayTrxnOption3/><ewayAuthCode>123456</ewayAuthCode><ewayReturnAmount>1000</ewayReturnAmount><ewayTrxnError>00,Transaction Approved(Test Gateway)</ewayTrxnError></ewayResponse>',
+    ];
+  }
+
+  /**
+   *  Get the expected request from eWAY.
+   *
+   * @return array
+   */
+  public function getExpectedSinglePaymentRequests() {
+    return [
+      '<ewaygateway><ewayCustomerID>87654321</ewayCustomerID><ewayTotalAmount>1000</ewayTotalAmount><ewayCardHoldersName>John O&apos;Connor</ewayCardHoldersName><ewayCardNumber>4444333322221111</ewayCardNumber><ewayCardExpiryMonth>10</ewayCardExpiryMonth><ewayCardExpiryYear>22</ewayCardExpiryYear><ewayTrxnNumber>xyz</ewayTrxnNumber><ewayCustomerInvoiceDescription>Test Contribution</ewayCustomerInvoiceDescription><ewayCustomerFirstName>John</ewayCustomerFirstName><ewayCustomerLastName>O&apos;Connor</ewayCustomerLastName><ewayCustomerEmail>unittesteway@civicrm.org</ewayCustomerEmail><ewayCustomerAddress>8 Hobbitton Road, The Shire, NSW.</ewayCustomerAddress><ewayCustomerPostcode>5010</ewayCustomerPostcode><ewayCustomerInvoiceRef>xyz</ewayCustomerInvoiceRef><ewayCVN>123</ewayCVN><ewayOption1></ewayOption1><ewayOption2></ewayOption2><ewayOption3></ewayOption3><ewayCustomerIPAddress>127.0.0.1</ewayCustomerIPAddress><ewayCustomerBillingCountry>AUS</ewayCustomerBillingCountry></ewaygateway>',
+    ];
+  }
+
+  /**
+   * Get the expected response from eWAY for a single payment.
+   *
+   * @return array
+   */
+  public function getExpectedSinglePaymentErrorResponses() {
+    return [
+      '<ewayResponse><ewayTrxnStatus>False</ewayTrxnStatus><ewayTrxnNumber>10003</ewayTrxnNumber><ewayTrxnReference>xyz</ewayTrxnReference><ewayTrxnOption1/><ewayTrxnOption2/><ewayTrxnOption3/><ewayAuthCode>123456</ewayAuthCode><ewayReturnAmount>524</ewayReturnAmount><ewayTrxnError>24,Do Not Honour(Test Gateway)</ewayTrxnError></ewayResponse>',
+    ];
+  }
+
+}
diff --git a/ext/ewaysingle/tests/phpunit/bootstrap.php b/ext/ewaysingle/tests/phpunit/bootstrap.php
new file mode 100644 (file)
index 0000000..a5b4925
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+ini_set('memory_limit', '2G');
+ini_set('safe_mode', 0);
+// phpcs:disable
+eval(cv('php:boot --level=classloader', 'phpcode'));
+// phpcs:enable
+// Allow autoloading of PHPUnit helper classes in this extension.
+$loader = new \Composer\Autoload\ClassLoader();
+$loader->add('CRM_', __DIR__);
+$loader->add('Civi\\', __DIR__);
+$loader->add('api_', __DIR__);
+$loader->add('api\\', __DIR__);
+$loader->register();
+
+/**
+ * Call the "cv" command.
+ *
+ * @param string $cmd
+ *   The rest of the command to send.
+ * @param string $decode
+ *   Ex: 'json' or 'phpcode'.
+ * @return string
+ *   Response output (if the command executed normally).
+ * @throws \RuntimeException
+ *   If the command terminates abnormally.
+ */
+function cv($cmd, $decode = 'json') {
+  $cmd = 'cv ' . $cmd;
+  $descriptorSpec = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => STDERR);
+  $oldOutput = getenv('CV_OUTPUT');
+  putenv("CV_OUTPUT=json");
+
+  // Execute `cv` in the original folder. This is a work-around for
+  // phpunit/codeception, which seem to manipulate PWD.
+  $cmd = sprintf('cd %s; %s', escapeshellarg(getenv('PWD')), $cmd);
+
+  $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__);
+  putenv("CV_OUTPUT=$oldOutput");
+  fclose($pipes[0]);
+  $result = stream_get_contents($pipes[1]);
+  fclose($pipes[1]);
+  if (proc_close($process) !== 0) {
+    throw new RuntimeException("Command failed ($cmd):\n$result");
+  }
+  switch ($decode) {
+    case 'raw':
+      return $result;
+
+    case 'phpcode':
+      // If the last output is /*PHPCODE*/, then we managed to complete execution.
+      if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") {
+        throw new \RuntimeException("Command failed ($cmd):\n$result");
+      }
+      return $result;
+
+    case 'json':
+      return json_decode($result, 1);
+
+    default:
+      throw new RuntimeException("Bad decoder format ($decode)");
+  }
+}