diff --git a/CRM/TwingleCampaign/BAO/Campaign.php b/CRM/TwingleCampaign/BAO/Campaign.php
index 89fb9bf..07c4fb2 100644
--- a/CRM/TwingleCampaign/BAO/Campaign.php
+++ b/CRM/TwingleCampaign/BAO/Campaign.php
@@ -30,6 +30,8 @@ abstract class Campaign {
protected $id_custom_field = NULL;
+ protected $prefix = NULL;
+
/**
* Campaign constructor.
@@ -54,7 +56,7 @@ abstract class Campaign {
$this->id = $campaign['id'];
// Translate custom field names into Twingle field names
- self::translateCustomFields($campaign, self::OUT);
+ $this->translateCustomFields($campaign, self::OUT);
// Translate keys and values
self::formatValues($campaign, self::OUT);
@@ -67,7 +69,6 @@ abstract class Campaign {
}
-
/**
* Check if a campaign already exists and if so set attributes
* to the values of the existing campaign.
@@ -111,12 +112,12 @@ abstract class Campaign {
$this->id = $values['id'];
// Translate custom field names back
- self::translateCustomFields($values, self::OUT);
+ $this->translateCustomFields($values, self::OUT);
// Translate keys from CiviCRM format to Twingle format
self::translateKeys($values, self::OUT);
- // Set attributes to the values of the existing TwingleEvent campaign
+ // Set attributes to the values of the existing campaign
// to reflect the state of the actual campaign in the database
$this->update($values);
@@ -154,7 +155,7 @@ abstract class Campaign {
$values_prepared_for_import,
self::IN
);
- self::translateCustomFields(
+ $this->translateCustomFields(
$values_prepared_for_import,
self::IN
);
@@ -290,10 +291,10 @@ abstract class Campaign {
*
* @throws Exception
*/
- public static function translateKeys(array &$values, string $direction) {
+ public function translateKeys(array &$values, string $direction) {
// Get translations for fields
- $field_translations = Cache::getInstance()->getTranslations()['fields'];
+ $field_translations = Cache::getInstance()->getTranslations()[$this->className];
// Set the direction of the translation
if ($direction == self::OUT) {
@@ -379,7 +380,7 @@ abstract class Campaign {
* names
*
*/
- public static function translateCustomFields(array &$values, string $direction) {
+ public function translateCustomFields(array &$values, string $direction) {
// Translate from Twingle field name to custom field name
if ($direction == self::IN) {
@@ -389,7 +390,7 @@ abstract class Campaign {
if (array_key_exists(
str_replace(
- 'twingle_project_',
+ $this->prefix,
'',
$field
),
@@ -397,13 +398,13 @@ abstract class Campaign {
) {
$values[$custom] = $values[str_replace(
- 'twingle_project_',
+ $this->prefix,
'',
$field
)];
unset($values[str_replace(
- 'twingle_project_',
+ $this->prefix,
'',
$field
)]
@@ -423,7 +424,7 @@ abstract class Campaign {
)
) {
$values[str_replace(
- 'twingle_project_',
+ $this->prefix,
'',
$field
)] = $values[$custom];
@@ -593,17 +594,6 @@ abstract class Campaign {
}
- /**
- * Return a timestamp of the last update of the Campaign
- *
- * @return int|null
- */
- public function lastUpdate() {
-
- return self::getTimestamp($this->values['last_update']);
- }
-
-
/**
* Returns the project_id of a Campaign
*
diff --git a/CRM/TwingleCampaign/BAO/TwingleApiCall.php b/CRM/TwingleCampaign/BAO/TwingleApiCall.php
new file mode 100644
index 0000000..6f131c9
--- /dev/null
+++ b/CRM/TwingleCampaign/BAO/TwingleApiCall.php
@@ -0,0 +1,261 @@
+apiKey = $apiKey;
+ $this->limit = CRM_Core_BAO_Setting::getItem('', 'twingle_request_size');
+
+ // Get organisation id
+ $curl = curl_init($this->protocol . 'organisation' . $this->baseUrl);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, [
+ "x-access-code: $apiKey",
+ 'Content-Type: application/json',
+ ]);
+
+ $response = json_decode(curl_exec($curl), TRUE);
+ curl_close($curl);
+
+ if (empty($response)) {
+ throw new API_Exception(
+ "Twingle API call failed. Please check your api key.");
+ }
+
+ $this->organisationId = array_column($response, 'id');
+ }
+
+ /**
+ * If $id parameter is empty, this function returns all projects for all
+ * organisations this API key is assigned to.
+ *
+ * TODO: Keys can only get assigned to one organisation. Save multiple keys
+ * in settings instead.
+ *
+ * If $id parameter is given, this function returns a single project.
+ *
+ * @param int|null $projectId
+ *
+ * @return mixed
+ */
+ public function getProject(int $projectId = NULL) {
+ $response = [];
+ foreach ($this->organisationId as $organisationId) {
+ $url = empty($projectId)
+ ? $this->protocol . 'project' . $this->baseUrl . 'by-organisation/' . $organisationId
+ : $this->protocol . 'project' . $this->baseUrl . $projectId;
+
+ $response = array_merge($this->curlGet($url));
+ }
+ return $response;
+ }
+
+ /**
+ * Sends an curl post call to Twingle to update an existing project and then
+ * updates the TwingleProject campaign.
+ *
+ * @param TwingleProject $project
+ * The TwingleProject object that should get pushed to Twingle
+ *
+ * @return array
+ * Returns a response array that contains title, id, project_id and status
+ */
+ public function pushProject(TwingleProject &$project) {
+
+ try {
+ $values = $project->export();
+ } catch (Exception $e) {
+ // Log Exception
+ Civi::log()->error(
+ "Could not export TwingleProject values: $e->getMessage()"
+ );
+ // Return result array with error description
+ return $project->getResponse(
+ "Could not export TwingleProject values: $e->getMessage()"
+ );
+ }
+
+ // Prepare url for curl
+ $url = $this->protocol . 'project' . $this->baseUrl . $values['id'];
+
+ // Send curl
+ $result = $this->curlPost($url, $values);
+
+ // Update TwingleProject in Civi with results from api call
+ if (is_array($result) && !array_key_exists('message', $result)) {
+ // Try to update the local TwingleProject campaign
+ try {
+ $project->update($result);
+ $project->create();
+ return $project->getResponse('TwingleProject pushed to Twingle');
+ } catch (Exception $e) {
+ // Log Exception
+ Civi::log()->error(
+ "Could not push TwingleProject campaign: $e->getMessage()"
+ );
+ // Return result array with error description
+ return $project->getResponse(
+ "TwingleProject was likely pushed to Twingle but the
+ local update of the campaign failed: $e->getMessage()"
+ );
+ }
+ }
+ else {
+ $message = $result['message'];
+ return $project->getResponse(
+ $message
+ ? "TwingleProject could not get pushed to Twingle: $message"
+ : 'TwingleProject could not get pushed to Twingle'
+ );
+ }
+
+ }
+
+ /**
+ * Returns all Events for the given $projectId or a single event if an
+ * $eventId is given, too.
+ *
+ * @param int $projectId
+ *
+ * @param null|int $eventId
+ *
+ * @return array
+ */
+ public function getEvent(int $projectId, $eventId = NULL) {
+ $result = [];
+
+ $url = empty($eventId)
+ ? $this->protocol . 'project' . $this->baseUrl . $projectId . '/event'
+ : $this->protocol . 'project' . $this->baseUrl . $projectId . '/event/'
+ . $eventId;
+
+ $offset = 0;
+ $finished = FALSE;
+
+ // Get only as much results per call as configured in $this->limit
+ while (!$finished) {
+ $params = [
+ 'orderby' => 'id',
+ 'direction' => 'desc',
+ 'limit' => $this->limit,
+ 'offset' => $offset,
+ 'image' => 'as-boolean',
+ 'public' => 0,
+ ];
+ $response = $this->curlGet($url, $params);
+ $finished = is_null($eventId) || count($response['data']) < $this->limit;
+ $offset = $offset + $this->limit;
+ $result = array_merge($result, $response['data']);
+ }
+ return $result;
+ }
+
+ /**
+ * @param $projectId
+ *
+ * @return array|NULL
+ */
+ public function getProjectEmbedData($projectId) {
+
+ $result = $this->getProject($projectId);
+
+ if ($result['embed']) {
+ // Include counter url into embed data
+ $result['embed']['counter'] = $result['counter-url']['url'];
+
+ return $result['embed'];
+ }
+ else {
+ Civi::log()->error("Could not get embed data for project $projectId.");
+ return NULL;
+ }
+ }
+
+
+ /**
+ * Does a cURL and gives back the result array.
+ *
+ * @param $url
+ * The url the curl should get sent to
+ *
+ * @param null $params
+ * The parameters you want to send (optional)
+ *
+ * @return array|bool
+ * Returns the result array of the curl or FALSE, if the curl failed
+ */
+ private function curlGet($url, $params = NULL) {
+ if (!empty($params)) {
+ $url = $url . '?' . http_build_query($params);
+ }
+ $curl = curl_init($url);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, [
+ "x-access-code: $this->apiKey",
+ 'Content-Type: application/json',
+ ]);
+ $response = json_decode(curl_exec($curl), TRUE);
+ if (empty($response)) {
+ $response = curl_error($curl);
+ }
+ curl_close($curl);
+ return $response;
+ }
+
+ /**
+ * Sends a curl post and gives back the result array.
+ *
+ * @param $url
+ * The url the curl should get sent to
+ *
+ * @param $data
+ * The data that should get posted
+ *
+ * @return false|mixed
+ * Returns the result array of the curl or FALSE, if the curl failed
+ */
+ private function curlPost($url, $data) {
+ $curl = curl_init($url);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
+ curl_setopt($curl, CURLOPT_POST, TRUE);
+ curl_setopt($curl, CURLOPT_HTTPHEADER, [
+ "x-access-code: $this->apiKey",
+ 'Content-Type: application/json',
+ ]);
+ $json = json_encode($data);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $json);
+ $response = json_decode(curl_exec($curl), TRUE);
+ if (empty($response)) {
+ $response = FALSE;
+ }
+ curl_close($curl);
+ return $response;
+ }
+
+}
diff --git a/CRM/TwingleCampaign/BAO/TwingleEvent.php b/CRM/TwingleCampaign/BAO/TwingleEvent.php
index c086d5c..32a00d8 100644
--- a/CRM/TwingleCampaign/BAO/TwingleEvent.php
+++ b/CRM/TwingleCampaign/BAO/TwingleEvent.php
@@ -4,16 +4,14 @@
namespace CRM\TwingleCampaign\BAO;
use Civi;
-use CRM\TwingleCampaign\Utils\ExtensionCache as Cache;
use CRM_TwingleCampaign_ExtensionUtil as E;
-use CRM_Utils_Array;
-use DateTime;
-use CRM\TwingleCampaign\BAO\CustomField as CustomField;
+use CRM\TwingleCampaign\Utils\ExtensionCache as Cache;
+use CRM\TwingleCampaign\BAO\Campaign;
use Exception;
use CiviCRM_API3_Exception;
-include_once E::path() . '/CRM/TwingleCampaign/BAO/CustomField.php';
-
+include_once E::path() . '/CRM/TwingleCampaign/BAO/Campaign.php';
+include_once E::path() . '/CRM/TwingleCampaign/Utils/ExtensionCache.php';
class TwingleEvent extends Campaign {
@@ -33,7 +31,8 @@ class TwingleEvent extends Campaign {
public function __construct(array $event, string $origin) {
parent::__construct($event, $origin);
- $this->className = get_class($this);
+ $this->className = (new \ReflectionClass($this))->getShortName();
+ $this->prefix = 'twingle_event_';
// Add value for campaign type
$event['campaign_type_id'] = 'twingle_event';
@@ -44,6 +43,112 @@ class TwingleEvent extends Campaign {
}
+ /**
+ * Synchronizes events between Twingle and CiviCRM (both directions)
+ * based on the timestamp.
+ *
+ * @param array $values
+ * @param TwingleApiCall $twingleApi
+ * @param bool $is_test
+ * If TRUE, don't do any changes
+ *
+ * @return array|null
+ * Returns a response array that contains title, id, event_id, project_
+ * and status or NULL if $values is not an array
+ *
+ * @throws CiviCRM_API3_Exception
+ */
+ public static function sync(
+ array $values,
+ TwingleApiCall &$twingleApi,
+ bool $is_test = FALSE
+ ) {
+
+ // If $values is an array
+ if (is_array($values)) {
+
+ // Instantiate TwingleEvent
+ try {
+ $event = new TwingleEvent(
+ $values,
+ self::TWINGLE
+ );
+ } catch (Exception $e) {
+
+ // Log Exception
+ Civi::log()->error(
+ "Failed to instantiate TwingleEvent: $e->getMessage()"
+ );
+
+ // Return result array with error description
+ return [
+ "title" => $values['description'],
+ "event_id" => (int) $values['id'],
+ "project_id" => (int) $values['project_id'],
+ "status" =>
+ "Failed to instantiate TwingleEvent: $e->getMessage()",
+ ];
+ }
+
+ // Check if the TwingleEvent campaign already exists
+ if (!$event->exists()) {
+
+ // ... if not, get embed data and create event
+ try {
+ $result = $event->create($is_test);
+ } catch (Exception $e) {
+
+ // Log Exception
+ Civi::log()->error(
+ "Could not create campaign from TwingleEvent: $e->getMessage()"
+ );
+
+ // Return result array with error description
+ return [
+ "title" => $values['description'],
+ "event_id" => (int) $values['id'],
+ "project_id" => (int) $values['project_id'],
+ "status" =>
+ "Could not create campaign from TwingleEvent: $e->getMessage()",
+ ];
+ }
+ }
+ else {
+ $result = $event->getResponse('TwingleEvent exists');
+
+ // If Twingle's version of the event is newer than the CiviCRM
+ // TwingleEvent campaign update the campaign
+ if ($values['updated_at'] > $event->lastUpdate()) {
+ try {
+ $event->update($values);
+ $result = $event->create();
+ $result['status'] = $result['status'] == 'TwingleEvent created'
+ ? 'TwingleEvent updated'
+ : 'TwingleEvent Update failed';
+ } catch (Exception $e) {
+ // Log Exception
+ Civi::log()->error(
+ "Could not update TwingleEvent campaign: $e->getMessage()"
+ );
+ // Return result array with error description
+ $result = $event->getResponse(
+ "Could not update TwingleEvent campaign: $e->getMessage()"
+ );
+ }
+ }
+ elseif ($result['status'] == 'TwingleEvent exists') {
+ $result = $event->getResponse('TwingleEvent up to date');
+ }
+ }
+
+ // Return a response of the synchronization
+ return $result;
+ }
+ else {
+ return NULL;
+ }
+ }
+
/**
* Translate values between CiviCRM Campaigns and Twingle
@@ -52,8 +157,8 @@ class TwingleEvent extends Campaign {
* array of which values shall be translated
*
* @param string $direction
- * TwingleEvent::IN -> translate array values from Twingle to CiviCRM
- * TwingleEvent::OUT -> translate array values from CiviCRM to Twingle
+ * self::IN -> translate array values from Twingle to CiviCRM
+ * self::OUT -> translate array values from CiviCRM to Twingle
*
* @throws Exception
*/
@@ -67,21 +172,16 @@ class TwingleEvent extends Campaign {
self::getDateTime($values['last_update']);
}
- // empty project_type to 'default'
- if (!$values['type']) {
- $values['type'] = 'default';
- }
}
elseif ($direction == self::OUT) {
// Change DateTime string into timestamp
- $values['last_update'] =
- self::getTimestamp($values['last_update']);
-
- // Default project_type to ''
- $values['type'] = $values['type'] == 'default'
- ? ''
- : $values['type'];
+ $values['updated_at'] =
+ self::getTimestamp($values['updated_at']);
+ $values['confirmed_at'] =
+ self::getTimestamp($values['confirmed_at']);
+ $values['created_at'] =
+ self::getTimestamp($values['created_at']);
// Cast project target to integer
$values['project_target'] = (int) $values['project_target'];
@@ -97,25 +197,6 @@ class TwingleEvent extends Campaign {
}
- /**
- * Set embed data fields
- *
- * @param array $embedData
- * Array with embed data from Twingle API
- */
- public function setEmbedData(array $embedData) {
-
- // Get all embed_data keys from template
- $embed_data_keys = Cache::getInstance()
- ->getTemplates()['event_embed_data'];
-
- // Transfer all embed_data values
- foreach ($embed_data_keys as $key) {
- $this->values[$key] = htmlspecialchars($embedData[$key]);
- }
- }
-
-
/**
* Get a response that describes the status of a TwingleEvent
*
@@ -127,7 +208,7 @@ class TwingleEvent extends Campaign {
*/
public function getResponse(string $status) {
return [
- 'title' => $this->values['name'],
+ 'title' => $this->values['description'],
'id' => (int) $this->id,
'event_id' => (int) $this->values['id'],
'project_id' => (int) $this->values['project_id'],
@@ -135,6 +216,16 @@ class TwingleEvent extends Campaign {
];
}
+ /**
+ * Return a timestamp of the last update of the Campaign
+ *
+ * @return int|null
+ */
+ public function lastUpdate() {
+
+ return self::getTimestamp($this->values['updated_at']);
+ }
+
/**
* Returns the project_id of a TwingleEvent
diff --git a/CRM/TwingleCampaign/BAO/TwingleProject.php b/CRM/TwingleCampaign/BAO/TwingleProject.php
index abb2f1f..bf77f23 100644
--- a/CRM/TwingleCampaign/BAO/TwingleProject.php
+++ b/CRM/TwingleCampaign/BAO/TwingleProject.php
@@ -3,12 +3,15 @@
namespace CRM\TwingleCampaign\BAO;
+use Civi;
use CRM_TwingleCampaign_ExtensionUtil as E;
use CRM\TwingleCampaign\Utils\ExtensionCache as Cache;
-use DateTime;
+use CRM\TwingleCampaign\BAO\Campaign;
use Exception;
use CiviCRM_API3_Exception;
+include_once E::path() . '/CRM/TwingleCampaign/BAO/Campaign.php';
+include_once E::path() . '/CRM/TwingleCampaign/Utils/ExtensionCache.php';
class TwingleProject extends Campaign {
@@ -28,7 +31,8 @@ class TwingleProject extends Campaign {
public function __construct(array $project, string $origin) {
parent::__construct($project, $origin);
- $this->className = get_class($this);
+ $this->className = (new \ReflectionClass($this))->getShortName();
+ $this->prefix = 'twingle_project_';
// Add value for campaign type
$this->values['campaign_type_id'] = 'twingle_project';
@@ -40,6 +44,130 @@ class TwingleProject extends Campaign {
}
+ /**
+ * Synchronizes projects between Twingle and CiviCRM (both directions)
+ * based on the timestamp.
+ *
+ * @param array $values
+ * @param TwingleApiCall $twingleApi
+ * @param bool $is_test
+ * If TRUE, don't do any changes
+ *
+ * @return array|null
+ * Returns a response array that contains title, id, project_id and status or
+ * NULL if $values is not an array
+ *
+ * @throws CiviCRM_API3_Exception
+ */
+ public static function sync(
+ array $values,
+ TwingleApiCall &$twingleApi,
+ bool $is_test = FALSE
+ ) {
+
+ // If $values is an array
+ if (is_array($values)) {
+
+ // Instantiate TwingleProject
+ try {
+ $project = new TwingleProject(
+ $values,
+ self::TWINGLE
+ );
+ } catch (Exception $e) {
+
+ // Log Exception
+ Civi::log()->error(
+ "Failed to instantiate TwingleProject: $e->getMessage()"
+ );
+
+ // Return result array with error description
+ return [
+ "title" => $values['name'],
+ "project_id" => (int) $values['id'],
+ "status" =>
+ "Failed to instantiate TwingleProject: $e->getMessage()",
+ ];
+ }
+
+ // Check if the TwingleProject campaign already exists
+ if (!$project->exists()) {
+
+ // ... if not, get embed data and create project
+ try {
+ $project->setEmbedData(
+ $twingleApi->getProjectEmbedData($project->getProjectId())
+ );
+ $result = $project->create($is_test);
+ } catch (Exception $e) {
+
+ // Log Exception
+ Civi::log()->error(
+ "Could not create campaign from TwingleProject: $e->getMessage()"
+ );
+
+ // Return result array with error description
+ return [
+ "title" => $values['name'],
+ "project_id" => (int) $values['id'],
+ "status" =>
+ "Could not create campaign from TwingleProject: $e->getMessage()",
+ ];
+ }
+ }
+ else {
+ $result = $project->getResponse('TwingleProject exists');
+
+ // If Twingle's version of the project is newer than the CiviCRM
+ // TwingleProject campaign update the campaign
+ if ($values['last_update'] > $project->lastUpdate()) {
+ try {
+ $project->update($values);
+ $project->setEmbedData(
+ $twingleApi->getProjectEmbedData($project->getProjectId())
+ );
+ $result = $project->create();
+ $result['status'] = $result['status'] == 'TwingleProject created'
+ ? 'TwingleProject updated'
+ : 'TwingleProject Update failed';
+ } catch (Exception $e) {
+ // Log Exception
+ Civi::log()->error(
+ "Could not update TwingleProject campaign: $e->getMessage()"
+ );
+ // Return result array with error description
+ $result = $project->getResponse(
+ "Could not update TwingleProject campaign: $e->getMessage()"
+ );
+ }
+ }
+ // If the CiviCRM TwingleProject campaign was changed, update the project
+ // on Twingle's side
+ elseif ($values['last_update'] < $project->lastUpdate()) {
+ // If this is a test do not make database changes
+ if ($is_test) {
+ $result = $project->getResponse(
+ 'TwingleProject ready to push'
+ );
+ }
+ else {
+ $result = $twingleApi->pushProject($project);
+ }
+ }
+ elseif ($result['status'] == 'TwingleProject exists') {
+ $result = $project->getResponse('TwingleProject up to date');
+ }
+ }
+
+ // Return a response of the synchronization
+ return $result;
+ }
+ else {
+ return NULL;
+ }
+ }
+
+
/**
* Export values. Ensures that only those values will be exported which the
* Twingle API expects.
@@ -137,16 +265,7 @@ class TwingleProject extends Campaign {
$this->values[$key] = htmlspecialchars($embedData[$key]);
}
}
-
- /**
- * Set counter url
- *
- * @param String $counterUrl
- * URL of the counter
- */
- public function setCounterUrl(string $counterUrl) {
- $this->values['counter'] = $counterUrl;
- }
+
/**
* Deactivate this TwingleProject campaign
@@ -182,6 +301,16 @@ class TwingleProject extends Campaign {
];
}
+ /**
+ * Return a timestamp of the last update of the Campaign
+ *
+ * @return int|null
+ */
+ public function lastUpdate() {
+
+ return self::getTimestamp($this->values['last_update']);
+ }
+
/**
* Returns the project_id of a TwingleProject
diff --git a/CRM/TwingleCampaign/Utils/ExtensionCache.php b/CRM/TwingleCampaign/Utils/ExtensionCache.php
index b6e6aef..12c9666 100644
--- a/CRM/TwingleCampaign/Utils/ExtensionCache.php
+++ b/CRM/TwingleCampaign/Utils/ExtensionCache.php
@@ -7,6 +7,8 @@ use CRM\TwingleCampaign\BAO\CustomField as CustomField;
use CRM_TwingleCampaign_ExtensionUtil as E;
use Exception;
+include_once E::path() . '/CRM/TwingleCampaign/BAO/CustomField.php';
+
/**
* A singleton that caches mappings and settings
*
@@ -24,6 +26,10 @@ class ExtensionCache {
private $templates;
+ /**
+ * Get an instance (singleton)
+ * @return ExtensionCache|null
+ */
public static function getInstance() {
if (null === self::$_instance) {
self::$_instance = new self;
@@ -31,6 +37,11 @@ class ExtensionCache {
return self::$_instance;
}
+ /**
+ * Protected ExtensionCache constructor.
+ *
+ * @throws \CiviCRM_API3_Exception
+ */
protected function __construct() {
// Get a mapping of custom fields
diff --git a/CRM/TwingleCampaign/resources/campaigns.json b/CRM/TwingleCampaign/resources/campaigns.json
index 221ab61..588b9c4 100644
--- a/CRM/TwingleCampaign/resources/campaigns.json
+++ b/CRM/TwingleCampaign/resources/campaigns.json
@@ -249,6 +249,159 @@
"is_active": 1,
"is_view": 1,
"weight": 7
+ },
+ "twingle_event_id": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event ID",
+ "name": "twingle_event_id",
+ "is_required": 1,
+ "is_searchable": 1,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 16,
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 1
+ },
+ "twingle_event_project_id": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Project ID",
+ "name": "twingle_event_project_id",
+ "is_required": 0,
+ "is_searchable": 1,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 16,
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 2
+ },
+ "twingle_event_identifier": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Identifier",
+ "name": "twingle_event_identifier",
+ "is_required": 0,
+ "is_searchable": 0,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 16,
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 3
+ },
+ "twingle_event_slug": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Slug",
+ "name": "twingle_event_slug",
+ "is_required": 0,
+ "is_searchable": 0,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 128,
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 4
+ },
+ "twingle_event_user_name": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Initiator",
+ "name": "twingle_event_user_name",
+ "is_required": 0,
+ "is_searchable": 1,
+ "data_type": "ContactReference",
+ "html_type": "Autocomplete-Select",
+ "is_active": 1,
+ "is_view": 0,
+ "weight": 5
+ },
+ "twingle_event_user_email": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Initiator Email",
+ "name": "twingle_event_user_email",
+ "is_required": 0,
+ "is_searchable": 0,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 128,
+ "is_active": 0,
+ "is_view": 1,
+ "weight": 6
+ },
+ "twingle_event_target": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Target",
+ "name": "twingle_event_target",
+ "is_required": 0,
+ "is_searchable": 0,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 10,
+ "is_active": 0,
+ "is_view": 1,
+ "weight": 6
+ },
+ "twingle_event_is_public": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event is public",
+ "name": "twingle_event_is_public",
+ "is_required": 0,
+ "is_searchable": 1,
+ "data_type": "Boolean",
+ "html_type": "Radio",
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 7
+ },
+ "twingle_event_deleted": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Deleted",
+ "name": "twingle_event_deleted",
+ "is_required": 0,
+ "is_searchable": 1,
+ "data_type": "Boolean",
+ "html_type": "Radio",
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 8
+ },
+ "twingle_event_confirmed_at": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Confirmed At",
+ "name": "twingle_event_confirmed_at",
+ "is_required": 0,
+ "is_searchable": 0,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 64,
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 9
+ },
+ "twingle_event_created_at": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Created At",
+ "name": "twingle_event_created_at",
+ "is_required": 0,
+ "is_searchable": 0,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 64,
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 10
+ },
+ "twingle_event_creation_url": {
+ "custom_group_id": "Twingle_Event_Information",
+ "label": "Twingle Event Creation URL",
+ "name": "twingle_event_creation_url",
+ "is_required": 0,
+ "is_searchable": 0,
+ "data_type": "String",
+ "html_type": "Text",
+ "text_length": 256,
+ "is_active": 1,
+ "is_view": 1,
+ "weight": 11
}
}
}
diff --git a/CRM/TwingleCampaign/resources/dictionary.json b/CRM/TwingleCampaign/resources/dictionary.json
index 2e6bdec..85f3b4a 100644
--- a/CRM/TwingleCampaign/resources/dictionary.json
+++ b/CRM/TwingleCampaign/resources/dictionary.json
@@ -1,6 +1,10 @@
{
- "fields": {
+ "TwingleProject": {
"name": "title",
"last_update": "last_modified_date"
+ },
+ "TwingleEvent": {
+ "description": "title",
+ "updated_at": "last_modified_date"
}
}
\ No newline at end of file
diff --git a/CRM/TwingleCampaign/resources/twingle_api_templates.json b/CRM/TwingleCampaign/resources/twingle_api_templates.json
index 620ed40..5488f8a 100644
--- a/CRM/TwingleCampaign/resources/twingle_api_templates.json
+++ b/CRM/TwingleCampaign/resources/twingle_api_templates.json
@@ -16,7 +16,8 @@
"form-single",
"widget-single",
"eventall",
- "eventlist"
+ "eventlist",
+ "counter"
],
"event": [
],
diff --git a/api/v3/TwingleSync/BAO/TwingleApiCall.php b/api/v3/TwingleSync/BAO/TwingleApiCall.php
deleted file mode 100644
index 9ee3b30..0000000
--- a/api/v3/TwingleSync/BAO/TwingleApiCall.php
+++ /dev/null
@@ -1,483 +0,0 @@
-apiKey = $apiKey;
-
- // Get organisation id
- $curl = curl_init($this->protocol . 'organisation' . $this->baseUrl);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
- curl_setopt($curl, CURLOPT_HTTPHEADER, [
- "x-access-code: $apiKey",
- 'Content-Type: application/json',
- ]);
-
- $response = json_decode(curl_exec($curl), TRUE);
- curl_close($curl);
-
- if (empty($response)) {
- throw new API_Exception(
- "Twingle API call failed. Please check your api key.");
- }
-
- $this->organisationId = array_column($response, 'id');
- }
-
- /**
- * If $id parameter is empty, this function returns all projects for all
- * organisations this API key is assigned to.
- *
- * TODO: Keys can only get assigned to one organisation. Save multiple keys
- * in settings instead.
- *
- * If $id parameter is given, this function returns a single project.
- *
- * @param int|null $projectId
- *
- * @return mixed
- */
- public function getProject(int $projectId = NULL) {
- $response = [];
- foreach ($this->organisationId as $organisationId) {
- $url = empty($projectId)
- ? $this->protocol . 'project' . $this->baseUrl . 'by-organisation/' . $organisationId
- : $this->protocol . 'project' . $this->baseUrl . $projectId;
-
- $response = array_merge($this->curlGet($url));
- }
- return $response;
- }
-
- /**
- *
- * Returns all Events for the given $projectId
- *
- * @param $projectId
- *
- * @return array
- */
- public function getEvent($projectId) {
- $result = [];
- $url = $this->protocol . 'project' . $this->baseUrl . $projectId . '/event';
- $limit = CRM_Core_BAO_Setting::getItem('', 'twingle_request_size');
- $offset = 0;
- $finished = FALSE;
-
- while (!$finished) {
- $params = [
- 'orderby' => 'id',
- 'direction' => 'desc',
- 'limit' => $limit,
- 'offset' => $offset,
- 'image' => 'as-boolean',
- 'public' => 0,
- ];
- $response = $this->curlGet($url, $params);
- $finished = count($response['data']) < $limit;
- $offset = $offset + $limit;
- $result = array_merge($result, $response['data']);
- }
- return $result;
- }
-
- /**
- * Synchronizes projects between Twingle and CiviCRM (both directions)
- * based on the timestamp.
- *
- * @param array $values
- *
- * @param bool $is_test
- * If TRUE, don't do any changes
- *
- * @return array|null
- * Returns a response array that contains title, id, project_id and status or
- * NULL if $values is not an array
- *
- * @throws \CiviCRM_API3_Exception
- */
- public function syncProject(array $values, bool $is_test = FALSE) {
-
- // If $values is an array
- if (is_array($values)) {
-
- // Instantiate TwingleProject
- try {
- $project = new TwingleProject(
- $values,
- TwingleProject::TWINGLE
- );
- } catch (Exception $e) {
-
- // Log Exception
- Civi::log()->error(
- "Failed to instantiate TwingleProject: $e->getMessage()"
- );
-
- // Return result array with error description
- return [
- "title" => $values['name'],
- "project_id" => (int) $values['id'],
- "status" =>
- "Failed to instantiate TwingleProject: $e->getMessage()",
- ];
- }
-
- // Check if the TwingleProject campaign already exists
- if (!$project->exists()) {
-
- // ... if not, get embed data and create project
- try {
- $this->getEmbedData($project);
- $result = $project->create($is_test);
- } catch (Exception $e) {
-
- // Log Exception
- Civi::log()->error(
- "Could not create campaign from TwingleProject: $e->getMessage()"
- );
-
- // Return result array with error description
- return [
- "title" => $values['name'],
- "project_id" => (int) $values['id'],
- "status" =>
- "Could not create campaign from TwingleProject: $e->getMessage()",
- ];
- }
- }
- else {
- $result = $project->getResponse('TwingleProject exists');
-
- // If Twingle's version of the project is newer than the CiviCRM
- // TwingleProject campaign update the campaign
- if ($values['last_update'] > $project->lastUpdate()) {
- try {
- $project->update($values);
- $this->getEmbedData($project);
- $result = $project->create();
- $result['status'] = $result['status'] == 'TwingleProject created'
- ? 'TwingleProject updated'
- : 'TwingleProject Update failed';
- } catch (Exception $e) {
- // Log Exception
- Civi::log()->error(
- "Could not update TwingleProject campaign: $e->getMessage()"
- );
- // Return result array with error description
- $result = $project->getResponse(
- "Could not update TwingleProject campaign: $e->getMessage()"
- );
- }
- }
- // If the CiviCRM TwingleProject campaign was changed, update the project
- // on Twingle's side
- elseif ($values['last_update'] < $project->lastUpdate()) {
- // If this is a test do not make database changes
- if ($is_test) {
- $result = $project->getResponse(
- 'TwingleProject ready to push'
- );
- }
- else {
- $result = $this->updateProject($project);
- }
- }
- elseif ($result['status'] == 'TwingleProject exists') {
- $result = $project->getResponse('TwingleProject up to date');
- }
- }
-
- // Return a response of the synchronization
- return $result;
- }
- else {
- return NULL;
- }
-
- }
-
- /**
- * @param \CRM\TwingleCampaign\BAO\TwingleProject $project
- */
- private function getEmbedData(TwingleProject &$project) {
-
- // Prepare url for curl
- $url = $this->protocol . 'project' . $this->baseUrl . $project->getProjectId();
-
- // Send curl
- $result = $this->curlGet($url);
-
- // Set embed data
- $project->setEmbedData($result['embed']);
-
- // Set counter-url
- $project->setCounterUrl($result['counter-url']['url']);
-
- }
-
- /**
- * Sends an curl post call to Twingle to update an existing project and then
- * updates the TwingleProject campaign.
- *
- * @param \CRM\TwingleCampaign\BAO\TwingleProject $project
- * The TwingleProject object that should get pushed to Twingle
- *
- * @return array
- * Returns a response array that contains title, id, project_id and status
- *
- */
- private function updateProject(TwingleProject &$project) {
-
- try {
- $values = $project->export();
- } catch (Exception $e) {
- // Log Exception
- Civi::log()->error(
- "Could not export TwingleProject values: $e->getMessage()"
- );
- // Return result array with error description
- return $project->getResponse(
- "Could not export TwingleProject values: $e->getMessage()"
- );
- }
-
- // Prepare url for curl
- $url = $this->protocol . 'project' . $this->baseUrl . $values['id'];
-
- // Send curl
- $result = $this->curlPost($url, $values);
-
- // Update TwingleProject in Civi with results from api call
- if (is_array($result) && !array_key_exists('message', $result)) {
- // Try to update the local TwingleProject campaign
- try {
- $project->update($result);
- $this->getEmbedData($project);
- $project->create();
- return $project->getResponse('TwingleProject pushed to Twingle');
- } catch (Exception $e) {
- // Log Exception
- Civi::log()->error(
- "Could not update TwingleProject campaign: $e->getMessage()"
- );
- // Return result array with error description
- return $project->getResponse(
- "TwingleProject was likely pushed to Twingle but the
- local update of the campaign failed: $e->getMessage()"
- );
- }
- }
- else {
- $message = $result['message'];
- return $project->getResponse(
- $message
- ? "TwingleProject could not get pushed to Twingle: $message"
- : 'TwingleProject could not get pushed to Twingle'
- );
- }
-
- }
-
- /**
- * @param array $values
- * @param bool $isTest
- *
- * @return array | NULL
- * @throws \CiviCRM_API3_Exception
- */
- public function syncEvent(array $values, bool $isTest) {
- // If $event is an array
- if (is_array($values)) {
-
- // Instantiate TwingleEvent
- try {
- $event = new TwingleEvent(
- $values,
- TwingleEvent::TWINGLE
- );
- } catch (Exception $e) {
-
- // Log Exception
- Civi::log()->error(
- "Failed to instantiate TwingleEvent: $e->getMessage()"
- );
-
- // Return result array with error description
- return [
- "title" => $values['description'],
- "event_id" => (int) $values['id'],
- "project_id" => (int) $values['id'],
- "status" =>
- "Failed to instantiate TwingleEvent: $e->getMessage()",
- ];
- }
-
- // Check if the TwingleProject campaign already exists
- if (!$event->exists()) {
-
- // ... if not, get embed data and create project
- try {
- $this->getEmbedData($event); // TODO: !
- $result = $event->create($is_test);
- } catch (Exception $e) {
-
- // Log Exception
- Civi::log()->error(
- "Could not create campaign from TwingleEvent: $e->getMessage()"
- );
-
- // Return result array with error description
- return [
- "title" => $values['description'],
- "event_id" => (int) $values['id'],
- "project_id" => (int) $values['id'],
- "Could not create campaign from TwingleEvent: $e->getMessage()",
- ];
- }
- }
- else {
- $result = $event->getResponse('TwingleEvent exists');
-
- // If Twingle's version of the event is newer than the CiviCRM
- // TwingleEvent campaign update the campaign
- if ($values['last_update'] > $event->lastUpdate()) {
- try {
- $event->update($values);
- $this->getEmbedData($event); // TODO: !
- $result = $event->create();
- $result['status'] = $result['status'] == 'TwingleEvent created'
- ? 'TwingleEvent updated'
- : 'TwingleEvent Update failed';
- } catch (Exception $e) {
- // Log Exception
- Civi::log()->error(
- "Could not update TwingleEvent campaign: $e->getMessage()"
- );
- // Return result array with error description
- $result = $event->getResponse(
- "Could not update TwingleProject campaign: $e->getMessage()"
- );
- }
- }
- elseif ($result['status'] == 'TwingleEvent exists') {
- $result = $event->getResponse('TwingleEvent up to date');
- }
- }
-
- // Return a response of the synchronization
- return $result;
- }
- else {
- return NULL;
- }
- }
-
-
- /**
- * Does a cURL and gives back the result array.
- *
- * @param $url
- * The url the curl should get sent to
- *
- * @param null $params
- * The parameters you want to send (optional)
- *
- * @return array|bool
- * Returns the result array of the curl or FALSE, if the curl failed
- */
- private function curlGet($url, $params = NULL) {
- if (!empty($params)) {
- $url = $url . '?' . http_build_query($params);
- }
- $curl = curl_init($url);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
- curl_setopt($curl, CURLOPT_HTTPHEADER, [
- "x-access-code: $this->apiKey",
- 'Content-Type: application/json',
- ]);
- $response = json_decode(curl_exec($curl), TRUE);
- if (empty($response)) {
- $response = curl_error($curl);
- }
- curl_close($curl);
- return $response;
- }
-
- /**
- * Sends a curl post and gives back the result array.
- *
- * @param $url
- * The url the curl should get sent to
- *
- * @param $data
- * The data that should get posted
- *
- * @return false|mixed
- * Returns the result array of the curl or FALSE, if the curl failed
- */
- private function curlPost($url, $data) {
- $curl = curl_init($url);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
- curl_setopt($curl, CURLOPT_POST, TRUE);
- curl_setopt($curl, CURLOPT_HTTPHEADER, [
- "x-access-code: $this->apiKey",
- 'Content-Type: application/json',
- ]);
- $json = json_encode($data);
- curl_setopt($curl, CURLOPT_POSTFIELDS, $json);
- $response = json_decode(curl_exec($curl), TRUE);
- if (empty($response)) {
- $response = FALSE;
- }
- curl_close($curl);
- return $response;
- }
-
- /**
- * Returns a result array
- *
- * @param $values
- * Project values to generate result array from
- *
- * @param $status
- * Status of the array
- *
- * @return array
- */
- private function getResultArray($values, $status) {
- return [
- "title" => $values['name'],
- "project_id" => (int) $values['id'],
- "project_type" => $values['project_type'],
- "status" => $status,
- ];
- }
-
-}
diff --git a/api/v3/TwingleSync/Post.php b/api/v3/TwingleSync/Post.php
index 1bf8264..16befa7 100644
--- a/api/v3/TwingleSync/Post.php
+++ b/api/v3/TwingleSync/Post.php
@@ -1,9 +1,15 @@
'twingle_api_key',
'title' => E::ts('Twingle API key'),
@@ -38,11 +44,11 @@ function _civicrm_api3_twingle_sync_Post_spec(&$spec) {
* @return array
* API result descriptor
*
- * @throws \CiviCRM_API3_Exception|\API_Exception
+ * @throws \CiviCRM_API3_Exception
+ * @throws \API_Exception
* @see civicrm_api3_create_success
- *
*/
-function civicrm_api3_twingle_sync_Post($params) {
+function civicrm_api3_twingle_sync_Post(array $params) {
$result_values = [];
// Is this call a test?
@@ -63,8 +69,8 @@ function civicrm_api3_twingle_sync_Post($params) {
$i = 0;
foreach ($projects as $project) {
if (is_array($project)) {
- $result_values['sync']['projects'][$i++] = $twingleApi
- ->syncProject($project, $is_test);
+ $result_values['sync']['projects'][$i++] =
+ TwingleProject::sync($project, $twingleApi, $is_test);
}
}
@@ -80,10 +86,12 @@ function civicrm_api3_twingle_sync_Post($params) {
$j = 0;
if ($events) {
foreach ($events as $event) {
- $result_values['sync']['events'][$j++] = $twingleApi
- ->syncEvent($event, $is_test);
+ $result_values['sync']['events'][$j++] =
+ TwingleEvent::sync($event, $twingleApi, $is_test);
}
}
return civicrm_api3_create_success($result_values);
}
+
+