CRM-13163 - APIv3 (*.create) - Add support for options.reload=1
authorTim Otten <totten@civicrm.org>
Thu, 8 Aug 2013 04:08:09 +0000 (21:08 -0700)
committerTim Otten <totten@civicrm.org>
Thu, 8 Aug 2013 04:29:52 +0000 (21:29 -0700)
This will refetch the created record with the API to ensure that the return value is really correct.

----------------------------------------
* CRM-13163: hrjob: Display/edit dates with jQuery date picker
  http://issues.civicrm.org/jira/browse/CRM-13163

CRM/Utils/API/ReloadOption.php [new file with mode: 0644]
api/api.php
tests/phpunit/CRM/Utils/API/ReloadOptionTest.php [new file with mode: 0644]

diff --git a/CRM/Utils/API/ReloadOption.php b/CRM/Utils/API/ReloadOption.php
new file mode 100644 (file)
index 0000000..45677c6
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.3                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2013                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+*/
+
+/**
+ * Implement the "api.reload" option. This option can be used with "create" to
+ * force the API to reload a clean copy of the entity before returning the result.
+ *
+ * @code
+ * $clean = civicrm_api('myentity', 'create', array(
+ *   'options' => array(
+ *     'reload' => 1
+ *   ),
+ * ));
+ * @endcode
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2013
+ * $Id$
+ */
+
+require_once 'api/Wrapper.php';
+class CRM_Utils_API_ReloadOption implements API_Wrapper {
+
+  /**
+   * @var null|'null'|'default'|'selected'
+   */
+  private $reloadMode = NULL;
+
+  /**
+   * {@inheritDoc}
+   */
+  public function fromApiInput($apiRequest) {
+    if ($apiRequest['action'] === 'create' && isset($apiRequest['params'], $apiRequest['params']['options'], $apiRequest['params']['options']['reload'])) {
+      $this->reloadMode = $apiRequest['params']['options']['reload'];
+    }
+    return $apiRequest;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function toApiOutput($apiRequest, $result) {
+    if ($result['is_error']) {
+      return $result;
+    }
+    switch ($this->reloadMode) {
+      case NULL:
+      case '0':
+      case 'null':
+        return $result;
+
+      case '1':
+      case 'default':
+        $params = array(
+          'id' => $result['id'],
+        );
+        $reloadResult = civicrm_api3($apiRequest['entity'], 'get', $params);
+        $result['values'][$result['id']] = array_merge($result['values'][$result['id']], $reloadResult['values'][$result['id']]);
+        return $result;
+
+      case 'selected':
+        $params = array(
+          'id' => $result['id'],
+          'return' => $this->pickReturnFields($apiRequest),
+        );
+        $reloadResult = civicrm_api3($apiRequest['entity'], 'get', $params);
+        $result['values'][$result['id']] = array_merge($result['values'][$result['id']], $reloadResult['values'][$result['id']]);
+        return $result;
+
+      default:
+        throw new API_Exception("Unknown reload mode: " . var_export($this->reloadMode, TRUE));
+    }
+  }
+
+  /**
+   * Identify the fields which should be returned
+   *
+   * @param $apiRequest
+   * @return array
+   */
+  public function pickReturnFields($apiRequest) {
+    $fields = civicrm_api3($apiRequest['entity'], 'getfields', array());
+    $returnKeys = array_intersect(
+      array_keys($apiRequest['params']),
+      array_keys($fields['values'])
+    );
+    return $returnKeys;
+  }
+}
index 692b54d0e818cbe80bf1c81c5562e261287e03cd..d339644e98bc69445b322a484cf3b1b488a7dfc8 100644 (file)
@@ -21,7 +21,8 @@
 function civicrm_api($entity, $action, $params, $extra = NULL) {
   $apiWrappers = array(
     CRM_Utils_API_HTMLInputCoder::singleton(),
-    CRM_Utils_API_NullOutputCoder::singleton()
+    CRM_Utils_API_NullOutputCoder::singleton(),
+    new CRM_Utils_API_ReloadOption(),
   );
   try {
     require_once ('api/v3/utils.php');
diff --git a/tests/phpunit/CRM/Utils/API/ReloadOptionTest.php b/tests/phpunit/CRM/Utils/API/ReloadOptionTest.php
new file mode 100644 (file)
index 0000000..b7bd307
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+
+require_once 'CiviTest/CiviUnitTestCase.php';
+/**
+ * Test that the API accepts the 'reload' option.
+ *
+ * To do this, each of our test cases will perform a 'create' call and use hook_civicrm_post
+ * to munge the database. If the reload option is present, then the return value should reflect
+ * the final SQL content (after calling hook_civicrm_post). If the reload option is missing,
+ * then the return should reflect the inputted (unmodified) data.
+ */
+class CRM_Utils_API_ReloadOptionTest extends CiviUnitTestCase {
+
+  function setUp() {
+    parent::setUp();
+    CRM_utils_Hook_UnitTests::singleton()->setHook('civicrm_post', array($this, 'onPost'));
+  }
+
+  /**
+   * If reload option is missing, then 'create' returns the inputted nick_name -- despite the
+   * fact that the hook manipulated the actual DB content.
+   */
+  function testNoReload() {
+    $result = $this->callAPISuccess('contact', 'create', array(
+      'contact_type' => 'Individual',
+      'first_name' => 'First',
+      'last_name' => 'Last',
+      'nick_name' => 'Firstie',
+    ));
+    $this->assertEquals('First', $result['values'][$result['id']]['first_name']);
+    $this->assertEquals('Firstie', $result['values'][$result['id']]['nick_name']); // munged by hook, but we haven't realized it
+  }
+
+  /**
+   * When the reload option is unrecognized, generate an error
+   */
+  function testReloadInvalid() {
+    $this->callAPIFailure('contact', 'create', array(
+      'contact_type' => 'Individual',
+      'first_name' => 'First',
+      'last_name' => 'Last',
+      'nick_name' => 'Firstie',
+      'options' => array(
+        'reload' => 'invalid',
+      ),
+    ));
+  }
+
+  /**
+   * If reload option is set, then 'create' returns the final nick_name -- even if it
+   * differs from the inputted nick_name.
+   */
+  function testReloadDefault() {
+    $result = $this->callAPISuccess('contact', 'create', array(
+      'contact_type' => 'Individual',
+      'first_name' => 'First',
+      'last_name' => 'Last',
+      'nick_name' => 'Firstie',
+      'options' => array(
+        'reload' => 1
+      ),
+    ));
+    $this->assertEquals('First', $result['values'][$result['id']]['first_name']);
+    $this->assertEquals('munged', $result['values'][$result['id']]['nick_name']);
+  }
+
+  /**
+   * When the reload option is combined with chaining, the reload should munge
+   * the chain results.
+   */
+  function testReloadNoChainInterference() {
+    $result = $this->callAPISuccess('contact', 'create', array(
+      'contact_type' => 'Individual',
+      'first_name' => 'First',
+      'last_name' => 'Last',
+      'nick_name' => 'Firstie',
+      'api.Email.create' => array(
+        'email' => 'test@example.com',
+      ),
+      'options' => array(
+        'reload' => 1
+      ),
+    ));
+    $this->assertEquals('First', $result['values'][$result['id']]['first_name']);
+    $this->assertEquals('munged', $result['values'][$result['id']]['nick_name']);
+    $this->assertAPISuccess($result['values'][$result['id']]['api.Email.create']);
+  }
+
+  /**
+   * Implementation of hook_civicrm_post used with all our test cases
+   *
+   * @param $op
+   * @param $objectName
+   * @param $objectId
+   * @param $objectRef
+   */
+  function onPost($op, $objectName, $objectId, &$objectRef) {
+    if ($op == 'create' && $objectName == 'Individual') {
+      CRM_Core_DAO::executeQuery(
+        "UPDATE civicrm_contact SET nick_name = 'munged' WHERE id = %1",
+        array(
+          1 => array($objectId, 'Integer')
+        )
+      );
+    }
+  }
+}