️ Pull all campaigns with newly created custom fields from Twingle

🇩🇪 update German translation
This commit is contained in:
Marc Michalsky forumZFD 2021-04-17 16:58:10 +02:00
parent c22911e886
commit ba9ab9f324
Signed by untrusted user who does not match committer: marc.koch
GPG key ID: 12406554CFB028B9
7 changed files with 206 additions and 44 deletions

View file

@ -62,10 +62,10 @@ class CRM_TwingleCampaign_BAO_CustomField {
* *
* @param bool $upgrade * @param bool $upgrade
* If true: Does not show UF message if custom field already exists * If true: Does not show UF message if custom field already exists
* * @returns array Result of custom field creation api call
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public function create(bool $upgrade = false) { public function create(bool $upgrade = FALSE): ?array {
// Check if the field already exists // Check if the field already exists
$field = civicrm_api3( $field = civicrm_api3(
@ -95,6 +95,7 @@ class CRM_TwingleCampaign_BAO_CustomField {
id: $this->id id: $this->id
group: $this->custom_group_id" group: $this->custom_group_id"
); );
return $this->result;
} }
// If the field could not get created: log error // If the field could not get created: log error
else { else {
@ -112,12 +113,17 @@ class CRM_TwingleCampaign_BAO_CustomField {
$this->result['error_message']"); $this->result['error_message']");
CRM_Utils_System::setUFMessage(E::ts("Creation of custom field failed. Find more information in the logs.")); CRM_Utils_System::setUFMessage(E::ts("Creation of custom field failed. Find more information in the logs."));
} }
return $this->result;
} }
} }
elseif (!$upgrade) { elseif (!$upgrade) {
CRM_Utils_System::setUFMessage(E::ts('Creation of custom field \'%1\' failed, because a custom field with that name already exists. Find more information in the logs.', [1 => $this->name])); CRM_Utils_System::setUFMessage(E::ts('Creation of custom field \'%1\' failed, because a custom field with that name already exists. Find more information in the logs.', [1 => $this->name]));
Civi::log() Civi::log()
->error("$this->extensionName could not create new custom field \"$this->name\" for group \"$this->custom_group_id\" because a field with that name already exists."); ->error("$this->extensionName could not create new custom field \"$this->name\" for group \"$this->custom_group_id\" because a field with that name already exists.");
return NULL;
}
else {
return NULL;
} }
} }

View file

@ -13,13 +13,14 @@ use CRM_TwingleCampaign_ExtensionUtil as E;
*/ */
class CRM_TwingleCampaign_Upgrader extends CRM_TwingleCampaign_Upgrader_Base { class CRM_TwingleCampaign_Upgrader extends CRM_TwingleCampaign_Upgrader_Base {
// By convention, functions that look like "function upgrade_NNNN()" are
// upgrade tasks. They are executed in order (like Drupal's hook_update_N).
/** /**
* This update function checks whether all custom fields defined in
* CRM/TwingleCampaign/resources/campaigns.php exist and creates them if not.
* To ensure that all newly created custom fields get filled with data, all
* changed campaigns will get pulled from Twingle.
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public function upgrade_01() { public function upgrade_01(): bool {
$campaign_info = require E::path() . $campaign_info = require E::path() .
'/CRM/TwingleCampaign/resources/campaigns.php'; '/CRM/TwingleCampaign/resources/campaigns.php';
@ -31,7 +32,7 @@ class CRM_TwingleCampaign_Upgrader extends CRM_TwingleCampaign_Upgrader_Base {
new CampaignType($campaign_type); new CampaignType($campaign_type);
} }
foreach (CampaignType::getCampaignTypes() as $campaign_type) { foreach (CampaignType::getCampaignTypes() as $campaign_type) {
$campaign_type->create(true); $campaign_type->create(TRUE);
} }
// Create custom groups // Create custom groups
@ -42,13 +43,61 @@ class CRM_TwingleCampaign_Upgrader extends CRM_TwingleCampaign_Upgrader_Base {
} }
} }
$cg = new CustomGroup($custom_group); $cg = new CustomGroup($custom_group);
$cg->create(true); $cg->create(TRUE);
} }
// If new fields get created during the update, set a flag to set all
// last_update values of the affected campaigns to "0" and trigger a
// synchronization. This ensures that settings on Twingle's side will not
// get overwritten with empty values.
$updatedCampaignTypes = [];
// Create custom fields // Create custom fields
foreach ($campaign_info['custom_fields'] as $custom_field) { foreach ($campaign_info['custom_fields'] as $custom_field) {
$cf = new CustomField($custom_field); $cf = new CustomField($custom_field);
$cf->create(true); $result = $cf->create(TRUE);
if (!empty($result)) {
preg_match(
'/^Twingle_[a-yA-Z]*/',
$cf->getCustomGroupId(),
$updatedCampaignTypes[]
);
}
}
// Filter changed campaign types
foreach ($updatedCampaignTypes as $key => $value) {
$updatedCampaignTypes[str_replace('_', '', $value[0])]
= TRUE;
unset($updatedCampaignTypes[$key]);
}
// Pull changed campaigns to fill new created fields with data
try {
foreach ($updatedCampaignTypes as $key => $value) {
if ($value === TRUE) {
civicrm_api3(
$key,
'sync',
['pull' => TRUE]
);
}
}
} catch (Exception $e) {
Civi::log()->error(
E::LONG_NAME .
' could not pull campaigns from Twingle to fill the campaign fields that were created on update.' .
$e->getMessage()
);
CRM_Core_Session::setStatus(
E::ts(
'Could not pull campaigns from Twingle to fill the campaign fields that were created within this update: %1',
[1 => $e->getMessage()]
),
E::ts('Scheduled Job'),
error
);
} }
// Create option values // Create option values
@ -124,7 +173,6 @@ class CRM_TwingleCampaign_Upgrader extends CRM_TwingleCampaign_Upgrader_Base {
E::ts('Scheduled Job'), E::ts('Scheduled Job'),
error error
); );
CRM_Utils_System::setUFMessage(E::ts('Could not create scheduled job "TwingleSync". Your Campaigns will not get synchronized to Twingle.'));
} }
} }

View file

@ -41,6 +41,13 @@ function _civicrm_api3_twingle_event_Sync_spec(array &$spec) {
'api.required' => 0, 'api.required' => 0,
'description' => E::ts('If this is set true, no database change will be made'), 'description' => E::ts('If this is set true, no database change will be made'),
]; ];
$spec['pull'] = [
'name' => 'pull',
'title' => E::ts('Pull'),
'type' => CRM_Utils_Type::T_BOOLEAN,
'api.required' => 0,
'description' => E::ts('If this is set true, the event(s) will be pulled from Twingle and updated locally'),
];
$spec['twingle_api_key'] = [ $spec['twingle_api_key'] = [
'name' => 'twingle_api_key', 'name' => 'twingle_api_key',
'title' => E::ts('Twingle API key'), 'title' => E::ts('Twingle API key'),
@ -111,6 +118,10 @@ function civicrm_api3_twingle_event_Sync(array $params): array {
Civi::cache('long')->set('twinglecampaign_twingle_api', $twingleApi); Civi::cache('long')->set('twinglecampaign_twingle_api', $twingleApi);
} }
// Set pull flag
$pull = (isset($params['pull']) && $params['pull']);
unset($params['pull']);
// If an id or a event_id is provided, synchronize only this one campaign // If an id or a event_id is provided, synchronize only this one campaign
if (isset($params['id']) || isset($params['event_id'])) { if (isset($params['id']) || isset($params['event_id'])) {
@ -141,7 +152,13 @@ function civicrm_api3_twingle_event_Sync(array $params): array {
} }
// Synchronize events // Synchronize events
if (!empty($event_from_twingle)) { if (!empty($event_from_twingle)) {
return _eventSync($event, $event_from_twingle, $twingleApi, $params); return _eventSync(
$event,
$event_from_twingle,
$twingleApi,
$params,
$pull
);
} }
// If Twingle does not know an event with the given event_id, give error // If Twingle does not know an event with the given event_id, give error
@ -271,7 +288,13 @@ function civicrm_api3_twingle_event_Sync(array $params): array {
$event = _instantiateEvent($event_from_civicrm, $event_from_civicrm['id']); $event = _instantiateEvent($event_from_civicrm, $event_from_civicrm['id']);
// sync event // sync event
$result = _eventSync($event, $event_from_twingle, $twingleApi, $params); $result = _eventSync(
$event,
$event_from_twingle,
$twingleApi,
$params,
$pull
);
if ($result['is_error'] != 0) { if ($result['is_error'] != 0) {
$errors_occurred++; $errors_occurred++;
$result_values[$event->getId()] = $result_values[$event->getId()] =
@ -394,6 +417,7 @@ function _updateEventLocally(array $event_from_twingle,
* @param array $event_from_twingle * @param array $event_from_twingle
* @param \CRM_TwingleCampaign_BAO_TwingleApiCall $twingleApi * @param \CRM_TwingleCampaign_BAO_TwingleApiCall $twingleApi
* @param array $params * @param array $params
* @param bool $pull Force pulling event from Twingle and update local campaign
* *
* @return array * @return array
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
@ -401,12 +425,13 @@ function _updateEventLocally(array $event_from_twingle,
function _eventSync(TwingleEvent $event, function _eventSync(TwingleEvent $event,
array $event_from_twingle, array $event_from_twingle,
TwingleApiCall $twingleApi, TwingleApiCall $twingleApi,
array $params): array { array $params,
bool $pull = FALSE): array {
// If Twingle's timestamp of the event differs from the timestamp of the // If Twingle's timestamp of the event differs from the timestamp of the
// CiviCRM TwingleEvent campaign, update the campaign on CiviCRM's side. // CiviCRM TwingleEvent campaign, update the campaign on CiviCRM's side.
// NOTE: Changes on TwingleEvents are not meant to get pushed to Twingle // NOTE: Changes on TwingleEvents are not meant to get pushed to Twingle
if ($event_from_twingle['updated_at'] != $event->lastUpdate()) { if ($event_from_twingle['updated_at'] != $event->lastUpdate() || $pull) {
return _updateEventLocally($event_from_twingle, $event, $params, $twingleApi); return _updateEventLocally($event_from_twingle, $event, $params, $twingleApi);
} }

View file

@ -35,6 +35,13 @@ function _civicrm_api3_twingle_project_Sync_spec(array &$spec) {
'api.required' => 0, 'api.required' => 0,
'description' => E::ts('If this is set true, no database change will be made'), 'description' => E::ts('If this is set true, no database change will be made'),
]; ];
$spec['pull'] = [
'name' => 'pull',
'title' => E::ts('Pull from Twingle'),
'type' => CRM_Utils_Type::T_BOOLEAN,
'api.required' => 0,
'description' => E::ts('If this is set true, the project(s) will be pulled from Twingle and updated locally'),
];
$spec['twingle_api_key'] = [ $spec['twingle_api_key'] = [
'name' => 'twingle_api_key', 'name' => 'twingle_api_key',
'title' => E::ts('Twingle API key'), 'title' => E::ts('Twingle API key'),
@ -90,6 +97,10 @@ function civicrm_api3_twingle_project_Sync(array $params): array {
Civi::cache('long')->set('twinglecampaign_twingle_api', $twingleApi); Civi::cache('long')->set('twinglecampaign_twingle_api', $twingleApi);
} }
// Set pull flag
$pull = (isset($params['pull']) && $params['pull']);
unset($params['pull']);
// If an id or a project_id is given, synchronize only this one campaign // If an id or a project_id is given, synchronize only this one campaign
if (isset($params['id']) || isset($params['project_id'])) { if (isset($params['id']) || isset($params['project_id'])) {
@ -106,9 +117,15 @@ function civicrm_api3_twingle_project_Sync(array $params): array {
$id = $result['id']; $id = $result['id'];
$project = new TwingleProject($result, $id); $project = new TwingleProject($result, $id);
// Synchronize projects // Synchronize project
if (!empty($project_from_twingle)) { if (!empty($project_from_twingle)) {
return _projectSync($project, $project_from_twingle, $twingleApi, $params); return _projectSync(
$project,
$project_from_twingle,
$twingleApi,
$params,
$pull
);
} }
// If Twingle does not know a project with the given project_id, give error // If Twingle does not know a project with the given project_id, give error
@ -175,8 +192,10 @@ function civicrm_api3_twingle_project_Sync(array $params): array {
// Push missing projects to Twingle // Push missing projects to Twingle
$returnValues = []; $returnValues = [];
foreach ($projects_from_civicrm['values'] as $project_from_civicrm) { foreach ($projects_from_civicrm['values'] as $project_from_civicrm) {
if (!in_array($project_from_civicrm['project_id'], if (
array_column($projects_from_twingle, 'id'))) { !in_array($project_from_civicrm['project_id'],
array_column($projects_from_twingle, 'id')
)) {
// store campaign id in $id // store campaign id in $id
$id = $project_from_civicrm['id']; $id = $project_from_civicrm['id'];
unset($project_from_civicrm['id']); unset($project_from_civicrm['id']);
@ -197,8 +216,11 @@ function civicrm_api3_twingle_project_Sync(array $params): array {
// Create missing projects as campaigns in CiviCRM // Create missing projects as campaigns in CiviCRM
foreach ($projects_from_twingle as $project_from_twingle) { foreach ($projects_from_twingle as $project_from_twingle) {
if (!in_array($project_from_twingle['id'], if (
array_column($projects_from_civicrm['values'], 'project_id'))) { !in_array($project_from_twingle['id'],
array_column($projects_from_civicrm['values'],
'project_id')
)) {
$project = new TwingleProject($project_from_twingle); $project = new TwingleProject($project_from_twingle);
try { try {
@ -241,10 +263,12 @@ function civicrm_api3_twingle_project_Sync(array $params): array {
// sync project // sync project
$result = _projectSync( $result = _projectSync(
$project, $project,
$project_from_twingle, $project_from_twingle,
$twingleApi, $twingleApi,
$params); $params,
$pull
);
if (!$result['is_error'] == 0) { if (!$result['is_error'] == 0) {
$errors[$result['id']] = $result['error_message']; $errors[$result['id']] = $result['error_message'];
$returnValues[$project->getId()] = $returnValues[$project->getId()] =
@ -457,6 +481,7 @@ function _pushProjectToTwingle(TwingleProject $project,
* @param array $project_from_twingle * @param array $project_from_twingle
* @param \CRM_TwingleCampaign_BAO_TwingleApiCall $twingleApi * @param \CRM_TwingleCampaign_BAO_TwingleApiCall $twingleApi
* @param array $params * @param array $params
* @param bool $pull Force pulling project from Twingle and update local campaign
* *
* @return array * @return array
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
@ -464,11 +489,13 @@ function _pushProjectToTwingle(TwingleProject $project,
function _projectSync(TwingleProject $project, function _projectSync(TwingleProject $project,
array $project_from_twingle, array $project_from_twingle,
TwingleApiCall $twingleApi, TwingleApiCall $twingleApi,
array $params): array { array $params,
bool $pull = FALSE): array {
// If Twingle's version of the project is newer than the CiviCRM // If Twingle's version of the project is newer than the CiviCRM
// TwingleProject campaign, update the campaign // TwingleProject campaign, update the campaign
if ($project_from_twingle['last_update'] > $project->lastUpdate()) { if ($project_from_twingle['last_update'] > $project->lastUpdate() ||
$pull) {
return _updateProjectLocally($project_from_twingle, $project, $params, $twingleApi); return _updateProjectLocally($project_from_twingle, $project, $params, $twingleApi);
} }

View file

@ -143,6 +143,18 @@ msgstr "keine"
msgid "Upgrade %1 to revision %2" msgid "Upgrade %1 to revision %2"
msgstr "Upgrade %1 auf Revision %2" msgstr "Upgrade %1 auf Revision %2"
#: CRM/TwingleCampaign/Upgrader.php
msgid ""
"Could not pull campaigns from Twingle to fill the campaign fields that were "
"created within this update: %1"
msgstr ""
"Die zusätzlichen Kampagnenfelder, die in diesem Update erstellt wurden, "
"konnten nicht von Twingle bezogen werden: %1"
#: CRM/TwingleCampaign/Upgrader.php
msgid "Scheduled Job"
msgstr "Geplante Audgabe"
#: CRM/TwingleCampaign/Upgrader.php #: CRM/TwingleCampaign/Upgrader.php
msgid "" msgid ""
"Syncronizes all TwingleProjects an TwingleEvents between CiviCRM and Twingle" "Syncronizes all TwingleProjects an TwingleEvents between CiviCRM and Twingle"
@ -154,18 +166,6 @@ msgstr ""
msgid "Could not create scheduled job \"TwingleSync\"." msgid "Could not create scheduled job \"TwingleSync\"."
msgstr "Geplante Aufgabe \"TwingleSync\" konnte nicht erstellt werden." msgstr "Geplante Aufgabe \"TwingleSync\" konnte nicht erstellt werden."
#: CRM/TwingleCampaign/Upgrader.php
msgid "Scheduled Job"
msgstr "Geplante Audgabe"
#: CRM/TwingleCampaign/Upgrader.php
msgid ""
"Could not create scheduled job \"TwingleSync\". Your Campaigns will not get "
"synchronized to Twingle."
msgstr ""
"Geplante Aufgabe \"TwingleSync\" konnte nicht erstellt werden. Ihre "
"Kampagnen werden nicht mit Twingle synchronisiert werden."
#: CRM/TwingleCampaign/Upgrader.php #: CRM/TwingleCampaign/Upgrader.php
msgid "Could not delete scheduled job \"TwingleSync\"." msgid "Could not delete scheduled job \"TwingleSync\"."
msgstr "Geplante Aufgabe \"TwingleSync\" konnte nicht gelöscht werden." msgstr "Geplante Aufgabe \"TwingleSync\" konnte nicht gelöscht werden."
@ -892,6 +892,14 @@ msgstr "Eltern Twingle Project ID"
msgid "Twingle ID of the parent TwingleProject" msgid "Twingle ID of the parent TwingleProject"
msgstr "Twingle ID des Eltern-TwingleProject" msgstr "Twingle ID des Eltern-TwingleProject"
#: api/v3/TwingleCampaign/Get.php api/v3/TwingleCampaign/Sync.php
msgid "Parent Project ID"
msgstr "Eltern-Projekt-ID"
#: api/v3/TwingleCampaign/Get.php api/v3/TwingleCampaign/Sync.php
msgid "ID of the parent TwingleProject"
msgstr "ID des Eltern-TWinglePRoject"
#: api/v3/TwingleCampaign/Get.php api/v3/TwingleCampaign/Getsingle.php #: api/v3/TwingleCampaign/Get.php api/v3/TwingleCampaign/Getsingle.php
#: api/v3/TwingleEvent/Get.php api/v3/TwingleEvent/Getsingle.php #: api/v3/TwingleEvent/Get.php api/v3/TwingleEvent/Getsingle.php
#: api/v3/TwingleProject/Get.php api/v3/TwingleProject/Getsingle.php #: api/v3/TwingleProject/Get.php api/v3/TwingleProject/Getsingle.php
@ -1021,6 +1029,18 @@ msgstr "Bestätigt am"
msgid "When the Event was confirmed by its initiator" msgid "When the Event was confirmed by its initiator"
msgstr "Wenn das Event vom Initiator bestätigt wurrde" msgstr "Wenn das Event vom Initiator bestätigt wurrde"
#: api/v3/TwingleEvent/Sync.php
msgid "Pull"
msgstr "Ziehen"
#: api/v3/TwingleEvent/Sync.php
msgid ""
"If this is set true, the event(s) will be pulled from Twingle and updated "
"locally"
msgstr ""
"Wenn dies auf wahr gesetzt ist, werden die Events von Twingle gezogen und "
"lokal geupdated."
#: api/v3/TwingleEvent/Sync.php api/v3/TwingleSync/Sync.php #: api/v3/TwingleEvent/Sync.php api/v3/TwingleSync/Sync.php
msgid "Limit" msgid "Limit"
msgstr "Limit" msgstr "Limit"
@ -1122,6 +1142,18 @@ msgstr "Twingle Organisations ID"
msgid "Your Twingle Organisation ID" msgid "Your Twingle Organisation ID"
msgstr "Ihre Twingle Organisations ID" msgstr "Ihre Twingle Organisations ID"
#: api/v3/TwingleProject/Sync.php
msgid "Pull from Twingle"
msgstr "Von Twingle ziehen"
#: api/v3/TwingleProject/Sync.php
msgid ""
"If this is set true, the project(s) will be pulled from Twingle and updated "
"locally"
msgstr ""
"Wenn dies auf wahr gesetzt ist, werden die Projekte von Twingle gezogen und "
"lokal geupdated."
#: templates/CRM/TwingleCampaign/Form/Settings.tpl #: templates/CRM/TwingleCampaign/Form/Settings.tpl
msgid "General Settings" msgid "General Settings"
msgstr "Allgemeine Einstellungen" msgstr "Allgemeine Einstellungen"

View file

@ -99,11 +99,7 @@ msgid "Upgrade %1 to revision %2"
msgstr "" msgstr ""
#: ./CRM/TwingleCampaign/Upgrader.php #: ./CRM/TwingleCampaign/Upgrader.php
msgid "Syncronizes all TwingleProjects an TwingleEvents between CiviCRM and Twingle" msgid "Could not pull campaigns from Twingle to fill the campaign fields that were created within this update: %1"
msgstr ""
#: ./CRM/TwingleCampaign/Upgrader.php
msgid "Could not create scheduled job \"TwingleSync\"."
msgstr "" msgstr ""
#: ./CRM/TwingleCampaign/Upgrader.php #: ./CRM/TwingleCampaign/Upgrader.php
@ -111,7 +107,11 @@ msgid "Scheduled Job"
msgstr "" msgstr ""
#: ./CRM/TwingleCampaign/Upgrader.php #: ./CRM/TwingleCampaign/Upgrader.php
msgid "Could not create scheduled job \"TwingleSync\". Your Campaigns will not get synchronized to Twingle." msgid "Syncronizes all TwingleProjects an TwingleEvents between CiviCRM and Twingle"
msgstr ""
#: ./CRM/TwingleCampaign/Upgrader.php
msgid "Could not create scheduled job \"TwingleSync\"."
msgstr "" msgstr ""
#: ./CRM/TwingleCampaign/Upgrader.php #: ./CRM/TwingleCampaign/Upgrader.php
@ -802,6 +802,14 @@ msgstr ""
msgid "Twingle ID of the parent TwingleProject" msgid "Twingle ID of the parent TwingleProject"
msgstr "" msgstr ""
#: ./api/v3/TwingleCampaign/Get.php ./api/v3/TwingleCampaign/Sync.php
msgid "Parent Project ID"
msgstr ""
#: ./api/v3/TwingleCampaign/Get.php ./api/v3/TwingleCampaign/Sync.php
msgid "ID of the parent TwingleProject"
msgstr ""
#: ./api/v3/TwingleCampaign/Get.php ./api/v3/TwingleCampaign/Getsingle.php ./api/v3/TwingleEvent/Get.php ./api/v3/TwingleEvent/Getsingle.php ./api/v3/TwingleProject/Get.php ./api/v3/TwingleProject/Getsingle.php #: ./api/v3/TwingleCampaign/Get.php ./api/v3/TwingleCampaign/Getsingle.php ./api/v3/TwingleEvent/Get.php ./api/v3/TwingleEvent/Getsingle.php ./api/v3/TwingleProject/Get.php ./api/v3/TwingleProject/Getsingle.php
msgid "Campaign Name" msgid "Campaign Name"
msgstr "" msgstr ""
@ -890,6 +898,14 @@ msgstr ""
msgid "When the Event was confirmed by its initiator" msgid "When the Event was confirmed by its initiator"
msgstr "" msgstr ""
#: ./api/v3/TwingleEvent/Sync.php
msgid "Pull"
msgstr ""
#: ./api/v3/TwingleEvent/Sync.php
msgid "If this is set true, the event(s) will be pulled from Twingle and updated locally"
msgstr ""
#: ./api/v3/TwingleEvent/Sync.php ./api/v3/TwingleSync/Sync.php #: ./api/v3/TwingleEvent/Sync.php ./api/v3/TwingleSync/Sync.php
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
@ -982,6 +998,14 @@ msgstr ""
msgid "Your Twingle Organisation ID" msgid "Your Twingle Organisation ID"
msgstr "" msgstr ""
#: ./api/v3/TwingleProject/Sync.php
msgid "Pull from Twingle"
msgstr ""
#: ./api/v3/TwingleProject/Sync.php
msgid "If this is set true, the project(s) will be pulled from Twingle and updated locally"
msgstr ""
#: ./templates/CRM/TwingleCampaign/Form/Settings.tpl #: ./templates/CRM/TwingleCampaign/Form/Settings.tpl
msgid "General Settings" msgid "General Settings"
msgstr "" msgstr ""