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); } + +