Skip to content

Commit

Permalink
Merge branch '17-support-civicrm-api-4'
Browse files Browse the repository at this point in the history
[#59] Support CiviCRM APIv4
  • Loading branch information
jensschuppe committed May 3, 2023
2 parents bd70259 + 7650085 commit 7c97b32
Show file tree
Hide file tree
Showing 17 changed files with 876 additions and 207 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/composer.lock
/vendor/
/.phpunit.result.cache
153 changes: 153 additions & 0 deletions CMRF/Connection/AbstractCurlConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation in version 3.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

declare(strict_types=1);

namespace CMRF\Connection;

use CMRF\Core\Call;
use CMRF\Core\Connection;

abstract class AbstractCurlConnection extends Connection {

/**
* @inheritDoc
*/
public function getSupportedApiVersions(): array {
return ['3', '4'];
}

/**
* @inheritDoc
*/
public function isReady() {
return extension_loaded('curl');
}

/**
* @inheritDoc
*/
public function executeCall(Call $call) {
$curl = $this->createCurl($call);

$response = curl_exec($curl);
if ('' !== curl_error($curl)){
$call->setStatus(Call::STATUS_FAILED, curl_error($curl), curl_errno($curl));
return NULL;
} else {
$reply = json_decode($response, TRUE);
if (!is_array($reply)) {
$call->setStatus(Call::STATUS_FAILED, sprintf('JSON error: %s', json_last_error_msg()), json_last_error());
return NULL;
} else {
$status = Call::STATUS_DONE;
if (isset($reply['is_error']) && $reply['is_error'] !== 0 || isset($reply['error_code'])) {
$status = Call::STATUS_FAILED;
}
$call->setReply($reply, $status);
return $reply;
}
}
}

/**
* @param \CMRF\Core\Call $call
*
* @return resource
*/
protected function createCurl(Call $call) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, self::postDataToString($this->createPostData($call)));
curl_setopt($curl, CURLOPT_URL, $this->getUrl($call));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);

// @todo Make disabling certificate verification optional
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);

return $curl;
}

/**
* @throws \InvalidArgumentException
* If API version is unsupported.
*/
protected function createPostData(Call $call): array {
switch ($call->getApiVersion()) {
case '3':
return $this->createPostDataV3($call);

case '4':
return $this->createPostDataV4($call);

default:
throw new \InvalidArgumentException(sprintf('Unsupported API version "%s"', $call->getApiVersion()));
}
}

protected function createPostDataV3(Call $call): array {
return [
'entity' => $call->getEntity(),
'action' => $call->getAction(),
'version' => $call->getApiVersion(),
'json' => urlencode(json_encode($this->getAPI3Params($call))),
];
}

protected function createPostDataV4(Call $call): array {
return [
'params' => urlencode(json_encode($call->getParameters())),
];
}

/**
* @throws \InvalidArgumentException
* If API version is unsupported.
* @throws \RuntimeException
* If API endpoint is not specified in profile.
*/
protected function getUrl(Call $call): string {
$profile = $this->getProfile();

if ('3' === $call->getApiVersion()) {
if (!isset($profile['url'])) {
throw new \RuntimeException('No APIv3 endpoint specified');
}

return $profile['url'];
}

if ('4' === $call->getApiVersion()) {
if (!isset($profile['urlV4'])) {
throw new \RuntimeException('No APIv4 endpoint specified');
}

return sprintf('%s/%s/%s', $profile['urlV4'], $call->getEntity(), $call->getAction());
}

throw new \InvalidArgumentException(sprintf('Unsupported API version "%s"', $call->getApiVersion()));
}

private static function postDataToString(array $postData): string {
$str = '';
foreach ($postData as $key => $value) {
$str .= sprintf('%s=%s&', $key, $value);
}

return $str;
}
}
64 changes: 13 additions & 51 deletions CMRF/Connection/Curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,27 @@

namespace CMRF\Connection;

use CMRF\Core\Call as Call;
use CMRF\Core\Connection as Connection;
use CMRF\Core\Call;

class Curl extends Connection {
class Curl extends AbstractCurlConnection {

/**
* @inheritDoc
*/
public function getType() {
return 'curl';
}

public function isReady() {
// TODO: check for CURL
return TRUE;
}

/**
* execute the given call synchroneously
*
* return call status
* @inheritDoc
*/
public function executeCall(Call $call) {
$profile = $this->getProfile();
protected function createPostData(Call $call): array {
$post_data = parent::createPostData($call);
$profile = $this->getProfile();
$post_data['api_key'] = $profile['api_key'];
$post_data['key'] = $profile['site_key'];

$request = $this->getAPI3Params($call);
// $request['api_key'] = $profile['api_key'];
// $request['key'] = $profile['site_key'];
// $request['version'] = 3;
// $request['entity'] = $call->getEntity();
// $request['action'] = $call->getAction();
$post_data = "entity=" . $call->getEntity();
$post_data .= "&action=" . $call->getAction();
$post_data .= "&api_key={$profile['api_key']}&key={$profile['site_key']}&version=3";
$post_data .= "&json=" . urlencode(json_encode($request));

$curl = curl_init();
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($curl, CURLOPT_URL, $profile['url']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_SSLVERSION, 1);

$response = curl_exec($curl);
if (curl_error($curl)){
$call->setStatus(Call::STATUS_FAILED, curl_error($curl));
return NULL;
} else {
$reply = json_decode($response, true);
if ($reply===NULL) {
$call->setStatus(Call::STATUS_FAILED, curl_error($curl));
return NULL;
} else {
$status = Call::STATUS_DONE;
if (isset($reply['is_error']) && $reply['is_error']) {
$status = Call::STATUS_FAILED;
}
$call->setReply($reply, $status);
return $reply;
}
}
return $post_data;
}

}
61 changes: 13 additions & 48 deletions CMRF/Connection/CurlAuthX.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,65 +10,30 @@

namespace CMRF\Connection;

use CMRF\Core\Call as Call;
use CMRF\Core\Connection as Connection;
use CMRF\Core\Call;

class CurlAuthX extends Connection {
class CurlAuthX extends AbstractCurlConnection {

/**
* @inheritDoc
*/
public function getType() {
return 'curlauthx';
}

public function isReady() {
// TODO: check for CURL
return TRUE;
}

/**
* execute the given call synchroneously
*
* return call status
* @inheritDoc
*/
public function executeCall(Call $call) {
$profile = $this->getProfile();

$request = $this->getAPI3Params($call);
$post_data = "entity=" . $call->getEntity();
$post_data .= "&action=" . $call->getAction();
$post_data .= "&version=3";
$post_data .= "&json=" . urlencode(json_encode($request));

$curl = curl_init();
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($curl, CURLOPT_URL, $profile['url']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_SSLVERSION, 1);
protected function createCurl(Call $call) {
$curl = parent::createCurl($call);
$profile = $this->getProfile();
curl_setopt($curl, CURLOPT_HTTPHEADER, [
"X-Requested-With: XMLHttpRequest",
'X-Requested-With: XMLHttpRequest',
"X-Civi-Auth: Bearer {$profile['api_key']}",
"X-Civi-Key: {$profile['site_key']}"
"X-Civi-Key: {$profile['site_key']}",
]);

$response = curl_exec($curl);
if (curl_error($curl)){
$call->setStatus(Call::STATUS_FAILED, curl_error($curl));
return NULL;
} else {
$reply = json_decode($response, true);
if ($reply===NULL) {
$call->setStatus(Call::STATUS_FAILED, curl_error($curl));
return NULL;
} else {
$status = Call::STATUS_DONE;
if (isset($reply['is_error']) && $reply['is_error']) {
$status = Call::STATUS_FAILED;
}
$call->setReply($reply, $status);
return $reply;
}
}
return $curl;
}

}
Loading

0 comments on commit 7c97b32

Please sign in to comment.