From 221f9c72f3ad1d6b825ea8ac4620e7eb3a8076bf Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Wed, 12 Jun 2024 15:21:10 +0200 Subject: [PATCH 01/43] Back to 1.5-dev --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index fe9fbb5..d0876c8 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - 2024-06-12 - 1.5-alpha1 - alpha + + 1.5-dev + dev 5.56 From 90f27f70c7b09163da30f3359fe037a55601debe Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 6 Jun 2024 15:08:44 +0200 Subject: [PATCH 02/43] improve newsletter subcription result values --- api/v3/TwingleDonation/Submit.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index daceb36..379def0 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -526,7 +526,7 @@ function civicrm_api3_twingle_donation_Submit($params) { // If usage of double opt-in is selected, use MailingEventSubscribe.create // to add contact to newsletter groups defined in the profile - $result_values['newsletter']['newsletter_double_opt_in'] + $result_values['newsletter_double_opt_in'] = (bool) $profile->getAttribute('newsletter_double_opt_in') ? 'true' : 'false'; @@ -555,7 +555,7 @@ function civicrm_api3_twingle_donation_Submit($params) { ] )['visibility'] == 'Public Pages'; if (!in_array($group_id, $group_memberships, FALSE) && $is_public_group) { - $result_values['newsletter'][][$group_id] = civicrm_api3( + $result = civicrm_api3( 'MailingEventSubscribe', 'create', [ @@ -564,9 +564,12 @@ function civicrm_api3_twingle_donation_Submit($params) { 'contact_id' => $contact_id, ] ); + $subscription = CRM_Utils_Array::first($result['values']); + $subscription['group_id'] = $group_id; + $result_values['newsletter_subscriptions'][] = $subscription; } elseif ($is_public_group) { - $result_values['newsletter'][] = $group_id; + $result_values['newsletter_group_ids'][] = $group_id; } } // If requested, add contact to newsletter groups defined in the profile. @@ -584,8 +587,7 @@ function civicrm_api3_twingle_donation_Submit($params) { 'contact_id' => $contact_id, ] ); - - $result_values['newsletter'][] = $group_id; + $result_values['newsletter_group_ids'][] = $group_id; } } From 67283fa1a78d3bc77103e8a2bd000662fb3162ea Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 6 Jun 2024 15:53:44 +0200 Subject: [PATCH 03/43] improve donation receipt result values --- api/v3/TwingleDonation/Submit.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index 379def0..5ce8e84 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -618,8 +618,7 @@ function civicrm_api3_twingle_donation_Submit($params) { 'group_id' => $group_id, 'contact_id' => $organisation_id ?? $contact_id, ]); - - $result_values['donation_receipt'][] = $group_id; + $result_values['donation_receipt_group_ids'][] = $group_id; } } From c0af2e16ab3ad10b725dafbf5024d972b7281429 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 6 Jun 2024 15:54:13 +0200 Subject: [PATCH 04/43] improve SEPA mandate result values --- api/v3/TwingleDonation/Submit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index 5ce8e84..43cf7b8 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -725,7 +725,7 @@ function civicrm_api3_twingle_donation_Submit($params) { // Create the mandate. $mandate = civicrm_api3('SepaMandate', 'createfull', $mandate_data); - $result_values['sepa_mandate'] = $mandate['values']; + $result_values['sepa_mandate'] = CRM_Utils_Array::first($mandate['values']); } else { // Set financial type depending on donation rhythm. This applies for From ff24256bc17fbc3c063c4623a961092f401e655b Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 6 Jun 2024 15:54:49 +0200 Subject: [PATCH 05/43] improve contribution result values --- api/v3/TwingleDonation/Submit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index 43cf7b8..c8ef884 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -822,7 +822,7 @@ function civicrm_api3_twingle_donation_Submit($params) { } } - $result_values['contribution'] = $contribution['values']; + $result_values['contribution'] = CRM_Utils_Array::first($contribution['values']); } // MEMBERSHIP CREATION From 61f45034c6dd83cc714f36d1e8152397063874ba Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 13 Jun 2024 13:03:17 +0200 Subject: [PATCH 06/43] Update help text for custom field mapping configuration option to reflect that all parameters are supported. --- templates/CRM/Twingle/Form/Profile.hlp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/CRM/Twingle/Form/Profile.hlp b/templates/CRM/Twingle/Form/Profile.hlp index b8d67a4..a8efdc5 100644 --- a/templates/CRM/Twingle/Form/Profile.hlp +++ b/templates/CRM/Twingle/Form/Profile.hlp @@ -62,10 +62,10 @@ {/htxt} {htxt id='id-custom_field_mapping'} - {ts domain="de.systopia.twingle"}

Map Twingle custom fields to CiviCRM custom fields using the following format (each assignment in a separate line):

+ {ts domain="de.systopia.twingle"}

Map Twingle custom fields to CiviCRM fields using the following format (each assignment in a separate line):

twingle_field_1=custom_123
twingle_field_2=custom_789

Always use the custom_[id] notation for CiviCRM custom fields.

-

This only works for fields that Twingle themselves provide in the custom_fields parameter, not for any parameters; e.g. user_extrafield always ends up in a note.

+

This works for fields that Twingle themselves provide in the custom_fields parameter, and for any other parameter (e.g. user_extrafield)

Only custom fields extending one of the following CiviCRM entities are allowed:

  • Contact – Will be set on the Individual contact
  • From a8c8401be3421c3da01eb38e93ad4c5a850c47b9 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 13 Jun 2024 13:03:49 +0200 Subject: [PATCH 07/43] Update translation template --- l10n/de.systopia.twingle.pot | 1016 ++++++++++++++++++++++++++++++++++ l10n/pot/twingle.pot | 312 ----------- 2 files changed, 1016 insertions(+), 312 deletions(-) create mode 100644 l10n/de.systopia.twingle.pot delete mode 100644 l10n/pot/twingle.pot diff --git a/l10n/de.systopia.twingle.pot b/l10n/de.systopia.twingle.pot new file mode 100644 index 0000000..439b9b5 --- /dev/null +++ b/l10n/de.systopia.twingle.pot @@ -0,0 +1,1016 @@ +#: ./CRM/Twingle/Config.php +msgid "No" +msgstr "" + +#: ./CRM/Twingle/Config.php +msgid "Raise Exception" +msgstr "" + +#: ./CRM/Twingle/Config.php +msgid "Create Activity" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Profile with ID \"%1\" not found" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Delete Twingle API profile %1" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Reset" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Delete" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "The profile is invalid and cannot be copied." +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Error" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "The profile to be copied could not be found." +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "A database error has occurred. See the log for details." +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "New Twingle API profile" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Edit Twingle API profile %1" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "New Profile" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Profile name" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Project IDs" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Contact Matcher (XCM) Profile" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Location type" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Location type for organisations" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Financial type" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Financial type (recurring)" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +msgid "Gender option for submitted value \"male\"" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +msgid "Gender option for submitted value \"female\"" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +msgid "Gender option for submitted value \"other\"" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +msgid "Prefix option for submitted value \"male\"" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +msgid "Prefix option for submitted value \"female\"" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +msgid "Prefix option for submitted value \"other\"" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Record %1 as" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Record %1 donations with contribution status" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +msgid "CiviSEPA creditor" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Use Double-Opt-In for newsletter" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Sign up for newsletter groups" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Sign up for postal mail groups" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Sign up for Donation receipt groups" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Default Campaign" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "- none -" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Set Campaign for" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Contribution" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Recurring Contribution" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Membership" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "SEPA Mandate" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Contacts (XCM)" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Create membership of type" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Create membership of type (recurring)" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "API Call for Membership Postprocessing" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "The API call must have the form 'Entity.Action'." +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Contribution source" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Required address components" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Street" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Postal Code" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +msgid "City" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +msgid "Country" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Custom field mapping" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Create contribution notes for" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +msgid "Purpose" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +msgid "Remarks" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Create contact notes for" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "User Extra Field" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Form/Settings.php +msgid "Save" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "Warning" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "No profile set." +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "<select profile>" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "none" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "CiviSEPA" +msgstr "" + +#: ./CRM/Twingle/Form/Profile.php +msgid "No mailing lists available" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Twingle ID Prefix" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Use CiviSEPA" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Use CiviSEPA generated reference" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Protect Recurring Contributions" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Activity Type" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Subject" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Status" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "Assigned To" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "This is required for activity creation" +msgstr "" + +#: ./CRM/Twingle/Form/Settings.php +msgid "-select-" +msgstr "" + +#: ./CRM/Twingle/Page/Profiles.php ./managed/Navigation__twingle_configuration.mgd.php +msgid "Twingle API Profiles" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Unknown attribute %1." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Profile name cannot be empty." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Only alphanumeric characters, space and the underscore (_) are allowed for profile names." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "A profile with the name '%1' already exists." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Project ID(s) [%1] already used in profile '%2'." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Could not parse custom field mapping." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Custom field custom_%1 does not exist." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Custom field custom_%1 is not in a CustomGroup that extends one of the supported CiviCRM entities." +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Could not save/update profile: %1" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Could not reset default profile: %1" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Could not delete profile: %1" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Contribution Status" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Bank transfer" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Debit manual" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Debit automatic" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Credit card" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Mobile phone Germany" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "PayPal" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "SOFORT Überweisung" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Amazon Pay" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Apple Pay" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Google Pay" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Paydirekt" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Twint" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "iDEAL" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Postfinance" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Bancontact" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "Generic Payment Method" +msgstr "" + +#: ./CRM/Twingle/Profile.php +msgid "never" +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Invalid donation rhythm." +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Payment method could not be matched to existing payment instrument." +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Invalid date for parameter \"confirmed_at\"." +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Invalid date for parameter \"user_birthdate\"." +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Gender could not be matched to existing gender." +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Invalid format for custom fields." +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "campaign_id must be a numeric string. " +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Unknown country %1." +msgstr "" + +#: ./CRM/Twingle/Submission.php +msgid "Could not calculate SEPA cycle day from configuration." +msgstr "" + +#: ./CRM/Twingle/Tools.php +msgid "This is a Twingle recurring contribution. It should be terminated through the Twingle interface, otherwise it will still be collected." +msgstr "" + +#: ./CRM/Twingle/Tools.php +msgid "Recurring contribution [%1] (Transaction ID '%2') was terminated by a user. You need to end the corresponding record in Twingle as well, or it will still be collected." +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +msgid "Project ID" +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +msgid "The Twingle project ID." +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +msgid "Transaction ID" +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +msgid "The unique transaction ID of the donation" +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php +msgid "Cancelled at" +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php +msgid "The date when the donation was cancelled, format: YmdHis." +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php +msgid "Cancel reason" +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php +msgid "The reason for the donation being cancelled." +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php +msgid "Invalid date for parameter \"cancelled_at\"." +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php +msgid "SEPA Mandate for contribution [%1 not found." +msgstr "" + +#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php +msgid "Could not terminate SEPA mandate" +msgstr "" + +#: ./api/v3/TwingleDonation/Endrecurring.php +msgid "Ended at" +msgstr "" + +#: ./api/v3/TwingleDonation/Endrecurring.php +msgid "The date when the recurring donation was ended, format: YmdHis." +msgstr "" + +#: ./api/v3/TwingleDonation/Endrecurring.php +msgid "Invalid date for parameter \"ended_at\"." +msgstr "" + +#: ./api/v3/TwingleDonation/Endrecurring.php +msgid "SEPA Mandate for recurring contribution [%1 not found." +msgstr "" + +#: ./api/v3/TwingleDonation/Endrecurring.php +msgid "SEPA Mandate [%1] already terminated." +msgstr "" + +#: ./api/v3/TwingleDonation/Endrecurring.php +msgid "Mandate closed by TwingleDonation.Endrecurring API call" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Confirmed at" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The date when the donation was issued, format: YmdHis." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The purpose of the donation." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Amount" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The donation amount in minor currency unit." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Currency" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The ISO-4217 currency code of the donation." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Newsletter" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Whether to subscribe the contact to the newsletter group defined in the profile." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Postal mailing" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Whether to subscribe the contact to the postal mailing group defined in the profile." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Donation receipt" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Whether the contact requested a donation receipt." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Payment method" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The Twingle payment method used for the donation." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Donation rhythm" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The interval which the donation is recurring in." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "SEPA IBAN" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The IBAN for SEPA Direct Debit payments, conforming with ISO 13616-1:2007." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "SEPA BIC" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The BIC for SEPA Direct Debit payments, conforming with ISO 9362." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "SEPA Direct Debit Mandate reference" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The mandate reference for SEPA Direct Debit payments." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "SEPA Direct Debit Account holder" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The account holder for SEPA Direct Debit payments." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Anonymous donation" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Whether the donation is submitted anonymously." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Gender" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The gender of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Date of birth" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The date of birth of the contact, format: Ymd." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Formal title" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The formal title of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Email address" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The e-mail address of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "First name" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The first name of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Last name" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The last name of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Street address" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The street address of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Postal code" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The postal code of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The city of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The country of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Telephone" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The telephone number of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Company" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The company of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Language" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The preferred language of the contact. A 2-digit ISO-639-1 language code." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "User extra field" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Additional information of the contact." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Campaign ID" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "The CiviCRM ID of a campaign to assign the contribution." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Custom fields" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Additional information for either the contact or the (recurring) contribution." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Additional remarks for the donation." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Contribution with the given transaction ID already exists." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Organisation contact could not be found or created." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Individual contact could not be found or created." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Missing attribute %1 for SEPA mandate" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "SEPA creditor is not configured for profile \"%1\"." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Could not create recurring contribution." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Could not find recurring contribution with given parent transaction ID." +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Could not create contribution" +msgstr "" + +#: ./api/v3/TwingleDonation/Submit.php +msgid "Twingle membership postprocessing call has failed, see log for more information" +msgstr "" + +#: ./managed/Navigation__twingle_configuration.mgd.php +msgid "Twingle API Configuration" +msgstr "" + +#: ./managed/Navigation__twingle_configuration.mgd.php +msgid "Twingle API Settings" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Select which location type to use for addresses for individuals, either when no organisation name is specified, or an organisation address can not be shared with the individual contact." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Put your project's Twingle ID in here, to activate this profile for that project." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "You can also provide multiple project IDs separated by a comma." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "The Contact Matcher (XCM) manages the identification or creation of the related contact." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "We recommend creating a new XCM profile only to be used with the Twingle API." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Select which location type to use for addresses for organisations and shared organisation addresses for individual contacts." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Select which financial type to use for one-time contributions." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Select which financial type to use for recurring contributions." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Select whether to use CiviCRM's Double-Opt-In feature for subscribing to mailing lists. Note that this only works for public mailing lists. Any non-public mailing list selected above will be ignored when this setting is enabled." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Also, do not forget to disable Twingle's own Double Opt-In option in the Twingle Manager to avoid subscribers receiving multiple confirmation e-mails. Only one or the other option should be enabled." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Some organisations have specific conventions on how a membership should be created. Since the Twingle-API can only create a \"bare bone\" membership object, you can enter a API Call (as 'Entity.Action') to adjust any newly created membership to your organisation's needs." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "The API call would receive the following parameters:
      \n
    • membership_id: The ID of the newly created membership
    • \n
    • contact_id: The ID of the contact involved
    • \n
    • organization_id: The ID of the contact's organisation, potentially empty
    • \n
    • contribution_id: The ID contribution received, potentially empty
    • \n
    • recurring_contribution_id: The ID of the recurring contribution. If empty, this was only a one-off donation.
    • \n
    " +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Select the address components that must be present to create or update an address for the contact." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Depending on your XCM settings, the transferred address might replace an existing one." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "Since in some cases Twingle send the country of the user as the only address parameter, depending on your XCM configuration, not declaring other address components as required might lead to the current user address being overwritten with an address containing the country only." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "

    Map Twingle custom fields to CiviCRM fields using the following format (each assignment in a separate line):

    \n
    twingle_field_1=custom_123
    twingle_field_2=custom_789
    \n

    Always use the custom_[id] notation for CiviCRM custom fields.

    \n

    This works for fields that Twingle themselves provide in the custom_fields parameter, and for any other parameter (e.g. user_extrafield)

    \n

    Only custom fields extending one of the following CiviCRM entities are allowed:

    \n
      \n
    • Contact – Will be set on the Individual contact
    • \n
    • Individual – Will be set on the Individual contact
    • \n
    • Organization – Will be set on the Organization contact, if an organisation name was submitted
    • \n
    • Contribution – Will be set on the contribution
    • \n
    • ContributionRecur – Will be set on the recurring contribution and deriving single contributions
    • \n
    " +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "

    Create a contribution note for each field specified in this selection.

    \n

    Tip: You can enable or disable this fields in the TwingleMANAGER.

    " +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.hlp +msgid "

    Create a contact note for each field specified in this selection.

    \n

    Tip: You can enable or disable this fields in the TwingleMANAGER.

    " +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "General settings" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Help" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "XCM Profile" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Gender/Prefix for value 'male'" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Gender/Prefix for value 'female'" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Gender/Prefix for value 'other'" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Payment methods" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Groups and Correlations" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Newsletter Double Opt-In" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Membership Postprocessing" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Are you sure you want to reset the default profile?" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Are you sure you want to delete the profile %1?" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Profile.tpl +msgid "Profile name not given or invalid." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Settings.hlp +msgid "When the %1 is enabled and one of its payment instruments is assigned to a Twingle payment method (practically the debit_manual payment method), submitting a Twingle donation through the API will create a SEPA mandate with the given data." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Settings.hlp +msgid "When the %1 is enabled, you can activate this to use your own references instead of the ones submitted by Twingle." +msgstr "" + +#: ./templates/CRM/Twingle/Form/Settings.hlp +msgid "Will protect all recurring contributions created by Twingle from termination, since this does NOT terminate the Twingle collection process" +msgstr "" + +#: ./templates/CRM/Twingle/Form/Settings.hlp +msgid "You can use this setting to add a prefix to the Twingle transaction ID, in order to avoid collisions with other transaction ids." +msgstr "" + +#: ./templates/CRM/Twingle/Page/Configuration.tpl +msgid "Profiles" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Configuration.tpl +msgid "Configure profiles" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Configuration.tpl +msgid "Settings" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Configuration.tpl +msgid "Configure extension settings" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "New profile" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Selectors" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Used" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Last Used" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Operations" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Edit profile %1" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Edit" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Copy profile %1" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Copy" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Reset profile %1" +msgstr "" + +#: ./templates/CRM/Twingle/Page/Profiles.tpl +msgid "Delete profile %1" +msgstr "" + +#: ./twingle.php +msgid "Twingle API: Access Twingle API" +msgstr "" + +#: ./twingle.php +msgid "Allows access to the Twingle API actions." +msgstr "" + diff --git a/l10n/pot/twingle.pot b/l10n/pot/twingle.pot deleted file mode 100644 index 7de3f72..0000000 --- a/l10n/pot/twingle.pot +++ /dev/null @@ -1,312 +0,0 @@ -#: ./CRM/Twingle/Form/Profile.php -msgid "Delete Twingle API profile %1" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Reset" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Delete" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Edit Twingle API profile %1" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "New Twingle API profile" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Profile name" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Project IDs" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Location type" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Financial Type" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Gender option for submitted value \"male\"" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Gender option for submitted value \"female\"" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Gender option for submitted value \"other\"" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Record %1 as" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "CiviSEPA creditor" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Sign up for newsletter groups" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Sign up for postal mail groups" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Sign up for Donation receipt groups" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Form/Settings.php -msgid "Save" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Only alphanumeric characters and the underscore (_) are allowed for profile names." -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Create contribution notes for" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php -msgid "Purpose" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php -msgid "Remarks" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "Create contact notes for" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "User Extra Field" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "CiviSEPA" -msgstr "" - -#: ./CRM/Twingle/Form/Profile.php -msgid "No mailing lists available" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Unknown attribute %1." -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Bank transfer" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Debit manual" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Debit automatic" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Credit card" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Mobile phone Germany" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "PayPal" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "SOFORT Überweisung" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Amazon Pay" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "paydirekt" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Apple Pay" -msgstr "" - -#: ./CRM/Twingle/Profile.php -msgid "Google Pay" -msgstr "" - -#: ./CRM/Twingle/Submission.php -msgid "Invalid donation rhythm." -msgstr "" - -#: ./CRM/Twingle/Submission.php -msgid "Payment method could not be matched to existing payment instrument." -msgstr "" - -#: ./CRM/Twingle/Submission.php -msgid "Invalid date for parameter \"confirmed_at\"." -msgstr "" - -#: ./CRM/Twingle/Submission.php -msgid "Invalid date for parameter \"user_birthdate\"." -msgstr "" - -#: ./CRM/Twingle/Submission.php -msgid "Gender could not be matched to existing gender." -msgstr "" - -#: ./CRM/Twingle/Submission.php -msgid "Unknown country %1." -msgstr "" - -#: ./api/v3/TwingleDonation/Cancel.php -msgid "Invalid date for parameter \"cancelled_at\"." -msgstr "" - -#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php -msgid "Could not terminate SEPA mandate" -msgstr "" - -#: ./api/v3/TwingleDonation/Endrecurring.php -msgid "Invalid date for parameter \"ended_at\"." -msgstr "" - -#: ./api/v3/TwingleDonation/Endrecurring.php -msgid "Mandate closed by TwingleDonation.Endrecurring API call" -msgstr "" - -#: ./api/v3/TwingleDonation/Submit.php -msgid "Contribution with the given transaction ID already exists." -msgstr "" - -#: ./api/v3/TwingleDonation/Submit.php -msgid "Individual contact could not be found or created." -msgstr "" - -#: ./api/v3/TwingleDonation/Submit.php -msgid "Organisation contact could not be found or created." -msgstr "" - -#: ./api/v3/TwingleDonation/Submit.php -msgid "Missing attribute %1 for SEPA mandate" -msgstr "" - -#: ./api/v3/TwingleDonation/Submit.php -msgid "Could not create recurring contribution." -msgstr "" - -#: ./api/v3/TwingleDonation/Submit.php -msgid "Could not create contribution" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "General settings" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "Payment methods" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "Groups" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "Create contribution note for" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "Create contact note for" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.hlp -msgid "

    Create a contribution note for each field specified in this selection.

    \n

    Tip: You can enable or disable this fields in the TwingleMANAGER.

    " -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.hlp -msgid "

    Create a contact note for each field specified in this selection.

    \n

    Tip: You can enable or disable this fields in the TwingleMANAGER.

    " -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "Are you sure you want to reset the default profile?" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "Are you sure you want to delete the profile %1?" -msgstr "" - -#: ./templates/CRM/Twingle/Form/Profile.tpl -msgid "Profile name not given or invalid." -msgstr "" - -#: ./templates/CRM/Twingle/Form/Settings.hlp -msgid "When the %1 is enabled and one of its payment instruments is assigned to a Twingle payment method (practically the debit_manual payment method), submitting a Twingle donation through the API will create a SEPA mandate with the given data." -msgstr "" - -#: ./templates/CRM/Twingle/Form/Settings.tpl -msgid "Help" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Configuration.tpl -msgid "Profiles" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Configuration.tpl -msgid "Configure profiles" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Configuration.tpl -msgid "Settings" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Configuration.tpl -msgid "Configure extension settings" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "New profile" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Properties" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Operations" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Selector" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Edit profile %1" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Edit" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Reset profile %1" -msgstr "" - -#: ./templates/CRM/Twingle/Page/Profiles.tpl -msgid "Delete profile %1" -msgstr "" - From f7b15ac4f69864a5b68998c0431de9772db4f4ef Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 13 Jun 2024 13:03:57 +0200 Subject: [PATCH 08/43] Update German translation --- l10n/de_DE/LC_MESSAGES/twingle.mo | Bin 7350 -> 29888 bytes l10n/de_DE/LC_MESSAGES/twingle.po | 1029 +++++++++++++++++++++++++++-- 2 files changed, 974 insertions(+), 55 deletions(-) diff --git a/l10n/de_DE/LC_MESSAGES/twingle.mo b/l10n/de_DE/LC_MESSAGES/twingle.mo index 9fd805479ba6759289b95892b87803fec1e57720..78498f0172762c1ca981c4a566147748306906b4 100644 GIT binary patch literal 29888 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 z zjISICdjg&MEc7BmlTSv^1a-<)n5tq!t8%RXD38%XthrLSwBkhF`Fj6$f~8n8O~=2U zpk$wA+NBlRm_L_XCt5U5YAl}6{;?cljv27Tm0`e%hE7@yvy1oiF>F3(#+yJ3OpUb< zYrN8O8GG}JlvpvM=5r#vBeQB6$hq61>rArHRg+4;Lb4*0L=K=BL-0uF!V3-JVmVvk zWlB+VC5sW67S;<6lbzYRPK1cD_T$xTm?0A+qy_|rgD!`W6=s)yZd+WI`Ay{ocRzo< zVQEcQp;ff&8gMMJL~BU?gW|B$X2KFt@w7Rh@+fqou zm;ksM(8W>;tPiiM{Sqv{8&Rvi8xf*miv!tpvlV7xSec(4A7YdknlGt%!iBo%6u0eF zimUaTOiu_<#7%EY*lJbi>AU27&D4XY3KgFF9R}Ysc|}VD7U8>(H?W>gkDKXqo=e!f zP;PIGg@hj?(@JX2XB4Fy*yUxH<`H%&*a z8aHZ-r8X?R=mzB*@#>P~qor20ZD_=D`;!wz&==c#8P->sVAZvn5kI++vqrHkEc-$^ zNx79sz0#8_$>4I2yPT|%7I!KLNAL#SNW#tx?_7k`HV<@SS@UJKIr3zZ+v7kWBsh}9 zvKyJzeW>dsUP@TG=FnGZYem!!S+aPJuz4vFj%60*&S9;h5cV3>_VNSs+5FJ~ac)nq zzRZzql{@A02H0v@IHQ*~B+Q#b3`|{$x_qw&>{;y!OOU{%h8y<*tK{ELQS55C8oTVDdAvzIATYtP&NiWE?9lHx^KFq{;W%^!n?tt?_~FcfPW zazR{|6$#2n!owUGhudSeVVj9DS3@R_1`t50g`5@$GZSgyAj2@Nvk?iA*%Ehn^RL}T zNXT1wv!$OGW>7y8?2^)yYOAL3SaL+8)^{nquyHWpcr`8-yBDiRp33yz22MKMwx|Ho z`*RE0fzTtU@nREetqADe;vlIuhSrAOyvL!1$P{ev1;XUbjr2xi?Bt0&FMa83%WRN!yXm?H3W<=M$8Ky?hpq*E0kS2 zdKc=%?dYZNedO)QqiTm{%uRHOF9uuT*;CanF@ zxIqq%hI)N>mUkQEP?Vii4a;^pH;miXzO;)LQKe4VP?pHZ&xEb{4nyUs_5S3Oe?Md= zbzK5d5>eWeHJasQsyt=yc~l=Hhz(A}LwnGY^R_2o&#kzsmKi{qjd&~Clu(Rmn>Qh^ zF7kT5D?gz_7%|6?Voo4U4Ex>M4n%dGi9mJJph7aoCchD|%pI*h+o`dgfzb-a3{0`@ z6)baeQsbA8B`={~ zaF@Gii|N+l9okd$X&djF$T{WQwGQYVEzhEn$u>lC}relh0qxi6Qz8aZ<{W<%23DpQGMegNyk zo&Z@XZJQJ~EC$k#;UG+IlseIMJ};rgU3h-+e*d3rlxptL!Px3iD5n>S_)TZ2T)rX- z@>9d)_v?0cHY%Uu%1C0kY~Zy1^XIkN7h<22y6!!fb@rkQi#wXod3O#=@*?kaSNRtG zCkQNZ+se=mvtPy};*|Q7hD?*i+b#EDc?%zi=I3MrL*Td+f)6(=O`!N*tAMj~-$ie8a;QUSc3kiyvgu_bosD@^Yo;mRU zf>^-o>E8%{qjldO5{zYa)N^m9Wrdx_x_1vM?TeRn!(~I z7uE4U%G9+R#KR(Tek#emGE?c%XN5=lkul{-9cFgNEsFytaUuFXIe`Rnma}88WmR%^ zqoaSYR(;OH<=foRl9k&%;Px3fmY_tQ)TS4MAlTw))fkk54DMl;S&Imgc~7R*#ZYfW zl_){bFx;sBG;cd(@b&IYIMT*K4FS95>BGrJCxln&{g^>r*(Wj^GPsa4Y@{p`_bTQO z%{c*+yvsL0DHLkS!h8v~*UJ@^J7P05RmJkwikHu;h)wLp7UrqIhjfu0ry6;btdT$1 oY05>)V&p{|ACS2hZM3v?<{#+H>Hl7HyIT2cH|#jureGHRAE|+|)c^nh delta 2578 zcmYk+Sxi({7{Kv^ORdP_zLn*=0HT13)&&I_6kI?Da5viKG92JC!=3ToJ0diezBKVg zTXQsxiD_b&n5Ooj(zv9ynrLFuHZ_e8ed^|e-Pg3yHrDq44FmCnGrx1MbI$$l_kH8_ z?=vp09{6(dm{W=oX67+Zj#4U$S!4NNTz*2SGk68h;;wN@W#R)IkE5SdY87VVGHk$X z>_`68Q9dT&36%3+$H{mdtCSj0cUa_eBZt!#U>RDt6KCKdoQcO#4mgMM{hK%e?_w_g zf?44%WC7yHWO^NqY+=kUy{tXAz~mPy=T3eDy4gmDqzt_$E%pk5Mx4HL_Pdz}Yy4 z{I+5tN+2Fe>W`oV^e(Q)%P5(7h-_6^yyaY+g(O|A#Q{0Eo`oFLf|OP5N7>MxzCMW3 z4t*#Y7{Dp`GRm93fpXwmX)oYBuHQ!~)mK=6|Kc>vBP!WnDfyQJ8@O>FcO&IfVQ#L; zP--t;;rbK>yejwU90@dw-SWK|xB};(G~phU^G=`y^cqU--$p6r2PgsD!y0@zh5Sox z){yTd*oGDwlxBMmCGyX(5P!otIFVB%bCoz9Yf!p+C(47numlex&r+vR0)Gdkmo6hm zsk;L#D37|2jX0LmmSGD@GrfqFcnPIxen2^BG>svp%0Vg3bXnNqj zrDG*i7D@@qPy*jMbbmk{U?CZBP~M~$C9)$!Ur@)=|9%)1eXHX72&n_-UhNN45HnMp!Q_3U{2s4L= z^oG2x^uU(%O`f013>8adPRhE0DGe#3h`EO6r$z&x(veatiF$afWkGACN+4-l&D_kC zQW0-zNLq)7qGV)R13|3@O!CrZhLBUM=TS{u7ZQb)BDD(cu_M zuNR^8hIF5p1jq{radX|)-dp&q+G?pn=upXts&yr9l@qgLSAuHT_Owca`8Y_D$) zjV!E)r`w$%?wOqh%guv=D)W3{*t}}ZobG8m&{ibw26iN{+&C89Re7)t+x&DS5#xSSkn^N$c&$D->>gZjn^z#??0M^=ijY zx}77n8CjG!4i{-dQ?2>CX`Q*fwb)$SS}|I?6{a{mH?L936b;I^#RI3`A_0Mp#k6a6 z+_>r)JG{gk3|CeUof5UZK&w!?UVfxYTcNhLWTZ=GY!PGnL`;ZFY|~pYL6=Mi14D*OHpmJDu7k61yYNuG-hD zBT+@QwAW$#KCf!Wd=+wOgeWnYj{OVGPY3eHZPziX5I0BdL*_rXVoIHMlR>}s%2QP< RZ=h&{O^HbvnIjcb{{tNlxQ74$ diff --git a/l10n/de_DE/LC_MESSAGES/twingle.po b/l10n/de_DE/LC_MESSAGES/twingle.po index 0e8d890..4907d37 100644 --- a/l10n/de_DE/LC_MESSAGES/twingle.po +++ b/l10n/de_DE/LC_MESSAGES/twingle.po @@ -16,6 +16,22 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0.1\n" +#: CRM/Twingle/Config.php +msgid "No" +msgstr "Nein" + +#: CRM/Twingle/Config.php +msgid "Raise Exception" +msgstr "Ausnahme auslösen" + +#: CRM/Twingle/Config.php +msgid "Create Activity" +msgstr "Aktivität erstellen" + +#: CRM/Twingle/Form/Profile.php +msgid "Profile with ID \"%1\" not found" +msgstr "Profil mit ID \"%1\" nicht gefunden" + #: CRM/Twingle/Form/Profile.php msgid "Delete Twingle API profile %1" msgstr "Twingle-API-Profil %1 löschen" @@ -29,49 +45,107 @@ msgid "Delete" msgstr "Löschen" #: CRM/Twingle/Form/Profile.php -msgid "Edit Twingle API profile %1" -msgstr "Twingle-API-Profil %1 bearbeiten" +msgid "The profile is invalid and cannot be copied." +msgstr "Das Profil ist ungültig und kann nicht kopiert werden." + +#: CRM/Twingle/Form/Profile.php +msgid "Error" +msgstr "Fehler" + +#: CRM/Twingle/Form/Profile.php +msgid "The profile to be copied could not be found." +msgstr "Das zu kopierende Profil konnte nicht gefunden werden." + +#: CRM/Twingle/Form/Profile.php +msgid "A database error has occurred. See the log for details." +msgstr "" +"Ein Datenbankfehler ist aufgetreten. Siehe das Protokoll für Einzeilheiten." #: CRM/Twingle/Form/Profile.php msgid "New Twingle API profile" msgstr "Neues Twingle-API-Profil" +#: CRM/Twingle/Form/Profile.php +msgid "Edit Twingle API profile %1" +msgstr "Twingle-API-Profil %1 bearbeiten" + +#: CRM/Twingle/Form/Profile.php +msgid "New Profile" +msgstr "Neues Profil" + #: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Page/Profiles.tpl msgid "Profile name" msgstr "Profil-Name" -#: CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Project IDs" msgstr "Projekt-IDs" #: CRM/Twingle/Form/Profile.php +msgid "Contact Matcher (XCM) Profile" +msgstr "Extended Contact Matcher (XCM)-Profil" + +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Location type" msgstr "Adresskategorie" -#: CRM/Twingle/Form/Profile.php -msgid "Financial Type" +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "Location type for organisations" +msgstr "Adresskategorie für Organisationen" + +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "Financial type" msgstr "Zuwendungsart" -#: CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "Financial type (recurring)" +msgstr "Zuwendungsart (wiederkehrend)" + +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Gender option for submitted value \"male\"" msgstr "Geschlechtsoption für übermittelten Wert \"male\"" -#: CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Gender option for submitted value \"female\"" msgstr "Geschlechtsoption für übermittelten Wert \"female\"" -#: CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Gender option for submitted value \"other\"" msgstr "Geschlechtsoption für übermittelten Wert \"other\"" +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +msgid "Prefix option for submitted value \"male\"" +msgstr "Anredeoption für übermittelten Wert \"male\"" + +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +msgid "Prefix option for submitted value \"female\"" +msgstr "Anredeoption für übermittelten Wert \"female\"" + +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php +msgid "Prefix option for submitted value \"other\"" +msgstr "Anredeoption für übermittelten Wert \"other\"" + #: CRM/Twingle/Form/Profile.php msgid "Record %1 as" msgstr "%1 erfassen als" #: CRM/Twingle/Form/Profile.php +msgid "Record %1 donations with contribution status" +msgstr "%1 erfassen mit Zuwendungsstatus" + +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "CiviSEPA creditor" msgstr "CiviSEPA-Kreditor" +#: CRM/Twingle/Form/Profile.php +msgid "Use Double-Opt-In for newsletter" +msgstr "Nutze Double-Opt-In für Newsletter" + #: CRM/Twingle/Form/Profile.php msgid "Sign up for newsletter groups" msgstr "Newsletter-Gruppen beitreten" @@ -84,27 +158,91 @@ msgstr "Postversand-Gruppen beitreten" msgid "Sign up for Donation receipt groups" msgstr "Zuwendungsbescheinigungs-Gruppen beitreten" -#: CRM/Twingle/Form/Profile.php CRM/Twingle/Form/Settings.php -msgid "Save" -msgstr "Speichern" +#: CRM/Twingle/Form/Profile.php +msgid "Default Campaign" +msgstr "Standardkampagne" #: CRM/Twingle/Form/Profile.php -msgid "" -"Only alphanumeric characters and the underscore (_) are allowed for profile " -"names." -msgstr "" -"Nur alphanumerische Zeichen und der Unterstrich (_) sind für Profilnamen " -"erlaubt." +msgid "- none -" +msgstr "- keine -" + +#: CRM/Twingle/Form/Profile.php +msgid "Set Campaign for" +msgstr "Kampagne setzen für" + +#: CRM/Twingle/Form/Profile.php +msgid "Contribution" +msgstr "Zuwendung" + +#: CRM/Twingle/Form/Profile.php +msgid "Recurring Contribution" +msgstr "Wiederkehrende Zuwendung" + +#: CRM/Twingle/Form/Profile.php +msgid "Membership" +msgstr "Mitgliedschaft" + +#: CRM/Twingle/Form/Profile.php +msgid "SEPA Mandate" +msgstr "SEPA-Lastschriftmandat" + +#: CRM/Twingle/Form/Profile.php +msgid "Contacts (XCM)" +msgstr "Kontakte (XCM)" + +#: CRM/Twingle/Form/Profile.php +msgid "Create membership of type" +msgstr "Mitgliedschaft mit Typ erstellen" + +#: CRM/Twingle/Form/Profile.php +msgid "Create membership of type (recurring)" +msgstr "Mitgliedschaft mit Typ erstellen (wiederkehrend)" + +#: CRM/Twingle/Form/Profile.php +msgid "API Call for Membership Postprocessing" +msgstr "API-Aufruf für Mitgliedschafts-Nachbearbeitung" + +#: CRM/Twingle/Form/Profile.php +msgid "The API call must have the form 'Entity.Action'." +msgstr "Der API-Aufruf muss in der Form 'Entität.Aktion' angegeben werden." + +#: CRM/Twingle/Form/Profile.php +msgid "Contribution source" +msgstr "Herkunft der Zuwendung" + +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl +msgid "Required address components" +msgstr "Erforderliche Adresskomponenten" + +#: CRM/Twingle/Form/Profile.php +msgid "Street" +msgstr "Straße" + +#: CRM/Twingle/Form/Profile.php +msgid "Postal Code" +msgstr "Postleitzahl" + +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php +msgid "City" +msgstr "Ort" + +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php +msgid "Country" +msgstr "Land" + +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl +msgid "Custom field mapping" +msgstr "Zuordnung benutzerdefinierter Felder" #: CRM/Twingle/Form/Profile.php msgid "Create contribution notes for" msgstr "Zuwendungs-Notizen erstellen für" -#: CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php msgid "Purpose" msgstr "Zweck" -#: CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php msgid "Remarks" msgstr "Anmerkungen" @@ -116,6 +254,26 @@ msgstr "Kontakt-Notizen erstellen für" msgid "User Extra Field" msgstr "Benutzer-Extra-Feld" +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Form/Settings.php +msgid "Save" +msgstr "Speichern" + +#: CRM/Twingle/Form/Profile.php +msgid "Warning" +msgstr "Warnung" + +#: CRM/Twingle/Form/Profile.php +msgid "No profile set." +msgstr "Kein Profil eingestellt." + +#: CRM/Twingle/Form/Profile.php +msgid "<select profile>" +msgstr "<Profil auswählen>" + +#: CRM/Twingle/Form/Profile.php +msgid "none" +msgstr "keines" + #: CRM/Twingle/Form/Profile.php msgid "CiviSEPA" msgstr "CiviSEPA" @@ -124,17 +282,114 @@ msgstr "CiviSEPA" msgid "No mailing lists available" msgstr "Keine Versandlisten verfügbar" +#: CRM/Twingle/Form/Settings.php +msgid "Twingle ID Prefix" +msgstr "Twingle-ID-Präfix" + +#: CRM/Twingle/Form/Settings.php +msgid "Use CiviSEPA" +msgstr "CiviSEPA verwenden" + +#: CRM/Twingle/Form/Settings.php +msgid "Use CiviSEPA generated reference" +msgstr "Von CiviSEPA erstellte Referenc verwenden" + +#: CRM/Twingle/Form/Settings.php +msgid "Protect Recurring Contributions" +msgstr "Wiederkehrende Zuwendungen schützen" + +#: CRM/Twingle/Form/Settings.php +msgid "Activity Type" +msgstr "Aktivitätstyp" + +#: CRM/Twingle/Form/Settings.php +msgid "Subject" +msgstr "Betreff" + +#: CRM/Twingle/Form/Settings.php +msgid "Status" +msgstr "Status" + +#: CRM/Twingle/Form/Settings.php +msgid "Assigned To" +msgstr "Zugewiesen an" + +#: CRM/Twingle/Form/Settings.php +msgid "This is required for activity creation" +msgstr "Diese Angaben sind erforderlich für das Erstellen von Aktivitäten" + +#: CRM/Twingle/Form/Settings.php +msgid "-select-" +msgstr "- auswählen -" + +#: CRM/Twingle/Page/Profiles.php +#: managed/Navigation__twingle_configuration.mgd.php +msgid "Twingle API Profiles" +msgstr "Twingle-API-Profile" + #: CRM/Twingle/Profile.php msgid "Unknown attribute %1." msgstr "Unbekanntes Attribut %1." +#: CRM/Twingle/Profile.php +msgid "Profile name cannot be empty." +msgstr "Profilname darf nicht leer sein." + +#: CRM/Twingle/Profile.php +msgid "" +"Only alphanumeric characters, space and the underscore (_) are allowed for " +"profile names." +msgstr "" +"Nur alphanumerische Zeichen und der Unterstrich (_) sind für Profilnamen " +"erlaubt." + +#: CRM/Twingle/Profile.php +msgid "A profile with the name '%1' already exists." +msgstr "Ein profil mit dem Namen '%1' existiert bereits." + +#: CRM/Twingle/Profile.php +msgid "Project ID(s) [%1] already used in profile '%2'." +msgstr "Projekt-ID(s) [%1] werden ebreits in Profil '%2' verwendet." + +#: CRM/Twingle/Profile.php +msgid "Could not parse custom field mapping." +msgstr "Zuordnung benutzerdefinierter Felder konnte nicht verarbeitet werden." + +#: CRM/Twingle/Profile.php +msgid "Custom field custom_%1 does not exist." +msgstr "Benutezrdefiniertes Feld custom_%1 existiert nicht." + +#: CRM/Twingle/Profile.php +msgid "" +"Custom field custom_%1 is not in a CustomGroup that extends one of the " +"supported CiviCRM entities." +msgstr "" +"Benutzerdefiniertes Feld custom_%1 ist in einer benutzerdefinierten " +"Feldgruppe, die eine nicht unterstützte Entität erweitert." + +#: CRM/Twingle/Profile.php +msgid "Could not save/update profile: %1" +msgstr "Speichern/Aktualisieren von Profil fehlgeschlagen: %1" + +#: CRM/Twingle/Profile.php +msgid "Could not reset default profile: %1" +msgstr "Zurücksetzen des Standardprofils fehlgeschlagen: %1" + +#: CRM/Twingle/Profile.php +msgid "Could not delete profile: %1" +msgstr "Löschen des Profils fehlgeschlagen: %1" + +#: CRM/Twingle/Profile.php +msgid "Contribution Status" +msgstr "Zuwendungsstatus" + #: CRM/Twingle/Profile.php msgid "Bank transfer" msgstr "Banküberweisung" #: CRM/Twingle/Profile.php msgid "Debit manual" -msgstr "manuelle Abbuchung" +msgstr "Manuelle Abbuchung" #: CRM/Twingle/Profile.php msgid "Debit automatic" @@ -160,10 +415,6 @@ msgstr "SOFORT-Überweisung" msgid "Amazon Pay" msgstr "Amazon Pay" -#: CRM/Twingle/Profile.php -msgid "paydirekt" -msgstr "paydirekt" - #: CRM/Twingle/Profile.php msgid "Apple Pay" msgstr "Apple Pay" @@ -172,9 +423,37 @@ msgstr "Apple Pay" msgid "Google Pay" msgstr "Google Pay" +#: CRM/Twingle/Profile.php +msgid "Paydirekt" +msgstr "Paydirekt" + +#: CRM/Twingle/Profile.php +msgid "Twint" +msgstr "Twint" + +#: CRM/Twingle/Profile.php +msgid "iDEAL" +msgstr "iDEAL" + +#: CRM/Twingle/Profile.php +msgid "Postfinance" +msgstr "Postfinance" + +#: CRM/Twingle/Profile.php +msgid "Bancontact" +msgstr "Bancontact" + +#: CRM/Twingle/Profile.php +msgid "Generic Payment Method" +msgstr "Generische Zahlungsmethode" + +#: CRM/Twingle/Profile.php +msgid "never" +msgstr "nie" + #: CRM/Twingle/Submission.php msgid "Invalid donation rhythm." -msgstr "Ungültiger Zuwendungsrhythmus" +msgstr "Ungültiger Zuwendungsrhythmus." #: CRM/Twingle/Submission.php msgid "Payment method could not be matched to existing payment instrument." @@ -195,77 +474,670 @@ msgid "Gender could not be matched to existing gender." msgstr "" "Geschlecht konnte keiner existierenden Geschlechtsoption zugeordnet werden." +#: CRM/Twingle/Submission.php +msgid "Invalid format for custom fields." +msgstr "Ungültiges Format für benutzerdefinierte Felder." + +#: CRM/Twingle/Submission.php +msgid "campaign_id must be a numeric string. " +msgstr "campaign_id muss eine numerische Zeichenkette sein. " + #: CRM/Twingle/Submission.php msgid "Unknown country %1." msgstr "Unbekanntes Land %1." +#: CRM/Twingle/Submission.php +msgid "Could not calculate SEPA cycle day from configuration." +msgstr "SEPA-Einzugstag konnte nicht aus der Konfiguration berechnet werden." + +#: CRM/Twingle/Tools.php +msgid "" +"This is a Twingle recurring contribution. It should be terminated through " +"the Twingle interface, otherwise it will still be collected." +msgstr "" +"Dies ist eine wiederkehrende Twingle-Zuwendung. Sie sollte durch die Twingle-" +"Benutzeroberfläche beendet werden, da sie ansonsten weiter eingezogen wird." + +#: CRM/Twingle/Tools.php +msgid "" +"Recurring contribution [%1] (Transaction ID '%2') was terminated by a user. " +"You need to end the corresponding record in Twingle as well, or it will " +"still be collected." +msgstr "" +"Wiederkehrende Zuwendung [%1] (Transaktions-ID '%2') wurde von einem " +"Benutzer beendet. Es ist erforderlich, den zugehörigen Datensatz auch in " +"Twingle zu beenden, da die Zuwendung ansonsten weiter eingezogen wird." + +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Submit.php +msgid "Project ID" +msgstr "Projekt-ID" + +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Submit.php +msgid "The Twingle project ID." +msgstr "Die Twingle-Projekt-ID." + +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Submit.php +msgid "Transaction ID" +msgstr "Transaktions-ID" + +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Submit.php +msgid "The unique transaction ID of the donation" +msgstr "Die eindeutige Transaktions-ID der Zuwendung" + +#: api/v3/TwingleDonation/Cancel.php +msgid "Cancelled at" +msgstr "Storniert am" + +#: api/v3/TwingleDonation/Cancel.php +msgid "The date when the donation was cancelled, format: YmdHis." +msgstr "Das Datum der Stornierung der Zuwendung, Format: YmdHis." + +#: api/v3/TwingleDonation/Cancel.php +msgid "Cancel reason" +msgstr "Stornierungsgrund" + +#: api/v3/TwingleDonation/Cancel.php +msgid "The reason for the donation being cancelled." +msgstr "Der Grund der Stornierung der Zuwendung." + #: api/v3/TwingleDonation/Cancel.php msgid "Invalid date for parameter \"cancelled_at\"." msgstr "Ungültiges Datum für Parameter \"cancelled_at\"." +#: api/v3/TwingleDonation/Cancel.php +msgid "SEPA Mandate for contribution [%1 not found." +msgstr "SEPA-Lastschriftmandat für Zuwendung [%1] nicht gefunden." + #: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php msgid "Could not terminate SEPA mandate" msgstr "Konnte SEPA-Mandat nicht beenden" +#: api/v3/TwingleDonation/Endrecurring.php +msgid "Ended at" +msgstr "Beendet am" + +#: api/v3/TwingleDonation/Endrecurring.php +msgid "The date when the recurring donation was ended, format: YmdHis." +msgstr "" +"Das Datum der Beendigung der wiederkehrenden Zuwendung, Format: YmdHis." + #: api/v3/TwingleDonation/Endrecurring.php msgid "Invalid date for parameter \"ended_at\"." msgstr "Ungültiges Datum für Parameter \"ended_at\"." +#: api/v3/TwingleDonation/Endrecurring.php +msgid "SEPA Mandate for recurring contribution [%1 not found." +msgstr "" +"SEPA-lastschriftmandat für wiederkehrende Zuwendung [%1] nicht gefunden." + +#: api/v3/TwingleDonation/Endrecurring.php +msgid "SEPA Mandate [%1] already terminated." +msgstr "SEPA-lastschriftmandat [%1] wurde bereits beendet." + #: api/v3/TwingleDonation/Endrecurring.php msgid "Mandate closed by TwingleDonation.Endrecurring API call" msgstr "Mandat geschlossen durch TwingleDonation.Endrecurring API-Aufruf" #: api/v3/TwingleDonation/Submit.php -msgid "Contribution with the given transaction ID already exists." -msgstr "Zuwendung mit der gegebenen Transkations-ID existiert bereits." +msgid "Confirmed at" +msgstr "Bestätigt am" #: api/v3/TwingleDonation/Submit.php -msgid "Individual contact could not be found or created." -msgstr "Kontakt für Person konnte nicht gefunden oder erstellt werden." +msgid "The date when the donation was issued, format: YmdHis." +msgstr "Das Datum der Ausstellung der Zuwendung, Format: YmdHis." + +#: api/v3/TwingleDonation/Submit.php +msgid "The purpose of the donation." +msgstr "Der Anlass der Zuwendung." + +#: api/v3/TwingleDonation/Submit.php +msgid "Amount" +msgstr "Betrag" + +#: api/v3/TwingleDonation/Submit.php +msgid "The donation amount in minor currency unit." +msgstr "" +"Der Zuwendungsbetrag in untergeordneter Währungseinheit (z. B. Euro-Cent)" + +#: api/v3/TwingleDonation/Submit.php +msgid "Currency" +msgstr "Währung" + +#: api/v3/TwingleDonation/Submit.php +msgid "The ISO-4217 currency code of the donation." +msgstr "Der ISO-4217-Code der Währung der Zuwendung." + +#: api/v3/TwingleDonation/Submit.php +msgid "Newsletter" +msgstr "Newsletter" + +#: api/v3/TwingleDonation/Submit.php +msgid "" +"Whether to subscribe the contact to the newsletter group defined in the " +"profile." +msgstr "" +"Ob der Kontakt in die im Profil definierte Newsletter-Gruppe aufgenommen " +"werden soll." + +#: api/v3/TwingleDonation/Submit.php +msgid "Postal mailing" +msgstr "Postverteiler" + +#: api/v3/TwingleDonation/Submit.php +msgid "" +"Whether to subscribe the contact to the postal mailing group defined in the " +"profile." +msgstr "" +"Ob der Kontakt in die im Profil definierte Postverteilergruppe aufgenommen " +"werden soll." + +#: api/v3/TwingleDonation/Submit.php +msgid "Donation receipt" +msgstr "Zuwendungsbescheinigung" + +#: api/v3/TwingleDonation/Submit.php +msgid "Whether the contact requested a donation receipt." +msgstr "Ob der Kontakt eine Zuwendungsbescheinigung angefordert hat." + +#: api/v3/TwingleDonation/Submit.php +msgid "Payment method" +msgstr "Zahlungsmethode" + +#: api/v3/TwingleDonation/Submit.php +msgid "The Twingle payment method used for the donation." +msgstr "Die für die Zuwendung verwendete Twingle-Zahlungsmethode." + +#: api/v3/TwingleDonation/Submit.php +msgid "Donation rhythm" +msgstr "Zuwendungsrhythmus" + +#: api/v3/TwingleDonation/Submit.php +msgid "The interval which the donation is recurring in." +msgstr "Das Intervall, in dem die Zuwendung wiederkehrt." + +#: api/v3/TwingleDonation/Submit.php +msgid "SEPA IBAN" +msgstr "SEPA-IBAN" + +#: api/v3/TwingleDonation/Submit.php +msgid "" +"The IBAN for SEPA Direct Debit payments, conforming with ISO 13616-1:2007." +msgstr "Die IBAN für SEPA-Lastschriftzahlungen, konform mit ISO 13616-1:2007." + +#: api/v3/TwingleDonation/Submit.php +msgid "SEPA BIC" +msgstr "SEPA-BIC" + +#: api/v3/TwingleDonation/Submit.php +msgid "The BIC for SEPA Direct Debit payments, conforming with ISO 9362." +msgstr "Die BIC für SEPA-Lastschriftzahlungen, konform mit ISO 9362." + +#: api/v3/TwingleDonation/Submit.php +msgid "SEPA Direct Debit Mandate reference" +msgstr "SEPA-Lastschriftmandatsreferenz" + +#: api/v3/TwingleDonation/Submit.php +msgid "The mandate reference for SEPA Direct Debit payments." +msgstr "Die Mandatsreferenz für SEPA-Lastschriftzahlungen." + +#: api/v3/TwingleDonation/Submit.php +msgid "SEPA Direct Debit Account holder" +msgstr "Inhaber des SEPA-Lastschriftkontos" + +#: api/v3/TwingleDonation/Submit.php +msgid "The account holder for SEPA Direct Debit payments." +msgstr "Der Kontoinhaber für SEPA-Lastschriften." + +#: api/v3/TwingleDonation/Submit.php +msgid "Anonymous donation" +msgstr "Anonyme Zuwendung" + +#: api/v3/TwingleDonation/Submit.php +msgid "Whether the donation is submitted anonymously." +msgstr "Ob die Zuwendung anonym übermittelt wird." + +#: api/v3/TwingleDonation/Submit.php +msgid "Gender" +msgstr "Geschlecht" + +#: api/v3/TwingleDonation/Submit.php +msgid "The gender of the contact." +msgstr "Das Geschlecht des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Date of birth" +msgstr "Geburtsdatum" + +#: api/v3/TwingleDonation/Submit.php +msgid "The date of birth of the contact, format: Ymd." +msgstr "Das Geburtsdatum des Kontakts, Format: Ymd." + +#: api/v3/TwingleDonation/Submit.php +msgid "Formal title" +msgstr "Formeller Titel" + +#: api/v3/TwingleDonation/Submit.php +msgid "The formal title of the contact." +msgstr "Der formelle Titel des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Email address" +msgstr "E-Mail-Adresse" + +#: api/v3/TwingleDonation/Submit.php +msgid "The e-mail address of the contact." +msgstr "Die E-Mail-Adresse des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "First name" +msgstr "Vorname" + +#: api/v3/TwingleDonation/Submit.php +msgid "The first name of the contact." +msgstr "Der Vorname des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Last name" +msgstr "Nachname" + +#: api/v3/TwingleDonation/Submit.php +msgid "The last name of the contact." +msgstr "Der Nachname des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Street address" +msgstr "Straße" + +#: api/v3/TwingleDonation/Submit.php +msgid "The street address of the contact." +msgstr "Die Straße des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Postal code" +msgstr "Postleitzahl" + +#: api/v3/TwingleDonation/Submit.php +msgid "The postal code of the contact." +msgstr "Die Postleitzahl des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "The city of the contact." +msgstr "Der Wohnort des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "The country of the contact." +msgstr "Das Land des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Telephone" +msgstr "Telefonnummer" + +#: api/v3/TwingleDonation/Submit.php +msgid "The telephone number of the contact." +msgstr "Die Telefonnummer des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Company" +msgstr "Firma" + +#: api/v3/TwingleDonation/Submit.php +msgid "The company of the contact." +msgstr "Die Firma/Arbeitgeber des Kontakts." + +#: api/v3/TwingleDonation/Submit.php +msgid "Language" +msgstr "Sprache" + +#: api/v3/TwingleDonation/Submit.php +msgid "" +"The preferred language of the contact. A 2-digit ISO-639-1 language code." +msgstr "" +"Die bevorzugte Sprache des Kontakts. Ein zweistelliger ISO-639-1-Sprachcode." + +#: api/v3/TwingleDonation/Submit.php +msgid "User extra field" +msgstr "Zusätzliches Benutzerfeld (user extra field)" + +#: api/v3/TwingleDonation/Submit.php +msgid "Additional information of the contact." +msgstr "Zusätzliche Kontaktinformationen." + +#: api/v3/TwingleDonation/Submit.php +msgid "Campaign ID" +msgstr "Kampagnen-ID" + +#: api/v3/TwingleDonation/Submit.php +msgid "The CiviCRM ID of a campaign to assign the contribution." +msgstr "" +"Die CiviCRM-ID einer Kampagne, der die Zuwendung zugeordnet werden soll." + +#: api/v3/TwingleDonation/Submit.php +msgid "Custom fields" +msgstr "Benutzerdefinierte Felder" + +#: api/v3/TwingleDonation/Submit.php +msgid "" +"Additional information for either the contact or the (recurring) " +"contribution." +msgstr "" +"Zusätzliche Informationen für entweder den Kontakt oder die (wiederkehrende) " +"Zuwendung." + +#: api/v3/TwingleDonation/Submit.php +msgid "Additional remarks for the donation." +msgstr "Zusätzliche Anmerkungen für die Zuwendung." + +#: api/v3/TwingleDonation/Submit.php +msgid "Contribution with the given transaction ID already exists." +msgstr "Zuwendung mit der gegebenen Transkations-ID existiert bereits." #: api/v3/TwingleDonation/Submit.php msgid "Organisation contact could not be found or created." msgstr "Kontakt für Organisation konnte nicht gefunden oder erstellt werden." +#: api/v3/TwingleDonation/Submit.php +msgid "Individual contact could not be found or created." +msgstr "Kontakt für Person konnte nicht gefunden oder erstellt werden." + #: api/v3/TwingleDonation/Submit.php msgid "Missing attribute %1 for SEPA mandate" msgstr "Fehlendes Attribute %1 für SEPA-Mandat" +#: api/v3/TwingleDonation/Submit.php +msgid "SEPA creditor is not configured for profile \"%1\"." +msgstr "SEPA-Kreditor ist nicht konfiguriert für Profil \"%1\"." + #: api/v3/TwingleDonation/Submit.php msgid "Could not create recurring contribution." -msgstr "Wiederkehrende Zuwendung konnte nicht erstellen." +msgstr "Wiederkehrende Zuwendung konnte nicht erstellt werden." + +#: api/v3/TwingleDonation/Submit.php +msgid "Could not find recurring contribution with given parent transaction ID." +msgstr "" +"Wiederkehrende Zuwendung mit angegebener Transkations-ID konnte nicht " +"gefunden werden." #: api/v3/TwingleDonation/Submit.php msgid "Could not create contribution" msgstr "Zuwendung konnte nicht erstellt werden" +#: api/v3/TwingleDonation/Submit.php +msgid "" +"Twingle membership postprocessing call has failed, see log for more " +"information" +msgstr "" +"Die Nachbearbeitung (postprocessing) der Twingle-Mitgliedschaft ist " +"fehlgeschlagen, siehe Protokoll für weitere Informationen." + +#: managed/Navigation__twingle_configuration.mgd.php +msgid "Twingle API Configuration" +msgstr "Twingle-API-Konfiguration" + +#: managed/Navigation__twingle_configuration.mgd.php +msgid "Twingle API Settings" +msgstr "Twingle-API-Einstellungen" + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Select which location type to use for addresses for individuals, either when " +"no organisation name is specified, or an organisation address can not be " +"shared with the individual contact." +msgstr "" +"Wählen Sie die zu verwendende Adresskategorie für Personen, entweder wenn " +"kein Organisationsname angegeben wurde, oder die Organisationsadresse nicht " +"mit dem Personen-Kontakt geteilt werden kann." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Put your project's Twingle ID in here, to activate this profile for that " +"project." +msgstr "" +"Geben Sie hier die Twingle-ID des Projekts ein, um das Profil für dieses " +"Projekt zu aktivieren." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "You can also provide multiple project IDs separated by a comma." +msgstr "" +"Es können auch mehrere durch Kommata getrennte Projekt-IDs angegeben werden." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"The Contact Matcher (XCM) manages the identification or creation of the " +"related contact." +msgstr "" +"Der Extended Contact Matcher (XCM) verwaltet die Identifizierung und " +"Erstellung des zugehörigen Kontakts." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"We recommend creating a new XCM profile only to be used with the Twingle API." +msgstr "" +"Es wird empfohlen, ein neues XCM-Profil zur ausschließlichen Verwendung mit " +"der Twingle-API zu erstellen." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Select which location type to use for addresses for organisations and shared " +"organisation addresses for individual contacts." +msgstr "" +"Wählen Sie die zu verwendende Adresskategorie für Organisationen und " +"geteilte Organisationsadressen für Personen-Kontakte." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "Select which financial type to use for one-time contributions." +msgstr "Wählen Sie die Zuwendungsart für einmalige Zuwendungen." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "Select which financial type to use for recurring contributions." +msgstr "Wählen Sie die Zuwendungsart für wiederkehrende Zuwendungen." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Select whether to use CiviCRM's Double-Opt-In feature for subscribing to " +"mailing lists. Note that this only works for public mailing lists. Any non-" +"public mailing list selected above will be ignored when this setting is " +"enabled." +msgstr "" +"Wählen Sie, ob das Double-Opt-In-Verfahren von CiviCRM für das Abonnieren " +"von E-Mail-Verteilern verwendet werden soll. Beachten Sie, dass dies nur für " +"öffentliche Verteilerlisten funktioniert. Alle ausgewählten nicht-" +"öffentlichen Verteilerlisten werden bei Aktivierung dieser Einstellung " +"ignoriert." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Also, do not forget to disable Twingle's own Double Opt-In option in the " +"Twingle Manager to avoid subscribers receiving multiple confirmation e-" +"mails. Only one or the other option should be enabled." +msgstr "" +"Vergessen Sie außerdem nicht, das Double-Opt-In-Verfahren im Twingle-Manager " +"auszuschalten, um zu verhindern, dass Abonnenten mehrere Bestätigungs-E-Mail-" +"Nachrichten erhalten. Nur eines der beiden Verfahren sollte aktiviert werden." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Some organisations have specific conventions on how a membership should be " +"created. Since the Twingle-API can only create a \"bare bone\" membership " +"object, you can enter a API Call (as 'Entity.Action') to adjust any newly " +"created membership to your organisation's needs." +msgstr "" +"Einige Organisationen haben bestimmte Vorschriften bzgl. der Erstellung von " +"Mitgliedschaften. Da die Twingle-API nur ein \"reines\" " +"Mitgliedschaftsobjekt erstellt, können Sie einen API-Aufruf (als 'Entität." +"Aktion') angeben, um neu erstellt Mitgliedschaften an die Anforderungen " +"Ihrer Organisation anzupassen." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"The API call would receive the following parameters:
      \n" +"
    • membership_id: The ID of the newly created " +"membership
    • \n" +"
    • contact_id: The ID of the contact involved
    • \n" +"
    • organization_id: The ID of the contact's " +"organisation, potentially empty
    • \n" +"
    • contribution_id: The ID contribution received, " +"potentially empty
    • \n" +"
    • recurring_contribution_id: The ID of the recurring " +"contribution. If empty, this was only a one-off donation.
    • \n" +"
    " +msgstr "" +"Der API-Aufruf nimmt die folgenden Parameter entgegen:
      \n" +"
    • membership_id: Die ID der neu erstellten " +"Mitgliedschaft
    • \n" +"
    • contact_id: Die ID des betreffenden Kontakts
    • \n" +"
    • organization_id: Die ID der dem Kontakt " +"zugeordneten Organisation (optional)
    • \n" +"
    • contribution_id: Die ID der erhaltenen Zuwendung " +"(optional)
    • \n" +"
    • recurring_contribution_id: Die ID der " +"wiederkehrenden Zuwendung (optional, falls leer war dies eine einmalige " +"Zuwendung)
    • \n" +"
    " + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Select the address components that must be present to create or update an " +"address for the contact." +msgstr "" +"Wählen Sie die erforderlichen Adresskomponenten für die Erstellung oder " +"Aktualisierung der Adresse des Kontakts." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Depending on your XCM settings, the transferred address might replace an " +"existing one." +msgstr "" +"Abhängig von den XCM-Einstellungen kann die übertragene Adresse eine " +"bestehende ersetzen." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Since in some cases Twingle send the country of the user as the only address " +"parameter, depending on your XCM configuration, not declaring other address " +"components as required might lead to the current user address being " +"overwritten with an address containing the country only." +msgstr "" +"Da Twingle in einigen Fällen das Land des Benutzers als den einzigen " +"Adressparameter übermittelt, kann - abhängig von Ihrer XCM-Konfiguration - " +"die Definition anderer Adresskomponenten als nicht erforderlich dazu führen, " +"dass die bestehende Adresse mit einer neuen überschrieben wird, die nur das " +"Land enthält." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"

    Map Twingle custom fields to CiviCRM fields using the following format " +"(each assignment in a separate line):

    \n" +"
    twingle_field_1=custom_123
    twingle_field_2=custom_789
    \n" +"

    Always use the custom_[id] notation for CiviCRM custom " +"fields.

    \n" +"

    This works for fields that Twingle themselves provide in the " +"custom_fields parameter, and for any other parameter (e.g. " +"user_extrafield)

    \n" +"

    Only custom fields extending one of the following CiviCRM entities " +"are allowed:

    \n" +"
      \n" +"
    • Contact – Will be set on the Individual " +"contact
    • \n" +"
    • Individual – Will be set on the Individual " +"contact
    • \n" +"
    • Organization – Will be set on the " +"Organization contact, if an organisation name was submitted
    • \n" +"
    • Contribution – Will be set on the " +"contribution
    • \n" +"
    • ContributionRecur – Will be set on the " +"recurring contribution and deriving single contributions
    • \n" +"
    " +msgstr "" +"

    Zuordnung von Twingle-Feldern zu benutzerdefinierten CiviCRM-Feldern mit " +"folgendem Format (jede Zuordnung in einer neuen Zeile):

    \n" +"
    twingle_field_1=custom_123
    twingle_field_2=custom_789
    \n" +"

    Verwenden Sie immer die Notation custom_[id] für " +"benutzerdefinierte Felder in CiviCRM.

    \n" +"

    Unterstützt werden Felder, die Twingle im Parameter " +"custom_fields bereitstellt, sowie für alle anderen Parameter (z." +" B. user_extrafield)

    \n" +"

    Es werden nur benutzerdefinierte CiviCRM-Felder der folgenden " +"Entitäten unterstützt:

    \n" +"
      \n" +"
    • Kontakt – wird am Personen-Kontakt gesetzt\n" +"
    • Person – wird am Personen-Kontakt gesetzt\n" +"
    • Organisation – wird am Organisations-" +"Kontakt gesetzt, sofern ein Organisationsname übermittelt wurde
    • \n" +"
    • Zuwendung – wird an der Zuwendung gesetzt\n" +"
    • Wiederkehrende Zuwendung – wird an der " +"wiederkehrenden Zuwendung sowie an abgeleiteten Einzel-Zuwendungen gesetzt\n" +"
    " + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"

    Create a contribution note for each field specified in this selection.\n" +"

    Tip: You can enable or disable this fields in the TwingleMANAGER.

    " +msgstr "" +"

    Erstelle eine Zuwendungs-Notiz für jedes Feld, das in dieser Auswahl " +"angegeben ist.

    \n" +"

    Tipp: Sie können diese Felder im TwingleMANAGER aktivieren oder " +"deaktivieren.

    " + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"

    Create a contact note for each field specified in this selection.

    \n" +"

    Tip: You can enable or disable this fields in the TwingleMANAGER.

    " +msgstr "" +"

    Erstelle eine Kontakt-Notiz für jedes Feld, das in dieser Auswahl " +"angegeben ist.

    \n" +"

    Tipp: Sie können diese Felder im TwingleMANAGER aktivieren oder " +"deaktivieren.

    " + #: templates/CRM/Twingle/Form/Profile.tpl msgid "General settings" msgstr "Allgemeine Einstellungen" +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "Help" +msgstr "Hilfe" + +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "XCM Profile" +msgstr "XCM-Profil" + +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "Gender/Prefix for value 'male'" +msgstr "Geschlechts-/Anredeoption für übermittelten Wert \"male\"" + +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "Gender/Prefix for value 'female'" +msgstr "Geschlechts-/Anredeoption für übermittelten Wert \"female\"" + +#: templates/CRM/Twingle/Form/Profile.tpl +msgid "Gender/Prefix for value 'other'" +msgstr "Geschlechts-/Anredeoption für übermittelten Wert \"other\"" + #: templates/CRM/Twingle/Form/Profile.tpl msgid "Payment methods" msgstr "Zahlungsmethoden" #: templates/CRM/Twingle/Form/Profile.tpl -msgid "Groups" -msgstr "Gruppen" +msgid "Groups and Correlations" +msgstr "Gruppen und Beziehungen" #: templates/CRM/Twingle/Form/Profile.tpl -msgid "Create contribution note for" -msgstr "Zuwendungs-Notiz erstellen für" +msgid "Newsletter Double Opt-In" +msgstr "Double-Opt-In für Newsletter" #: templates/CRM/Twingle/Form/Profile.tpl -msgid "Create contact note for" -msgstr "Kontakt-Notiz erstellen für" - -#: templates/CRM/Twingle/Form/Profile.hlp -msgid "Create a contribution note for each field specified in this selection. Tip: You can enable or disable this fields in the TwingleMANAGER." -msgstr "Erstelle eine Zuwendungs-Notiz für jedes Feld, das in dieser Auswahl angegeben ist. Tipp: Sie können diese Felder im TwingleMANAGER aktivieren oder deaktivieren." - -#: templates/CRM/Twingle/Form/Profile.hlp -msgid "Create a contact note for each field specified in this selection. Tip: You can enable or disable this fields in the TwingleMANAGER." -msgstr "Erstelle eine Kontakt-Notiz für jedes Feld, das in dieser Auswahl angegeben ist. Tipp: Sie können diese Felder im TwingleMANAGER aktivieren oder deaktivieren." +msgid "Membership Postprocessing" +msgstr "Mitgliedschafts--Nachbearbeitung (Postprocessing)" #: templates/CRM/Twingle/Form/Profile.tpl msgid "Are you sure you want to reset the default profile?" @@ -291,9 +1163,30 @@ msgstr "" "debit_manual), wird bei Einsendung einer Twingle-Zuwendung durch " "die API ein SEPA-Mandat mit den gegebenen Daten erzeugt." -#: templates/CRM/Twingle/Form/Settings.tpl -msgid "Help" -msgstr "Hilfe" +#: templates/CRM/Twingle/Form/Settings.hlp +msgid "" +"When the %1 is enabled, you can activate this to use your own references " +"instead of the ones submitted by Twingle." +msgstr "" +"Wenn die %1 aktiviert ist, können Sie diese Option aktivieren, um Ihre " +"eigenen Referenzen statt der von Twingle übermittelten zu verwenden." + +#: templates/CRM/Twingle/Form/Settings.hlp +msgid "" +"Will protect all recurring contributions created by Twingle from " +"termination, since this does NOT terminate the Twingle collection process" +msgstr "" +"Schützt alle von Twingle erstellten wiederkehrenden Zuwendungen vor " +"Beendigung, da dies nicht den Einzug bei Twingle beendet." + +#: templates/CRM/Twingle/Form/Settings.hlp +msgid "" +"You can use this setting to add a prefix to the Twingle transaction ID, in " +"order to avoid collisions with other transaction ids." +msgstr "" +"Sie können diese Einstellung verwenden, um ein Präfix der Twingle-" +"Transaktions-ID voranzustellen, um Kollisionen mit anderen Transaktions-IDs " +"auszuschließen." #: templates/CRM/Twingle/Page/Configuration.tpl msgid "Profiles" @@ -316,17 +1209,21 @@ msgid "New profile" msgstr "Neues Profil" #: templates/CRM/Twingle/Page/Profiles.tpl -msgid "Properties" -msgstr "Eigenschaften" +msgid "Selectors" +msgstr "Selektoren" + +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "Used" +msgstr "Verwendet" + +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "Last Used" +msgstr "Zuletzt verwendet" #: templates/CRM/Twingle/Page/Profiles.tpl msgid "Operations" msgstr "Operationen" -#: templates/CRM/Twingle/Page/Profiles.tpl -msgid "Selector" -msgstr "Selektor" - #: templates/CRM/Twingle/Page/Profiles.tpl msgid "Edit profile %1" msgstr "Profil %1 bearbeiten" @@ -335,6 +1232,14 @@ msgstr "Profil %1 bearbeiten" msgid "Edit" msgstr "Bearbeiten" +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "Copy profile %1" +msgstr "Profil %1 kopieren" + +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "Copy" +msgstr "Kopieren" + #: templates/CRM/Twingle/Page/Profiles.tpl msgid "Reset profile %1" msgstr "Profil %1 zurücksetzen" @@ -343,5 +1248,19 @@ msgstr "Profil %1 zurücksetzen" msgid "Delete profile %1" msgstr "Profil % 1 löschen" -#~ msgid "Use Double-Opt-In for newsletter" -#~ msgstr "Nutze Double-Opt-In für Newsletter" +#: twingle.php +msgid "Twingle API: Access Twingle API" +msgstr "Twingle-API: Zugriff auf Twingle-API" + +#: twingle.php +msgid "Allows access to the Twingle API actions." +msgstr "Gewährt Zugriff auf Aktionen der Twingle-API." + +#~ msgid "Groups" +#~ msgstr "Gruppen" + +#~ msgid "Create contact note for" +#~ msgstr "Kontakt-Notiz erstellen für" + +#~ msgid "Properties" +#~ msgstr "Eigenschaften" From 96d0e5fbec5ad4953f8e1598713475aed0126bec Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 13 Jun 2024 13:24:34 +0200 Subject: [PATCH 09/43] Code style --- api/v3/TwingleDonation/Submit.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index c8ef884..cef2e33 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -535,6 +535,7 @@ function civicrm_api3_twingle_donation_Submit($params) { && (bool) ($params['newsletter'] ?? FALSE) && is_array($groups = $profile->getAttribute('newsletter_groups')) ) { + // TODO: Ensure the values being integers. $group_memberships = array_column( civicrm_api3( 'GroupContact', @@ -564,7 +565,7 @@ function civicrm_api3_twingle_donation_Submit($params) { 'contact_id' => $contact_id, ] ); - $subscription = CRM_Utils_Array::first($result['values']); + $subscription = reset($result['values']); $subscription['group_id'] = $group_id; $result_values['newsletter_subscriptions'][] = $subscription; } @@ -725,7 +726,7 @@ function civicrm_api3_twingle_donation_Submit($params) { // Create the mandate. $mandate = civicrm_api3('SepaMandate', 'createfull', $mandate_data); - $result_values['sepa_mandate'] = CRM_Utils_Array::first($mandate['values']); + $result_values['sepa_mandate'] = reset($mandate['values']); } else { // Set financial type depending on donation rhythm. This applies for @@ -798,12 +799,15 @@ function civicrm_api3_twingle_donation_Submit($params) { } $contribution = civicrm_api3('Contribution', 'create', $contribution_data); - if ($contribution['is_error']) { + /** @phpstan-var array{'values': array>, 'is_error'?: string} $contribution */ + if ((bool) ($contribution['is_error'] ?? FALSE)) { throw new CRM_Core_Exception( E::ts('Could not create contribution'), 'api_error' ); } + $contribution = reset($contribution['values']); + /** @phpstan-var array{'id': int} $contribution */ // Add notes to the contribution. /** @phpstan-var array $contribution_note_mappings */ @@ -816,13 +820,13 @@ function civicrm_api3_twingle_donation_Submit($params) { ) { Note::create(FALSE) ->addValue('entity_table', 'civicrm_contribution') - ->addValue('entity_id', reset($contribution['values'])['id']) + ->addValue('entity_id', $contribution['id']) ->addValue('note', reset($params[$target])) ->execute(); } } - $result_values['contribution'] = CRM_Utils_Array::first($contribution['values']); + $result_values['contribution'] = $contribution; } // MEMBERSHIP CREATION From 8d30b2a52a38f1c976388c1fa4d01d62b5dec6a4 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 13 Jun 2024 13:34:49 +0200 Subject: [PATCH 10/43] Fix excess `reset()` in note creation --- api/v3/TwingleDonation/Submit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index daceb36..bcaf275 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -816,7 +816,7 @@ function civicrm_api3_twingle_donation_Submit($params) { Note::create(FALSE) ->addValue('entity_table', 'civicrm_contribution') ->addValue('entity_id', reset($contribution['values'])['id']) - ->addValue('note', reset($params[$target])) + ->addValue('note', $params[$target]) ->execute(); } } From 07435ad9976756dae2edeaa19bd2a902dcb59fbb Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 13 Jun 2024 13:34:34 +0200 Subject: [PATCH 11/43] remove unnecessary `reset()` for $params[$target] --- api/v3/TwingleDonation/Submit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index cef2e33..be27fc8 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -821,7 +821,7 @@ function civicrm_api3_twingle_donation_Submit($params) { Note::create(FALSE) ->addValue('entity_table', 'civicrm_contribution') ->addValue('entity_id', $contribution['id']) - ->addValue('note', reset($params[$target])) + ->addValue('note', $params[$target]) ->execute(); } } From a363e1c888a83d44889ae9e3b345331833c77de1 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 13 Jun 2024 13:47:07 +0200 Subject: [PATCH 12/43] Version 1.5-alpha2 --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index d0876c8..d0aa028 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - - 1.5-dev - dev + 2024-06-13 + 1.5-alpha2 + alpha 5.56 From 6606d09dce06292659f0ee9516cb728a43304edb Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 13 Jun 2024 13:47:22 +0200 Subject: [PATCH 13/43] Back to 1.5-dev --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index d0aa028..d0876c8 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - 2024-06-13 - 1.5-alpha2 - alpha + + 1.5-dev + dev 5.56 From 72bfa3fb2c91a486e4bdedb4050b9c173d101959 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 3 Aug 2023 14:52:30 +0200 Subject: [PATCH 14/43] create custom exceptions --- CRM/Twingle/Exceptions/BaseException.php | 43 +++++++++++++++++++ CRM/Twingle/Exceptions/ProfileException.php | 16 +++++++ .../Exceptions/ProfileValidationError.php | 34 +++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 CRM/Twingle/Exceptions/BaseException.php create mode 100644 CRM/Twingle/Exceptions/ProfileException.php create mode 100644 CRM/Twingle/Exceptions/ProfileValidationError.php diff --git a/CRM/Twingle/Exceptions/BaseException.php b/CRM/Twingle/Exceptions/BaseException.php new file mode 100644 index 0000000..f61a4fe --- /dev/null +++ b/CRM/Twingle/Exceptions/BaseException.php @@ -0,0 +1,43 @@ +log_message = !empty($message) ? E::LONG_NAME . ': ' . $message : ''; + $this->error_code = $error_code; + } + + /** + * Returns the error message, but with the extension name prefixed. + * @return string + */ + public function getLogMessage() { + return $this->log_message; + } + + /** + * Returns the error code. + * @return string + */ + public function getErrorCode() { + return $this->error_code; + } + +} diff --git a/CRM/Twingle/Exceptions/ProfileException.php b/CRM/Twingle/Exceptions/ProfileException.php new file mode 100644 index 0000000..1141d10 --- /dev/null +++ b/CRM/Twingle/Exceptions/ProfileException.php @@ -0,0 +1,16 @@ +affected_field_name = $affected_field_name; + } + + /** + * Returns the name of the profile field that caused the exception. + * @return string + */ + public function getAffectedFieldName() { + return $this->affected_field_name; + } + +} From c971b6f8ebe82c66ea333e961a4aa5ad3047791f Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Mon, 7 Aug 2023 16:46:57 +0200 Subject: [PATCH 15/43] let CRM_Twingle_Profile class handle its validation --- CRM/Twingle/Form/Profile.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CRM/Twingle/Form/Profile.php b/CRM/Twingle/Form/Profile.php index 086dc2b..41d50be 100644 --- a/CRM/Twingle/Form/Profile.php +++ b/CRM/Twingle/Form/Profile.php @@ -566,6 +566,9 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { CRM_Core_Session::setStatus($e->getMessage(), E::ts('Warning')); } } + catch (ProfileValidationError $e) { + $this->setElementError($e->getAffectedFieldName(), $e->getMessage()); + } } return parent::validate(); From db94f26d6d0601509830c3ac37aacc4767656611 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Wed, 16 Aug 2023 14:03:44 +0200 Subject: [PATCH 16/43] use new namespace style --- CRM/Twingle/Exceptions/BaseException.php | 4 +++- CRM/Twingle/Exceptions/ProfileException.php | 4 +++- CRM/Twingle/Exceptions/ProfileValidationError.php | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CRM/Twingle/Exceptions/BaseException.php b/CRM/Twingle/Exceptions/BaseException.php index f61a4fe..eeb5354 100644 --- a/CRM/Twingle/Exceptions/BaseException.php +++ b/CRM/Twingle/Exceptions/BaseException.php @@ -1,12 +1,14 @@ Date: Wed, 16 Aug 2023 17:02:48 +0200 Subject: [PATCH 17/43] override the $code property inherited from Exception in BaseException --- CRM/Twingle/Exceptions/BaseException.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CRM/Twingle/Exceptions/BaseException.php b/CRM/Twingle/Exceptions/BaseException.php index eeb5354..cebf692 100644 --- a/CRM/Twingle/Exceptions/BaseException.php +++ b/CRM/Twingle/Exceptions/BaseException.php @@ -10,8 +10,11 @@ use CRM_Twingle_ExtensionUtil as E; */ class BaseException extends \Exception { - private string $error_code; - private string $log_message; + /** + * @var int|string + */ + protected $code; + protected string $log_message; /** * BaseException Constructor @@ -23,7 +26,7 @@ class BaseException extends \Exception { public function __construct(string $message = '', string $error_code = '') { parent::__construct($message, 1); $this->log_message = !empty($message) ? E::LONG_NAME . ': ' . $message : ''; - $this->error_code = $error_code; + $this->code = $error_code; } /** @@ -39,7 +42,7 @@ class BaseException extends \Exception { * @return string */ public function getErrorCode() { - return $this->error_code; + return $this->code; } } From 7c7c040b30874db7cded9c7529a442da8c911a23 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 17 Aug 2023 10:29:00 +0200 Subject: [PATCH 18/43] add error code for profile validation warning --- CRM/Twingle/Exceptions/ProfileValidationError.php | 1 + 1 file changed, 1 insertion(+) diff --git a/CRM/Twingle/Exceptions/ProfileValidationError.php b/CRM/Twingle/Exceptions/ProfileValidationError.php index e00c6c2..97a50ab 100644 --- a/CRM/Twingle/Exceptions/ProfileValidationError.php +++ b/CRM/Twingle/Exceptions/ProfileValidationError.php @@ -10,6 +10,7 @@ class ProfileValidationError extends BaseException { private string $affected_field_name; public const ERROR_CODE_PROFILE_VALIDATION_FAILED = 'profile_validation_failed'; + public const ERROR_CODE_PROFILE_VALIDATION_WARNING = 'profile_validation_warning'; /** * ProfileValidationError Constructor From eacc9cf496df268d495e63732f86b41326d54290 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 17 Aug 2023 12:51:28 +0200 Subject: [PATCH 19/43] pass $error_code to parent BaseException --- CRM/Twingle/Exceptions/ProfileValidationError.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CRM/Twingle/Exceptions/ProfileValidationError.php b/CRM/Twingle/Exceptions/ProfileValidationError.php index 97a50ab..b7a8f52 100644 --- a/CRM/Twingle/Exceptions/ProfileValidationError.php +++ b/CRM/Twingle/Exceptions/ProfileValidationError.php @@ -22,7 +22,7 @@ class ProfileValidationError extends BaseException { * A meaningful error code */ public function __construct(string $affected_field_name, string $message = '', string $error_code = '') { - parent::__construct($message, 1); + parent::__construct($message, $error_code); $this->affected_field_name = $affected_field_name; } From 1a5f77c0908a204990051f95e3d137642b3b9355 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Wed, 6 Sep 2023 16:26:02 +0200 Subject: [PATCH 20/43] refactoring --- CRM/Twingle/Profile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CRM/Twingle/Profile.php b/CRM/Twingle/Profile.php index b6f6225..3e4f6cc 100644 --- a/CRM/Twingle/Profile.php +++ b/CRM/Twingle/Profile.php @@ -666,7 +666,7 @@ class CRM_Twingle_Profile { * @param string $project_id * * @return CRM_Twingle_Profile - * @throws \CRM_Twingle_Exceptions_ProfileException + * @throws \CRM\Twingle\Exceptions\ProfileException * @throws \Civi\Core\Exception\DBQueryException */ public static function getProfileForProject($project_id) { From ea46e6a74780ffb46bae056b967b23199444b47b Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Fri, 8 Sep 2023 16:04:21 +0200 Subject: [PATCH 21/43] cherry pick of "make sure that default values are present" --- CRM/Twingle/Profile.php | 1 + 1 file changed, 1 insertion(+) diff --git a/CRM/Twingle/Profile.php b/CRM/Twingle/Profile.php index 3e4f6cc..10d412c 100644 --- a/CRM/Twingle/Profile.php +++ b/CRM/Twingle/Profile.php @@ -683,6 +683,7 @@ class CRM_Twingle_Profile { } // If none matches, use the default profile. + $default_profile = $profiles['default']; if (!empty($default_profile)) { return $default_profile; } From 8cfa270dff697e064eb318fd4dab57a8634a5958 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 21 Mar 2024 11:53:57 +0100 Subject: [PATCH 22/43] implement TwingleShop integration --- CRM/Twingle/Exceptions/BaseException.php | 48 -- CRM/Twingle/Form/Profile.php | 45 +- CRM/Twingle/Form/Settings.php | 55 +- CRM/Twingle/Page/Profiles.php | 1 + CRM/Twingle/Profile.php | 32 +- CRM/Twingle/Submission.php | 158 +++- CRM/Twingle/Upgrader.php | 11 + Civi/Api4/TwingleProduct.php | 12 + Civi/Api4/TwingleShop.php | 12 + Civi/Twingle/Exceptions/BaseException.php | 31 +- .../remotes/origin/master | 67 ++ ...tion.php~implement TwingleShop integration | 2 +- .../remotes/origin/master} | 0 ...rror.php~implement TwingleShop integration | 2 +- .../remotes/origin/master} | 0 Civi/Twingle/Shop/ApiCall.php | 269 +++++++ Civi/Twingle/Shop/BAO/TwingleProduct.php | 649 +++++++++++++++ Civi/Twingle/Shop/BAO/TwingleShop.php | 478 +++++++++++ Civi/Twingle/Shop/DAO/TwingleProduct.php | 327 ++++++++ Civi/Twingle/Shop/DAO/TwingleShop.php | 307 ++++++++ Civi/Twingle/Shop/Exceptions/ApiCallError.php | 19 + .../Shop/Exceptions/LineItemException.php | 14 + .../Shop/Exceptions/ProductException.php | 26 + .../Twingle/Shop/Exceptions/ShopException.php | 22 + Civi/Twingle/Shop/Utils/TwingleShopUtils.php | 155 ++++ api/v3/TwingleDonation/Submit.php | 41 +- api/v3/TwingleProduct/Create.php | 138 ++++ api/v3/TwingleProduct/Delete.php | 72 ++ api/v3/TwingleProduct/Get.php | 137 ++++ api/v3/TwingleProduct/Getsingle.php | 54 ++ api/v3/TwingleShop/Create.php | 80 ++ api/v3/TwingleShop/Delete.php | 79 ++ api/v3/TwingleShop/Fetch.php | 97 +++ api/v3/TwingleShop/Get.php | 111 +++ api/v3/TwingleShop/Getsingle.php | 54 ++ css/twingle_shop.css | 37 + info.xml | 2 +- js/twingle_shop.js | 741 ++++++++++++++++++ sql/auto_uninstall.sql | 21 + sql/civicrm_twingle_shop.sql | 66 ++ templates/CRM/Twingle/Form/Profile.hlp | 9 + templates/CRM/Twingle/Form/Profile.tpl | 76 +- templates/CRM/Twingle/Form/Settings.hlp | 4 + templates/CRM/Twingle/Form/Settings.tpl | 26 +- templates/CRM/Twingle/Page/Profiles.tpl | 7 +- .../api/v3/TwingleProduct/CreateTest.php | 54 ++ .../api/v3/TwingleProduct/DeleteTest.php | 54 ++ .../phpunit/api/v3/TwingleProduct/GetTest.php | 54 ++ .../api/v3/TwingleProduct/GetsingleTest.php | 54 ++ .../phpunit/api/v3/TwingleShop/CreateTest.php | 54 ++ .../phpunit/api/v3/TwingleShop/DeleteTest.php | 54 ++ tests/phpunit/api/v3/TwingleShop/GetTest.php | 54 ++ .../api/v3/TwingleShop/GetsingleTest.php | 54 ++ tests/phpunit/bootstrap.php | 65 ++ twingle.civix.php | 22 + twingle.php | 25 + .../CRM/Twingle/TwingleProduct.entityType.php | 10 + xml/schema/CRM/Twingle/TwingleProduct.xml | 75 ++ .../CRM/Twingle/TwingleShop.entityType.php | 10 + xml/schema/CRM/Twingle/TwingleShop.xml | 73 ++ 60 files changed, 5200 insertions(+), 106 deletions(-) delete mode 100644 CRM/Twingle/Exceptions/BaseException.php create mode 100644 Civi/Api4/TwingleProduct.php create mode 100644 Civi/Api4/TwingleShop.php create mode 100644 Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master rename CRM/Twingle/Exceptions/ProfileException.php => Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration (94%) rename Civi/Twingle/Exceptions/{ProfileException.php => ProfileException.php~refs/remotes/origin/master} (100%) rename CRM/Twingle/Exceptions/ProfileValidationError.php => Civi/Twingle/Exceptions/ProfileValidationError.php~implement TwingleShop integration (96%) rename Civi/Twingle/Exceptions/{ProfileValidationError.php => ProfileValidationError.php~refs/remotes/origin/master} (100%) create mode 100644 Civi/Twingle/Shop/ApiCall.php create mode 100644 Civi/Twingle/Shop/BAO/TwingleProduct.php create mode 100644 Civi/Twingle/Shop/BAO/TwingleShop.php create mode 100644 Civi/Twingle/Shop/DAO/TwingleProduct.php create mode 100644 Civi/Twingle/Shop/DAO/TwingleShop.php create mode 100644 Civi/Twingle/Shop/Exceptions/ApiCallError.php create mode 100644 Civi/Twingle/Shop/Exceptions/LineItemException.php create mode 100644 Civi/Twingle/Shop/Exceptions/ProductException.php create mode 100644 Civi/Twingle/Shop/Exceptions/ShopException.php create mode 100644 Civi/Twingle/Shop/Utils/TwingleShopUtils.php create mode 100644 api/v3/TwingleProduct/Create.php create mode 100644 api/v3/TwingleProduct/Delete.php create mode 100644 api/v3/TwingleProduct/Get.php create mode 100644 api/v3/TwingleProduct/Getsingle.php create mode 100644 api/v3/TwingleShop/Create.php create mode 100644 api/v3/TwingleShop/Delete.php create mode 100644 api/v3/TwingleShop/Fetch.php create mode 100644 api/v3/TwingleShop/Get.php create mode 100644 api/v3/TwingleShop/Getsingle.php create mode 100644 css/twingle_shop.css create mode 100644 js/twingle_shop.js create mode 100644 sql/auto_uninstall.sql create mode 100644 sql/civicrm_twingle_shop.sql create mode 100644 tests/phpunit/api/v3/TwingleProduct/CreateTest.php create mode 100644 tests/phpunit/api/v3/TwingleProduct/DeleteTest.php create mode 100644 tests/phpunit/api/v3/TwingleProduct/GetTest.php create mode 100644 tests/phpunit/api/v3/TwingleProduct/GetsingleTest.php create mode 100644 tests/phpunit/api/v3/TwingleShop/CreateTest.php create mode 100644 tests/phpunit/api/v3/TwingleShop/DeleteTest.php create mode 100644 tests/phpunit/api/v3/TwingleShop/GetTest.php create mode 100644 tests/phpunit/api/v3/TwingleShop/GetsingleTest.php create mode 100644 tests/phpunit/bootstrap.php create mode 100644 xml/schema/CRM/Twingle/TwingleProduct.entityType.php create mode 100644 xml/schema/CRM/Twingle/TwingleProduct.xml create mode 100644 xml/schema/CRM/Twingle/TwingleShop.entityType.php create mode 100644 xml/schema/CRM/Twingle/TwingleShop.xml diff --git a/CRM/Twingle/Exceptions/BaseException.php b/CRM/Twingle/Exceptions/BaseException.php deleted file mode 100644 index cebf692..0000000 --- a/CRM/Twingle/Exceptions/BaseException.php +++ /dev/null @@ -1,48 +0,0 @@ -log_message = !empty($message) ? E::LONG_NAME . ': ' . $message : ''; - $this->code = $error_code; - } - - /** - * Returns the error message, but with the extension name prefixed. - * @return string - */ - public function getLogMessage() { - return $this->log_message; - } - - /** - * Returns the error code. - * @return string - */ - public function getErrorCode() { - return $this->code; - } - -} diff --git a/CRM/Twingle/Form/Profile.php b/CRM/Twingle/Form/Profile.php index 41d50be..73022cd 100644 --- a/CRM/Twingle/Form/Profile.php +++ b/CRM/Twingle/Form/Profile.php @@ -251,6 +251,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { // Assign template variables. $this->assign('op', $this->_op); + $this->assign('twingle_use_shop', (int) Civi::settings()->get('twingle_use_shop')); $this->assign('profile_name', $profile_name); $this->assign('is_default', $is_default); @@ -354,6 +355,10 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { static::getPrefixOptions() ); + // Add script and css for Twingle Shop integration + Civi::resources()->addScriptUrl(E::url('js/twingle_shop.js')); + Civi::resources()->addStyleFile(E::LONG_NAME, 'css/twingle_shop.css'); + $payment_instruments = CRM_Twingle_Profile::paymentInstruments(); $this->assign('payment_instruments', $payment_instruments); foreach ($payment_instruments as $pi_name => $pi_label) { @@ -523,6 +528,42 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { ['class' => 'crm-select2 huge', 'multiple' => 'multiple'] ); + if (Civi::settings()->get('twingle_use_shop')) { + $this->add( + 'checkbox', // field type + 'enable_shop_integration', // field name + E::ts('Enable Shop Integration'), // field label + FALSE, + [] + ); + + $this->add( + 'select', // field type + 'shop_financial_type', // field name + E::ts('Default Financial Type'), // field label + static::getFinancialTypes(), // list of options + TRUE, + ['class' => 'crm-select2 huge'] + ); + + $this->add( + 'select', // field type + 'shop_donation_financial_type', // field name + E::ts('Financial Type for top up donations'), // field label + static::getFinancialTypes(), // list of options + TRUE, + ['class' => 'crm-select2 huge'] + ); + + $this->add( + 'checkbox', // field type + 'shop_map_products', // field name + E::ts('Map Products as Price Fields'), // field label + FALSE, // is not required + [] + ); + } + $this->addButtons([ [ 'type' => 'submit', @@ -566,9 +607,6 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { CRM_Core_Session::setStatus($e->getMessage(), E::ts('Warning')); } } - catch (ProfileValidationError $e) { - $this->setElementError($e->getAffectedFieldName(), $e->getMessage()); - } } return parent::validate(); @@ -993,5 +1031,4 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { } return static::$_campaigns; } - } diff --git a/CRM/Twingle/Form/Settings.php b/CRM/Twingle/Form/Settings.php index 0634dbc..a92535e 100644 --- a/CRM/Twingle/Form/Settings.php +++ b/CRM/Twingle/Form/Settings.php @@ -29,14 +29,16 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form { * List of all settings options. */ public static $SETTINGS_LIST = [ - 'twingle_prefix', - 'twingle_use_sepa', - 'twingle_dont_use_reference', - 'twingle_protect_recurring', - 'twingle_protect_recurring_activity_type', - 'twingle_protect_recurring_activity_subject', - 'twingle_protect_recurring_activity_status', - 'twingle_protect_recurring_activity_assignee', + 'twingle_prefix', + 'twingle_use_sepa', + 'twingle_dont_use_reference', + 'twingle_protect_recurring', + 'twingle_protect_recurring_activity_type', + 'twingle_protect_recurring_activity_subject', + 'twingle_protect_recurring_activity_status', + 'twingle_protect_recurring_activity_assignee', + 'twingle_use_shop', + 'twingle_access_key', ]; /** @@ -105,13 +107,25 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form { ] ); - $this->addButtons([ - [ - 'type' => 'submit', - 'name' => E::ts('Save'), - 'isDefault' => TRUE, - ], - ]); + $this->add( + 'checkbox', + 'twingle_use_shop', + E::ts("Use Twingle Shop Integration") + ); + + $this->add( + 'text', + 'twingle_access_key', + E::ts("Twingle Access Key") + ); + + $this->addButtons(array( + array ( + 'type' => 'submit', + 'name' => E::ts('Save'), + 'isDefault' => TRUE, + ) + )); // set defaults foreach (self::$SETTINGS_LIST as $setting) { @@ -124,8 +138,7 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form { } /** - * Custom form validation, because the activity creation fields - * are only mandatory if activity creation is active + * Custom form validation, as some fields are mandatory only when others are active. * @return bool */ public function validate() { @@ -146,6 +159,14 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form { } } + // Twingle Access Key is required if Shop Integration is enabled + if ( + CRM_Utils_Array::value('twingle_use_shop', $this->_submitValues) && + !CRM_Utils_Array::value('twingle_access_key', $this->_submitValues, FALSE) + ) { + $this->_errors['twingle_access_key'] = E::ts("An Access Key is required to enable Twingle Shop Integration"); + } + return (0 == count($this->_errors)); } diff --git a/CRM/Twingle/Page/Profiles.php b/CRM/Twingle/Page/Profiles.php index 790d340..da8743e 100644 --- a/CRM/Twingle/Page/Profiles.php +++ b/CRM/Twingle/Page/Profiles.php @@ -33,6 +33,7 @@ class CRM_Twingle_Page_Profiles extends CRM_Core_Page { } $this->assign('profiles', $profiles); $this->assign('profile_stats', CRM_Twingle_Profile::getProfileStats()); + $this->assign('twingle_use_shop', (int) Civi::settings()->get('twingle_use_shop')); // Add custom css Civi::resources()->addStyleFile(E::LONG_NAME, 'css/twingle.css'); diff --git a/CRM/Twingle/Profile.php b/CRM/Twingle/Profile.php index 10d412c..a0d0cff 100644 --- a/CRM/Twingle/Profile.php +++ b/CRM/Twingle/Profile.php @@ -43,6 +43,16 @@ class CRM_Twingle_Profile { */ protected $data; + /** + * @var array $check_box_fields + * List of check box fields + */ + public $check_box_fields = [ + 'newsletter_double_opt_in', + 'enable_shop_integration', + 'shop_map_products', + ]; + /** * CRM_Twingle_Profile constructor. * @@ -197,6 +207,16 @@ class CRM_Twingle_Profile { ); } + /** + * Determine if Twingle Shop integration is enabled in general and + * specifically for this profile. + * @return bool + */ + public function isShopEnabled(): bool { + return Civi::settings()->get('twingle_use_shop') && + $this->data['enable_shop_integration']; + } + /** * Retrieves an attribute of the profile. * @@ -532,6 +552,10 @@ class CRM_Twingle_Profile { 'required_address_components' => ['required' => FALSE], 'map_as_contribution_notes' => ['required' => FALSE], 'map_as_contact_notes' => ['required' => FALSE], + 'enable_shop_integration' => ['required' => FALSE], + 'shop_financial_type' => ['required' => FALSE], + 'shop_donation_financial_type' => ['required' => FALSE], + 'shop_map_products' => ['required' => FALSE], ], // Add payment methods. array_combine( @@ -650,6 +674,10 @@ class CRM_Twingle_Profile { ], 'map_as_contribution_notes' => [], 'map_as_contact_notes' => [], + 'enable_shop_integration' => FALSE, + 'shop_financial_type' => 1, + 'shop_donation_financial_type' => 1, + 'shop_map_products' => FALSE, ] // Add contribution status for all payment methods. // phpcs:ignore Drupal.Formatting.SpaceUnaryOperator.PlusMinus @@ -666,7 +694,7 @@ class CRM_Twingle_Profile { * @param string $project_id * * @return CRM_Twingle_Profile - * @throws \CRM\Twingle\Exceptions\ProfileException + * @throws \Civi\Twingle\Exceptions\ProfileException * @throws \Civi\Core\Exception\DBQueryException */ public static function getProfileForProject($project_id) { @@ -683,7 +711,6 @@ class CRM_Twingle_Profile { } // If none matches, use the default profile. - $default_profile = $profiles['default']; if (!empty($default_profile)) { return $default_profile; } @@ -780,5 +807,4 @@ class CRM_Twingle_Profile { } return $stats; } - } diff --git a/CRM/Twingle/Submission.php b/CRM/Twingle/Submission.php index 258f153..606a714 100644 --- a/CRM/Twingle/Submission.php +++ b/CRM/Twingle/Submission.php @@ -17,6 +17,8 @@ declare(strict_types = 1); use CRM_Twingle_ExtensionUtil as E; use Civi\Twingle\Exceptions\BaseException; +use Civi\Twingle\Shop\Exceptions\LineItemException; +use Civi\Twingle\Shop\BAO\TwingleProduct; class CRM_Twingle_Submission { @@ -41,7 +43,18 @@ class CRM_Twingle_Submission { public const EMPLOYER_RELATIONSHIP_TYPE_ID = 5; /** - * @param array &$params + * List of allowed product attributes. + */ + const ALLOWED_PRODUCT_ATTRIBUTES = [ + 'name', + 'internal_id', + 'price', + 'count', + 'total_value', + ]; + + /** + * @param array &$params * A reference to the parameters array of the submission. * * @param \CRM_Twingle_Profile $profile @@ -123,6 +136,22 @@ class CRM_Twingle_Submission { } } + // Validate products + if (!empty($params['products']) && $profile->isShopEnabled()) { + if (is_string($params['products'])) { + $products = json_decode($params['products'], TRUE); + $params['products'] = array_map(function ($product) { + return array_intersect_key($product, array_flip(self::ALLOWED_PRODUCT_ATTRIBUTES)); + }, $products); + } + if (!is_array($params['products'])) { + throw new CiviCRM_API3_Exception( + E::ts('Invalid format for products.'), + 'invalid_format' + ); + } + } + // Validate campaign_id, if given. if (isset($params['campaign_id'])) { // Check whether campaign_id is a numeric string and cast it to an integer. @@ -433,4 +462,131 @@ class CRM_Twingle_Submission { } } + /** + * @param $values + * Processed data + * @param $submission + * Submission data + * @param $profile + * The twingle profile used + * + * @throws \CiviCRM_API3_Exception + * @throws \CRM_Core_Exception + * @throws \Civi\Twingle\Shop\Exceptions\LineItemException + */ + public static function createLineItems($values, $submission, $profile): array { + $line_items = []; + $sum_line_items = 0; + + $contribution_id = $values['contribution']['id']; + if (empty($contribution_id)) { + throw new LineItemException( + "Could not find contribution id for line item assignment.", + LineItemException::ERROR_CODE_CONTRIBUTION_NOT_FOUND + ); + } + + foreach ($submission['products'] as $product) { + + $line_item_data = [ + 'entity_table' => "civicrm_contribution", + 'contribution_id' => $contribution_id, + 'entity_id' => $contribution_id, + 'label' => $product['name'], + 'qty' => $product['count'], + 'unit_price' => $product['price'], + 'line_total' => $product['total_value'], + 'sequential' => 1, + ]; + + // Try to find the TwingleProduct with its corresponding PriceField + // for this product + try { + $price_field = TwingleProduct::findByExternalId($product['id']); + } + catch (Exception $e) { + Civi::log()->error(E::LONG_NAME . + ": An error occurred when searching for TwingleShop with the external ID " . + $product['id'], ['exception' => $e]); + $price_field = NULL; + } + // If found, use the financial type and price field id from the price field + if ($price_field) { + + // Log warning if price differs from the submission + if ($price_field->price != (int) $product['price']) { + Civi::log()->warning(E::LONG_NAME . + ": Price for product " . $product['name'] . " differs from the PriceField. " . + "Using the price from the submission.", ['price_field' => $price_field->price, 'submission' => $product['price']]); + } + + // Log warning if name differs from the submission + if ($price_field->name != $product['name']) { + Civi::log()->warning(E::LONG_NAME . + ": Name for product " . $product['name'] . " differs from the PriceField " . + "Using the name from the submission.", ['price_field' => $price_field->name, 'submission' => $product['name']]); + } + + // Set the financial type and price field id + $line_item_data['financial_type_id'] = $price_field->financial_type_id; + $line_item_data['price_field_value_id'] = $price_field->getPriceFieldValueId(); + $line_item_data['price_field_id'] = $price_field->price_field_id; + $line_item_data['description'] = $price_field->description; + } + // If not found, use the shops default financial type + else { + $financial_type_id = $profile->getAttribute('shop_financial_type', 1); + $line_item_data['financial_type_id'] = $financial_type_id; + } + + // Create the line item + $line_item = civicrm_api3('LineItem', 'create', $line_item_data); + + if (!empty($line_item['is_error'])) { + $line_item_name = $line_item_data['name']; + throw new CiviCRM_API3_Exception( + E::ts("Could not create line item for product '$line_item_name'"), + 'api_error' + ); + } + $line_items[] = array_pop($line_item['values']); + + $sum_line_items += $product['total_value']; + } + + // Create line item for donation part + $donation_sum = (float) $values['contribution']['total_amount'] - $sum_line_items; + if ($donation_sum > 0) { + $donation_financial_type_id = $profile->getAttribute('shop_donation_financial_type', 1); + $donation_label = civicrm_api3('FinancialType', 'getsingle', [ + 'return' => ['name'], + 'id' => $donation_financial_type_id, + ])['name']; + + $donation_line_item_data = [ + 'entity_table' => "civicrm_contribution", + 'contribution_id' => $contribution_id, + 'entity_id' => $contribution_id, + 'label' => $donation_label, + 'qty' => 1, + 'unit_price' => $donation_sum, + 'line_total' => $donation_sum, + 'financial_type_id' => $donation_financial_type_id, + 'sequential' => 1, + ]; + + $donation_line_item = civicrm_api3('LineItem', 'create', $donation_line_item_data); + + if (!empty($donation_line_item['is_error'])) { + throw new CiviCRM_API3_Exception( + E::ts("Could not create line item for donation"), + 'api_error' + ); + } + + $line_items[] = array_pop($donation_line_item['values']); + } + + return $line_items; + } } diff --git a/CRM/Twingle/Upgrader.php b/CRM/Twingle/Upgrader.php index 70f0180..aa80b58 100644 --- a/CRM/Twingle/Upgrader.php +++ b/CRM/Twingle/Upgrader.php @@ -137,4 +137,15 @@ class CRM_Twingle_Upgrader extends CRM_Extension_Upgrader_Base { return TRUE; } + /** + * The Upgrade to 1.5.1 creates the tables civicrm_twingle_product and + * civicrm_twingle_shop. + * + * @return TRUE on success + */ + public function upgrade_5151() { + $this->ctx->log->info('Creating tables for Twingle Shop.'); + $this->executeSqlFile('sql/civicrm_twingle_shop.sql'); + return TRUE; + } } diff --git a/Civi/Api4/TwingleProduct.php b/Civi/Api4/TwingleProduct.php new file mode 100644 index 0000000..839de01 --- /dev/null +++ b/Civi/Api4/TwingleProduct.php @@ -0,0 +1,12 @@ +. - */ - -declare(strict_types = 1); namespace Civi\Twingle\Exceptions; @@ -28,7 +11,7 @@ use CRM_Twingle_ExtensionUtil as E; class BaseException extends \Exception { /** - * @var string + * @var int|string */ protected $code; protected string $log_message; @@ -36,15 +19,13 @@ class BaseException extends \Exception { /** * BaseException Constructor * @param string $message - * Error message + * Error message * @param string $error_code - * A meaningful error code - * @param \Throwable $previous - * A previously thrown exception to include. + * A meaningful error code */ - public function __construct(string $message = '', string $error_code = '', \Throwable $previous = NULL) { - parent::__construct($message, 1, $previous); - $this->log_message = '' !== $message ? E::LONG_NAME . ': ' . $message : ''; + public function __construct(string $message = '', string $error_code = '') { + parent::__construct($message, 1); + $this->log_message = !empty($message) ? E::LONG_NAME . ': ' . $message : ''; $this->code = $error_code; } diff --git a/Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master b/Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master new file mode 100644 index 0000000..a5413d5 --- /dev/null +++ b/Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Twingle\Exceptions; + +use CRM_Twingle_ExtensionUtil as E; + +/** + * A simple custom exception class that indicates a problem within a class + * of the Twingle API extension. + */ +class BaseException extends \Exception { + + /** + * @var string + */ + protected $code; + protected string $log_message; + + /** + * BaseException Constructor + * @param string $message + * Error message + * @param string $error_code + * A meaningful error code + * @param \Throwable $previous + * A previously thrown exception to include. + */ + public function __construct(string $message = '', string $error_code = '', \Throwable $previous = NULL) { + parent::__construct($message, 1, $previous); + $this->log_message = '' !== $message ? E::LONG_NAME . ': ' . $message : ''; + $this->code = $error_code; + } + + /** + * Returns the error message, but with the extension name prefixed. + * @return string + */ + public function getLogMessage() { + return $this->log_message; + } + + /** + * Returns the error code. + * @return string + */ + public function getErrorCode() { + return $this->code; + } + +} diff --git a/CRM/Twingle/Exceptions/ProfileException.php b/Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration similarity index 94% rename from CRM/Twingle/Exceptions/ProfileException.php rename to Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration index 7011507..b9e5954 100644 --- a/CRM/Twingle/Exceptions/ProfileException.php +++ b/Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration @@ -1,6 +1,6 @@ + * @var string $apiToken + */ + private string $apiToken; + + /** + * The ID of your organization in the Twingle database. + * Automatically retrieved by sending a request with the associated API token. + * @var int $organisationId + */ + public int $organisationId; + + /** + * This boolean indicates whether the connection was successful. + * + * @var bool $isConnected + */ + public bool $isConnected; + + /** + * Limit the number of items requested per API call. + * @var int $limit + */ + public int $limit = 40; + + /** + * Header for cURL request. + * @var string[] $header + */ + private array $header; + + /** + * The cURL wrapper + * @var \Civi\Twingle\Shop\CurlWrapper $curlWrapper + */ + private CurlWrapper $curlWrapper; + + /** + * Protected TwingleApiCall constructor. + * Use \Civi\Twingle\ApiCall::singleton() instead. + * @param \Civi\Twingle\Shop\CurlWrapper $curlWrapper + */ + protected function __construct(CurlWrapper $curlWrapper) { + $this->curlWrapper = $curlWrapper; + $this->isConnected = FALSE; + } + + /** + * Returns \Civi\Twingle\Shop\ApiCall singleton + * + * @param \Civi\Twingle\Shop\CurlWrapper|null $curlWrapper + * Optional cURL wrapper for testing purposes + * @return \Civi\Twingle\Shop\ApiCall + */ + public static function singleton(CurlWrapper $curlWrapper = null): ApiCall { + if (empty(self::$singleton)) { + $curlWrapper = $curlWrapper ?? new CurlWrapper(); + self::$singleton = new ApiCall($curlWrapper); + return self::$singleton; + } + else { + return self::$singleton; + } + } + + /** + * Try to connect to the Twingle API and retrieve the organisation ID. + * + * @return bool + * returns TRUE if the connection was successfully established + * + * @throws \Civi\Twingle\Shop\Exceptions\ApiCallError + */ + public function connect(): bool { + + $this->isConnected = FALSE; + + try { + // Get api token from settings + $apiToken = \Civi::settings()->get("twingle_access_key"); + if (empty($apiToken)) { + throw new \TypeError(); + } + $this->apiToken = $apiToken; + } catch (\TypeError $e) { + throw new ApiCallError( + E::ts("Could not find Twingle API token"), + ApiCallError::ERROR_CODE_API_TOKEN_MISSING, + ); + } + + $this->header = [ + "x-access-code: $this->apiToken", + 'Content-Type: application/json', + ]; + + $url = self::PROTOCOL . 'organisation' . self::BASE_URL . "/"; + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header); + + $response = json_decode(curl_exec($curl), TRUE); + + if (empty($response)) { + curl_close($curl); + throw new ApiCallError( + E::ts("Call to Twingle API failed. Please check your api token."), + ApiCallError::ERROR_CODE_CONNECTION_FAILED, + ); + } + self::check_response_and_close($response, $curl); + + $this->organisationId = array_column($response, 'id')[0]; + $this->isConnected = TRUE; + return $this->isConnected; + } + + /** + * Check response on cURL + * + * @param $response + * the cURL response to check + * @param $curl + * the cURL resource + * + * @return bool + * returns true if the response is fine + * + * @throws \Civi\Twingle\Shop\Exceptions\ApiCallError + */ + protected static function check_response_and_close($response, $curl) { + + $curl_status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + + if ($response == FALSE) { + throw new ApiCallError( + E::ts('GET curl failed'), + ApiCallError::ERROR_CODE_GET_REQUEST_FAILED, + ); + } + if ($curl_status_code == 404) { + throw new ApiCallError( + E::ts('http status code 404 (not found)'), + ApiCallError::ERROR_CODE_404, + ); + } + elseif ($curl_status_code == 500) { + throw new ApiCallError( + E::ts('https status code 500 (internal error)'), + ApiCallError::ERROR_CODE_500, + ); + } + + return TRUE; + } + + /** + * Sends a GET cURL and returns the result array. + * + * @param $entity + * Twingle entity + * + * @param null $params + * Optional GET parameters + * + * @return array + * Returns the result array of the or FALSE, if the cURL failed + * @throws \Civi\Twingle\Shop\Exceptions\ApiCallError + */ + public function get( + string $entity, + string $entityId = NULL, + string $endpoint = NULL, + string $endpointId = NULL, + array $params = NULL + ): array { + + // Throw an error, if connection is not yet established + if ($this->isConnected == FALSE) { + throw new ApiCallError( + E::ts("Connection not yet established. Use connect() method."), + ApiCallError::ERROR_CODE_NOT_CONNECTED, + ); + } + + // Build URL and initialize cURL + $url = self::PROTOCOL . $entity . self::BASE_URL; + if (!empty($entityId)) { + $url .= "/$entityId"; + } + if (!empty($endpoint)) { + $url .= "/$endpoint"; + } + if (!empty($endpointId)) { + $url .= "/$endpointId"; + } + if (!empty($params)) { + $url .= '?' . http_build_query($params); + } + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($curl, CURLOPT_HTTPHEADER, $this->header); + + // Execute cURL + $response = json_decode(curl_exec($curl), TRUE); + self::check_response_and_close($response, $curl); + + return $response; + } +} + +/** + * A simple wrapper for the cURL functions to allow for easier testing. + */ +class CurlWrapper { + public function init($url) { + return curl_init($url); + } + + public function setopt($ch, $option, $value) { + return curl_setopt($ch, $option, $value); + } + + public function exec($ch) { + return curl_exec($ch); + } + + public function getinfo($ch, $option) { + return curl_getinfo($ch, $option); + } + + public function close($ch) { + curl_close($ch); + } +} diff --git a/Civi/Twingle/Shop/BAO/TwingleProduct.php b/Civi/Twingle/Shop/BAO/TwingleProduct.php new file mode 100644 index 0000000..3e2d120 --- /dev/null +++ b/Civi/Twingle/Shop/BAO/TwingleProduct.php @@ -0,0 +1,649 @@ + CRM_Utils_Type::T_INT, + "external_id" => CRM_Utils_Type::T_INT, + "name" => CRM_Utils_Type::T_STRING, + "is_active" => CRM_Utils_Type::T_BOOLEAN, + "description" => CRM_Utils_Type::T_STRING, + "price" => CRM_Utils_Type::T_INT, + "created_at" => CRM_Utils_Type::T_INT, + "tw_created_at" => CRM_Utils_Type::T_INT, + "updated_at" => CRM_Utils_Type::T_INT, + "tw_updated_at" => CRM_Utils_Type::T_INT, + "is_orphaned" => CRM_Utils_Type::T_BOOLEAN, + "is_outdated" => CRM_Utils_Type::T_BOOLEAN, + "project_id" => CRM_Utils_Type::T_INT, + "sort" => CRM_Utils_Type::T_INT, + "financial_type_id" => CRM_Utils_Type::T_INT, + "twingle_shop_id" => CRM_Utils_Type::T_INT, + "price_field_id" => CRM_Utils_Type::T_INT, + # "text" => \CRM_Utils_Type::T_STRING, + # "images" => \CRM_Utils_Type::T_STRING, + # "categories" = \CRM_Utils_Type::T_STRING, + # "internal_id" => \CRM_Utils_Type::T_STRING, + # "has_zero_price" => \CRM_Utils_Type::T_BOOLEAN, + # "name_plural" => \CRM_Utils_Type::T_STRING, + # "max_count" => \CRM_Utils_Type::T_INT, + # "has_textinput" => \CRM_Utils_Type::T_BOOLEAN, + # "count" => \CRM_Utils_Type::T_INT, + ]; + + /** + * Change attribute names to match the database column names. + * + * @param array $values + * Array with product data from Twingle API + * + * @return array + */ + public static function renameTwingleAttrs(array $values) { + $new_values = []; + foreach ($values as $key => $value) { + // replace 'id' with 'external_id' + if ($key == 'id') { + $key = 'external_id'; + } + // replace 'updated_at' with 'tw_updated_at' + if ($key == 'updated_at') { + $key = 'tw_updated_at'; + } + // replace 'created_at' with 'tw_created_at' + if ($key == 'created_at') { + $key = 'tw_created_at'; + } + $new_values[$key] = $value; + } + return $new_values; + } + + /** + * Load product data. + * + * @param array $product_data + * Array with product data + * + * @return void + * + * @throws ProductException + * @throws \Exception + */ + public function load(array $product_data): void { + // Filter for allowed attributes + filter_attributes( + $product_data, + self::ALLOWED_ATTRIBUTES, + self::CAN_BE_ZERO, + ); + + // Amend data from corresponding PriceFieldValue + if (isset($product_data['price_field_id'])) { + try { + $price_field_value = civicrm_api3('PriceFieldValue', 'getsingle', [ + 'price_field_id' => $product_data['price_field_id'], + ]); + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts("Could not find PriceFieldValue for Twingle Product ['id': %1, 'external_id': %2]: %3", + [ + 1 => $product_data['id'], + 2 => $product_data['external_id'], + 3 => $e->getMessage(), + ]), + ProductException::ERROR_CODE_PRICE_FIELD_VALUE_NOT_FOUND); + } + $product_data['name'] = $product_data['name'] ?? $price_field_value['label']; + $product_data['price'] = $product_data['price'] ?? $price_field_value['amount']; + $product_data['financial_type_id'] = $product_data['financial_type_id'] ?? $price_field_value['financial_type_id']; + $product_data['is_active'] = $product_data['is_active'] ?? $price_field_value['is_active']; + $product_data['sort'] = $product_data['sort'] ?? $price_field_value['weight']; + $product_data['description'] = $product_data['description'] ?? $price_field_value['description']; + } + + // Change data types + try { + convert_str_to_int($product_data, self::STR_TO_INT_CONVERSION); + convert_int_to_bool($product_data, self::INT_TO_BOOL_CONVERSION); + convert_str_to_date($product_data, self::STR_TO_DATE_CONVERSION); + convert_null_to_int($product_data, self::NULL_TO_INT_CONVERSION); + } + catch (\Exception $e) { + throw new ProductException($e->getMessage(), ProductException::ERROR_CODE_ATTRIBUTE_WRONG_DATA_TYPE); + } + + // Validate data types + try { + validate_data_types($product_data, self::ALLOWED_ATTRIBUTES); + } + catch (\Exception $e) { + throw new ProductException($e->getMessage(), ProductException::ERROR_CODE_ATTRIBUTE_WRONG_DATA_TYPE); + } + + // Set attributes + foreach ($product_data as $key => $value) { + $this->$key = $value; + } + } + + /** + * Creates a price field to represents this product in CiviCRM. + * + * @param string $mode + * 'create' or 'edit' + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + public function createPriceField() { + // Define mode for PriceField + $mode = $this->price_field_id ? 'edit' : 'create'; + $action = $mode == 'create' ? 'create' : 'update'; + + // Check if PriceSet for this Shop already exists + try { + $price_field = civicrm_api3('PriceField', 'get', [ + 'name' => 'tw_product_' . $this->external_id, + ]); + if ($price_field['count'] > 0 && $mode == 'create') { + throw new ProductException( + E::ts('PriceField for this Twingle Product already exists.'), + ProductException::ERROR_CODE_PRICE_FIELD_ALREADY_EXISTS, + ); + } elseif ($price_field['count'] == 0 && $mode == 'edit') { + throw new ProductException( + E::ts('PriceField for this Twingle Product does not exist and cannot be edited.'), + ProductException::ERROR_CODE_PRICE_FIELD_NOT_FOUND, + ); + } + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('Could not check if PriceField for this Twingle Product already exists.'), + ProductException::ERROR_CODE_PRICE_FIELD_NOT_FOUND, + ); + } + + // Try to find corresponding price set via TwingleShop + try { + $shop = civicrm_api3('TwingleShop', 'getsingle', [ + 'id' => $this->twingle_shop_id, + ]); + $this->price_set_id = (int) $shop['price_set_id']; + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('Could not find PriceSet for this Twingle Product.'), + ProductException::ERROR_CODE_PRICE_SET_NOT_FOUND, + ); + } + + // Create PriceField + $price_field_data = [ + 'price_set_id' => $this->price_set_id, + 'name' => 'tw_product_' . $this->external_id, + 'label' => $this->name, + 'is_active' => $this->is_active, + 'weight' => $this->sort, + 'html_type' => 'Text', + ]; + // Add id if in edit mode + if ($mode == 'edit') { + $price_field_data['id'] = $this->price_field_id; + } + try { + $price_field = civicrm_api4( + 'PriceField', + $action, + ['values' => $price_field_data], + )->first(); + $this->price_field_id = (int) $price_field['id']; + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('Could not create PriceField for this Twingle Product: %1', + [1 => $e->getMessage()]), + ProductException::ERROR_CODE_COULD_NOT_CREATE_PRICE_FIELD); + } + + // Try to find existing PriceFieldValue if in edit mode + $price_field_value = NULL; + if ($mode == 'edit') { + try { + $price_field_value = civicrm_api3('PriceFieldValue', 'getsingle', [ + 'price_field_id' => $this->price_field_id, + ]); + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('Could not find PriceFieldValue for this Twingle Product: %1', + [1 => $e->getMessage()]), + ProductException::ERROR_CODE_PRICE_FIELD_VALUE_NOT_FOUND); + } + } + + // Create PriceFieldValue + $price_field_value_data = [ + 'price_field_id' => $this->price_field_id, + 'financial_type_id' => $this->financial_type_id, + 'label' => $this->name, + 'amount' => $this->price, + 'is_active' => $this->is_active, + 'description' => $this->description, + ]; + // Add id if in edit mode + if ($mode == 'edit' && $price_field_value) { + $price_field_value_data['id'] = $price_field_value['id']; + } + try { + civicrm_api4( + 'PriceFieldValue', + $action, + ['values' => $price_field_value_data], + ); + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('Could not create PriceFieldValue for this Twingle Product: %1', + [1 => $e->getMessage()]), + ProductException::ERROR_CODE_COULD_NOT_CREATE_PRICE_FIELD_VALUE); + } + } + + /** + * Returns this TwingleProduct's attributes. + * + * @return array + * @throws \CRM_Core_Exception + */ + public function getAttributes() { + // Filter for allowed attributes + return array_intersect_key( + get_object_vars($this), + $this::ALLOWED_ATTRIBUTES + ) // Add financial type id of this product if it exists + + ['financial_type_id' => $this->getFinancialTypeId()]; + } + + /** + * Find TwingleProduct by its external ID. + * + * @param int $external_id + * External id of the product (by Twingle) + * + * @return TwingleProduct|null + * TwingleProduct object or NULL if not found + * + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + * @throws \Civi\Core\Exception\DBQueryException + */ + public static function findByExternalId($external_id) { + $dao = TwingleShopDAO::executeQuery("SELECT * FROM civicrm_twingle_product WHERE external_id = %1", + [1 => [$external_id, 'String']]); + if ($dao->fetch()) { + $product = new self(); + $product->load($dao->toArray()); + return $product; + } + return NULL; + } + + /** + * Add Twingle Product + * + * @param string $mode + * 'create' or 'edit' + * @return array + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + * @throws \Exception + */ + public function add($mode = 'create') { + + $tx = new CRM_Core_Transaction(); + + // Define mode + $mode = $this->id ? 'edit' : 'create'; + + // Try to lookup object in database + try { + $dao = TwingleShopDAO::executeQuery("SELECT * FROM civicrm_twingle_product WHERE external_id = %1", + [1 => [$this->external_id, 'String']]); + if ($dao->fetch()) { + $this->copyValues(array_merge($dao->toArray(), $this->getAttributes())); + } + } + catch (\Civi\Core\Exception\DBQueryException $e) { + throw new ProductException( + E::ts('Could not find TwingleProduct in database: ' . $e->getMessage()), + ShopException::ERROR_CODE_COULD_NOT_FIND_SHOP_IN_DB); + } + + // Register pre-hook + $twingle_product_values = $this->getAttributes(); + try { + \CRM_Utils_Hook::pre($mode, 'TwingleProduct', $this->id, $twingle_product_values); + } catch (\Exception $e) { + $tx->rollback(); + throw $e; + } + $this->load($twingle_product_values); + + // Set latest tw_updated_at as new updated_at + $this->updated_at = \CRM_Utils_Time::date('Y-m-d H:i:s', $this->tw_updated_at); + + // Convert created_at to date string + $this->created_at = \CRM_Utils_Time::date('Y-m-d H:i:s', $this->created_at); + + // Save object to database + try { + $this->save(); + } catch (\Exception $e) { + $tx->rollback(); + throw new ProductException( + E::ts('Could not save TwingleProduct to database: ' . $e->getMessage()), + ProductException::ERROR_CODE_COULD_NOT_CREATE_PRODUCT); + } + $result = self::findById($this->id); + /* @var self $result */ + $this->load($result->getAttributes()); + + // Register post-hook + $twingle_product_values = $this->getAttributes(); + try { + \CRM_Utils_Hook::post($mode, 'TwingleProduct', $this->id, $twingle_product_values); + } + catch (\Exception $e) { + $tx->rollback(); + throw $e; + } + $this->load($twingle_product_values); + + return $result->toArray(); + } + + /** + * Delete TwingleProduct along with associated PriceField and PriceFieldValue. + * + * @override \Civi\Twingle\Shop\DAO\TwingleProduct::delete + * @throws \CRM_Core_Exception + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + function delete($useWhere = FALSE) { + // Register post-hook + $twingle_product_values = $this->getAttributes(); + \CRM_Utils_Hook::pre('delete', 'TwingleProduct', $this->id, $twingle_product_values); + $this->load($twingle_product_values); + + // Delete TwingleProduct + parent::delete($useWhere); + + // Register post-hook + \CRM_Utils_Hook::post('delete', 'TwingleProduct', $this->id, $instance); + + // Free global arrays associated with this object + $this->free(); + + return true; + } + + /** + * Complements the data with the data that was fetched from Twingle. + * + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + public function complementWithDataFromTwingle($product_from_twingle) { + // Complement with data from Twingle + $this->load([ + 'project_id' => $product_from_twingle['project_id'], + 'tw_updated_at' => $product_from_twingle['updated_at'], + 'tw_created_at' => $product_from_twingle['created_at'], + ]); + } + + /** + * Check if the product is outdated. + * + * @param $product_from_twingle + * + * @return void + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + public function checkOutdated($product_from_twingle) { + // Mark outdated products which have a newer timestamp in Twingle + if ($this->updated_at < intval($product_from_twingle['updated_at'])) { + // Overwrite the product with the data from Twingle + $this->load(self::renameTwingleAttrs($product_from_twingle)); + $this->is_outdated = TRUE; + } + } + + /** + * Compare two products + * + * @param TwingleProduct $product_to_compare_with + * Product from database + * + * @return bool + */ + public function equals($product_to_compare_with) { + return + $this->name === $product_to_compare_with->name && + $this->description === $product_to_compare_with->description && + $this->text === $product_to_compare_with->text && + $this->price === $product_to_compare_with->price && + $this->external_id === $product_to_compare_with->external_id; + } + + /** + * Returns the financial type id of this product. + * + * @return int|null + * @throws \CRM_Core_Exception + */ + public function getFinancialTypeId(): ?int { + if (!empty($this->price_field_id)) { + $price_set = \Civi\Api4\PriceField::get() + ->addSelect('financial_type_id') + ->addWhere('id', '=', $this->price_field_id) + ->execute() + ->first(); + return $price_set['financial_type_id']; + } + return NULL; + } + + /** + * Returns the price field value id of this product. + * + * @return int|null + * @throws \CRM_Core_Exception + */ + public function getPriceFieldValueId() { + if (!empty($this->price_field_id)) { + $price_field_value = \Civi\Api4\PriceFieldValue::get() + ->addSelect('id') + ->addWhere('price_field_id', '=', $this->price_field_id) + ->execute() + ->first(); + return $price_field_value['id']; + } + return NULL; + } + + /** + * Delete PriceField and PriceFieldValue of this TwingleProduct if they exist. + * + * @return void + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + public function deletePriceField(): void { + // Before we can delete the PriceField we need to delete the associated + // PriceFieldValue + try { + $result = civicrm_api3('PriceFieldValue', 'getsingle', + ['price_field_id' => $this->price_field_id]); + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('An Error occurred while searching for the associated PriceFieldValue: ' . $e->getMessage()), + ProductException::ERROR_CODE_PRICE_FIELD_VALUE_NOT_FOUND); + } + try { + civicrm_api3('PriceFieldValue', 'delete', ['id' => $result['id']]); + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('Could not delete associated PriceFieldValue: ' . $e->getMessage()), + ProductException::ERROR_CODE_COULD_NOT_DELETE_PRICE_FIELD_VALUE); + } + + // Try to delete PriceField + // If no PriceField is found, we assume that it has already been deleted + try { + civicrm_api3('PriceField', 'delete', + ['id' => $this->price_field_id]); + } + catch (CRM_Core_Exception $e) { + // Check if PriceField yet exists + try { + $result = civicrm_api3('PriceField', 'get', + ['id' => $this->price_field_id]); + // Throw exception if PriceField still exists + if ($result['count'] > 0) { + throw new ProductException( + E::ts('PriceField for this Twingle Product still exists.'), + ProductException::ERROR_CODE_PRICE_FIELD_STILL_EXISTS); + } + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts('An Error occurred while searching for the associated PriceField: ' . $e->getMessage()), + ProductException::ERROR_CODE_PRICE_FIELD_NOT_FOUND); + } + throw new ProductException( + E::ts('Could not delete associated PriceField: ' . $e->getMessage()), + ProductException::ERROR_CODE_COULD_NOT_DELETE_PRICE_FIELD); + } + $this->price_field_id = NULL; + } +} diff --git a/Civi/Twingle/Shop/BAO/TwingleShop.php b/Civi/Twingle/Shop/BAO/TwingleShop.php new file mode 100644 index 0000000..c27853c --- /dev/null +++ b/Civi/Twingle/Shop/BAO/TwingleShop.php @@ -0,0 +1,478 @@ + \CRM_Utils_Type::T_INT, + 'project_identifier' => \CRM_Utils_Type::T_STRING, + 'numerical_project_id' => \CRM_Utils_Type::T_INT, + 'name' => \CRM_Utils_Type::T_STRING, + 'price_set_id' => \CRM_Utils_Type::T_INT, + 'financial_type_id' => \CRM_Utils_Type::T_INT, + ]; + + public const STR_TO_INT_CONVERSION = [ + 'id', + 'numerical_project_id', + 'price_set_id', + 'financial_type_id', + ]; + + /** + * @var array $products + * Array of Twingle Shop products (Cache) + */ + public $products; + + /** + * FK to Financial Type + * + * @var int + */ + public $financial_type_id; + + /** + * TwingleShop constructor + */ + public function __construct() { + parent::__construct(); + // Get TwingleApiCall singleton + $this->twingleApi = ApiCall::singleton(); + } + + /** + * Get Twingle Shop from database by its project identifier + * (like 'tw620214349ac97') + * + * @param string $project_identifier + * Twingle project identifier + * + * @return TwingleShop + * + * @throws ShopException + * @throws \Civi\Twingle\Shop\Exceptions\ApiCallError + * @throws \CRM_Core_Exception + */ + public static function findByProjectIdentifier(string $project_identifier) { + $shop = new TwingleShop(); + $shop->get('project_identifier', $project_identifier); + if (!$shop->id) { + $shop->fetchDataFromTwingle($project_identifier); + } + else { + $shop->price_set_id = civicrm_api3('PriceSet', 'getvalue', + ['return' => 'id', 'name' => $project_identifier]); + } + return $shop; + } + + /** + * Load Twingle Shop data + * + * @param array $shop_data + * Array with shop data + * + * @return void + * + * @throws ShopException + */ + public function load(array $shop_data): void { + // Filter for allowed attributes + filter_attributes($shop_data, self::ALLOWED_ATTRIBUTES); + + // Convert string to int + try { + convert_str_to_int($shop_data, self::STR_TO_INT_CONVERSION); + } + catch (Exception $e) { + throw new ShopException($e->getMessage(), ShopException::ERROR_CODE_ATTRIBUTE_WRONG_DATA_TYPE); + } + + // Validate data types + try { + validate_data_types($shop_data, self::ALLOWED_ATTRIBUTES); + } + catch (Exception $e) { + throw new ShopException($e->getMessage(), ShopException::ERROR_CODE_ATTRIBUTE_WRONG_DATA_TYPE); + } + + // Set attributes + foreach ($shop_data as $key => $value) { + $this->$key = $value; + } + } + + /** + * Get attributes + * + * @return array + */ + function getAttributes(): array { + return [ + 'id' => $this->id, + 'project_identifier' => $this->project_identifier, + 'numerical_project_id' => $this->numerical_project_id, + 'name' => $this->name, + 'price_set_id' => $this->price_set_id, + 'financial_type_id' => $this->financial_type_id, + ]; + } + + /** + * Add Twingle Shop + * + * @param string $mode + * 'create' or 'edit' + * @return array + * @throws \Civi\Twingle\Shop\Exceptions\ShopException + */ + public function add($mode = 'create') { + + // Try to lookup object in database + try { + $dao = TwingleShopDAO::executeQuery("SELECT * FROM civicrm_twingle_shop WHERE project_identifier = %1", + [1 => [$this->project_identifier, 'String']]); + if ($dao->fetch()) { + $this->load($dao->toArray()); + } + } catch (\Civi\Core\Exception\DBQueryException $e) { + throw new ShopException( + E::ts('Could not find TwingleShop in database: ' . $e->getMessage()), + ShopException::ERROR_CODE_COULD_NOT_FIND_SHOP_IN_DB); + } + + // Register pre-hook + $twingle_shop_values = $this->getAttributes(); + \CRM_Utils_Hook::pre($mode, 'TwingleShop', $this->id, $twingle_shop_values); + $this->load($twingle_shop_values); + + // Save object to database + $result = $this->save(); + + // Register post-hook + \CRM_Utils_Hook::post($mode, 'TwingleShop', $this->id, $instance); + + return $result->toArray(); + } + + /** + * Delete object by deleting the associated PriceSet and letting the foreign + * key constraint do the rest. + * + * @throws \Civi\Twingle\Shop\Exceptions\ShopException* + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + function deleteByConstraint() { + // Register post-hook + $twingle_shop_values = $this->getAttributes(); + \CRM_Utils_Hook::pre('delete', 'TwingleShop', $this->id, $twingle_shop_values); + $this->load($twingle_shop_values); + + // Delete associated products + $this->deleteProducts(); + + // Try to get single PriceSet + try { + civicrm_api3('PriceSet', 'getsingle', + ['id' => $this->price_set_id]); + } + catch (\CRM_Core_Exception $e) { + if ($e->getMessage() != 'Expected one PriceSet but found 0') { + throw new ShopException( + E::ts('Could not find associated PriceSet: ' . $e->getMessage()), + ShopException::ERROR_CODE_PRICE_SET_NOT_FOUND); + } + else { + // If no PriceSet is found, we can simply delete the TwingleShop + return $this->delete(); + } + } + + // Deleting the associated PriceSet will also lead to the deletion of this + // TwingleShop because of the foreign key constraint and cascading. + try { + $result = civicrm_api3('PriceSet', 'delete', + ['id' => $this->price_set_id]); + } catch (\CRM_Core_Exception $e) { + throw new ShopException( + E::ts('Could not delete associated PriceSet: ' . $e->getMessage()), + ShopException::ERROR_CODE_COULD_NOT_DELETE_PRICE_SET); + } + + // Register post-hook + \CRM_Utils_Hook::post('delete', 'TwingleShop', $this->id, $instance); + + // Free global arrays associated with this object + $this->free(); + + return $result['is_error'] == 0; + } + + /** + * Fetch Twingle Shop products from Twingle + * + * @return array + * array of CRM_Twingle_Shop_BAO_Product + * + * @throws \Civi\Twingle\Shop\Exceptions\ApiCallError; + * @throws \Civi\Twingle\Shop\Exceptions\ProductException; + * @throws \Civi\Core\Exception\DBQueryException + * @throws \CRM_Core_Exception + */ + public function fetchProducts(): array { + // Establish connection, if not already connected + if (!$this->twingleApi->isConnected) { + $this->twingleApi->connect(); + } + + // Fetch products from Twingle API + $products_from_twingle = $this->twingleApi->get( + 'project', + $this->numerical_project_id, + 'products', + ); + + // Fetch products from database + if ($this->id) { + $products_from_db = $this->getProducts(); + + $products_from_twingle = array_reduce($products_from_twingle, function($carry, $product) { + $carry[$product['id']] = $product; + return $carry; + }, []); + + foreach ($products_from_db as $product) { + /* @var TwingleProductBAO $product */ + + // Find orphaned products which are in the database but not in Twingle + $found = array_key_exists($product->external_id, $products_from_twingle); + if (!$found) { + $product->is_orphaned = TRUE; + } + else { + // Complement with data from Twingle + $product->complementWithDataFromTwingle($products_from_twingle[$product->external_id]); + // Mark outdated products which have a newer version in Twingle + $product->checkOutdated($products_from_twingle[$product->external_id]); + } + $this->products[] = $product; + } + } + + // Create array with external_id as key + $products = array_reduce($this->products ?? [], function($carry, $product) { + $carry[$product->external_id] = $product; + return $carry; + }, []); + + // Add new products from Twingle + foreach ($products_from_twingle as $product_from_twingle) { + $found = array_key_exists($product_from_twingle['id'], $products); + if (!$found) { + $product = new TwingleProduct(); + $product->load(TwingleProduct::renameTwingleAttrs($product_from_twingle)); + $product->twingle_shop_id = $this->id; + $this->products[] = $product; + } + } + return $this->products; + } + + /** + * Get associated products. + * + * @return array[Civi\Twingle\Shop\BAO\TwingleProduct] + * @throws \Civi\Core\Exception\DBQueryException + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + public function getProducts() { + $products = []; + + $result = TwingleProductBAO::executeQuery( + "SELECT * FROM civicrm_twingle_product WHERE twingle_shop_id = %1", + [1 => [$this->id, 'Integer']] + ); + + while ($result->fetch()) { + $product = new TwingleProductBAO(); + $product->load($result->toArray()); + $products[] = $product; + } + + return $products; + } + + /** + * Creates Twingle Shop as a price set in CiviCRM. + * + * @param string $mode + * 'create' or 'edit' + * @throws \Civi\Twingle\Shop\Exceptions\ShopException + */ + public function createPriceSet($mode = 'create') { + + // Define mode + $mode = $this->price_set_id ? 'edit' : 'create'; + + // Check if PriceSet for this Shop already exists + try { + $price_set = civicrm_api3('PriceSet', 'get', [ + 'name' => $this->project_identifier, + ]); + if ($price_set['count'] > 0 && $mode == 'create') { + throw new ShopException( + E::ts('PriceSet for this Twingle Shop already exists.'), + ShopException::ERROR_CODE_PRICE_SET_ALREADY_EXISTS, + ); + } + elseif ($price_set['count'] == 0 && $mode == 'edit') { + throw new ShopException( + E::ts('PriceSet for this Twingle Shop does not exist and cannot be edited.'), + ShopException::ERROR_CODE_PRICE_SET_NOT_FOUND, + ); + } + } catch (\CRM_Core_Exception $e) { + throw new ShopException( + E::ts('Could not check if PriceSet for this TwingleShop already exists.'), + ShopException::ERROR_CODE_PRICE_SET_NOT_FOUND, + ); + } + + // Create PriceSet + $price_set_data = [ + 'name' => $this->project_identifier, + 'title' => "$this->name ($this->project_identifier)", + 'is_active' => 1, + 'extends' => 2, + 'financial_type_id' => $this->financial_type_id, + ]; + // Set id if in edit mode + if ($mode == 'edit') { + $price_set_data['id'] = $this->price_set_id; + } + try { + $price_set = civicrm_api4('PriceSet', 'create', + ['values' => $price_set_data])->first(); + $this->price_set_id = (int) $price_set['id']; + } catch (\CRM_Core_Exception $e) { + throw new ShopException( + E::ts('Could not create PriceSet for this TwingleShop.'), + ShopException::ERROR_CODE_COULD_NOT_CREATE_PRICE_SET, + ); + } + } + + /** + * Retrieves the numerical project ID and the name of this shop from Twingle. + * + * @throws \Civi\Twingle\Shop\Exceptions\ShopException + * @throws \Civi\Twingle\Shop\Exceptions\ApiCallError + */ + private function fetchDataFromTwingle() { + + // Establish connection, if not already connected + if (!$this->twingleApi->isConnected) { + $this->twingleApi->connect(); + } + + // Get shops from Twingle if not cached + $shops = \Civi::cache('long')->get('twingle_shops'); + if (empty($shops)) { + $this::fetchShops($this->twingleApi); + $shops = \Civi::cache('long')->get('twingle_shops'); + } + + // Set Shop ID and name + foreach ($shops as $shop) { + if (isset($shop['identifier']) && $shop['identifier'] == $this->project_identifier) { + $this->numerical_project_id = $shop['id']; + $this->name = $shop['name']; + } + } + + // Throw an Exception if this Twingle Project is not of type 'shop' + if (!isset($this->numerical_project_id)) { + throw new ShopException( + E::ts('This Twingle Project is not a shop.'), + ShopException::ERROR_CODE_NOT_A_SHOP, + ); + } + } + + /** + * Retrieves all Twingle projects of the type 'shop'. + * + * @throws \Civi\Twingle\Shop\Exceptions\ShopException + */ + static private function fetchShops(ApiCall $api): void { + $organisationId = $api->organisationId; + try { + $projects = $api->get( + 'project', + NULL, + 'by-organisation', + $organisationId, + ); + $shops = array_filter( + $projects, + function($project) { + return isset($project['type']) && $project['type'] == 'shop'; + } + ); + \Civi::cache('long')->set('twingle_shops', $shops); + } + catch (Exception $e) { + throw new ShopException( + E::ts('Could not retrieve Twingle projects from API. + Please check your API credentials.'), + ShopException::ERROR_CODE_COULD_NOT_GET_PROJECTS, + ); + } + } + + /** + * Deletes all associated products. + * + * @return void + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + */ + public function deleteProducts() { + try { + $products = $this->getProducts(); + } catch (\Civi\Core\Exception\DBQueryException $e) { + throw new ProductException( + E::ts('Could not retrieve associated products: ' . $e->getMessage()), + ProductException::ERROR_CODE_COULD_NOT_GET_PRODUCTS + ); + } + try { + foreach ($products as $product) { + $product->delete(); + } + } + catch (ProductException $e) { + throw new ProductException( + E::ts('Could not delete associated products: ' . $e->getMessage()), + ProductException::ERROR_CODE_COULD_NOT_DELETE_PRICE_SET + ); + } + } + +} diff --git a/Civi/Twingle/Shop/DAO/TwingleProduct.php b/Civi/Twingle/Shop/DAO/TwingleProduct.php new file mode 100644 index 0000000..9d4f075 --- /dev/null +++ b/Civi/Twingle/Shop/DAO/TwingleProduct.php @@ -0,0 +1,327 @@ +__table = 'civicrm_twingle_product'; + parent::__construct(); + } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Twingle Products') : E::ts('Twingle Product'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(\Civi::$statics[__CLASS__]['links'])) { + \Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + \Civi::$statics[__CLASS__]['links'][] = new \CRM_Core_Reference_Basic(self::getTableName(), 'price_field_id', 'civicrm_contact', 'id'); + \Civi::$statics[__CLASS__]['links'][] = new \CRM_Core_Reference_Basic(self::getTableName(), 'twingle_shop_id', 'civicrm_twingle_shop', 'id'); + \CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', \Civi::$statics[__CLASS__]['links']); + } + return \Civi::$statics[__CLASS__]['links']; + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(\Civi::$statics[__CLASS__]['fields'])) { + \Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => \CRM_Utils_Type::T_INT, + 'title' => E::ts('ID'), + 'description' => E::ts('Unique TwingleProduct ID'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_product.id', + 'table_name' => 'civicrm_twingle_product', + 'entity' => 'TwingleProduct', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'readonly' => TRUE, + 'add' => NULL, + ], + 'external_id' => [ + 'name' => 'external_id', + 'type' => \CRM_Utils_Type::T_INT, + 'title' => E::ts('External ID'), + 'description' => E::ts('The ID of this product in the Twingle database'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_product.external_id', + 'table_name' => 'civicrm_twingle_product', + 'entity' => 'TwingleProduct', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'add' => NULL, + ], + 'price_field_id' => [ + 'name' => 'price_field_id', + 'type' => \CRM_Utils_Type::T_INT, + 'title' => E::ts('Price Field ID'), + 'description' => E::ts('FK to Price Field'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_product.price_field_id', + 'table_name' => 'civicrm_twingle_product', + 'entity' => 'TwingleProduct', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'localizable' => 0, + 'FKClassName' => 'CRM_Contact_DAO_Contact', + 'add' => NULL, + ], + 'twingle_shop_id' => [ + 'name' => 'twingle_shop_id', + 'type' => \CRM_Utils_Type::T_INT, + 'title' => E::ts('Twingle Shop ID'), + 'description' => E::ts('FK to Twingle Shop'), + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_product.twingle_shop_id', + 'table_name' => 'civicrm_twingle_product', + 'entity' => 'TwingleProduct', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'localizable' => 0, + 'FKClassName' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'add' => NULL, + ], + 'created_at' => [ + 'name' => 'created_at', + 'type' => \CRM_Utils_Type::T_DATE + \CRM_Utils_Type::T_TIME, + 'title' => E::ts('Created At'), + 'description' => E::ts('Timestamp of when the product was created in the database'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_product.created_at', + 'table_name' => 'civicrm_twingle_product', + 'entity' => 'TwingleProduct', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'localizable' => 0, + 'add' => NULL, + ], + 'updated_at' => [ + 'name' => 'updated_at', + 'type' => \CRM_Utils_Type::T_DATE + \CRM_Utils_Type::T_TIME, + 'title' => E::ts('Updated At'), + 'description' => E::ts('Timestamp of when the product was last updated in the database'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_product.updated_at', + 'table_name' => 'civicrm_twingle_product', + 'entity' => 'TwingleProduct', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'localizable' => 0, + 'add' => NULL, + ], + ]; + \CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', \Civi::$statics[__CLASS__]['fields']); + } + return \Civi::$statics[__CLASS__]['fields']; + } + + /** + * Return a mapping from field-name to the corresponding key (as used in fields()). + * + * @return array + * Array(string $name => string $uniqueName). + */ + public static function &fieldKeys() { + if (!isset(\Civi::$statics[__CLASS__]['fieldKeys'])) { + \Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(\CRM_Utils_Array::collect('name', self::fields())); + } + return \Civi::$statics[__CLASS__]['fieldKeys']; + } + + /** + * Returns the names of this table + * + * @return string + */ + public static function getTableName() { + return self::$_tableName; + } + + /** + * Returns if this table needs to be logged + * + * @return bool + */ + public function getLog() { + return self::$_log; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = \CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'twingle_product', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = \CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'twingle_product', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? \CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/Civi/Twingle/Shop/DAO/TwingleShop.php b/Civi/Twingle/Shop/DAO/TwingleShop.php new file mode 100644 index 0000000..bef6db7 --- /dev/null +++ b/Civi/Twingle/Shop/DAO/TwingleShop.php @@ -0,0 +1,307 @@ +__table = 'civicrm_twingle_shop'; + parent::__construct(); + } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Twingle Shops') : E::ts('Twingle Shop'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(\Civi::$statics[__CLASS__]['links'])) { + \Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + \Civi::$statics[__CLASS__]['links'][] = new \CRM_Core_Reference_Basic(self::getTableName(), 'price_set_id', 'civicrm_price_set', 'id'); + \CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', \Civi::$statics[__CLASS__]['links']); + } + return \Civi::$statics[__CLASS__]['links']; + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(\Civi::$statics[__CLASS__]['fields'])) { + \Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => \CRM_Utils_Type::T_INT, + 'title' => E::ts('ID'), + 'description' => E::ts('Unique TwingleShop ID'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_shop.id', + 'table_name' => 'civicrm_twingle_shop', + 'entity' => 'TwingleShop', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'readonly' => TRUE, + 'add' => NULL, + ], + 'project_identifier' => [ + 'name' => 'project_identifier', + 'type' => \CRM_Utils_Type::T_STRING, + 'title' => E::ts('Project Identifier'), + 'description' => E::ts('Twingle Project Identifier'), + 'required' => TRUE, + 'maxlength' => 32, + 'size' => \CRM_Utils_Type::MEDIUM, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_shop.project_identifier', + 'table_name' => 'civicrm_twingle_shop', + 'entity' => 'TwingleShop', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => NULL, + ], + 'numerical_project_id' => [ + 'name' => 'numerical_project_id', + 'type' => \CRM_Utils_Type::T_INT, + 'title' => E::ts('Numerical Project ID'), + 'description' => E::ts('Numerical Twingle Project Identifier'), + 'required' => TRUE, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_shop.numerical_project_id', + 'table_name' => 'civicrm_twingle_shop', + 'entity' => 'TwingleShop', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'localizable' => 0, + 'html' => [ + 'type' => 'Number', + ], + 'add' => NULL, + ], + 'price_set_id' => [ + 'name' => 'price_set_id', + 'type' => \CRM_Utils_Type::T_INT, + 'title' => E::ts('Price Set ID'), + 'description' => E::ts('FK to Price Set'), + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_shop.price_set_id', + 'table_name' => 'civicrm_twingle_shop', + 'entity' => 'TwingleShop', + 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'localizable' => 0, + 'FKClassName' => 'CRM_Price_DAO_PriceSet', + 'add' => NULL, + ], + 'name' => [ + 'name' => 'name', + 'type' => \CRM_Utils_Type::T_STRING, + 'title' => E::ts('Name'), + 'description' => E::ts('name of the shop'), + 'required' => TRUE, + 'maxlength' => 64, + 'size' => \CRM_Utils_Type::BIG, + 'usage' => [ + 'import' => FALSE, + 'export' => FALSE, + 'duplicate_matching' => FALSE, + 'token' => FALSE, + ], + 'where' => 'civicrm_twingle_shop.name', + 'table_name' => 'civicrm_twingle_shop', + 'entity' => 'TwingleShop', + 'bao' => 'CRM_Twingle_Shop_DAO_TwingleShop', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + 'add' => NULL, + ], + ]; + \CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', \Civi::$statics[__CLASS__]['fields']); + } + return \Civi::$statics[__CLASS__]['fields']; + } + + /** + * Return a mapping from field-name to the corresponding key (as used in fields()). + * + * @return array + * Array(string $name => string $uniqueName). + */ + public static function &fieldKeys() { + if (!isset(\Civi::$statics[__CLASS__]['fieldKeys'])) { + \Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(\CRM_Utils_Array::collect('name', self::fields())); + } + return \Civi::$statics[__CLASS__]['fieldKeys']; + } + + /** + * Returns the names of this table + * + * @return string + */ + public static function getTableName() { + return self::$_tableName; + } + + /** + * Returns if this table needs to be logged + * + * @return bool + */ + public function getLog() { + return self::$_log; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = \CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'twingle_shop', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = \CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'twingle_shop', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? \CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/Civi/Twingle/Shop/Exceptions/ApiCallError.php b/Civi/Twingle/Shop/Exceptions/ApiCallError.php new file mode 100644 index 0000000..7d611ca --- /dev/null +++ b/Civi/Twingle/Shop/Exceptions/ApiCallError.php @@ -0,0 +1,19 @@ + $value) { + // Skip empty values + if (empty($value)) { + continue; + } + + // Find expected data type + $expected_data_type = strtolower(\CRM_Utils_Type::typeToString($allowed_attributes[$key])); // It could be so easy... + + // Validate data type + if (!\CRM_Utils_Type::validatePhpType($value, $expected_data_type)) { + $given_type = gettype($value); + throw new \Exception( + "Data type of attribute '$key' is $given_type, but $expected_data_type was expected." + ); + } + } +} + diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index be27fc8..17cb107 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -254,7 +254,14 @@ function _civicrm_api3_twingle_donation_Submit_spec(&$params) { 'title' => E::ts('Custom fields'), 'type' => CRM_Utils_Type::T_STRING, 'api.required' => 0, - 'description' => E::ts('Additional information for either the contact or the (recurring) contribution.'), + 'description' => E::ts('Additional information for either the contact or the (recurring) contribution.'), + ); + $params['products'] = [ + 'name' => 'products', + 'title' => E::ts('Products'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => E::ts('Products ordered via TwingleShop'), ]; $params['remarks'] = [ 'name' => 'remarks', @@ -634,6 +641,11 @@ function civicrm_api3_twingle_donation_Submit($params) { 'total_amount' => $params['amount'] / 100, ]; + // If the submission contains products, do not auto-create a line item + if (!empty($params['products']) && $profile->isShopEnabled()) { + $contribution_data['skipLineItem'] = 1; + } + // Add custom field values. if (isset($custom_fields['Contribution'])) { $contribution_data += $custom_fields['Contribution']; @@ -727,6 +739,27 @@ function civicrm_api3_twingle_donation_Submit($params) { $mandate = civicrm_api3('SepaMandate', 'createfull', $mandate_data); $result_values['sepa_mandate'] = reset($mandate['values']); + + // Add contribution data to result_values for later use + $contribution_id = $result_values['sepa_mandate']['entity_id']; + if ($contribution_id) { + $contribution = civicrm_api3( + 'Contribution', + 'getsingle', + ['id' => $contribution_id] + ); + $result_values['contribution'] = $contribution; + } else { + $mandate_id = $result_values['sepa_mandate']['id']; + $message = E::LONG_NAME . ": could not find contribution for sepa mandate $mandate_id"; + throw new CiviCRM_API3_Exception($message, 'api_error'); + } + + // Add products as line items to the contribution + if (!empty($params['products']) && $profile->isShopEnabled()) { + $line_items = CRM_Twingle_Submission::createLineItems($result_values, $params, $profile); + $result_values['contribution']['line_items'] = $line_items; + } } else { // Set financial type depending on donation rhythm. This applies for @@ -827,6 +860,12 @@ function civicrm_api3_twingle_donation_Submit($params) { } $result_values['contribution'] = $contribution; + + // Add products as line items to the contribution + if (!empty($params['products']) && $profile->isShopEnabled()) { + $line_items = CRM_Twingle_Submission::createLineItems($result_values, $params, $profile); + $result_values['contribution']['line_items'] = $line_items; + } } // MEMBERSHIP CREATION diff --git a/api/v3/TwingleProduct/Create.php b/api/v3/TwingleProduct/Create.php new file mode 100644 index 0000000..36bb48a --- /dev/null +++ b/api/v3/TwingleProduct/Create.php @@ -0,0 +1,138 @@ + 'id', + 'title' => E::ts('TwingleProduct ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('The TwingleProduct ID in the database'), + ]; + $spec['external_id'] = [ + 'name' => 'external_id', + 'title' => E::ts('Twingle ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => E::ts('External product ID in Twingle database'), + ]; + $spec['project_id'] = [ + 'name' => 'project_id', + 'title' => E::ts('Twingle Shop ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => E::ts('ID of the corresponding Twingle Shop'), + ]; + $spec['name'] = [ + 'name' => 'name', + 'title' => E::ts('Product Name'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => E::ts('Name of the product'), + ]; + $spec['is_active'] = [ + 'name' => 'is_active', + 'title' => E::ts('Is active?'), + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'api.required' => 0, + 'api.default' => 1, + 'description' => E::ts('Is the product active?'), + ]; + $spec['description'] = [ + 'name' => 'description', + 'title' => E::ts('Product Description'), + 'type' => CRM_Utils_Type::T_TEXT, + 'api.required' => 0, + 'description' => E::ts('Short description of the product'), + ]; + $spec['price'] = [ + 'name' => 'price', + 'title' => E::ts('Product Price'), + 'type' => CRM_Utils_Type::T_FLOAT, + 'api.required' => 0, + 'description' => E::ts('Price of the product'), + ]; + $spec['sort'] = [ + 'name' => 'sort', + 'title' => E::ts('Sort'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('Sort order of the product'), + ]; + $spec['financial_type_id'] = [ + 'name' => 'financial_type_id', + 'title' => E::ts('Financial Type ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => E::ts('ID of the financial type of the product'), + ]; + $spec['twingle_shop_id'] = [ + 'name' => 'twingle_shop_id', + 'title' => E::ts('FK to TwingleShop'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => E::ts('FK to TwingleShop'), + ]; + $spec['tw_updated_at'] = [ + 'name' => 'tw_updated_at', + 'title' => E::ts('Twingle timestamp'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => E::ts('Timestamp of last update in Twingle db'), + ]; + $spec['price_field_id'] = [ + 'name' => 'price_field_id', + 'title' => E::ts('FK to PriceField'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('FK to PriceField'), + ]; +} + +/** + * TwingleProduct.Create API + * + * @param array $params + * + * @return array + * API result descriptor + * + * @see civicrm_api3_create_success + * + * @throws API_Exception + * @throws \Exception + */ +function civicrm_api3_twingle_product_Create($params): array { + // Filter for allowed params + $allowed_params = []; + _civicrm_api3_twingle_product_Create_spec($allowed_params); + $params = array_intersect_key($params, $allowed_params); + + try { + // Create TwingleProduct and load params + $product = new TwingleProduct(); + $product->load($params); + + // Save TwingleProduct + $product->add(); + $result = $product->getAttributes(); + return civicrm_api3_create_success($result, $params, 'TwingleProduct', 'Create'); + } + catch (ProductException $e) { + return civicrm_api3_create_error($e->getMessage(), [ + 'error_code' => $e->getCode(), + 'params' => $params, + ]); + } +} diff --git a/api/v3/TwingleProduct/Delete.php b/api/v3/TwingleProduct/Delete.php new file mode 100644 index 0000000..60c9591 --- /dev/null +++ b/api/v3/TwingleProduct/Delete.php @@ -0,0 +1,72 @@ + 'id', + 'title' => E::ts('TwingleProduct ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('The TwingleProduct ID in CiviCRM'), + ]; + $spec['external_id'] = [ + 'name' => 'external_id', + 'title' => E::ts('External TwingleProduct ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('Twingle\'s ID of the product'), + ]; +} + +/** + * TwingleProduct.Delete API + * + * @param array $params + * + * @return array + * API result descriptor + * + * @throws API_Exception*@throws \Exception + * @throws \Exception + * @see civicrm_api3_create_success + * + */ +function civicrm_api3_twingle_product_Delete($params) { + // Filter for allowed params + $allowed_params = []; + _civicrm_api3_twingle_product_Delete_spec($allowed_params); + $params = array_intersect_key($params, $allowed_params); + + // Find TwingleProduct via getsingle API + $product_data = civicrm_api3('TwingleProduct', 'getsingle', $params); + if ($product_data['is_error']) { + return civicrm_api3_create_error($product_data['error_message'], + ['error_code' => $product_data['error_code'], 'params' => $params] + ); + } + + // Get TwingleProduct object + $product = TwingleProduct::findById($product_data['id']); + + // Delete TwingleProduct and associated PriceField and PriceFieldValue + $result = $product->delete(); + if ($result) { + return civicrm_api3_create_success(1, $params, 'TwingleProduct', 'Delete'); + } + else { + return civicrm_api3_create_error( + E::ts('TwingleProduct could not be deleted.'), + ['error_code' => 'delete_failed', 'params' => $params] + ); + } +} diff --git a/api/v3/TwingleProduct/Get.php b/api/v3/TwingleProduct/Get.php new file mode 100644 index 0000000..a7f64ca --- /dev/null +++ b/api/v3/TwingleProduct/Get.php @@ -0,0 +1,137 @@ + 'id', + 'title' => E::ts('TwingleProduct ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('The TwingleProduct ID in CiviCRM'), + ]; + $spec['external_id'] = [ + 'name' => 'external_id', + 'title' => E::ts('External TwingleProduct ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('Twingle\'s ID of the product'), + ]; + $spec['price_field_id'] = [ + 'name' => 'Price Field ID', + 'title' => E::ts('Price Field ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('FK to civicrm_price_field'), + ]; + $spec['twingle_shop_id'] = [ + 'name' => 'twingle_shop_id', + 'title' => E::ts('TwingleShop ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('The TwingleShop ID in CiviCRM'), + ]; + $spec['project_identifier'] = [ + 'name' => 'project_identifier', + 'title' => E::ts('Project Identifier'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => E::ts('Twingle project identifier'), + ]; + $spec['numerical_project_id'] = [ + 'name' => 'numerical_project_id', + 'title' => E::ts('Numerical Project Identifier'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('Twingle numerical project identifier'), + ]; +} + +/** + * TwingleProduct.Get API + * + * @param array $params + * + * @return array + * API result descriptor + * + * @throws API_Exception + * @see civicrm_api3_create_success + * + */ +function civicrm_api3_twingle_product_Get($params) { + // Filter for allowed params + $allowed_params = []; + _civicrm_api3_twingle_product_Get_spec($allowed_params); + $params = array_intersect_key($params, $allowed_params); + + // Build query + $query = 'SELECT ctp.* FROM civicrm_twingle_product ctp + INNER JOIN civicrm_twingle_shop cts ON ctp.twingle_shop_id = cts.id'; + $query_params = []; + + if (!empty($params)) { + $query = $query . ' WHERE'; + $possible_params = []; + _civicrm_api3_twingle_product_Get_spec($possible_params); + $param_count = 1; + $altered_params = []; + + // Specify product fields to define table prefix + $productFields = array_keys(TwingleProduct::fields()); + + // Alter params (prefix with table name) + foreach ($possible_params as $param) { + if (!empty($params[$param['name']])) { + // Prefix with table name + $table_prefix = in_array($param['name'], $productFields) ? 'ctp.' : 'cts.'; + $altered_params[] = [ + 'name' => $table_prefix . $param['name'], + 'value' => $params[$param['name']], + 'type' => $param['type'], + ]; + } + } + + // Add altered params to query + foreach ($altered_params as $param) { + $query = $query . ' ' . $param['name'] . " = %$param_count AND"; + $query_params[$param_count] = [ + $param['value'], + $param['type'] == CRM_Utils_Type::T_INT ? 'Integer' : 'String', + ]; + $param_count++; + } + } + + // Cut away last 'AND' + $query = substr($query, 0, -4); + + // Execute query + try { + $dao = TwingleProduct::executeQuery($query, $query_params); + } + catch (Exception $e) { + return civicrm_api3_create_error($e->getMessage(), [ + 'error_code' => $e->getCode(), + 'params' => $params, + ]); + } + + // Prepare return values + $returnValues = []; + while ($dao->fetch()) { + $returnValues[] = $dao->toArray(); + } + + return civicrm_api3_create_success($returnValues, $params, 'TwingleProduct', 'Get'); +} diff --git a/api/v3/TwingleProduct/Getsingle.php b/api/v3/TwingleProduct/Getsingle.php new file mode 100644 index 0000000..06eb88a --- /dev/null +++ b/api/v3/TwingleProduct/Getsingle.php @@ -0,0 +1,54 @@ + 'missing_parameter', 'params' => $params] + ); + } + + // Find TwingleProduct via get API + $returnValues = civicrm_api3('TwingleProduct', 'get', $params); + $count = $returnValues['count']; + + // Check whether only a single TwingleProduct is found + if ($count != 1) { + return civicrm_api3_create_error( + "Expected one TwingleProduct but found $count", + ['error_code' => 'not_found', 'params' => $params] + ); + } + return $returnValues['values'][$returnValues['id']]; +} diff --git a/api/v3/TwingleShop/Create.php b/api/v3/TwingleShop/Create.php new file mode 100644 index 0000000..ebb34f4 --- /dev/null +++ b/api/v3/TwingleShop/Create.php @@ -0,0 +1,80 @@ + 'project_identifier', + 'title' => E::ts('Project Identifier'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => E::ts('Twingle project identifier'), + ]; + $spec['numerical_project_id'] = [ + 'name' => 'numerical_project_id', + 'title' => E::ts('Numerical Project Identifier'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => E::ts('Numerical Twingle project identifier'), + ]; + $spec['name'] = [ + 'name' => 'name', + 'title' => E::ts('Shop Name'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => E::ts('Name of the shop'), + ]; + $spec['financial_type_id'] = [ + 'name' => 'financial_type_id', + 'title' => E::ts('Financial Type ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => E::ts('FK to civicrm_financial_type'), + ]; +} + +/** + * TwingleShop.Create API + * + * @param array $params + * + * @return array + * API result descriptor + * + * @see civicrm_api3_create_success + * + * @throws API_Exception + */ +function civicrm_api3_twingle_shop_Create($params) { + // Filter for allowed params + $allowed_params = []; + _civicrm_api3_twingle_shop_Create_spec($allowed_params); + $params = array_intersect_key($params, $allowed_params); + + try { + // Create TwingleShop and load params + $shop = new TwingleShop(); + $shop->load($params); + + // Save TwingleShop + $result = $shop->add(); + + // Return success + return civicrm_api3_create_success($result, $params, 'TwingleShop', 'Create'); + } + catch (ShopException $e) { + return civicrm_api3_create_error($e->getMessage(), [ + 'error_code' => $e->getErrorCode(), + 'params' => $params, + ]); + } +} diff --git a/api/v3/TwingleShop/Delete.php b/api/v3/TwingleShop/Delete.php new file mode 100644 index 0000000..d1f3323 --- /dev/null +++ b/api/v3/TwingleShop/Delete.php @@ -0,0 +1,79 @@ + 'id', + 'title' => E::ts('TwingleShop ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('The TwingleShop ID in CiviCRM'), + ]; + $spec['project_identifier'] = [ + 'name' => 'project_identifier', + 'title' => E::ts('Project Identifier'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => E::ts('Twingle project identifier'), + ]; +} + +/** + * TwingleShop.Delete API + * + * @param array $params + * + * @return array + * API result descriptor + * + * @throws \API_Exception + * @throws \Civi\Twingle\Shop\Exceptions\ShopException + * @throws \Exception + * @see civicrm_api3_create_success + * + */ +function civicrm_api3_twingle_shop_Delete($params) { + // Filter for allowed params + $allowed_params = []; + _civicrm_api3_twingle_shop_Delete_spec($allowed_params); + $params = array_intersect_key($params, $allowed_params); + + // Find TwingleShop via getsingle API + $shop_data = civicrm_api3('TwingleShop', 'getsingle', $params); + if ($shop_data['is_error']) { + return civicrm_api3_create_error($shop_data['error_message'], + ['error_code' => $shop_data['error_code'], 'params' => $params] + ); + } + + // Get TwingleShop object + $shop = TwingleShop::findById($shop_data['id']); + + // Delete TwingleShop + /* @var \Civi\Twingle\Shop\BAO\TwingleShop $shop */ + $result = $shop->deleteByConstraint(); + if ($result) { + return civicrm_api3_create_success(1, $params, 'TwingleShop', 'Delete'); + } + elseif ($result === 0) { + return civicrm_api3_create_error( + E::ts('TwingleShop could not be found.'), + ['error_code' => 'not_found', 'params' => $params] + ); + } + else { + return civicrm_api3_create_error( + E::ts('TwingleShop could not be deleted.'), + ['error_code' => 'delete_failed', 'params' => $params] + ); + } +} diff --git a/api/v3/TwingleShop/Fetch.php b/api/v3/TwingleShop/Fetch.php new file mode 100644 index 0000000..961f42b --- /dev/null +++ b/api/v3/TwingleShop/Fetch.php @@ -0,0 +1,97 @@ + 'project_identifiers', + 'title' => E::ts('Project Identifiers'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => E::ts('Comma separated list of Twingle project identifiers.'), + ]; +} + +/** + * TwingleShop.Fetch API + * + * @param array $params + * + * @return array + * API result descriptor + * + * @see civicrm_api3_create_success + * + * @throws API_Exception + */ +function civicrm_api3_twingle_shop_Fetch($params) { + // Filter for allowed params + $allowed_params = []; + _civicrm_api3_twingle_shop_Fetch_spec($allowed_params); + $params = array_intersect_key($params, $allowed_params); + + $returnValues = []; + + // Explode string with project IDs and trim + $projectIds = array_map( + function ($projectId) { + return trim($projectId); + }, + explode(',', $params['project_identifiers']) + ); + + // Get products for all projects of type 'shop' + foreach ($projectIds as $projectId) { + try { + $shop = TwingleShop::findByProjectIdentifier($projectId); + $products = $shop->fetchProducts(); + $returnValues[$projectId] = []; + $returnValues[$projectId] += $shop->getAttributes(); + $returnValues[$projectId]['products'] = array_map(function ($product) { + return $product->getAttributes(); + }, $products); + } + catch (ShopException|ApiCallError|ProductException $e) { + // If this project identifier doesn't belong to a project of type + // 'shop', just skip it + if ($e->getErrorCode() == ShopException::ERROR_CODE_NOT_A_SHOP) { + $returnValues[$projectId] = "project is not of type 'shop'"; + continue; + } + // Else, log error and throw exception + else { + Civi::log()->error( + $e->getMessage(), + [ + 'project_identifier' => $projectId, + 'params' => $params, + ] + ); + return civicrm_api3_create_error($e->getMessage(), [ + 'error_code' => $e->getErrorCode(), + 'project_identifier' => $projectId, + 'params' => $params, + ]); + } + } + } + + return civicrm_api3_create_success( + $returnValues, + $params, + 'TwingleShop', + 'Fetch' + ); +} diff --git a/api/v3/TwingleShop/Get.php b/api/v3/TwingleShop/Get.php new file mode 100644 index 0000000..2915212 --- /dev/null +++ b/api/v3/TwingleShop/Get.php @@ -0,0 +1,111 @@ + 'id', + 'title' => E::ts('TwingleShop ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('The TwingleShop ID in CiviCRM'), + ]; + $spec['project_identifier'] = [ + 'name' => 'project_identifier', + 'title' => E::ts('Project Identifier'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => E::ts('Twingle project identifier'), + ]; + $spec['numerical_project_id'] = [ + 'name' => 'numerical_project_id', + 'title' => E::ts('Numerical Project Identifier'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('Twingle numerical project identifier'), + ]; + $spec['name'] = [ + 'name' => 'name', + 'title' => E::ts('Name'), + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => E::ts('Name of the TwingleShop'), + ]; + $spec['price_set_id'] = [ + 'name' => 'price_set_id', + 'title' => E::ts('Price Set ID'), + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 0, + 'description' => E::ts('FK to civicrm_price_set'), + ]; +} + +/** + * TwingleShop.Get API + * + * @param array $params + * + * @return array + * API result descriptor + * + * @see civicrm_api3_create_success + * + * @throws API_Exception + */ +function civicrm_api3_twingle_shop_Get($params) { + // Filter for allowed params + $allowed_params = []; + _civicrm_api3_twingle_shop_Get_spec($allowed_params); + $params = array_intersect_key($params, $allowed_params); + + // Build query + $query = 'SELECT * FROM civicrm_twingle_shop'; + $query_params = []; + + if (!empty($params)) { + $query = $query . ' WHERE'; + $possible_params = []; + _civicrm_api3_twingle_shop_Get_spec($possible_params); + $param_count = 1; + + foreach ($possible_params as $param) { + if (!empty($params[$param['name']])) { + $query = $query . ' ' . $param['name'] . " = %$param_count AND"; + $query_params[$param_count] = [ + $params[$param['name']], + $param['type'] == CRM_Utils_Type::T_INT ? 'Integer' : 'String' + ]; + $param_count++; + } + } + // Cut away last 'AND' + $query = substr($query, 0, -4); + } + + // Execute query + try { + $dao = TwingleShop::executeQuery($query, $query_params); + } + catch (\Exception $e) { + return civicrm_api3_create_error($e->getMessage(), [ + 'error_code' => $e->getCode(), + 'params' => $params, + ]); + } + + // Prepare return values + $returnValues = []; + while ($dao->fetch()) { + $returnValues[] = $dao->toArray(); + } + + return civicrm_api3_create_success($returnValues, $params, 'TwingleShop', 'Get'); +} diff --git a/api/v3/TwingleShop/Getsingle.php b/api/v3/TwingleShop/Getsingle.php new file mode 100644 index 0000000..ff5cf86 --- /dev/null +++ b/api/v3/TwingleShop/Getsingle.php @@ -0,0 +1,54 @@ + 'missing_parameter', 'params' => $params] + ); + } + + // Find TwingleShop via get API + $returnValues = civicrm_api3('TwingleShop', 'get', $params); + $count = $returnValues['count']; + + // Check whether only a single TwingleShop is found + if ($count != 1) { + return civicrm_api3_create_error( + "Expected one TwingleShop but found $count", + ['error_code' => 'not_found', 'params' => $params] + ); + } + return $returnValues['values'][$returnValues['id']]; +} diff --git a/css/twingle_shop.css b/css/twingle_shop.css new file mode 100644 index 0000000..898001f --- /dev/null +++ b/css/twingle_shop.css @@ -0,0 +1,37 @@ +.twingle-shop-table-caption { + font-weight: bold; + font-size: 13px; + padding: 6px; + text-align: left; +} + +.twingle-shop-table-divider { + border-top: 1px solid #ddd; + margin-top: 20px; + margin-bottom: 4px; +} + +#twingle-shop-spinner { + animation: spin 2s linear infinite; +} + +.twingle-shop-table tbody tr { + height: 32px; +} + +.twingle-shop-table-button { + margin: 10px 15px 0 0 !important; +} + +.twingle-shop-cell-button { + margin: 3px 5px 3px 5px; +} + +.strikethrough { + text-decoration: line-through; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/info.xml b/info.xml index d0876c8..d715ec8 100644 --- a/info.xml +++ b/info.xml @@ -18,7 +18,7 @@ 1.5-dev dev - 5.56 + 5.58 diff --git a/js/twingle_shop.js b/js/twingle_shop.js new file mode 100644 index 0000000..fb7dd40 --- /dev/null +++ b/js/twingle_shop.js @@ -0,0 +1,741 @@ +/** + * This file contains the JavaScript code for the Twingle Shop integration. + */ + +/** + * This function initializes the Twingle Shop integration. + */ +function twingleShopInit() { + cj('#twingle-shop-spinner').hide(); + + // Run once on page load + load_financial_types(); + twingle_shop_active_changed(); + twingle_map_products_changed(); + twingle_fetch_products(); + + // Add event listeners + cj('#enable_shop_integration:checkbox').change(twingle_shop_active_changed); + cj('#shop_map_products:checkbox').change(twingle_map_products_changed); + cj('#btn_fetch_products').click(function (event) { + event.preventDefault(); // Prevent the default form submission behavior + twingle_fetch_products(); + }); +} + +// Define financial types as global variable +let financialTypes = {}; + +/** + * Load financial types from CiviCRM + */ +function load_financial_types() { + CRM.api3('FinancialType', 'get', { + 'sequential': 1, + 'options': { 'limit': 0 }, + }).then(function (result) { + financialTypes = result.values.reduce((obj, item) => { + obj[item.id] = item.name; + return obj; + }, {}); + }); +} + +/** + * Fetches the Twingle products for the given project identifiers. + */ +function twingle_fetch_products() { + let active = cj('#shop_map_products:checkbox:checked').length; + if (active) { + cj('#twingle-shop-spinner').show(); + CRM.api3('TwingleShop', 'fetch', { + 'project_identifiers': cj('#selectors :input').val(), + }).then(function (result) { + if (result.is_error === 1) { + cj('#btn_fetch_products').crmError(result.error_message, ts('Could not fetch products', [])); + cj('#twingle-shop-spinner').hide(); + return; + } + buildShopTables(result); + cj('#twingle-shop-spinner').hide(); + }, function () { + cj('#btn_fetch_products').crmError(ts('Could not fetch products. Please check your Twingle API key.', [])); + cj('#twingle-shop-spinner').hide(); + }); + } +} + +/** + * Update the form fields based on whether shop integration is currently active + */ +function twingle_shop_active_changed() { + let active = cj('#enable_shop_integration:checkbox:checked').length; + if (active) { + cj('.twingle-shop-element').show(); + } else { + cj('.twingle-shop-element').hide(); + } +} + +/** + * Display fetch button and product mapping when the corresponding option is active + */ +function twingle_map_products_changed() { + let active = cj('#shop_map_products:checkbox:checked').length; + if (active) { + cj('.twingle-product-mapping').show(); + } else { + cj('.twingle-product-mapping').hide(); + } +} + +/** + * This function builds the shop tables. + * @param shopData + */ +function buildShopTables(shopData) { + + let productTables = []; + + // Create table for each project (shop) + for (const key in shopData.values) { + productTables.push(new ProductsTable(shopData.values[key])); + } + + // Add table container to DOM + const tableContainer = document.getElementById('tableContainer'); + + // Add tables to table container + for (const productTable of productTables) { + tableContainer.appendChild(productTable.table); + } +} + +/** + * Get the value of the default financial type for the shops defined in this profile. + * @returns {string|string} + */ +function getShopDefaultFinancialType() { + const default_selection = document.getElementById('s2id_shop_financial_type'); + const selected = default_selection.getElementsByClassName('select2-chosen')[0]; + return selected ? selected.textContent : ''; +} + +/** + * Get the value of the default financial type. + * @returns {string} + */ +function getShopDefaultFinancialTypeValue() { + const shopDefaultFinancialType = getShopDefaultFinancialType(); + return Object.keys(financialTypes).find(key => financialTypes[key] === shopDefaultFinancialType); +} + +/** + * This class represents a Twingle Product. + */ +class Product { + + /** + * Creates a new Product object. + * @param productData + * @param parentTable + */ + constructor(productData, parentTable) { + this.parentTable = parentTable; + this.setProps(productData); + } + + /** + * Sets the properties of this product. + * @param productData + * @private + */ + setProps(productData) { + this.id = productData.id; + this.name = productData.name; + this.isActive = productData.is_active; + this.price = productData.price; + this.sort = productData.sort; + this.description = productData.description; + this.projectId = productData.project_id; + this.externalId = productData.external_id; + this.isOutdated = productData.is_outdated; + this.isOrphaned = productData.is_orphaned; + // this.updatedAt = productData.updated_at; + this.createdAt = productData.created_at; + this.twUpdatedAt = productData.tw_updated_at; + this.financialTypeId = productData.financial_type_id; + this.priceFieldId = productData.price_field_id; + } + + /** + * Dumps the product data. + * @returns {{id, name, is_active, price, sort, description, project_id, external_id, financial_type_id, tw_updated_at, twingle_shop_id: *}} + */ + dumpData() { + return { + 'id': this.id, + 'name': this.name, + 'is_active': this.isActive, + 'price': this.price, + 'sort': this.sort, + 'description': this.description, + 'project_id': this.projectId, + 'external_id': this.externalId, + 'financial_type_id': this.financialTypeId, + 'price_field_id': this.priceFieldId, + 'tw_updated_at': this.twUpdatedAt, + 'twingle_shop_id': this.parentTable.id, + }; + } + + /** + * Creates a button for creating, updating or deleting the price field for + * this product. + * @param action + * @param handler + * @returns {HTMLButtonElement} + * @private + */ + createProductButton(action, handler) { + // Create button + const button = document.createElement('button'); + button.id = action + '_twingle_product_tw_' + this.externalId; + button.classList.add('twingle-shop-cell-button'); + + // Add button text + let text = action === 'create' ? ts('Create', []) : action === 'update' ? ts('Update', []) : ts('Delete', []); + button.textContent = ' ' + ts(text, []); + + // Add button handler + if (handler) { + button.onclick = handler; + } else { + button.disabled = true; + } + + // Deactivate 'create' button if product hast no financial type + if (action === 'create' && this.financialTypeId === null) { + button.disabled = true; + } + + // Add icon + const icon = document.createElement('i'); + const iconClass = action === 'create' ? 'fa-plus-circle' : action === 'update' ? 'fa-refresh' : 'fa-trash'; + icon.classList.add('crm-i', iconClass); + button.insertBefore(icon, button.firstChild); + + return button; + } + + /** + * Creates a handler for creating a price field for this product. + * @returns {(function(*): void)|*} + * @private + */ + createPriceFieldHandler() { + const self = this; + return function (event) { + event.preventDefault(); + const action = event.target.innerText.includes('Update') ? 'updated' : 'created'; + CRM.api3('TwingleProduct', 'create', self.dumpData()) + .then(function (result) { + if (result.is_error === 1) { + cj('#create_twingle_product_tw_' + self.id).crmError(result.error_message, ts('Could not create Price Field for this product', [])); + } else { + self.update(result.values); + CRM.alert(ts(`The Price Field was ${action} successfully.`, []), ts(`Price Field ${action}`, []), 'success', {'expires': 5000}); + } + }, function (error) { + cj('#create_twingle_product_tw_' + self.id).crmError(error.message, ts('Could not create Price Field for this product', [])); + }); + }; + } + + /** + * Creates a handler for creating a price field for this product. + * @returns {(function(*): void)|*} + * @private + */ + deletePriceFieldHandler() { + let self = this; + return function (event) { + event.preventDefault(); + const options = { + 'title': ts('Delete Price Field', []), + 'message': ts('Are you sure you want to delete the price field associated with this product?', []), + }; + CRM.confirm(options) + .on('crmConfirm:yes', function () { + CRM.api3('TwingleProduct', 'delete', { 'id': self.id }) + .then(function (result) { + if (result.is_error === 1) { + cj('#create_twingle_product_tw_' + self.id).crmError(result.error_message, ts('Could not delete Price Field', [])); + } else { + self.update(); + } + CRM.alert(ts('The Price Field was deleted successfully.', []), ts('Price Field deleted', []), 'success', {'expires': 5000}); + }, function (error) { + cj('#create_twingle_product_tw_' + self.id).crmError(error.message, ts('Could not delete Price Field', [])); + }); + }); + }; + } + + /** + * Creates a new row with the product name and buttons for creating, updating + * or deleting the price field for this product. + * @returns {*} + */ + createRow() { + let row; + + // Clear row + if (this.row) { + for (let i = this.row.cells.length - 1; i >= 0; i--) { + // Delete everything from row + this.row.deleteCell(i); + } + row = this.row; + } else { + // Create new row element + row = document.createElement('tr'); + + // Add id to row + row.id = 'twingle_product_tw_' + this.externalId; + } + + // Add cell with product name + const nameCell = document.createElement('td'); + if (this.isOrphaned) { + nameCell.classList.add('strikethrough'); + } + nameCell.textContent = this.name; + row.appendChild(nameCell); + + // Add cell for buttons + let buttonCell = row.insertCell(1); + + // Add product buttons which allow to create, update or delete the price + // field for this product + if (this.parentTable.id) { + let buttons = this.createProductButtons(); + for (const button of buttons) { + buttonCell.appendChild(button); + } + } + + // Add financial type dropdown for each product if price set exists + if (this.parentTable.id) { + let dropdown = this.createFinancialTypeDropdown(); + const cell = document.createElement('td'); + cell.classList.add('twingle-shop-financial-type-select'); + cell.appendChild(dropdown); + row.insertCell(2).appendChild(cell); + } + // else add default financial type + else { + const cell = document.createElement('td'); + cell.classList.add('twingle-shop-financial-type-default'); + cell.innerHTML = '' + getShopDefaultFinancialType() + ''; + row.insertCell(1).appendChild(cell); + } + + this.row = row; + return this.row; + } + + /** + * Determining which actions are available for this product and creating a + * button for each of them. + * @returns {Array} Array of buttons + */ + createProductButtons() { + let actionsAndHandlers = []; + let buttons = []; + + // Determine actions; if product has price field id, it can be updated or + // deleted, otherwise it can be created + if (this.priceFieldId) { + if (this.isOutdated) { + actionsAndHandlers.push(['update', this.createPriceFieldHandler()]); + } else if (!this.isOrphaned) { + actionsAndHandlers.push(['update', null]); + } + actionsAndHandlers.push(['delete', this.deletePriceFieldHandler()]); + } else { + actionsAndHandlers.push(['create', this.createPriceFieldHandler()]); + } + + // Create button for each action + for (const [action, handler] of actionsAndHandlers) { + buttons.push(this.createProductButton(action, handler)); + } + + return buttons; + } + + /** + * Creates a dropdown for selecting the financial type for this product. + * @returns {HTMLSelectElement} + * @private + */ + createFinancialTypeDropdown() { + // Create new dropdown element + const dropdown = document.createElement('select'); + dropdown.id = 'twingle_product_tw_' + this.externalId + '_financial_type'; + + // Add empty option if no price field exists + if (!this.priceFieldId) { + let option = document.createElement('option'); + option.value = ''; + option.innerHTML = '<' + ts('select financial type', []) + '>'; + option.selected = true; + option.disabled = true; + dropdown.appendChild(option); + } + + // Add options for each financial type available in CiviCRM + for (const key in financialTypes) { + let option = document.createElement('option'); + option.value = key; + option.text = financialTypes[key]; // financialTypes is defined in twingle_shop.tpl as smarty variable + if (this.financialTypeId !== null && this.financialTypeId.toString() === key) { + option.selected = true; + } + dropdown.appendChild(option); + } + + // Add handlers + let self = this; + dropdown.onchange = function () { + + // Enable 'create' button if financial type is selected + const createButton = document.getElementById('twingle_product_tw_' + self.externalId).getElementsByClassName('twingle-shop-cell-button')[0]; + if (createButton.textContent.includes('Create')) { + createButton.disabled = dropdown.value === '0'; + } + + // Update financial type + self.financialTypeId = dropdown.value; + }; + + return dropdown; + } + + /** + * Updates the product properties and rebuilds the row. + * @param productData + */ + update(productData = null) { + if (productData) { + this.setProps(productData); + } else { + this.reset(); + } + this.createRow(); + } + + /** + * Resets the product properties. + */ + reset() { + this.financialTypeId = null; + this.priceFieldId = null; + this.isOutdated = null; + this.isOutdated = null; + // this.updatedAt = null; + this.createdAt = null; + this.id = null; + } + +} + +/** + * This class represents a Twingle Shop. + */ +class ProductsTable { + + /** + * Creates a new ProductsTable object. + * @param projectData + */ + constructor(projectData) { + this.setProps(projectData); + } + + /** + * Sets the properties of this project. + * @param projectData + * @private + */ + setProps(projectData) { + this.id = projectData.id; + this.name = projectData.name; + this.numericalProjectId = projectData.numerical_project_id; + this.projectIdentifier = projectData.project_identifier; + this.products = projectData.products.map(productData => new Product(productData, this)); + this.priceSetId = projectData.price_set_id; + this.table = this.buildTable(); + } + + /** + * Dumps the projects data. + * @returns {{price_set_id, financial_type_id, numerical_project_id, name, id, project_identifier, products: *}} + */ + dumpData() { + return { + 'id': this.id, + 'name': this.name, + 'numerical_project_id': this.numericalProjectId, + 'project_identifier': this.projectIdentifier, + 'price_set_id': this.priceSetId, + 'products': this.products.map(product => product.dumpData()), + 'financial_type_id': getShopDefaultFinancialTypeValue() + }; + } + + /** + * Builds the table for this project (shop). + * @returns {HTMLTableElement} + * @private + */ + buildTable() { + let table; + + // Clear table body + if (this.table) { + this.clearTableHeader(); + this.clearTableBody(); + this.updateTableButtons(); + table = this.table; + } else { + // Create new table element + table = document.createElement('table'); + table.classList.add('twingle-shop-table'); + table.id = this.projectIdentifier; + + // Add caption + const caption = table.createCaption(); + caption.textContent = this.name + ' (' + this.projectIdentifier + ')'; + caption.classList.add('twingle-shop-table-caption'); + + // Add table body + const tbody = document.createElement('tbody'); + table.appendChild(tbody); + + // Add table buttons + this.addTableButtons(table); + } + + // Add header row + const thead = table.createTHead(); + const headerRow = thead.insertRow(); + const headers = [ts('Product', []), ts('Financial Type', [])]; + + // Add price field column if price set exists + if (this.priceSetId) { + headers.splice(1, 0, ts('Price Field', [])); + } + + for (const headerText of headers) { + const headerCell = document.createElement('th'); + headerCell.textContent = headerText; + headerRow.appendChild(headerCell); + } + + // Add products to table + this.addProductsToTable(table); + + return table; + } + + /** + * Adds buttons for creating, updating or deleting the price set for the + * given project (shop). + * @private + */ + addTableButtons(table) { + table.appendChild(this.createTableButton('update', this.updatePriceSetHandler())); + if (this.priceSetId === null) { + table.appendChild(this.createTableButton('create', this.createPriceSetHandler())); + } else { + table.appendChild(this.createTableButton('delete', this.deletePriceSetHandler())); + } + } + + /** + * Creates a button for creating, updating or deleting the price set for the + * given project (shop). + * @param action + * @param handler + * @returns {HTMLButtonElement} + * @private + */ + createTableButton(action, handler) { + // Create button + const button = document.createElement('button'); + button.id = 'btn_' + action + '_twingle_shop_' + this.projectIdentifier; + button.classList.add('crm-button', 'twingle-shop-table-button'); + + // Add button text + const text = action === 'create' ? ts('Create Price Set', []) : action === 'update' ? ts('Update Price Set', []) : ts('Delete Price Set', []); + button.textContent = ' ' + ts(text, []); + + // Add button handler + button.onclick = handler; + + // Add icon + const icon = document.createElement('i'); + const iconClass = action === 'create' ? 'fa-plus-circle' : action === 'update' ? 'fa-refresh' : 'fa-trash'; + icon.classList.add('crm-i', iconClass); + button.insertBefore(icon, button.firstChild); + + return button; + } + + /** + * Adds products to table body. + * @param table + * @private + */ + addProductsToTable(table) { + // Get table body + const tbody = table.getElementsByTagName('tbody')[0]; + + // Add products to table body + for (const product of this.products) { + // Add row for product + const row = product.createRow(); + // Add row to table + tbody.appendChild(row); + } + } + + /** + * Updates the table buttons. + */ + updateTableButtons() { + const table_buttons = this.table.getElementsByClassName('twingle-shop-table-button'); + // Remove all price set buttons from table + while (table_buttons.length > 0) { + table_buttons[0].remove(); + } + this.addTableButtons(this.table); + } + + /** + * Clears the table header. + * @private + */ + clearTableHeader() { + const thead = this.table.getElementsByTagName('thead')[0]; + while (thead.firstChild) { + thead.removeChild(thead.firstChild); + } + } + + /** + * Clears the table body. + */ + clearTableBody() { + const tbody = this.table.getElementsByTagName('tbody')[0]; + while (tbody.firstChild) { + tbody.removeChild(tbody.firstChild); + } + } + + /** + * Creates a handler for creating the price set for the given project (shop). + * @returns {(function(*): void)|*} + */ + createPriceSetHandler() { + let self = this; + return function (event) { + event.preventDefault(); + CRM.api3('TwingleShop', 'create', self.dumpData()) + .then(function (result) { + if (result.is_error === 1) { + cj('#btn_create_price_set_' + self.projectIdentifier).crmError(result.error_message, ts('Could not create Twingle Shop', [])); + } else { + self.update(); + CRM.alert(ts('The Price Set was created successfully.', []), ts('Price Field created', []), 'success', {'expires': 5000}); + } + }, function (error) { + cj('#btn_create_price_set_' + self.projectIdentifier).crmError(error.message, ts('Could not create TwingleShop', [])); + }); + }; + } + + /** + * Creates a handler for deleting the price set for the given project (shop). + * @returns {(function(*): void)|*} + */ + deletePriceSetHandler() { + let self = this; + return function (event) { + event.preventDefault(); + const options = { + 'title': ts('Delete Price Set', []), + 'message': ts('Are you sure you want to delete the price set associated with this Twingle Shop?', []), + }; + CRM.confirm(options) + .on('crmConfirm:yes', function () { + CRM.api3('TwingleShop', 'delete', { + 'project_identifier': self.projectIdentifier, + }).then(function (result) { + if (result.is_error === 1) { + cj('#btn_create_price_set_' + self.projectIdentifier).crmError(result.error_message, ts('Could not delete Twingle Shop', [])); + } else { + self.update(); + CRM.alert(ts('The Price Set was deleted successfully.', []), ts('Price Set deleted', []), 'success', {'expires': 5000}); + } + }, function (error) { + cj('#btn_delete_price_set_' + self.projectIdentifier).crmError(error.message, ts('Could not delete Twingle Shop', [])); + }); + }); + }; + } + + /** + * Creates a handler for updating the price set for the given project (shop). + * @returns {(function(*): void)|*} + */ + updatePriceSetHandler() { + let self = this; + return function (event) { + cj('#twingle-shop-spinner').show(); + if (event) { + event.preventDefault(); + } + CRM.api3('TwingleShop', 'fetch', { + 'project_identifiers': self.projectIdentifier, + }).then(function (result) { + if (result.is_error === 1) { + cj('#btn_create_price_set_' + self.projectIdentifier).crmError(result.error_message, ts('Could not delete Twingle Shop', [])); + cj('#twingle-shop-spinner').hide(); + } else { + self.update(result.values[self.projectIdentifier]); + cj('#twingle-shop-spinner').hide(); + } + }, function (error) { + cj('#btn_update_price_set_' + self.projectIdentifier).crmError(error.message, ts('Could not update Twingle Shop', [])); + cj('#twingle-shop-spinner').hide(); + }); + }; + } + + /** + * Updates the project properties and rebuilds the table. + * @param projectData + */ + update(projectData) { + if (!projectData) { + const updatePriceSet = this.updatePriceSetHandler(); + updatePriceSet(); + } else { + this.setProps(projectData); + this.buildTable(); + } + } +} diff --git a/sql/auto_uninstall.sql b/sql/auto_uninstall.sql new file mode 100644 index 0000000..b37b946 --- /dev/null +++ b/sql/auto_uninstall.sql @@ -0,0 +1,21 @@ +-- +--------------------------------------------------------------------+ +-- | Copyright CiviCRM LLC. All rights reserved. | +-- | | +-- | This work is published under the GNU AGPLv3 license with some | +-- | permitted exceptions and without any warranty. For full license | +-- | and copyright information, see https://civicrm.org/licensing | +-- +--------------------------------------------------------------------+ +-- +-- Generated from drop.tpl +-- DO NOT EDIT. Generated by CRM_Core_CodeGen +---- /******************************************************* +-- * +-- * Clean up the existing tables-- * +-- *******************************************************/ + +SET FOREIGN_KEY_CHECKS=0; + +DROP TABLE IF EXISTS `civicrm_twingle_product`; +DROP TABLE IF EXISTS `civicrm_twingle_shop`; + +SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file diff --git a/sql/civicrm_twingle_shop.sql b/sql/civicrm_twingle_shop.sql new file mode 100644 index 0000000..918b0ef --- /dev/null +++ b/sql/civicrm_twingle_shop.sql @@ -0,0 +1,66 @@ +-- +--------------------------------------------------------------------+ +-- | Copyright CiviCRM LLC. All rights reserved. | +-- | | +-- | This work is published under the GNU AGPLv3 license with some | +-- | permitted exceptions and without any warranty. For full license | +-- | and copyright information, see https://civicrm.org/licensing | +-- +--------------------------------------------------------------------+ +-- +-- Generated from schema.tpl +-- DO NOT EDIT. Generated by CRM_Core_CodeGen +-- +-- /******************************************************* +-- * +-- * Clean up the existing tables - this section generated from drop.tpl +-- * +-- *******************************************************/ + +SET FOREIGN_KEY_CHECKS=0; + +DROP TABLE IF EXISTS `civicrm_twingle_product`; +DROP TABLE IF EXISTS `civicrm_twingle_shop`; + +SET FOREIGN_KEY_CHECKS=1; +-- /******************************************************* +-- * +-- * Create new tables +-- * +-- *******************************************************/ + +-- /******************************************************* +-- * +-- * civicrm_twingle_shop +-- * +-- * This table contains the Twingle Shop data. Each Twingle Shop is linked to a corresponding Price Set. +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_twingle_shop` ( + `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique TwingleShop ID', + `project_identifier` varchar(32) NOT NULL COMMENT 'Twingle Project Identifier', + `numerical_project_id` int unsigned NOT NULL COMMENT 'Numerical Twingle Project Identifier', + `price_set_id` int unsigned COMMENT 'FK to Price Set', + `name` varchar(64) NOT NULL COMMENT 'name of the shop', + PRIMARY KEY (`id`), + CONSTRAINT FK_civicrm_twingle_shop_price_set_id FOREIGN KEY (`price_set_id`) REFERENCES `civicrm_price_set`(`id`) ON DELETE CASCADE +) + ENGINE=InnoDB; + +-- /******************************************************* +-- * +-- * civicrm_twingle_product +-- * +-- * This table contains the Twingle Product data. +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_twingle_product` ( + `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique TwingleProduct ID', + `external_id` int unsigned NOT NULL COMMENT 'The ID of this product in the Twingle database', + `price_field_id` int unsigned NOT NULL COMMENT 'FK to Price Field', + `twingle_shop_id` int unsigned COMMENT 'FK to Twingle Shop', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Timestamp of when the product was created in the database', + `updated_at` datetime NOT NULL COMMENT 'Timestamp of when the product was last updated in the Twingle database', + PRIMARY KEY (`id`), + CONSTRAINT FK_civicrm_twingle_product_price_field_id FOREIGN KEY (`price_field_id`) REFERENCES `civicrm_price_field`(`id`) ON DELETE CASCADE, + CONSTRAINT FK_civicrm_twingle_product_twingle_shop_id FOREIGN KEY (`twingle_shop_id`) REFERENCES `civicrm_twingle_shop`(`id`) ON DELETE CASCADE +) + ENGINE=InnoDB; diff --git a/templates/CRM/Twingle/Form/Profile.hlp b/templates/CRM/Twingle/Form/Profile.hlp index a8efdc5..8098450 100644 --- a/templates/CRM/Twingle/Form/Profile.hlp +++ b/templates/CRM/Twingle/Form/Profile.hlp @@ -85,4 +85,13 @@ {ts domain="de.systopia.twingle"}

    Create a contact note for each field specified in this selection.

    Tip: You can enable or disable this fields in the TwingleMANAGER.

    {/ts} {/htxt} + +{htxt id='id-enable_shop_integration'} +

    {ts domain="de.systopia.twingle"}Enable the processing of orders via Twingle Shop for this profile. The ordered products will then appear as line items in the contribution.{/ts}

    +{/htxt} + +{htxt id='id-shop_map_products'} +

    {ts domain="de.systopia.twingle"}If this option is enabled, all Twingle Shop products corresponding to the specified project IDs will be retrieved from Twingle and mapped as price sets and price fields. Each Twingle Shop is mapped as a price set with its products as price fields.

    +

    This allows you to manually create contributions with the same line items for phone orders, for example, as would be the case for orders placed through the Twingle Shop.

    +{/htxt} {/crmScope} diff --git a/templates/CRM/Twingle/Form/Profile.tpl b/templates/CRM/Twingle/Form/Profile.tpl index 1577b5f..8224e84 100644 --- a/templates/CRM/Twingle/Form/Profile.tpl +++ b/templates/CRM/Twingle/Form/Profile.tpl @@ -46,7 +46,7 @@ class="helpicon" > - {$form.selector.html} + {$form.selector.html} {/if} @@ -353,6 +353,71 @@ + {if $twingle_use_shop eq 1} + + {ts domain="de.systopia.twingle"}Shop Integration{/ts} + + + + + + + + + + + + + + + + + + + + + +
    + {$form.enable_shop_integration.label} + + {$form.enable_shop_integration.html}
    {$form.shop_financial_type.label}{$form.shop_financial_type.html}
    {$form.shop_donation_financial_type.label}{$form.shop_donation_financial_type.html}
    {$form.shop_map_products.label} + {$form.shop_map_products.html} + +
    +
    +
    +
    + + {/if} + {elseif $op == 'delete'} @@ -389,11 +454,18 @@ } } - // register events and run once + // register events cj(document).ready(function (){ cj('#membership_type_id').change(twingle_membership_active_changed); cj('#membership_type_id_recur').change(twingle_membership_active_changed); + + // init Twingle Shop integration + if ({/literal}{if $twingle_use_shop eq 1}true{else}false{/if}{literal}) { + twingleShopInit(); + } }); + + // run once twingle_membership_active_changed(); {/literal} diff --git a/templates/CRM/Twingle/Form/Settings.hlp b/templates/CRM/Twingle/Form/Settings.hlp index 7b82e7b..bfd00e2 100644 --- a/templates/CRM/Twingle/Form/Settings.hlp +++ b/templates/CRM/Twingle/Form/Settings.hlp @@ -27,3 +27,7 @@ {htxt id='id-twingle_prefix'} {ts domain="de.systopia.twingle"}You can use this setting to add a prefix to the Twingle transaction ID, in order to avoid collisions with other transaction ids.{/ts} {/htxt} + +{htxt id='id-twingle_use_shop'} + {ts domain="de.systopia.twingle"}If you enable Twingle Shop integration, you can configure Twingle API profiles to include products ordered through Twingle Shop as line items in the created contribution.{/ts} +{/htxt} diff --git a/templates/CRM/Twingle/Form/Settings.tpl b/templates/CRM/Twingle/Form/Settings.tpl index 42953e2..906a91c 100644 --- a/templates/CRM/Twingle/Form/Settings.tpl +++ b/templates/CRM/Twingle/Form/Settings.tpl @@ -37,7 +37,6 @@ - {$form.twingle_prefix.label} {help id="id-twingle_prefix" title=$form.twingle_prefix.label} @@ -86,6 +85,31 @@ +

    Twingle Shop Integration

    + + + + + + + + + + +
    {$form.twingle_use_shop.label}   + {$form.twingle_use_shop.html} +
    + + {$formElements.twingle_use_shop.description} + +
    {$form.twingle_access_key.label}   + {$form.twingle_access_key.html} +
    + + {$formElements.twingle_access_key.description} + +
    +
    {include file="CRM/common/formButtons.tpl" location="bottom"}
    diff --git a/templates/CRM/Twingle/Page/Profiles.tpl b/templates/CRM/Twingle/Page/Profiles.tpl index 6d4bc90..1ef751b 100644 --- a/templates/CRM/Twingle/Page/Profiles.tpl +++ b/templates/CRM/Twingle/Page/Profiles.tpl @@ -26,6 +26,9 @@ {ts domain="de.systopia.twingle"}Profile name{/ts} {ts domain="de.systopia.twingle"}Selectors{/ts} + {if $twingle_use_shop eq 1} + {ts domain="de.systopia.twingle"}Shop Integration{/ts} + {/if} {ts domain="de.systopia.twingle"}Used{/ts} {ts domain="de.systopia.twingle"}Last Used{/ts} {ts domain="de.systopia.twingle"}Operations{/ts} @@ -46,6 +49,9 @@
{/if} + {if $twingle_use_shop eq 1} + {if $profile.enable_shop_integration}{ts domain="de.systopia.twingle"}enabled{/ts}{else}{ts domain="de.systopia.twingle"}disabled{/ts}{/if} + {/if} {ts domain="de.systopia.twingle"}{$profile_stats.$profile_name.access_counter_txt}{/ts} {ts domain="de.systopia.twingle"}{$profile_stats.$profile_name.last_access_txt}{/ts} @@ -56,7 +62,6 @@ {else} {ts domain="de.systopia.twingle"}Delete{/ts} {/if} - {/foreach} diff --git a/tests/phpunit/api/v3/TwingleProduct/CreateTest.php b/tests/phpunit/api/v3/TwingleProduct/CreateTest.php new file mode 100644 index 0000000..7d489aa --- /dev/null +++ b/tests/phpunit/api/v3/TwingleProduct/CreateTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleProduct', 'create', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/api/v3/TwingleProduct/DeleteTest.php b/tests/phpunit/api/v3/TwingleProduct/DeleteTest.php new file mode 100644 index 0000000..7edac01 --- /dev/null +++ b/tests/phpunit/api/v3/TwingleProduct/DeleteTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleProduct', 'delete', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/api/v3/TwingleProduct/GetTest.php b/tests/phpunit/api/v3/TwingleProduct/GetTest.php new file mode 100644 index 0000000..670de99 --- /dev/null +++ b/tests/phpunit/api/v3/TwingleProduct/GetTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleProduct', 'get', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/api/v3/TwingleProduct/GetsingleTest.php b/tests/phpunit/api/v3/TwingleProduct/GetsingleTest.php new file mode 100644 index 0000000..1bf43da --- /dev/null +++ b/tests/phpunit/api/v3/TwingleProduct/GetsingleTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleProduct', 'getsingle', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/api/v3/TwingleShop/CreateTest.php b/tests/phpunit/api/v3/TwingleShop/CreateTest.php new file mode 100644 index 0000000..12ccac6 --- /dev/null +++ b/tests/phpunit/api/v3/TwingleShop/CreateTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleShop', 'create', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/api/v3/TwingleShop/DeleteTest.php b/tests/phpunit/api/v3/TwingleShop/DeleteTest.php new file mode 100644 index 0000000..8e1a9ca --- /dev/null +++ b/tests/phpunit/api/v3/TwingleShop/DeleteTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleShop', 'delete', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/api/v3/TwingleShop/GetTest.php b/tests/phpunit/api/v3/TwingleShop/GetTest.php new file mode 100644 index 0000000..e9fa453 --- /dev/null +++ b/tests/phpunit/api/v3/TwingleShop/GetTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleShop', 'get', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/api/v3/TwingleShop/GetsingleTest.php b/tests/phpunit/api/v3/TwingleShop/GetsingleTest.php new file mode 100644 index 0000000..96a4efa --- /dev/null +++ b/tests/phpunit/api/v3/TwingleShop/GetsingleTest.php @@ -0,0 +1,54 @@ +installMe(__DIR__) + ->apply(); + } + + /** + * The setup() method is executed before the test is executed (optional). + */ + public function setUp() { + parent::setUp(); + } + + /** + * The tearDown() method is executed after the test was executed (optional) + * This can be used for cleanup. + */ + public function tearDown() { + parent::tearDown(); + } + + /** + * Simple example test case. + * + * Note how the function name begins with the word "test". + */ + public function testApiExample() { + $result = civicrm_api3('TwingleShop', 'getsingle', array('magicword' => 'sesame')); + $this->assertEquals('Twelve', $result['values'][12]['name']); + } + +} diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php new file mode 100644 index 0000000..eaa8379 --- /dev/null +++ b/tests/phpunit/bootstrap.php @@ -0,0 +1,65 @@ +add('CRM_', [__DIR__ . '/../..', __DIR__]); +$loader->addPsr4('Civi\\', [__DIR__ . '/../../Civi', __DIR__ . '/Civi']); +$loader->add('api_', [__DIR__ . '/../..', __DIR__]); +$loader->addPsr4('api\\', [__DIR__ . '/../../api', __DIR__ . '/api']); + +$loader->register(); + +/** + * Call the "cv" command. + * + * @param string $cmd + * The rest of the command to send. + * @param string $decode + * Ex: 'json' or 'phpcode'. + * @return mixed + * Response output (if the command executed normally). + * For 'raw' or 'phpcode', this will be a string. For 'json', it could be any JSON value. + * @throws \RuntimeException + * If the command terminates abnormally. + */ +function cv(string $cmd, string $decode = 'json') { + $cmd = 'cv ' . $cmd; + $descriptorSpec = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => STDERR]; + $oldOutput = getenv('CV_OUTPUT'); + putenv('CV_OUTPUT=json'); + + // Execute `cv` in the original folder. This is a work-around for + // phpunit/codeception, which seem to manipulate PWD. + $cmd = sprintf('cd %s; %s', escapeshellarg(getenv('PWD')), $cmd); + + $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__); + putenv("CV_OUTPUT=$oldOutput"); + fclose($pipes[0]); + $result = stream_get_contents($pipes[1]); + fclose($pipes[1]); + if (proc_close($process) !== 0) { + throw new RuntimeException("Command failed ($cmd):\n$result"); + } + switch ($decode) { + case 'raw': + return $result; + + case 'phpcode': + // If the last output is /*PHPCODE*/, then we managed to complete execution. + if (substr(trim($result), 0, 12) !== '/*BEGINPHP*/' || substr(trim($result), -10) !== '/*ENDPHP*/') { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + return $result; + + case 'json': + return json_decode($result, 1); + + default: + throw new RuntimeException("Bad decoder format ($decode)"); + } +} diff --git a/twingle.civix.php b/twingle.civix.php index c207adf..6d84d1c 100644 --- a/twingle.civix.php +++ b/twingle.civix.php @@ -198,3 +198,25 @@ function _twingle_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) { } } } + +/** + * (Delegated) Implements hook_civicrm_entityTypes(). + * + * Find any *.entityType.php files, merge their content, and return. + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes + */ +function _twingle_civix_civicrm_entityTypes(&$entityTypes) { + $entityTypes = array_merge($entityTypes, [ + 'Civi\Twingle\Shop\DAO\TwingleProduct' => [ + 'name' => 'TwingleProduct', + 'class' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'table' => 'civicrm_twingle_product', + ], + 'Civi\Twingle\Shop\DAO\TwingleShop' => [ + 'name' => 'TwingleShop', + 'class' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'table' => 'civicrm_twingle_shop', + ], + ]); +} diff --git a/twingle.php b/twingle.php index 8a838ea..c00ac35 100644 --- a/twingle.php +++ b/twingle.php @@ -5,11 +5,36 @@ use CRM_Twingle_ExtensionUtil as E; /** * Implements hook_civicrm_pre(). + * + * @throws \Civi\Twingle\Shop\Exceptions\ProductException + * @throws \CRM_Core_Exception + * @throws \Civi\Twingle\Shop\Exceptions\ShopException */ function twingle_civicrm_pre($op, $objectName, $id, &$params) { if ($objectName == 'ContributionRecur' && $op == 'edit') { CRM_Twingle_Tools::checkRecurringContributionChange((int) $id, $params); } + + // Create/delete PriceField and PriceFieldValue for TwingleProduct + elseif ($objectName == 'TwingleProduct') { + $twingle_product = new \Civi\Twingle\Shop\BAO\TwingleProduct(); + $twingle_product->load($params); + if ($op == 'create' || $op == 'edit') { + $twingle_product->createPriceField(); + } + elseif ($op == 'delete') { + $twingle_product->deletePriceField(); + } + $params = $twingle_product->getAttributes(); + } + + // Create PriceSet for TwingleShop + elseif ($objectName == 'TwingleShop' && ($op == 'create' || $op == 'edit')) { + $twingle_shop = new \Civi\Twingle\Shop\BAO\TwingleShop(); + $twingle_shop->load($params); + $twingle_shop->createPriceSet(); + $params = $twingle_shop->getAttributes(); + } } /** diff --git a/xml/schema/CRM/Twingle/TwingleProduct.entityType.php b/xml/schema/CRM/Twingle/TwingleProduct.entityType.php new file mode 100644 index 0000000..5b8879b --- /dev/null +++ b/xml/schema/CRM/Twingle/TwingleProduct.entityType.php @@ -0,0 +1,10 @@ + 'TwingleProduct', + 'class' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'table' => 'civicrm_twingle_product', + ], +]; diff --git a/xml/schema/CRM/Twingle/TwingleProduct.xml b/xml/schema/CRM/Twingle/TwingleProduct.xml new file mode 100644 index 0000000..5787e69 --- /dev/null +++ b/xml/schema/CRM/Twingle/TwingleProduct.xml @@ -0,0 +1,75 @@ + + + + CRM/Twingle/Shop + TwingleProduct + civicrm_twingle_product + This table contains the Twingle Product data. + false + + + + id + int unsigned + true + true + Unique TwingleProduct ID + + Number + + + + id + true + + + + external_id + int unsigned + true + The ID of this product in the Twingle database + + Number + + + + + price_field_id + int unsigned + FK to Price Field + true + + + price_field_id +
civicrm_contact
+ id + CASCADE + + + + twingle_shop_id + int unsigned + true + FK to Twingle Shop + + + twingle_shop_id + civicrm_twingle_shop
+ id + CASCADE +
+ + + created_at + datetime + true + Timestamp of when the product was created in the database + + + + updated_at + datetime + true + Timestamp of when the product was last updated in the database + + diff --git a/xml/schema/CRM/Twingle/TwingleShop.entityType.php b/xml/schema/CRM/Twingle/TwingleShop.entityType.php new file mode 100644 index 0000000..34e6ce2 --- /dev/null +++ b/xml/schema/CRM/Twingle/TwingleShop.entityType.php @@ -0,0 +1,10 @@ + 'TwingleShop', + 'class' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'table' => 'civicrm_twingle_shop', + ], +]; diff --git a/xml/schema/CRM/Twingle/TwingleShop.xml b/xml/schema/CRM/Twingle/TwingleShop.xml new file mode 100644 index 0000000..3d9d6ba --- /dev/null +++ b/xml/schema/CRM/Twingle/TwingleShop.xml @@ -0,0 +1,73 @@ + + + + CRM/Twingle/Shop + TwingleShop + civicrm_twingle_shop + This table contains the Twingle Shop data. Each Twingle Shop is linked to a corresponding Price Set. + false + + + + id + int unsigned + true + true + Unique TwingleShop ID + + Number + + + + id + true + + + + project_identifier + varchar + 32 + true + true + Twingle Project Identifier + + Text + + + + + numerical_project_id + int unsigned + true + true + Numerical Twingle Project Identifier + + Number + + + + + price_set_id + int unsigned + true + FK to Price Set + + + price_set_id +
civicrm_price_set
+ id + CASCADE + + + + name + varchar + false + 64 + true + name of the shop + + Text + + + From 1fc6529064e2b6a5e39dc757c21b2120e8743e5f Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 21 Mar 2024 12:04:02 +0100 Subject: [PATCH 23/43] set price_field to is_required = false --- Civi/Twingle/Shop/BAO/TwingleProduct.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Civi/Twingle/Shop/BAO/TwingleProduct.php b/Civi/Twingle/Shop/BAO/TwingleProduct.php index 3e2d120..d6448b2 100644 --- a/Civi/Twingle/Shop/BAO/TwingleProduct.php +++ b/Civi/Twingle/Shop/BAO/TwingleProduct.php @@ -306,6 +306,7 @@ class TwingleProduct extends TwingleProductDAO { 'is_active' => $this->is_active, 'weight' => $this->sort, 'html_type' => 'Text', + 'is_required' => false, ]; // Add id if in edit mode if ($mode == 'edit') { From ac892c9afc14e2cde17d90de6c103bd5dcdd0499 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Fri, 5 Apr 2024 16:08:44 +0200 Subject: [PATCH 24/43] fix messed up merge conflicts --- Civi/Twingle/Exceptions/BaseException.php | 17 +++++ .../remotes/origin/master | 67 ------------------- .../origin/master => ProfileException.php} | 0 ...tion.php~implement TwingleShop integration | 18 ----- .../master => ProfileValidationError.php} | 9 +-- ...rror.php~implement TwingleShop integration | 37 ---------- 6 files changed, 19 insertions(+), 129 deletions(-) delete mode 100644 Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master rename Civi/Twingle/Exceptions/{ProfileException.php~refs/remotes/origin/master => ProfileException.php} (100%) delete mode 100644 Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration rename Civi/Twingle/Exceptions/{ProfileValidationError.php~refs/remotes/origin/master => ProfileValidationError.php} (88%) delete mode 100644 Civi/Twingle/Exceptions/ProfileValidationError.php~implement TwingleShop integration diff --git a/Civi/Twingle/Exceptions/BaseException.php b/Civi/Twingle/Exceptions/BaseException.php index 57e4660..f1ce9e3 100644 --- a/Civi/Twingle/Exceptions/BaseException.php +++ b/Civi/Twingle/Exceptions/BaseException.php @@ -1,4 +1,21 @@ . + */ + +declare(strict_types = 1); namespace Civi\Twingle\Exceptions; diff --git a/Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master b/Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master deleted file mode 100644 index a5413d5..0000000 --- a/Civi/Twingle/Exceptions/BaseException.php~refs/remotes/origin/master +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -declare(strict_types = 1); - -namespace Civi\Twingle\Exceptions; - -use CRM_Twingle_ExtensionUtil as E; - -/** - * A simple custom exception class that indicates a problem within a class - * of the Twingle API extension. - */ -class BaseException extends \Exception { - - /** - * @var string - */ - protected $code; - protected string $log_message; - - /** - * BaseException Constructor - * @param string $message - * Error message - * @param string $error_code - * A meaningful error code - * @param \Throwable $previous - * A previously thrown exception to include. - */ - public function __construct(string $message = '', string $error_code = '', \Throwable $previous = NULL) { - parent::__construct($message, 1, $previous); - $this->log_message = '' !== $message ? E::LONG_NAME . ': ' . $message : ''; - $this->code = $error_code; - } - - /** - * Returns the error message, but with the extension name prefixed. - * @return string - */ - public function getLogMessage() { - return $this->log_message; - } - - /** - * Returns the error code. - * @return string - */ - public function getErrorCode() { - return $this->code; - } - -} diff --git a/Civi/Twingle/Exceptions/ProfileException.php~refs/remotes/origin/master b/Civi/Twingle/Exceptions/ProfileException.php similarity index 100% rename from Civi/Twingle/Exceptions/ProfileException.php~refs/remotes/origin/master rename to Civi/Twingle/Exceptions/ProfileException.php diff --git a/Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration b/Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration deleted file mode 100644 index b9e5954..0000000 --- a/Civi/Twingle/Exceptions/ProfileException.php~implement TwingleShop integration +++ /dev/null @@ -1,18 +0,0 @@ -affected_field_name = $affected_field_name; } diff --git a/Civi/Twingle/Exceptions/ProfileValidationError.php~implement TwingleShop integration b/Civi/Twingle/Exceptions/ProfileValidationError.php~implement TwingleShop integration deleted file mode 100644 index a678ba7..0000000 --- a/Civi/Twingle/Exceptions/ProfileValidationError.php~implement TwingleShop integration +++ /dev/null @@ -1,37 +0,0 @@ -affected_field_name = $affected_field_name; - } - - /** - * Returns the name of the profile field that caused the exception. - * @return string - */ - public function getAffectedFieldName() { - return $this->affected_field_name; - } - -} From 477c57ca53f2a485d6d72d3d84dc64d174052a2f Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Fri, 26 Apr 2024 16:24:23 +0200 Subject: [PATCH 25/43] fix another messed up merge --- api/v3/TwingleDonation/Submit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php index 17cb107..013dc73 100644 --- a/api/v3/TwingleDonation/Submit.php +++ b/api/v3/TwingleDonation/Submit.php @@ -255,7 +255,7 @@ function _civicrm_api3_twingle_donation_Submit_spec(&$params) { 'type' => CRM_Utils_Type::T_STRING, 'api.required' => 0, 'description' => E::ts('Additional information for either the contact or the (recurring) contribution.'), - ); + ]; $params['products'] = [ 'name' => 'products', 'title' => E::ts('Products'), From d3ccb3b09264a27c87e15c8ffb53801908819cd1 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Wed, 8 May 2024 17:27:39 +0200 Subject: [PATCH 26/43] allow products with self-selected price --- CRM/Twingle/Submission.php | 5 ++- Civi/Twingle/Shop/BAO/TwingleProduct.php | 41 +++++++++++++++++--- Civi/Twingle/Shop/Utils/TwingleShopUtils.php | 15 ++++--- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/CRM/Twingle/Submission.php b/CRM/Twingle/Submission.php index 606a714..48c658a 100644 --- a/CRM/Twingle/Submission.php +++ b/CRM/Twingle/Submission.php @@ -46,6 +46,7 @@ class CRM_Twingle_Submission { * List of allowed product attributes. */ const ALLOWED_PRODUCT_ATTRIBUTES = [ + 'id', 'name', 'internal_id', 'price', @@ -513,8 +514,8 @@ class CRM_Twingle_Submission { // If found, use the financial type and price field id from the price field if ($price_field) { - // Log warning if price differs from the submission - if ($price_field->price != (int) $product['price']) { + // Log warning if price is not variable and differs from the submission + if ($price_field->price !== Null && $price_field->price != (int) $product['price']) { Civi::log()->warning(E::LONG_NAME . ": Price for product " . $product['name'] . " differs from the PriceField. " . "Using the price from the submission.", ['price_field' => $price_field->price, 'submission' => $product['price']]); diff --git a/Civi/Twingle/Shop/BAO/TwingleProduct.php b/Civi/Twingle/Shop/BAO/TwingleProduct.php index d6448b2..62f37c3 100644 --- a/Civi/Twingle/Shop/BAO/TwingleProduct.php +++ b/Civi/Twingle/Shop/BAO/TwingleProduct.php @@ -14,7 +14,7 @@ use CRM_Utils_Type; use function Civi\Twingle\Shop\Utils\convert_int_to_bool; use function Civi\Twingle\Shop\Utils\convert_str_to_date; use function Civi\Twingle\Shop\Utils\convert_str_to_int; -use function Civi\Twingle\Shop\Utils\convert_null_to_int; +use function Civi\Twingle\Shop\Utils\convert_empty_string_to_null; use function Civi\Twingle\Shop\Utils\filter_attributes; use function Civi\Twingle\Shop\Utils\validate_data_types; @@ -112,9 +112,9 @@ class TwingleProduct extends TwingleProductDAO { ]; /** - * Attributes that need to be converted from NULL to int. + * Empty string to null conversion. */ - protected const NULL_TO_INT_CONVERSION = [ + protected const EMPTY_STRING_TO_NULL = [ "price", ]; @@ -198,6 +198,28 @@ class TwingleProduct extends TwingleProductDAO { self::CAN_BE_ZERO, ); + // Does this product allow to enter a custom price? + $custom_price = array_key_exists('price', $product_data) && $product_data['price'] === Null; + if (!$custom_price && isset($product_data['price_field_id'])) { + try { + $price_field = civicrm_api3('PriceField', 'getsingle', [ + 'id' => $product_data['price_field_id'], + 'return' => 'is_enter_qty', + ]); + $custom_price = (bool) $price_field['is_enter_qty']; + } + catch (CRM_Core_Exception $e) { + throw new ProductException( + E::ts("Could not find PriceField for Twingle Product ['id': %1, 'external_id': %2]: %3", + [ + 1 => $product_data['id'], + 2 => $product_data['external_id'], + 3 => $e->getMessage(), + ]), + ProductException::ERROR_CODE_PRICE_FIELD_NOT_FOUND); + } + } + // Amend data from corresponding PriceFieldValue if (isset($product_data['price_field_id'])) { try { @@ -216,7 +238,7 @@ class TwingleProduct extends TwingleProductDAO { ProductException::ERROR_CODE_PRICE_FIELD_VALUE_NOT_FOUND); } $product_data['name'] = $product_data['name'] ?? $price_field_value['label']; - $product_data['price'] = $product_data['price'] ?? $price_field_value['amount']; + $product_data['price'] = $custom_price ? Null : $product_data['price'] ?? $price_field_value['amount']; $product_data['financial_type_id'] = $product_data['financial_type_id'] ?? $price_field_value['financial_type_id']; $product_data['is_active'] = $product_data['is_active'] ?? $price_field_value['is_active']; $product_data['sort'] = $product_data['sort'] ?? $price_field_value['weight']; @@ -228,7 +250,7 @@ class TwingleProduct extends TwingleProductDAO { convert_str_to_int($product_data, self::STR_TO_INT_CONVERSION); convert_int_to_bool($product_data, self::INT_TO_BOOL_CONVERSION); convert_str_to_date($product_data, self::STR_TO_DATE_CONVERSION); - convert_null_to_int($product_data, self::NULL_TO_INT_CONVERSION); + convert_empty_string_to_null($product_data, self::EMPTY_STRING_TO_NULL); } catch (\Exception $e) { throw new ProductException($e->getMessage(), ProductException::ERROR_CODE_ATTRIBUTE_WRONG_DATA_TYPE); @@ -308,6 +330,13 @@ class TwingleProduct extends TwingleProductDAO { 'html_type' => 'Text', 'is_required' => false, ]; + + // If the product has no fixed price, allow the user to enter a custom price + if ($this->price === Null) { + $price_field_data['is_enter_qty'] = true; + $price_field_data['is_display_amounts'] = false; + } + // Add id if in edit mode if ($mode == 'edit') { $price_field_data['id'] = $this->price_field_id; @@ -348,7 +377,7 @@ class TwingleProduct extends TwingleProductDAO { 'price_field_id' => $this->price_field_id, 'financial_type_id' => $this->financial_type_id, 'label' => $this->name, - 'amount' => $this->price, + 'amount' => $this->price === Null ? 1 : $this->price, 'is_active' => $this->is_active, 'description' => $this->description, ]; diff --git a/Civi/Twingle/Shop/Utils/TwingleShopUtils.php b/Civi/Twingle/Shop/Utils/TwingleShopUtils.php index 5ee45f7..0638615 100644 --- a/Civi/Twingle/Shop/Utils/TwingleShopUtils.php +++ b/Civi/Twingle/Shop/Utils/TwingleShopUtils.php @@ -45,7 +45,7 @@ function filter_attributes(array &$data, array $allowed_attributes, array $can_b function convert_str_to_int(array &$data, array $str_to_int_conversion): void { // Convert string to int foreach ($str_to_int_conversion as $attribute) { - if (isset($data[$attribute])) { + if (isset($data[$attribute]) && $data[$attribute] !== '') { try { $data[$attribute] = (int) $data[$attribute]; } catch (\Exception $e) { @@ -108,18 +108,17 @@ function convert_str_to_date(array &$data, array $str_to_date_conversion): void } /** - * Convert null to int + * Convert an empty string to null. * * @param array $data - * @param array $null_to_int_conversion + * @param array $empty_string_to_null * * @return void */ -function convert_null_to_int(array &$data, array $null_to_int_conversion): void { - // Convert null to int - foreach ($null_to_int_conversion as $attribute) { - if (array_key_exists($attribute, $data) && $data[$attribute] === NULL) { - $data[$attribute] = 0; +function convert_empty_string_to_null(array &$data, array $empty_string_to_null): void { + foreach ($empty_string_to_null as $attribute) { + if (isset($data[$attribute]) && $data[$attribute] === '') { + $data[$attribute] = NULL; } } } From 33cb076d42a51d2c40b7aae0cb6ba4aee8238637 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Fri, 10 May 2024 10:51:19 +0200 Subject: [PATCH 27/43] activate update button on financial type change --- js/twingle_shop.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/js/twingle_shop.js b/js/twingle_shop.js index fb7dd40..68c9d89 100644 --- a/js/twingle_shop.js +++ b/js/twingle_shop.js @@ -219,6 +219,11 @@ class Product { button.disabled = true; } + // Deactivate 'update' button if product is not outdated + if (action === 'update' && !this.isOutdated) { + button.disabled = true; + } + // Add icon const icon = document.createElement('i'); const iconClass = action === 'create' ? 'fa-plus-circle' : action === 'update' ? 'fa-refresh' : 'fa-trash'; @@ -357,13 +362,11 @@ class Product { // Determine actions; if product has price field id, it can be updated or // deleted, otherwise it can be created if (this.priceFieldId) { - if (this.isOutdated) { + if (!this.isOrphaned) { actionsAndHandlers.push(['update', this.createPriceFieldHandler()]); - } else if (!this.isOrphaned) { - actionsAndHandlers.push(['update', null]); } actionsAndHandlers.push(['delete', this.deletePriceFieldHandler()]); - } else { + } else if (!this.isOrphaned) { actionsAndHandlers.push(['create', this.createPriceFieldHandler()]); } @@ -410,10 +413,10 @@ class Product { let self = this; dropdown.onchange = function () { - // Enable 'create' button if financial type is selected - const createButton = document.getElementById('twingle_product_tw_' + self.externalId).getElementsByClassName('twingle-shop-cell-button')[0]; - if (createButton.textContent.includes('Create')) { - createButton.disabled = dropdown.value === '0'; + // Enable 'create' or 'update' button if financial type is selected + const button = document.getElementById('twingle_product_tw_' + self.externalId).getElementsByClassName('twingle-shop-cell-button')[0]; + if (button.textContent.includes('Create') || button.textContent.includes('Update')) { + button.disabled = dropdown.value === '0'; } // Update financial type From 8de34f7b2ab7733825a7bdc064c743535e880419 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 3 Sep 2024 13:19:27 +0200 Subject: [PATCH 28/43] Version 1.5-beta1 --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index d715ec8..f58beb4 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - - 1.5-dev - dev + 2024-09-03 + 1.5-beta1 + beta 5.58 From 7cca29b458322b32065433dda2cd4e727e8e78ed Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 3 Sep 2024 13:19:48 +0200 Subject: [PATCH 29/43] Back to 1.5-dev --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index f58beb4..d715ec8 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - 2024-09-03 - 1.5-beta1 - beta + + 1.5-dev + dev 5.58 From 64d6b48813338c1f210d1d9cc9adbd51aeb3afe2 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Wed, 11 Sep 2024 12:59:22 +0200 Subject: [PATCH 30/43] Fix DAO/BAO namespaces and definitions --- .../Twingle}/BAO/TwingleProduct.php | 24 +++++++------------ .../Shop => CRM/Twingle}/BAO/TwingleShop.php | 23 +++++++----------- .../Twingle}/DAO/TwingleProduct.php | 18 +++++++------- .../Shop => CRM/Twingle}/DAO/TwingleShop.php | 12 ++++------ info.xml | 11 +++++---- twingle.civix.php | 22 ----------------- .../CRM/Twingle/TwingleProduct.entityType.php | 2 +- xml/schema/CRM/Twingle/TwingleProduct.xml | 2 +- .../CRM/Twingle/TwingleShop.entityType.php | 2 +- xml/schema/CRM/Twingle/TwingleShop.xml | 2 +- 10 files changed, 41 insertions(+), 77 deletions(-) rename {Civi/Twingle/Shop => CRM/Twingle}/BAO/TwingleProduct.php (96%) rename {Civi/Twingle/Shop => CRM/Twingle}/BAO/TwingleShop.php (95%) rename {Civi/Twingle/Shop => CRM/Twingle}/DAO/TwingleProduct.php (94%) rename {Civi/Twingle/Shop => CRM/Twingle}/DAO/TwingleShop.php (96%) diff --git a/Civi/Twingle/Shop/BAO/TwingleProduct.php b/CRM/Twingle/BAO/TwingleProduct.php similarity index 96% rename from Civi/Twingle/Shop/BAO/TwingleProduct.php rename to CRM/Twingle/BAO/TwingleProduct.php index 62f37c3..f696807 100644 --- a/Civi/Twingle/Shop/BAO/TwingleProduct.php +++ b/CRM/Twingle/BAO/TwingleProduct.php @@ -1,16 +1,10 @@ [$external_id, 'String']]); if ($dao->fetch()) { $product = new self(); @@ -456,7 +450,7 @@ class TwingleProduct extends TwingleProductDAO { // Try to lookup object in database try { - $dao = TwingleShopDAO::executeQuery("SELECT * FROM civicrm_twingle_product WHERE external_id = %1", + $dao = CRM_Twingle_BAO_TwingleShop::executeQuery("SELECT * FROM civicrm_twingle_product WHERE external_id = %1", [1 => [$this->external_id, 'String']]); if ($dao->fetch()) { $this->copyValues(array_merge($dao->toArray(), $this->getAttributes())); @@ -514,7 +508,7 @@ class TwingleProduct extends TwingleProductDAO { /** * Delete TwingleProduct along with associated PriceField and PriceFieldValue. * - * @override \Civi\Twingle\Shop\DAO\TwingleProduct::delete + * @override \CRM_Twingle_DAO_TwingleProduct::delete * @throws \CRM_Core_Exception * @throws \Civi\Twingle\Shop\Exceptions\ProductException */ @@ -570,7 +564,7 @@ class TwingleProduct extends TwingleProductDAO { /** * Compare two products * - * @param TwingleProduct $product_to_compare_with + * @param CRM_Twingle_BAO_TwingleProduct $product_to_compare_with * Product from database * * @return bool @@ -592,7 +586,7 @@ class TwingleProduct extends TwingleProductDAO { */ public function getFinancialTypeId(): ?int { if (!empty($this->price_field_id)) { - $price_set = \Civi\Api4\PriceField::get() + $price_set = PriceField::get() ->addSelect('financial_type_id') ->addWhere('id', '=', $this->price_field_id) ->execute() @@ -610,7 +604,7 @@ class TwingleProduct extends TwingleProductDAO { */ public function getPriceFieldValueId() { if (!empty($this->price_field_id)) { - $price_field_value = \Civi\Api4\PriceFieldValue::get() + $price_field_value = PriceFieldValue::get() ->addSelect('id') ->addWhere('price_field_id', '=', $this->price_field_id) ->execute() diff --git a/Civi/Twingle/Shop/BAO/TwingleShop.php b/CRM/Twingle/BAO/TwingleShop.php similarity index 95% rename from Civi/Twingle/Shop/BAO/TwingleShop.php rename to CRM/Twingle/BAO/TwingleShop.php index c27853c..dd49596 100644 --- a/Civi/Twingle/Shop/BAO/TwingleShop.php +++ b/CRM/Twingle/BAO/TwingleShop.php @@ -1,15 +1,10 @@ \CRM_Utils_Type::T_INT, @@ -64,14 +59,14 @@ class TwingleShop extends TwingleShopDAO { * @param string $project_identifier * Twingle project identifier * - * @return TwingleShop + * @return CRM_Twingle_BAO_TwingleShop * * @throws ShopException * @throws \Civi\Twingle\Shop\Exceptions\ApiCallError * @throws \CRM_Core_Exception */ public static function findByProjectIdentifier(string $project_identifier) { - $shop = new TwingleShop(); + $shop = new CRM_Twingle_BAO_TwingleShop(); $shop->get('project_identifier', $project_identifier); if (!$shop->id) { $shop->fetchDataFromTwingle($project_identifier); @@ -147,7 +142,7 @@ class TwingleShop extends TwingleShopDAO { // Try to lookup object in database try { - $dao = TwingleShopDAO::executeQuery("SELECT * FROM civicrm_twingle_shop WHERE project_identifier = %1", + $dao = self::executeQuery("SELECT * FROM civicrm_twingle_shop WHERE project_identifier = %1", [1 => [$this->project_identifier, 'String']]); if ($dao->fetch()) { $this->load($dao->toArray()); @@ -259,7 +254,7 @@ class TwingleShop extends TwingleShopDAO { }, []); foreach ($products_from_db as $product) { - /* @var TwingleProductBAO $product */ + /* @var CRM_Twingle_BAO_TwingleProduct $product */ // Find orphaned products which are in the database but not in Twingle $found = array_key_exists($product->external_id, $products_from_twingle); @@ -286,8 +281,8 @@ class TwingleShop extends TwingleShopDAO { foreach ($products_from_twingle as $product_from_twingle) { $found = array_key_exists($product_from_twingle['id'], $products); if (!$found) { - $product = new TwingleProduct(); - $product->load(TwingleProduct::renameTwingleAttrs($product_from_twingle)); + $product = new CRM_Twingle_BAO_TwingleProduct(); + $product->load(CRM_Twingle_BAO_TwingleProduct::renameTwingleAttrs($product_from_twingle)); $product->twingle_shop_id = $this->id; $this->products[] = $product; } @@ -305,13 +300,13 @@ class TwingleShop extends TwingleShopDAO { public function getProducts() { $products = []; - $result = TwingleProductBAO::executeQuery( + $result = CRM_Twingle_BAO_TwingleProduct::executeQuery( "SELECT * FROM civicrm_twingle_product WHERE twingle_shop_id = %1", [1 => [$this->id, 'Integer']] ); while ($result->fetch()) { - $product = new TwingleProductBAO(); + $product = new CRM_Twingle_BAO_TwingleProduct(); $product->load($result->toArray()); $products[] = $product; } diff --git a/Civi/Twingle/Shop/DAO/TwingleProduct.php b/CRM/Twingle/DAO/TwingleProduct.php similarity index 94% rename from Civi/Twingle/Shop/DAO/TwingleProduct.php rename to CRM/Twingle/DAO/TwingleProduct.php index 9d4f075..3adeae7 100644 --- a/Civi/Twingle/Shop/DAO/TwingleProduct.php +++ b/CRM/Twingle/DAO/TwingleProduct.php @@ -1,7 +1,5 @@ 'civicrm_twingle_product.id', 'table_name' => 'civicrm_twingle_product', 'entity' => 'TwingleProduct', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'bao' => 'CRM_Twingle_DAO_TwingleProduct', 'localizable' => 0, 'html' => [ 'type' => 'Number', @@ -167,7 +165,7 @@ class TwingleProduct extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_product.external_id', 'table_name' => 'civicrm_twingle_product', 'entity' => 'TwingleProduct', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'bao' => 'CRM_Twingle_DAO_TwingleProduct', 'localizable' => 0, 'html' => [ 'type' => 'Number', @@ -189,7 +187,7 @@ class TwingleProduct extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_product.price_field_id', 'table_name' => 'civicrm_twingle_product', 'entity' => 'TwingleProduct', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'bao' => 'CRM_Twingle_DAO_TwingleProduct', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', 'add' => NULL, @@ -208,9 +206,9 @@ class TwingleProduct extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_product.twingle_shop_id', 'table_name' => 'civicrm_twingle_product', 'entity' => 'TwingleProduct', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'bao' => 'CRM_Twingle_DAO_TwingleProduct', 'localizable' => 0, - 'FKClassName' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'FKClassName' => 'CRM_Twingle_DAO_TwingleShop', 'add' => NULL, ], 'created_at' => [ @@ -228,7 +226,7 @@ class TwingleProduct extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_product.created_at', 'table_name' => 'civicrm_twingle_product', 'entity' => 'TwingleProduct', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'bao' => 'CRM_Twingle_DAO_TwingleProduct', 'localizable' => 0, 'add' => NULL, ], @@ -247,7 +245,7 @@ class TwingleProduct extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_product.updated_at', 'table_name' => 'civicrm_twingle_product', 'entity' => 'TwingleProduct', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'bao' => 'CRM_Twingle_DAO_TwingleProduct', 'localizable' => 0, 'add' => NULL, ], diff --git a/Civi/Twingle/Shop/DAO/TwingleShop.php b/CRM/Twingle/DAO/TwingleShop.php similarity index 96% rename from Civi/Twingle/Shop/DAO/TwingleShop.php rename to CRM/Twingle/DAO/TwingleShop.php index bef6db7..4b6e704 100644 --- a/Civi/Twingle/Shop/DAO/TwingleShop.php +++ b/CRM/Twingle/DAO/TwingleShop.php @@ -1,7 +1,5 @@ 'civicrm_twingle_shop.id', 'table_name' => 'civicrm_twingle_shop', 'entity' => 'TwingleShop', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'bao' => 'CRM_Twingle_DAO_TwingleShop', 'localizable' => 0, 'html' => [ 'type' => 'Number', @@ -159,7 +157,7 @@ class TwingleShop extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_shop.project_identifier', 'table_name' => 'civicrm_twingle_shop', 'entity' => 'TwingleShop', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'bao' => 'CRM_Twingle_DAO_TwingleShop', 'localizable' => 0, 'html' => [ 'type' => 'Text', @@ -181,7 +179,7 @@ class TwingleShop extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_shop.numerical_project_id', 'table_name' => 'civicrm_twingle_shop', 'entity' => 'TwingleShop', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'bao' => 'CRM_Twingle_DAO_TwingleShop', 'localizable' => 0, 'html' => [ 'type' => 'Number', @@ -202,7 +200,7 @@ class TwingleShop extends \CRM_Core_DAO { 'where' => 'civicrm_twingle_shop.price_set_id', 'table_name' => 'civicrm_twingle_shop', 'entity' => 'TwingleShop', - 'bao' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'bao' => 'CRM_Twingle_DAO_TwingleShop', 'localizable' => 0, 'FKClassName' => 'CRM_Price_DAO_PriceSet', 'add' => NULL, diff --git a/info.xml b/info.xml index d715ec8..c019d80 100644 --- a/info.xml +++ b/info.xml @@ -20,7 +20,11 @@ 5.58 - + + + + + de.systopia.xcm @@ -32,10 +36,7 @@ menu-xml@1.0.0 mgd-php@1.0.0 smarty-v2@1.0.1 + entity-types-php@1.0.0 - - - - CRM_Twingle_Upgrader diff --git a/twingle.civix.php b/twingle.civix.php index 6d84d1c..c207adf 100644 --- a/twingle.civix.php +++ b/twingle.civix.php @@ -198,25 +198,3 @@ function _twingle_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) { } } } - -/** - * (Delegated) Implements hook_civicrm_entityTypes(). - * - * Find any *.entityType.php files, merge their content, and return. - * - * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes - */ -function _twingle_civix_civicrm_entityTypes(&$entityTypes) { - $entityTypes = array_merge($entityTypes, [ - 'Civi\Twingle\Shop\DAO\TwingleProduct' => [ - 'name' => 'TwingleProduct', - 'class' => 'Civi\Twingle\Shop\DAO\TwingleProduct', - 'table' => 'civicrm_twingle_product', - ], - 'Civi\Twingle\Shop\DAO\TwingleShop' => [ - 'name' => 'TwingleShop', - 'class' => 'Civi\Twingle\Shop\DAO\TwingleShop', - 'table' => 'civicrm_twingle_shop', - ], - ]); -} diff --git a/xml/schema/CRM/Twingle/TwingleProduct.entityType.php b/xml/schema/CRM/Twingle/TwingleProduct.entityType.php index 5b8879b..153d50b 100644 --- a/xml/schema/CRM/Twingle/TwingleProduct.entityType.php +++ b/xml/schema/CRM/Twingle/TwingleProduct.entityType.php @@ -4,7 +4,7 @@ return [ [ 'name' => 'TwingleProduct', - 'class' => 'Civi\Twingle\Shop\DAO\TwingleProduct', + 'class' => 'CRM_Twingle_DAO_TwingleProduct', 'table' => 'civicrm_twingle_product', ], ]; diff --git a/xml/schema/CRM/Twingle/TwingleProduct.xml b/xml/schema/CRM/Twingle/TwingleProduct.xml index 5787e69..4b6e48a 100644 --- a/xml/schema/CRM/Twingle/TwingleProduct.xml +++ b/xml/schema/CRM/Twingle/TwingleProduct.xml @@ -1,7 +1,7 @@ - CRM/Twingle/Shop + CRM/TwingleTwingleProductcivicrm_twingle_productThis table contains the Twingle Product data. diff --git a/xml/schema/CRM/Twingle/TwingleShop.entityType.php b/xml/schema/CRM/Twingle/TwingleShop.entityType.php index 34e6ce2..5fe53ea 100644 --- a/xml/schema/CRM/Twingle/TwingleShop.entityType.php +++ b/xml/schema/CRM/Twingle/TwingleShop.entityType.php @@ -4,7 +4,7 @@ return [ [ 'name' => 'TwingleShop', - 'class' => 'Civi\Twingle\Shop\DAO\TwingleShop', + 'class' => 'CRM_Twingle_DAO_TwingleShop', 'table' => 'civicrm_twingle_shop', ], ]; diff --git a/xml/schema/CRM/Twingle/TwingleShop.xml b/xml/schema/CRM/Twingle/TwingleShop.xml index 3d9d6ba..5b45a77 100644 --- a/xml/schema/CRM/Twingle/TwingleShop.xml +++ b/xml/schema/CRM/Twingle/TwingleShop.xml @@ -1,7 +1,7 @@
- CRM/Twingle/Shop + CRM/TwingleTwingleShopcivicrm_twingle_shopThis table contains the Twingle Shop data. Each Twingle Shop is linked to a corresponding Price Set. From 4ae20a1b04f283c900b0866003050f1ca9d06d6c Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Wed, 11 Sep 2024 13:19:54 +0200 Subject: [PATCH 31/43] Fix form template issues (help texts, undefined template variables, etc.) --- CRM/Twingle/Form/Settings.php | 40 ++++++++++++------------- templates/CRM/Twingle/Form/Profile.hlp | 4 +-- templates/CRM/Twingle/Form/Settings.hlp | 4 +++ templates/CRM/Twingle/Form/Settings.tpl | 20 ++++--------- 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/CRM/Twingle/Form/Settings.php b/CRM/Twingle/Form/Settings.php index a92535e..450b52a 100644 --- a/CRM/Twingle/Form/Settings.php +++ b/CRM/Twingle/Form/Settings.php @@ -29,16 +29,16 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form { * List of all settings options. */ public static $SETTINGS_LIST = [ - 'twingle_prefix', - 'twingle_use_sepa', - 'twingle_dont_use_reference', - 'twingle_protect_recurring', - 'twingle_protect_recurring_activity_type', - 'twingle_protect_recurring_activity_subject', - 'twingle_protect_recurring_activity_status', - 'twingle_protect_recurring_activity_assignee', - 'twingle_use_shop', - 'twingle_access_key', + 'twingle_prefix', + 'twingle_use_sepa', + 'twingle_dont_use_reference', + 'twingle_protect_recurring', + 'twingle_protect_recurring_activity_type', + 'twingle_protect_recurring_activity_subject', + 'twingle_protect_recurring_activity_status', + 'twingle_protect_recurring_activity_assignee', + 'twingle_use_shop', + 'twingle_access_key', ]; /** @@ -110,22 +110,22 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form { $this->add( 'checkbox', 'twingle_use_shop', - E::ts("Use Twingle Shop Integration") + E::ts('Use Twingle Shop Integration') ); $this->add( 'text', 'twingle_access_key', - E::ts("Twingle Access Key") + E::ts('Twingle Access Key') ); - $this->addButtons(array( - array ( - 'type' => 'submit', - 'name' => E::ts('Save'), - 'isDefault' => TRUE, - ) - )); + $this->addButtons([ + [ + 'type' => 'submit', + 'name' => E::ts('Save'), + 'isDefault' => TRUE, + ], + ]); // set defaults foreach (self::$SETTINGS_LIST as $setting) { @@ -164,7 +164,7 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form { CRM_Utils_Array::value('twingle_use_shop', $this->_submitValues) && !CRM_Utils_Array::value('twingle_access_key', $this->_submitValues, FALSE) ) { - $this->_errors['twingle_access_key'] = E::ts("An Access Key is required to enable Twingle Shop Integration"); + $this->_errors['twingle_access_key'] = E::ts('An Access Key is required to enable Twingle Shop Integration'); } return (0 == count($this->_errors)); diff --git a/templates/CRM/Twingle/Form/Profile.hlp b/templates/CRM/Twingle/Form/Profile.hlp index 8098450..5e1a401 100644 --- a/templates/CRM/Twingle/Form/Profile.hlp +++ b/templates/CRM/Twingle/Form/Profile.hlp @@ -91,7 +91,7 @@ {/htxt} {htxt id='id-shop_map_products'} -

{ts domain="de.systopia.twingle"}If this option is enabled, all Twingle Shop products corresponding to the specified project IDs will be retrieved from Twingle and mapped as price sets and price fields. Each Twingle Shop is mapped as a price set with its products as price fields.

-

This allows you to manually create contributions with the same line items for phone orders, for example, as would be the case for orders placed through the Twingle Shop.

+

{ts domain="de.systopia.twingle"}If this option is enabled, all Twingle Shop products corresponding to the specified project IDs will be retrieved from Twingle and mapped as price sets and price fields. Each Twingle Shop is mapped as a price set with its products as price fields.{/ts}

+

{ts domain="de.systopia.twingle"}This allows you to manually create contributions with the same line items for phone orders, for example, as would be the case for orders placed through the Twingle Shop.{/ts}

{/htxt} {/crmScope} diff --git a/templates/CRM/Twingle/Form/Settings.hlp b/templates/CRM/Twingle/Form/Settings.hlp index bfd00e2..fe0d483 100644 --- a/templates/CRM/Twingle/Form/Settings.hlp +++ b/templates/CRM/Twingle/Form/Settings.hlp @@ -31,3 +31,7 @@ {htxt id='id-twingle_use_shop'} {ts domain="de.systopia.twingle"}If you enable Twingle Shop integration, you can configure Twingle API profiles to include products ordered through Twingle Shop as line items in the created contribution.{/ts} {/htxt} + +{htxt id='id-twingle_access_key'} + {ts domain="de.systopia.twingle"}Enter your twingle API access key.{/ts} +{/htxt} diff --git a/templates/CRM/Twingle/Form/Settings.tpl b/templates/CRM/Twingle/Form/Settings.tpl index 906a91c..b13760a 100644 --- a/templates/CRM/Twingle/Form/Settings.tpl +++ b/templates/CRM/Twingle/Form/Settings.tpl @@ -89,24 +89,16 @@
- - + - - +
{$form.twingle_use_shop.label}   - {$form.twingle_use_shop.html} -
- - {$formElements.twingle_use_shop.description} - +
{$form.twingle_use_shop.label} + {help id="id-twingle_use_shop" title=$form.twingle_use_shop.label} {$form.twingle_use_shop.html}
{$form.twingle_access_key.label}   - {$form.twingle_access_key.html} -
- - {$formElements.twingle_access_key.description} - +
{$form.twingle_access_key.label} + {help id="id-twingle_access_key" title=$form.twingle_access_key.label} {$form.twingle_access_key.html}
From fa301676e389ee9d8ebf448f7f5e2ba85cd594eb Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Wed, 11 Sep 2024 13:30:39 +0200 Subject: [PATCH 32/43] Update translation template and fix incorrect use of ts() --- CRM/Twingle/BAO/TwingleProduct.php | 21 +- CRM/Twingle/BAO/TwingleShop.php | 18 +- CRM/Twingle/Form/Profile.php | 3 +- CRM/Twingle/Submission.php | 2 +- l10n/de.systopia.twingle.pot | 1092 +++++++++++++++++++++------- 5 files changed, 862 insertions(+), 274 deletions(-) diff --git a/CRM/Twingle/BAO/TwingleProduct.php b/CRM/Twingle/BAO/TwingleProduct.php index f696807..c782314 100644 --- a/CRM/Twingle/BAO/TwingleProduct.php +++ b/CRM/Twingle/BAO/TwingleProduct.php @@ -458,7 +458,7 @@ class CRM_Twingle_BAO_TwingleProduct extends CRM_Twingle_DAO_TwingleProduct { } catch (\Civi\Core\Exception\DBQueryException $e) { throw new ProductException( - E::ts('Could not find TwingleProduct in database: ' . $e->getMessage()), + E::ts('Could not find TwingleProduct in database: %1', [1 => $e->getMessage()]), ShopException::ERROR_CODE_COULD_NOT_FIND_SHOP_IN_DB); } @@ -466,7 +466,8 @@ class CRM_Twingle_BAO_TwingleProduct extends CRM_Twingle_DAO_TwingleProduct { $twingle_product_values = $this->getAttributes(); try { \CRM_Utils_Hook::pre($mode, 'TwingleProduct', $this->id, $twingle_product_values); - } catch (\Exception $e) { + } + catch (\Exception $e) { $tx->rollback(); throw $e; } @@ -481,14 +482,15 @@ class CRM_Twingle_BAO_TwingleProduct extends CRM_Twingle_DAO_TwingleProduct { // Save object to database try { $this->save(); - } catch (\Exception $e) { + } + catch (\Exception $e) { $tx->rollback(); throw new ProductException( - E::ts('Could not save TwingleProduct to database: ' . $e->getMessage()), + E::ts('Could not save TwingleProduct to database: %1', [1 => $e->getMessage()]), ProductException::ERROR_CODE_COULD_NOT_CREATE_PRODUCT); } $result = self::findById($this->id); - /* @var self $result */ + /** @var self $result */ $this->load($result->getAttributes()); // Register post-hook @@ -629,7 +631,7 @@ class CRM_Twingle_BAO_TwingleProduct extends CRM_Twingle_DAO_TwingleProduct { } catch (CRM_Core_Exception $e) { throw new ProductException( - E::ts('An Error occurred while searching for the associated PriceFieldValue: ' . $e->getMessage()), + E::ts('An Error occurred while searching for the associated PriceFieldValue: %1', [1 => $e->getMessage()]), ProductException::ERROR_CODE_PRICE_FIELD_VALUE_NOT_FOUND); } try { @@ -637,7 +639,7 @@ class CRM_Twingle_BAO_TwingleProduct extends CRM_Twingle_DAO_TwingleProduct { } catch (CRM_Core_Exception $e) { throw new ProductException( - E::ts('Could not delete associated PriceFieldValue: ' . $e->getMessage()), + E::ts('Could not delete associated PriceFieldValue: %1', [1 => $e->getMessage()]), ProductException::ERROR_CODE_COULD_NOT_DELETE_PRICE_FIELD_VALUE); } @@ -661,13 +663,14 @@ class CRM_Twingle_BAO_TwingleProduct extends CRM_Twingle_DAO_TwingleProduct { } catch (CRM_Core_Exception $e) { throw new ProductException( - E::ts('An Error occurred while searching for the associated PriceField: ' . $e->getMessage()), + E::ts('An Error occurred while searching for the associated PriceField: %1', [1 => $e->getMessage()]), ProductException::ERROR_CODE_PRICE_FIELD_NOT_FOUND); } throw new ProductException( - E::ts('Could not delete associated PriceField: ' . $e->getMessage()), + E::ts('Could not delete associated PriceField: %1', [1 => $e->getMessage()]), ProductException::ERROR_CODE_COULD_NOT_DELETE_PRICE_FIELD); } $this->price_field_id = NULL; } + } diff --git a/CRM/Twingle/BAO/TwingleShop.php b/CRM/Twingle/BAO/TwingleShop.php index dd49596..a4b006c 100644 --- a/CRM/Twingle/BAO/TwingleShop.php +++ b/CRM/Twingle/BAO/TwingleShop.php @@ -147,9 +147,10 @@ class CRM_Twingle_BAO_TwingleShop extends CRM_Twingle_DAO_TwingleShop { if ($dao->fetch()) { $this->load($dao->toArray()); } - } catch (\Civi\Core\Exception\DBQueryException $e) { + } + catch (\Civi\Core\Exception\DBQueryException $e) { throw new ShopException( - E::ts('Could not find TwingleShop in database: ' . $e->getMessage()), + E::ts('Could not find TwingleShop in database: %1', [1 => $e->getMessage()]), ShopException::ERROR_CODE_COULD_NOT_FIND_SHOP_IN_DB); } @@ -191,7 +192,7 @@ class CRM_Twingle_BAO_TwingleShop extends CRM_Twingle_DAO_TwingleShop { catch (\CRM_Core_Exception $e) { if ($e->getMessage() != 'Expected one PriceSet but found 0') { throw new ShopException( - E::ts('Could not find associated PriceSet: ' . $e->getMessage()), + E::ts('Could not find associated PriceSet: %1', [1 => $e->getMessage()]), ShopException::ERROR_CODE_PRICE_SET_NOT_FOUND); } else { @@ -207,7 +208,7 @@ class CRM_Twingle_BAO_TwingleShop extends CRM_Twingle_DAO_TwingleShop { ['id' => $this->price_set_id]); } catch (\CRM_Core_Exception $e) { throw new ShopException( - E::ts('Could not delete associated PriceSet: ' . $e->getMessage()), + E::ts('Could not delete associated PriceSet: %1', [1 => $e->getMessage()]), ShopException::ERROR_CODE_COULD_NOT_DELETE_PRICE_SET); } @@ -451,9 +452,10 @@ class CRM_Twingle_BAO_TwingleShop extends CRM_Twingle_DAO_TwingleShop { public function deleteProducts() { try { $products = $this->getProducts(); - } catch (\Civi\Core\Exception\DBQueryException $e) { + } + catch (\Civi\Core\Exception\DBQueryException $e) { throw new ProductException( - E::ts('Could not retrieve associated products: ' . $e->getMessage()), + E::ts('Could not retrieve associated products: %1', [1 => $e->getMessage()]), ProductException::ERROR_CODE_COULD_NOT_GET_PRODUCTS ); } @@ -464,8 +466,8 @@ class CRM_Twingle_BAO_TwingleShop extends CRM_Twingle_DAO_TwingleShop { } catch (ProductException $e) { throw new ProductException( - E::ts('Could not delete associated products: ' . $e->getMessage()), - ProductException::ERROR_CODE_COULD_NOT_DELETE_PRICE_SET + E::ts('Could not delete associated products: %1', [1 => $e->getMessage()]), + ProductException::ERROR_CODE_COULD_NOT_DELETE_PRICE_SET, ); } } diff --git a/CRM/Twingle/Form/Profile.php b/CRM/Twingle/Form/Profile.php index 73022cd..76c9cb4 100644 --- a/CRM/Twingle/Form/Profile.php +++ b/CRM/Twingle/Form/Profile.php @@ -648,8 +648,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { if (!isset($profile_data[$key]) && $required) { CRM_Core_Session::setStatus( E::ts( - 'The required configuration option "%1" has no value.' - . ' Saving the profile might set this option to a possibly unwanted default value.', + 'The required configuration option "%1" has no value. Saving the profile might set this option to a possibly unwanted default value.', [1 => $metadata['label'] ?? $key] ), E::ts('Error'), diff --git a/CRM/Twingle/Submission.php b/CRM/Twingle/Submission.php index 48c658a..2c3e85d 100644 --- a/CRM/Twingle/Submission.php +++ b/CRM/Twingle/Submission.php @@ -546,7 +546,7 @@ class CRM_Twingle_Submission { if (!empty($line_item['is_error'])) { $line_item_name = $line_item_data['name']; throw new CiviCRM_API3_Exception( - E::ts("Could not create line item for product '$line_item_name'"), + E::ts("Could not create line item for product '%1'", [1 => $line_item_name]), 'api_error' ); } diff --git a/l10n/de.systopia.twingle.pot b/l10n/de.systopia.twingle.pot index 439b9b5..2a38969 100644 --- a/l10n/de.systopia.twingle.pot +++ b/l10n/de.systopia.twingle.pot @@ -1,1016 +1,1600 @@ -#: ./CRM/Twingle/Config.php +#: CRM/Twingle/Config.php msgid "No" msgstr "" -#: ./CRM/Twingle/Config.php +#: CRM/Twingle/Config.php msgid "Raise Exception" msgstr "" -#: ./CRM/Twingle/Config.php +#: CRM/Twingle/Config.php msgid "Create Activity" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Profile with ID \"%1\" not found" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Delete Twingle API profile %1" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Page/Profiles.tpl msgid "Reset" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl +#: CRM/Twingle/Form/Profile.php js/twingle_shop.js templates/CRM/Twingle/Page/Profiles.tpl msgid "Delete" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "The profile is invalid and cannot be copied." msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Error" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "The profile to be copied could not be found." msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "A database error has occurred. See the log for details." msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "New Twingle API profile" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Edit Twingle API profile %1" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "New Profile" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Page/Profiles.tpl +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Page/Profiles.tpl msgid "Profile name" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php templates/CRM/Twingle/Form/Profile.tpl msgid "Project IDs" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Contact Matcher (XCM) Profile" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php templates/CRM/Twingle/Form/Profile.tpl msgid "Location type" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php templates/CRM/Twingle/Form/Profile.tpl msgid "Location type for organisations" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php templates/CRM/Twingle/Form/Profile.tpl msgid "Financial type" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php templates/CRM/Twingle/Form/Profile.tpl msgid "Financial type (recurring)" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Gender option for submitted value \"male\"" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Gender option for submitted value \"female\"" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Gender option for submitted value \"other\"" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Prefix option for submitted value \"male\"" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Prefix option for submitted value \"female\"" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "Prefix option for submitted value \"other\"" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Record %1 as" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Record %1 donations with contribution status" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Profile.php +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Profile.php msgid "CiviSEPA creditor" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Use Double-Opt-In for newsletter" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Sign up for newsletter groups" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Sign up for postal mail groups" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Sign up for Donation receipt groups" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Default Campaign" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "- none -" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Set Campaign for" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Contribution" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Recurring Contribution" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Membership" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "SEPA Mandate" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Contacts (XCM)" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Create membership of type" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Create membership of type (recurring)" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "API Call for Membership Postprocessing" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "The API call must have the form 'Entity.Action'." msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Contribution source" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl msgid "Required address components" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Street" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Postal Code" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php msgid "City" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php msgid "Country" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./templates/CRM/Twingle/Form/Profile.tpl +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl msgid "Custom field mapping" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Create contribution notes for" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php msgid "Purpose" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./api/v3/TwingleDonation/Submit.php +#: CRM/Twingle/Form/Profile.php api/v3/TwingleDonation/Submit.php msgid "Remarks" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Create contact notes for" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "User Extra Field" msgstr "" -#: ./CRM/Twingle/Form/Profile.php ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl +msgid "Enable Shop Integration" +msgstr "" + +#: CRM/Twingle/Form/Profile.php +msgid "Default Financial Type" +msgstr "" + +#: CRM/Twingle/Form/Profile.php +msgid "Financial Type for top up donations" +msgstr "" + +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl +msgid "Map Products as Price Fields" +msgstr "" + +#: CRM/Twingle/Form/Profile.php CRM/Twingle/Form/Settings.php msgid "Save" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "Warning" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php +msgid "The required configuration option \"%1\" has no value. Saving the profile might set this option to a possibly unwanted default value." +msgstr "" + +#: CRM/Twingle/Form/Profile.php msgid "No profile set." msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "<select profile>" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "none" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "CiviSEPA" msgstr "" -#: ./CRM/Twingle/Form/Profile.php +#: CRM/Twingle/Form/Profile.php msgid "No mailing lists available" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Twingle ID Prefix" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Use CiviSEPA" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Use CiviSEPA generated reference" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Protect Recurring Contributions" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Activity Type" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Subject" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Status" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php msgid "Assigned To" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php +msgid "Use Twingle Shop Integration" +msgstr "" + +#: CRM/Twingle/Form/Settings.php +msgid "Twingle Access Key" +msgstr "" + +#: CRM/Twingle/Form/Settings.php msgid "This is required for activity creation" msgstr "" -#: ./CRM/Twingle/Form/Settings.php +#: CRM/Twingle/Form/Settings.php +msgid "An Access Key is required to enable Twingle Shop Integration" +msgstr "" + +#: CRM/Twingle/Form/Settings.php msgid "-select-" msgstr "" -#: ./CRM/Twingle/Page/Profiles.php ./managed/Navigation__twingle_configuration.mgd.php +#: CRM/Twingle/Page/Profiles.php managed/Navigation__twingle_configuration.mgd.php msgid "Twingle API Profiles" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Unknown attribute %1." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Profile name cannot be empty." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Only alphanumeric characters, space and the underscore (_) are allowed for profile names." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "A profile with the name '%1' already exists." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Project ID(s) [%1] already used in profile '%2'." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Could not parse custom field mapping." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Custom field custom_%1 does not exist." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Custom field custom_%1 is not in a CustomGroup that extends one of the supported CiviCRM entities." msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Could not save/update profile: %1" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Could not reset default profile: %1" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Could not delete profile: %1" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Contribution Status" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Bank transfer" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Debit manual" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Debit automatic" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Credit card" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Mobile phone Germany" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "PayPal" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "SOFORT Überweisung" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Amazon Pay" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Apple Pay" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Google Pay" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Paydirekt" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Twint" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "iDEAL" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Postfinance" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Bancontact" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "Generic Payment Method" msgstr "" -#: ./CRM/Twingle/Profile.php +#: CRM/Twingle/Profile.php msgid "never" msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Invalid donation rhythm." msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Payment method could not be matched to existing payment instrument." msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Invalid date for parameter \"confirmed_at\"." msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Invalid date for parameter \"user_birthdate\"." msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Gender could not be matched to existing gender." msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Invalid format for custom fields." msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php +msgid "Invalid format for products." +msgstr "" + +#: CRM/Twingle/Submission.php msgid "campaign_id must be a numeric string. " msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Unknown country %1." msgstr "" -#: ./CRM/Twingle/Submission.php +#: CRM/Twingle/Submission.php msgid "Could not calculate SEPA cycle day from configuration." msgstr "" -#: ./CRM/Twingle/Tools.php +#: CRM/Twingle/Submission.php +msgid "Could not create line item for product '%1'" +msgstr "" + +#: CRM/Twingle/Submission.php +msgid "Could not create line item for donation" +msgstr "" + +#: CRM/Twingle/Tools.php msgid "This is a Twingle recurring contribution. It should be terminated through the Twingle interface, otherwise it will still be collected." msgstr "" -#: ./CRM/Twingle/Tools.php +#: CRM/Twingle/Tools.php msgid "Recurring contribution [%1] (Transaction ID '%2') was terminated by a user. You need to end the corresponding record in Twingle as well, or it will still be collected." msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +#: Civi/Twingle/Shop/ApiCall.php +msgid "Could not find Twingle API token" +msgstr "" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "Call to Twingle API failed. Please check your api token." +msgstr "" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "GET curl failed" +msgstr "" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "http status code 404 (not found)" +msgstr "" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "https status code 500 (internal error)" +msgstr "" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "Connection not yet established. Use connect() method." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not find PriceField for Twingle Product ['id': %1, 'external_id': %2]: %3" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not find PriceFieldValue for Twingle Product ['id': %1, 'external_id': %2]: %3" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "PriceField for this Twingle Product already exists." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "PriceField for this Twingle Product does not exist and cannot be edited." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not check if PriceField for this Twingle Product already exists." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not find PriceSet for this Twingle Product." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not create PriceField for this Twingle Product: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not find PriceFieldValue for this Twingle Product: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not create PriceFieldValue for this Twingle Product: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not find TwingleProduct in database: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not save TwingleProduct to database: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "An Error occurred while searching for the associated PriceFieldValue: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not delete associated PriceFieldValue: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "PriceField for this Twingle Product still exists." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "An Error occurred while searching for the associated PriceField: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleProduct.php +msgid "Could not delete associated PriceField: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not find TwingleShop in database: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not find associated PriceSet: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not delete associated PriceSet: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "PriceSet for this Twingle Shop already exists." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "PriceSet for this Twingle Shop does not exist and cannot be edited." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not check if PriceSet for this TwingleShop already exists." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not create PriceSet for this TwingleShop." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "This Twingle Project is not a shop." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not retrieve Twingle projects from API.\n Please check your API credentials." +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not retrieve associated products: %1" +msgstr "" + +#: Civi/Twingle/Shop/BAO/TwingleShop.php +msgid "Could not delete associated products: %1" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "Twingle Products" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "Twingle Product" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "Unique TwingleProduct ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "External ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "The ID of this product in the Twingle database" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php api/v3/TwingleProduct/Get.php +msgid "Price Field ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "FK to Price Field" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php api/v3/TwingleProduct/Create.php +msgid "Twingle Shop ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "FK to Twingle Shop" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "Created At" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "Timestamp of when the product was created in the database" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "Updated At" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleProduct.php +msgid "Timestamp of when the product was last updated in the database" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "Twingle Shops" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "Twingle Shop" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "Unique TwingleShop ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Create.php api/v3/TwingleShop/Delete.php api/v3/TwingleShop/Get.php +msgid "Project Identifier" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "Twingle Project Identifier" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "Numerical Project ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "Numerical Twingle Project Identifier" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php api/v3/TwingleShop/Get.php +msgid "Price Set ID" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "FK to Price Set" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php api/v3/TwingleShop/Get.php +msgid "Name" +msgstr "" + +#: Civi/Twingle/Shop/DAO/TwingleShop.php +msgid "name of the shop" +msgstr "" + +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php api/v3/TwingleDonation/Submit.php msgid "Project ID" msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php api/v3/TwingleDonation/Submit.php msgid "The Twingle project ID." msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php api/v3/TwingleDonation/Submit.php msgid "Transaction ID" msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php api/v3/TwingleDonation/Submit.php msgid "The unique transaction ID of the donation" msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php +#: api/v3/TwingleDonation/Cancel.php msgid "Cancelled at" msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php +#: api/v3/TwingleDonation/Cancel.php msgid "The date when the donation was cancelled, format: YmdHis." msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php +#: api/v3/TwingleDonation/Cancel.php msgid "Cancel reason" msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php +#: api/v3/TwingleDonation/Cancel.php msgid "The reason for the donation being cancelled." msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php +#: api/v3/TwingleDonation/Cancel.php msgid "Invalid date for parameter \"cancelled_at\"." msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php +#: api/v3/TwingleDonation/Cancel.php msgid "SEPA Mandate for contribution [%1 not found." msgstr "" -#: ./api/v3/TwingleDonation/Cancel.php ./api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php msgid "Could not terminate SEPA mandate" msgstr "" -#: ./api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Endrecurring.php msgid "Ended at" msgstr "" -#: ./api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Endrecurring.php msgid "The date when the recurring donation was ended, format: YmdHis." msgstr "" -#: ./api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Endrecurring.php msgid "Invalid date for parameter \"ended_at\"." msgstr "" -#: ./api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Endrecurring.php msgid "SEPA Mandate for recurring contribution [%1 not found." msgstr "" -#: ./api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Endrecurring.php msgid "SEPA Mandate [%1] already terminated." msgstr "" -#: ./api/v3/TwingleDonation/Endrecurring.php +#: api/v3/TwingleDonation/Endrecurring.php msgid "Mandate closed by TwingleDonation.Endrecurring API call" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Confirmed at" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The date when the donation was issued, format: YmdHis." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The purpose of the donation." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Amount" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The donation amount in minor currency unit." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Currency" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The ISO-4217 currency code of the donation." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Newsletter" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Whether to subscribe the contact to the newsletter group defined in the profile." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Postal mailing" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Whether to subscribe the contact to the postal mailing group defined in the profile." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Donation receipt" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Whether the contact requested a donation receipt." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Payment method" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The Twingle payment method used for the donation." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Donation rhythm" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The interval which the donation is recurring in." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "SEPA IBAN" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The IBAN for SEPA Direct Debit payments, conforming with ISO 13616-1:2007." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "SEPA BIC" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The BIC for SEPA Direct Debit payments, conforming with ISO 9362." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "SEPA Direct Debit Mandate reference" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The mandate reference for SEPA Direct Debit payments." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "SEPA Direct Debit Account holder" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The account holder for SEPA Direct Debit payments." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Anonymous donation" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Whether the donation is submitted anonymously." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Gender" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The gender of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Date of birth" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The date of birth of the contact, format: Ymd." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Formal title" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The formal title of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Email address" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The e-mail address of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "First name" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The first name of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Last name" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The last name of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Street address" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The street address of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Postal code" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The postal code of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The city of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The country of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Telephone" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The telephone number of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Company" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The company of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Language" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The preferred language of the contact. A 2-digit ISO-639-1 language code." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "User extra field" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Additional information of the contact." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Campaign ID" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "The CiviCRM ID of a campaign to assign the contribution." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Custom fields" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Additional information for either the contact or the (recurring) contribution." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php +msgid "Products" +msgstr "" + +#: api/v3/TwingleDonation/Submit.php +msgid "Products ordered via TwingleShop" +msgstr "" + +#: api/v3/TwingleDonation/Submit.php msgid "Additional remarks for the donation." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Contribution with the given transaction ID already exists." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Organisation contact could not be found or created." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Individual contact could not be found or created." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Missing attribute %1 for SEPA mandate" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "SEPA creditor is not configured for profile \"%1\"." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Could not create recurring contribution." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Could not find recurring contribution with given parent transaction ID." msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Could not create contribution" msgstr "" -#: ./api/v3/TwingleDonation/Submit.php +#: api/v3/TwingleDonation/Submit.php msgid "Twingle membership postprocessing call has failed, see log for more information" msgstr "" -#: ./managed/Navigation__twingle_configuration.mgd.php +#: api/v3/TwingleProduct/Create.php api/v3/TwingleProduct/Delete.php api/v3/TwingleProduct/Get.php +msgid "TwingleProduct ID" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "The TwingleProduct ID in the database" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Twingle ID" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "External product ID in Twingle database" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "ID of the corresponding Twingle Shop" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Product Name" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Name of the product" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Is active?" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Is the product active?" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Product Description" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Short description of the product" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Product Price" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Price of the product" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Sort" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Sort order of the product" +msgstr "" + +#: api/v3/TwingleProduct/Create.php api/v3/TwingleShop/Create.php +msgid "Financial Type ID" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "ID of the financial type of the product" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "FK to TwingleShop" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Twingle timestamp" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "Timestamp of last update in Twingle db" +msgstr "" + +#: api/v3/TwingleProduct/Create.php +msgid "FK to PriceField" +msgstr "" + +#: api/v3/TwingleProduct/Delete.php api/v3/TwingleProduct/Get.php +msgid "The TwingleProduct ID in CiviCRM" +msgstr "" + +#: api/v3/TwingleProduct/Delete.php api/v3/TwingleProduct/Get.php +msgid "External TwingleProduct ID" +msgstr "" + +#: api/v3/TwingleProduct/Delete.php api/v3/TwingleProduct/Get.php +msgid "Twingle's ID of the product" +msgstr "" + +#: api/v3/TwingleProduct/Delete.php +msgid "TwingleProduct could not be deleted." +msgstr "" + +#: api/v3/TwingleProduct/Get.php +msgid "FK to civicrm_price_field" +msgstr "" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Delete.php api/v3/TwingleShop/Get.php +msgid "TwingleShop ID" +msgstr "" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Delete.php api/v3/TwingleShop/Get.php +msgid "The TwingleShop ID in CiviCRM" +msgstr "" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Create.php api/v3/TwingleShop/Delete.php api/v3/TwingleShop/Get.php +msgid "Twingle project identifier" +msgstr "" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Create.php api/v3/TwingleShop/Get.php +msgid "Numerical Project Identifier" +msgstr "" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Get.php +msgid "Twingle numerical project identifier" +msgstr "" + +#: api/v3/TwingleShop/Create.php +msgid "Numerical Twingle project identifier" +msgstr "" + +#: api/v3/TwingleShop/Create.php +msgid "Shop Name" +msgstr "" + +#: api/v3/TwingleShop/Create.php +msgid "Name of the shop" +msgstr "" + +#: api/v3/TwingleShop/Create.php +msgid "FK to civicrm_financial_type" +msgstr "" + +#: api/v3/TwingleShop/Delete.php +msgid "TwingleShop could not be found." +msgstr "" + +#: api/v3/TwingleShop/Delete.php +msgid "TwingleShop could not be deleted." +msgstr "" + +#: api/v3/TwingleShop/Fetch.php +msgid "Project Identifiers" +msgstr "" + +#: api/v3/TwingleShop/Fetch.php +msgid "Comma separated list of Twingle project identifiers." +msgstr "" + +#: api/v3/TwingleShop/Get.php +msgid "Name of the TwingleShop" +msgstr "" + +#: api/v3/TwingleShop/Get.php +msgid "FK to civicrm_price_set" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not fetch products" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not fetch products. Please check your Twingle API key." +msgstr "" + +#: js/twingle_shop.js +msgid "Create" +msgstr "" + +#: js/twingle_shop.js +msgid "Update" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not create Price Field for this product" +msgstr "" + +#: js/twingle_shop.js +msgid "Delete Price Field" +msgstr "" + +#: js/twingle_shop.js +msgid "Are you sure you want to delete the price field associated with this product?" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not delete Price Field" +msgstr "" + +#: js/twingle_shop.js +msgid "The Price Field was deleted successfully." +msgstr "" + +#: js/twingle_shop.js +msgid "Price Field deleted" +msgstr "" + +#: js/twingle_shop.js +msgid "select financial type" +msgstr "" + +#: js/twingle_shop.js +msgid "Product" +msgstr "" + +#: js/twingle_shop.js +msgid "Financial Type" +msgstr "" + +#: js/twingle_shop.js +msgid "Price Field" +msgstr "" + +#: js/twingle_shop.js +msgid "Create Price Set" +msgstr "" + +#: js/twingle_shop.js +msgid "Update Price Set" +msgstr "" + +#: js/twingle_shop.js +msgid "Delete Price Set" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not create Twingle Shop" +msgstr "" + +#: js/twingle_shop.js +msgid "The Price Set was created successfully." +msgstr "" + +#: js/twingle_shop.js +msgid "Price Field created" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not create TwingleShop" +msgstr "" + +#: js/twingle_shop.js +msgid "Are you sure you want to delete the price set associated with this Twingle Shop?" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not delete Twingle Shop" +msgstr "" + +#: js/twingle_shop.js +msgid "The Price Set was deleted successfully." +msgstr "" + +#: js/twingle_shop.js +msgid "Price Set deleted" +msgstr "" + +#: js/twingle_shop.js +msgid "Could not update Twingle Shop" +msgstr "" + +#: managed/Navigation__twingle_configuration.mgd.php msgid "Twingle API Configuration" msgstr "" -#: ./managed/Navigation__twingle_configuration.mgd.php +#: managed/Navigation__twingle_configuration.mgd.php msgid "Twingle API Settings" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Select which location type to use for addresses for individuals, either when no organisation name is specified, or an organisation address can not be shared with the individual contact." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Put your project's Twingle ID in here, to activate this profile for that project." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "You can also provide multiple project IDs separated by a comma." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "The Contact Matcher (XCM) manages the identification or creation of the related contact." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "We recommend creating a new XCM profile only to be used with the Twingle API." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Select which location type to use for addresses for organisations and shared organisation addresses for individual contacts." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Select which financial type to use for one-time contributions." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Select which financial type to use for recurring contributions." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Select whether to use CiviCRM's Double-Opt-In feature for subscribing to mailing lists. Note that this only works for public mailing lists. Any non-public mailing list selected above will be ignored when this setting is enabled." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Also, do not forget to disable Twingle's own Double Opt-In option in the Twingle Manager to avoid subscribers receiving multiple confirmation e-mails. Only one or the other option should be enabled." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Some organisations have specific conventions on how a membership should be created. Since the Twingle-API can only create a \"bare bone\" membership object, you can enter a API Call (as 'Entity.Action') to adjust any newly created membership to your organisation's needs." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "The API call would receive the following parameters:
    \n
  • membership_id: The ID of the newly created membership
  • \n
  • contact_id: The ID of the contact involved
  • \n
  • organization_id: The ID of the contact's organisation, potentially empty
  • \n
  • contribution_id: The ID contribution received, potentially empty
  • \n
  • recurring_contribution_id: The ID of the recurring contribution. If empty, this was only a one-off donation.
  • \n
" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Select the address components that must be present to create or update an address for the contact." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Depending on your XCM settings, the transferred address might replace an existing one." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "Since in some cases Twingle send the country of the user as the only address parameter, depending on your XCM configuration, not declaring other address components as required might lead to the current user address being overwritten with an address containing the country only." msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "

Map Twingle custom fields to CiviCRM fields using the following format (each assignment in a separate line):

\n
twingle_field_1=custom_123
twingle_field_2=custom_789
\n

Always use the custom_[id] notation for CiviCRM custom fields.

\n

This works for fields that Twingle themselves provide in the custom_fields parameter, and for any other parameter (e.g. user_extrafield)

\n

Only custom fields extending one of the following CiviCRM entities are allowed:

\n
    \n
  • Contact – Will be set on the Individual contact
  • \n
  • Individual – Will be set on the Individual contact
  • \n
  • Organization – Will be set on the Organization contact, if an organisation name was submitted
  • \n
  • Contribution – Will be set on the contribution
  • \n
  • ContributionRecur – Will be set on the recurring contribution and deriving single contributions
  • \n
" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "

Create a contribution note for each field specified in this selection.

\n

Tip: You can enable or disable this fields in the TwingleMANAGER.

" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.hlp +#: templates/CRM/Twingle/Form/Profile.hlp msgid "

Create a contact note for each field specified in this selection.

\n

Tip: You can enable or disable this fields in the TwingleMANAGER.

" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "Enable the processing of orders via Twingle Shop for this profile. The ordered products will then appear as line items in the contribution." +msgstr "" + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "If this option is enabled, all Twingle Shop products corresponding to the specified project IDs will be retrieved from Twingle and mapped as price sets and price fields. Each Twingle Shop is mapped as a price set with its products as price fields." +msgstr "" + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "This allows you to manually create contributions with the same line items for phone orders, for example, as would be the case for orders placed through the Twingle Shop." +msgstr "" + +#: templates/CRM/Twingle/Form/Profile.tpl msgid "General settings" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Help" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "XCM Profile" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Gender/Prefix for value 'male'" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Gender/Prefix for value 'female'" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Gender/Prefix for value 'other'" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Payment methods" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Groups and Correlations" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Newsletter Double Opt-In" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Membership Postprocessing" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl templates/CRM/Twingle/Page/Profiles.tpl +msgid "Shop Integration" +msgstr "" + +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Are you sure you want to reset the default profile?" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Are you sure you want to delete the profile %1?" msgstr "" -#: ./templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Form/Profile.tpl msgid "Profile name not given or invalid." msgstr "" -#: ./templates/CRM/Twingle/Form/Settings.hlp +#: templates/CRM/Twingle/Form/Settings.hlp msgid "When the %1 is enabled and one of its payment instruments is assigned to a Twingle payment method (practically the debit_manual payment method), submitting a Twingle donation through the API will create a SEPA mandate with the given data." msgstr "" -#: ./templates/CRM/Twingle/Form/Settings.hlp +#: templates/CRM/Twingle/Form/Settings.hlp msgid "When the %1 is enabled, you can activate this to use your own references instead of the ones submitted by Twingle." msgstr "" -#: ./templates/CRM/Twingle/Form/Settings.hlp +#: templates/CRM/Twingle/Form/Settings.hlp msgid "Will protect all recurring contributions created by Twingle from termination, since this does NOT terminate the Twingle collection process" msgstr "" -#: ./templates/CRM/Twingle/Form/Settings.hlp +#: templates/CRM/Twingle/Form/Settings.hlp msgid "You can use this setting to add a prefix to the Twingle transaction ID, in order to avoid collisions with other transaction ids." msgstr "" -#: ./templates/CRM/Twingle/Page/Configuration.tpl +#: templates/CRM/Twingle/Form/Settings.hlp +msgid "If you enable Twingle Shop integration, you can configure Twingle API profiles to include products ordered through Twingle Shop as line items in the created contribution." +msgstr "" + +#: templates/CRM/Twingle/Form/Settings.hlp +msgid "Enter your twingle API access key." +msgstr "" + +#: templates/CRM/Twingle/Page/Configuration.tpl msgid "Profiles" msgstr "" -#: ./templates/CRM/Twingle/Page/Configuration.tpl +#: templates/CRM/Twingle/Page/Configuration.tpl msgid "Configure profiles" msgstr "" -#: ./templates/CRM/Twingle/Page/Configuration.tpl +#: templates/CRM/Twingle/Page/Configuration.tpl msgid "Settings" msgstr "" -#: ./templates/CRM/Twingle/Page/Configuration.tpl +#: templates/CRM/Twingle/Page/Configuration.tpl msgid "Configure extension settings" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "New profile" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Selectors" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Used" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Last Used" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Operations" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "enabled" +msgstr "" + +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "disabled" +msgstr "" + +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Edit profile %1" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Edit" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Copy profile %1" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Copy" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Reset profile %1" msgstr "" -#: ./templates/CRM/Twingle/Page/Profiles.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Delete profile %1" msgstr "" -#: ./twingle.php +#: twingle.php msgid "Twingle API: Access Twingle API" msgstr "" -#: ./twingle.php +#: twingle.php msgid "Allows access to the Twingle API actions." msgstr "" From 612224901aa1dbf4b6969302f0133f1fe7cd606d Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 24 Sep 2024 14:28:34 +0200 Subject: [PATCH 33/43] Update translation template --- l10n/de.systopia.twingle.pot | 416 +++++++++++++++++------------------ 1 file changed, 208 insertions(+), 208 deletions(-) diff --git a/l10n/de.systopia.twingle.pot b/l10n/de.systopia.twingle.pot index 2a38969..e358b75 100644 --- a/l10n/de.systopia.twingle.pot +++ b/l10n/de.systopia.twingle.pot @@ -1,3 +1,111 @@ +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not find PriceField for Twingle Product ['id': %1, 'external_id': %2]: %3" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not find PriceFieldValue for Twingle Product ['id': %1, 'external_id': %2]: %3" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "PriceField for this Twingle Product already exists." +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "PriceField for this Twingle Product does not exist and cannot be edited." +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not check if PriceField for this Twingle Product already exists." +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not find PriceSet for this Twingle Product." +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not create PriceField for this Twingle Product: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not find PriceFieldValue for this Twingle Product: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not create PriceFieldValue for this Twingle Product: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not find TwingleProduct in database: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not save TwingleProduct to database: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "An Error occurred while searching for the associated PriceFieldValue: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not delete associated PriceFieldValue: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "PriceField for this Twingle Product still exists." +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "An Error occurred while searching for the associated PriceField: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleProduct.php +msgid "Could not delete associated PriceField: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not find TwingleShop in database: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not find associated PriceSet: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not delete associated PriceSet: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "PriceSet for this Twingle Shop already exists." +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "PriceSet for this Twingle Shop does not exist and cannot be edited." +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not check if PriceSet for this TwingleShop already exists." +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not create PriceSet for this TwingleShop." +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "This Twingle Project is not a shop." +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not retrieve Twingle projects from API.\n Please check your API credentials." +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not retrieve associated products: %1" +msgstr "" + +#: CRM/Twingle/BAO/TwingleShop.php +msgid "Could not delete associated products: %1" +msgstr "" + #: CRM/Twingle/Config.php msgid "No" msgstr "" @@ -10,6 +118,106 @@ msgstr "" msgid "Create Activity" msgstr "" +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "Twingle Products" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "Twingle Product" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php CRM/Twingle/DAO/TwingleShop.php +msgid "ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "Unique TwingleProduct ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "External ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "The ID of this product in the Twingle database" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php api/v3/TwingleProduct/Get.php +msgid "Price Field ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "FK to Price Field" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php api/v3/TwingleProduct/Create.php +msgid "Twingle Shop ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "FK to Twingle Shop" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "Created At" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "Timestamp of when the product was created in the database" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "Updated At" +msgstr "" + +#: CRM/Twingle/DAO/TwingleProduct.php +msgid "Timestamp of when the product was last updated in the database" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "Twingle Shops" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "Twingle Shop" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "Unique TwingleShop ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Create.php api/v3/TwingleShop/Delete.php api/v3/TwingleShop/Get.php +msgid "Project Identifier" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "Twingle Project Identifier" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "Numerical Project ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "Numerical Twingle Project Identifier" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php api/v3/TwingleShop/Get.php +msgid "Price Set ID" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "FK to Price Set" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php api/v3/TwingleShop/Get.php +msgid "Name" +msgstr "" + +#: CRM/Twingle/DAO/TwingleShop.php +msgid "name of the shop" +msgstr "" + #: CRM/Twingle/Form/Profile.php msgid "Profile with ID \"%1\" not found" msgstr "" @@ -530,214 +738,6 @@ msgstr "" msgid "Connection not yet established. Use connect() method." msgstr "" -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not find PriceField for Twingle Product ['id': %1, 'external_id': %2]: %3" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not find PriceFieldValue for Twingle Product ['id': %1, 'external_id': %2]: %3" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "PriceField for this Twingle Product already exists." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "PriceField for this Twingle Product does not exist and cannot be edited." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not check if PriceField for this Twingle Product already exists." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not find PriceSet for this Twingle Product." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not create PriceField for this Twingle Product: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not find PriceFieldValue for this Twingle Product: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not create PriceFieldValue for this Twingle Product: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not find TwingleProduct in database: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not save TwingleProduct to database: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "An Error occurred while searching for the associated PriceFieldValue: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not delete associated PriceFieldValue: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "PriceField for this Twingle Product still exists." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "An Error occurred while searching for the associated PriceField: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleProduct.php -msgid "Could not delete associated PriceField: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not find TwingleShop in database: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not find associated PriceSet: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not delete associated PriceSet: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "PriceSet for this Twingle Shop already exists." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "PriceSet for this Twingle Shop does not exist and cannot be edited." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not check if PriceSet for this TwingleShop already exists." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not create PriceSet for this TwingleShop." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "This Twingle Project is not a shop." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not retrieve Twingle projects from API.\n Please check your API credentials." -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not retrieve associated products: %1" -msgstr "" - -#: Civi/Twingle/Shop/BAO/TwingleShop.php -msgid "Could not delete associated products: %1" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "Twingle Products" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "Twingle Product" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "Unique TwingleProduct ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "External ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "The ID of this product in the Twingle database" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php api/v3/TwingleProduct/Get.php -msgid "Price Field ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "FK to Price Field" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php api/v3/TwingleProduct/Create.php -msgid "Twingle Shop ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "FK to Twingle Shop" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "Created At" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "Timestamp of when the product was created in the database" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "Updated At" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleProduct.php -msgid "Timestamp of when the product was last updated in the database" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "Twingle Shops" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "Twingle Shop" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "Unique TwingleShop ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Create.php api/v3/TwingleShop/Delete.php api/v3/TwingleShop/Get.php -msgid "Project Identifier" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "Twingle Project Identifier" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "Numerical Project ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "Numerical Twingle Project Identifier" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php api/v3/TwingleShop/Get.php -msgid "Price Set ID" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "FK to Price Set" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php api/v3/TwingleShop/Get.php -msgid "Name" -msgstr "" - -#: Civi/Twingle/Shop/DAO/TwingleShop.php -msgid "name of the shop" -msgstr "" - #: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php api/v3/TwingleDonation/Submit.php msgid "Project ID" msgstr "" From 30c34f72bea38791c0b9f57eed0aa567aa79eeae Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 24 Sep 2024 14:29:26 +0200 Subject: [PATCH 34/43] Version 1.5-beta2 --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index c019d80..6e2d126 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - - 1.5-dev - dev + 2024-09-24 + 1.5-beta2 + beta 5.58 From 9c9fed20d7d698bac1eb4ffba15de79bae613ed9 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 24 Sep 2024 14:29:38 +0200 Subject: [PATCH 35/43] Back to 1.5-dev --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index 6e2d126..c019d80 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - 2024-09-24 - 1.5-beta2 - beta + + 1.5-dev + dev 5.58 From 82456d2ae43370915c56e9e0c43a73e4c27c2368 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 1 Oct 2024 14:24:23 +0200 Subject: [PATCH 36/43] Update German translation --- l10n/de_DE/LC_MESSAGES/twingle.mo | Bin 29888 -> 44836 bytes l10n/de_DE/LC_MESSAGES/twingle.po | 658 +++++++++++++++++++++++++++++- 2 files changed, 657 insertions(+), 1 deletion(-) diff --git a/l10n/de_DE/LC_MESSAGES/twingle.mo b/l10n/de_DE/LC_MESSAGES/twingle.mo index 78498f0172762c1ca981c4a566147748306906b4..b7c97292a885e91d1eba63ab908501a13fe7c5f9 100644 GIT binary patch literal 44836 zcmd6w37lS4dH*lSnm_;r1Od55LMD)zkc2gaz$BSK$Z9fdL5%OrduQfl-g)1bcS*>& zfcu6@sbZ@rV5zuN{aY;+rM5P8soTF*+qzV1ts7eF(pG6*+VAf<=iKF;caq`X`uY6l z!{zthd(S=hoaa2}+0JuMzW&6+ULW!ITZcr^>EHv$M9~?CM$yt!bv26ads-Bo4BihO z1%A-uN5OkI{}gyXIKD87)*Kc^M?XD^F5>*E#nb`b4xSCZ8$1^LBDfX&roX@M7?Y zbE9Y_csV#9Y=QH@+d#GJm7vP~4mcnDYk&P~AS8)?0MfnaG4LqxSKtZY5p?o=@D%V! za0@6pUIVIKP4F1-CEx|%UEmquC&2~acfe!7e+Qog9@_8lWN;Pd=YV=|0v-q63|!9AdfD^TEDX8;}AVU$2gLEl+EePqO4}&WA zEl}kSIghr0XM$Xct_D?Z+T-tmD*qYq6!1}SHF)UqDB1>I4i1B_1uq4^1@d1MuiyZ8 zfNFmW)O+p+#W$Y;uLS=UTm)XqAi`o%3ETy~1XTHNfO>vDg8@rMmw+*NBdGR27u5K? z5fneY6+8}n2Y5XAFsOEa3RL-j0yPB=VbJv6(?IddAUGe~42ln~2i49JI0C*LJQ4f> zcr2*F5-tGszSBXC_fk;ry#zcNyc$%yO5hX02~hW+1FHTzz!SjtgV%x|19ks&CY$Q* z2gN6sftpWi{q=EB@81hP8GH%&D)7yq+P?&*XX-{Pz{A07K=toN@G0OpDE@dMsQ14U z)cYO*_5RO*YVX(l{U3wkgF`QI^g9#OdoKYOfX@I`&$B^|W7A*18`S%L9~7Pc3>2L{ z4eEVg0!5$ify=-jfg1NSnIz)Ft3gCU)Bp;D~4XQo2ff|pyLCLXuJ-!3f_`M%gKRyaFbkRS8s^`?pT|F0qr*b|BieGO8 z#fL8d)vvoj_47^Olfd_bqQ@sdjrW)Q{crj6UxI4iQCGP3KM53FmV!?Kmw_6Wt3kbI z7pV5sLDB6FkM9F-;QVhv)py>Ylf##S8qYeY{_X+AAGd>&@2>?_{}({D@1H#W2voUW z`15&d9sN%R#Rr3+_~2$x&%F**`TIci`@J6bgW|8h1I3Tu0@aV7fryf5K8?5vyd2ax zzZ@I@KLqk$^eF$6Jn3KW>b(F|eH%fI*R`O=vkb=Il)wKP@C?r11TF+02A>N4J*e`( z07a+6hM*OA9H@R=0cxIY2Gx(<;F(|xR6Bkfdgx26s!H2;WS0l5*e+Kow$|l#Y7I+lrFZSoJ26g`} zU<|$;R6G9?6rFzvioWwVN6|@#z$@Uf;1ye3d$w+M^JXV_1m&hYw!sCQKL z;PX7*1*)A7f=>iL0cu`-1v~-#HmG^>bMScZ$Qxb1PXUkNd?l#+SAd$&+re$%vp}`~ zJ>XNoFMz7=U%=DA=$Y>PbWr66zy;t=@M+)(sCK;=)O>pY)HuEqJPF+I@ypuATEh_2V>9?K~Td!Id6|!P7bKfQNy1f$HBIJiZ+iKR*mI1)`6FKL?L{7P=Tb za<}8dX;6Io2Oz8+eHDatqNUGfE`Top=Yz+T9ABLR-p={;pvLXXpysJ2yWV#+7=tH( zy1xQc|8D@*zTN)30jk}5K+)ql;H8J)pMVs0H}W72tEqF5}XINt8N@_1NGcX!L8so zK&D=_Y|4%Mi@>8fe?5q3i|z%*H{Sy<1TU^RIZ+3X<^1iS>VF6n9X<-`xzB^gfTz`6 zy?vn0SAkCeuLMQkP5%BcxQg>JP;|W?>;oSJMaRDa^}g?bnm_*rE&^9J96fFT)$R#U z?P!80gD(PA{*9pC_hnG?>6_qs@ZZ5_gR5?F_V}AXwc}^tmEdxiO1KYHJs$;^fKkh} z?;KG4uoGMfPJd}$= zTstb@I?nG0F95#{#^7nw&OccV!k*F1py*u(PXzaYCxNd4p9($zJ`LOt#^ATX^S~pX zQ_BgExR0#|C&j_#%J(b)f2dm%sl3 zQ0@B!sB!pv@JZld&tr~&r+`le+o0;Z0~9~s3u>I-3F`STf(yV$!PCIUz zs{EDy{Mn%BGX-i~ZUt4(-Jr(*O`ytu2-N)f0;qmH3X1>czrx`K;114rgY&^Z1c$(n zfJcCbz0%Fc`JnoF61Wvy1ZsY?LACEC9`6FhNB4rN@7>@4_%JBCe-G>jPkNQBcO9tb zw}6@_yFksODNy}?0jT!h>F>V{yoB?I!Rx_)1;x*s?{ISGMc_`(KL{QP9`kBf--+OS z&ilY)!SlhRz_s96;3iP_8(<7R7wiMy42o_a2UXwqzGh6(CqT9HdEoKjt3cIz4=6r;8+Z=*7og_VkNovx z?nch>zH`9y!SOe^dhhuiH?JN9#gCr`H4dNm_!W=e1ohtUgX_S@z`;YQ^LL%Tdj6Z7 z{qP8QGxra_$HfU7;4;qt80-hX3#xr5z1hjtD?nYZf}-CWLDBzjz!>}?cs97;Ev|nT zfJ->v37!f*50qSZ1E}%%FnBKbdGKQJ*B&pr*Xi8`cs18Q2*%(O?sN5=1?qeacsjTn zJP&*>D0%sAa3%Oj@TuT0!Oh@l_dESN0cyPO1J$m-1I6Dz0pA8b<*n}eM?vw?SHY#= zFTh1$-|u<<82nw%e+I4t?|i_?*)M|X&xyb9?4K>*ah%@;>V0no#b=NB^S=T$KfVvD z{m0RWdEhyq=Ent~p1%qdA6*Zg0aieb^UM7G*ZcF|2XEv0`$3K0$_HKfXMuX}bHP)< zJHZpccYqq#4}%N9M?pRJ7^v}k`rF;S-2#fso1ofNdbjgus^EQ`e+WDuyzxCwzT62O&G|i`#{F%e#^vMS z;ouLz1>jG>CxFp=y+?bVt>O^RUCXu0Io5LgC+8bDG&jXB z;qR6H$3DtT`QPIn2f6kUexC_4%+Y^v4Dnlk4?8efdx`)3OYlhUkNGm+^VjbLujlwz zjxxu`IK=DU;&>y+{T%wcl4F|V6&%<2I+pNTa^njed%5-$j+;5o;0#qj}-ZT?;jyeU_XTc-2BtRMUx!Eqh;&jvrL3;g{f zN1S`+H2?e8cGvw+e11IFpUZIt$5;Kmdp-91XAS{79GbuSyOHA*e^2K>;`l!tS8=Th z?&hd*Ea&`RK>bzu{a2uL=2DLL@cTI3@P7-rb}q+doPW|^TjcTof-mQu{wBZ&z&CTe zmg8w0O|D%FmVKEu{BH1D^YLbm_i}uWqmOG3fj5FNs6WLBZgM|uE%R{y`&r;H_ihD8 zz}Ip}emofre`TcqD$sGFo znFIU(-|+u=++Pj87Qx6IlmITh2!ZQqa2rUyqxQQ$FZ5;FW`6^zyE^cHT*sY{1V6M{MKK`LG0)Gr1&G8%#{hiM7R*s+Ogumk`vzy~r z{C+mLo};B3XII;owvuWx+KwB|`dFo!oIT#YH0qCQ^;#15NB!<*f3&)>W?eHWwUf9M zkJfAL5>M3XoQ~C-aZ(zch{q~PwH&t^$!LY2<+xId+Y^;m>}sggYXhs7HP+0F`J=Mc zl{Lea#;W*+dM6$&)#9X98mT5Rm6awA%G4N3vGjuE*;tdn)U8YzbF7EjkMMMBZw(u5OFa zQ>Av?XT&bGT9xtIR8nh;HYJGMC^a=Y)k-Z{vMNXEX0oR3$hO;vxqJC#?(MsmuUNTy zq!};ky1ydazv$visE6mnmzI8)*cz>6EHrimqNpRW^LZTC%N1{Hj+bhcX&e4IRh@qzw0db= z8Dq#02CA&(c&Wx%#(PW1lg`LgrQJ@-vvp1+3ld;XZIC?69_UT6e5E^*(N1$tH8)kV zDbC2h!`QK$G#M=JXsRRm@&#I1*H$lsFQUP?Txyp_O3b{Z*<@BtK#2ONDo@G-@y^7} z$ZFlhL|jhVrAoCmz;j-z$9pU731bqYKy)Pc#*GQtlav7$s*WkHI&50-pUegM^l9?tc zrp`%Y&~lyjrF2}Ax$x6+o)PDA*9y|s9Ky(3MvtQ(wVZ$YTr_-mB!Wg zdR*F5ub>t?Bdt+*43frlK5;`o)v2~C4LA5>6^VDzF6p1L(Th#gm`HJCbCS!yu{BZe zpl^`?PIZ>)##CvV=eL*k@w48kwWGmWJm|W=DcKhzW}C?^oeDgomU)#MD6(^+-iSBW z+Q~SIgm&oRA!D|{Xz|_&33+m}a!bFNLYw`W~>n$ERCv%cch$<&%f%U3T04)9}XSS;|8V6gXHLtBh-?P4TugXCOx#j%?*_ddcT6HK%;b z>a`j*>lsb$WU`*KN$g z-Ilv-r$L<*PU4-|MICEUs&C>J6dp~A|#7h8m1`bl)hmrd-+=k2Ntt>8lab%go$kHvTB$Y)1R}&Rhw60|hi^ zdNfJ0$=+V5W+&u!LL;lhW#gGq&p*sRI?w^u>%;byv+W9kgOkkXG-4+?B)Jb#p91 zL0EYBW%H`BKg#C>PCvu0A#s zWJamv2;GIQ9X=WM%qOx?jlyXV_MaJD})j7IQy9TX$`m{<}3&Oa_ zR`VWCSDRIOS*O8W*>JsP9GpH?!RYr2ZVG3glU&>U(}7olKt+I}H%$_Iz*u&^O*nU= z42}#JBzEPRVquJEClMaz-+v`*^Rd^^UecQKT(3c=~6*cG|5S?@G3EQ!6is0Kby7Sm{T8jX5W zW`1`}qw5u(KMU_|Me8+2^naw%Y)?e%lM%X8>JSLS_!#x)j2`g|PMldA?9KGT21dGu zuTgdG=Xx{nxodWQUh@<#X$1X+zy;!E&rf-yI}{7IFs0TfjWoRD3}URm)Z$!fW_Yly zWhP@Qo~n#bv@wqx)e@eEVw`fcUyiGA&fsc4Adw*RH( z)@*H7$+R=Hct{RpXh$^ULaQd!^8TH_?wq6G-fMj7O@b4xcu%F2zS$m5EjLe`fp{3l z%1YC1F4}BIwh=?1R=BFsz*odYvaqBXBxx)+Z@>)D#>Hpwv<>3PP}PF zb4sHjui@m0q`&>(Zuc57f6F&d1~mL0xF86fXu~G?i+KjhUlIT0&dosd7eY-YRa_Z= zDR*TQS7@|3MFiG!?{1R;u6VfFkcaAmw7%}*ot7FADsb#yJQXRruGo;_*E6Yu71+v^ zaji(})M-KdY^XGG%oH+;HYohexK`TLB)W2F7=_d%RAf$LbR`o6x6%RjVX%3KPB6J~ znOwjj@c+0K9|+}rL_=*&gM0BFx&HCOF~U35WZ_(j&bgq)Q9_l=wl|Zp%B^Nmxpyvh z?_BKP*`t8obb#Adzuixokc@G2zA5A(=r^j6g+=bSn0HCCbzK2K1u{(h60*YBTUSTK z|m z8Jw>(o7~0sGG)}5Y|)3h&2itjo{+wePzTe15WK96P_H^EJ)wE8=!?QLkjnMmosKDv z)EbC~w7wLcGf_mdvu8`$)6TG}P>b&+)rHD^ZR)pzMZLmbl`QVnb@8{pN@C6y|*|tJEYv@TV-*2yCKV?FuBHv@|6`;#VSL&5~R=JFhh+0u$>el;!s`l8y)+C}RT z9yT$ZW#d=JdX@;J=cYowm~qMF`YHDG_tMLoOSSP%X*`KGmym8me9G>Z$+^w-QJZwm zN-LaMQ}T-vt%yLcQF^pmrV1xepJYI zjAx)skbknRU#4kX9JA}RC`x+i>4ty2Qe*XzuGS!YvC3AY)geY<9c#hiw^o6}Z$F-P z>eLapoW`At+Mh#Fr}pPi(5wC2RHG%*I%dwvc*>u8iCVZ;HaDZ}uk9q5nLebG;%%>} z8DWc2THWqV63e|pA~V9Mb9tud>Y3?)oR?dtr6C*xX@$8|glmG<4~v2JggP~&v8I2! zMJ!m4&9!jR@`dJ3n^F!nShIqBqE`R2ueBt;anbUd(jiAZ zTFOkrw{h{J6^rd1{()RQnY2plOD|cmww6wIq)X51a-m}GcbW}!70Bv`R(E)#-off- zkc-YmVx1$wxn%`Y+ElK)@oZ?I;|uh&e|{j^QK}H98oG5fabrdc7zwGRQY*?%Qa{Am zYI#2}MwDoQq<7nMmkSRW9s7ohizK4o63Z7z!$YQ?OG;m94p`n%E%AN{Hq=a;y6Kvq za~9Fld$FIE%HYD`Hv87i8m8JPi8_!`vu%f4k_P=^YlsMpvVw&ouC>)g)R_B!s@U+{ zO?j`~xX%4pj}&JpU8IDCT&?NG6ZNWmEh||l5Nr{oD?^8==gMqcJGj;T@&yVfMJ~)} zHBc<2(e5UC#_T-`)9_xI4~*4}$B?v$Dbdu0qj-;7-*Y)Tf#Ed2c+s{E+jb1c`|n{X zeQ#1}b;wBANr*7oiEM^e8poOB<}#c*MHEa@v7uP0TOf8)UIXfbgxLGxamV4d9H^Kd z?{=@)JAvQk7Wd($5DH`dxXgh5Z7wrl42xWgl&U5y|I4yrMiq>lfq1L(=&Xg#A5(@= zmJih+-2kVt>zP3^naI)W?{OCc$#G&_8mSX%4SpEQwwNJhR`Io_=u-2{GZo>THw5#h zovc_+Ux>C@Xn>}x$LbMm+~2NXLgu*w3ucaw_hygo=c{>JT!ag6_d`UGQ*B}CfcT2? zo-n@>mvv&DtkoIW?i?aIoBFFZl^OCvsWATX{k&FaVc&^zA&Dk?qxHYY9U|NuTb_Tf zbA0`q@;3rQ&ggA;cS^Q;v&8_k;}A6!b#%dnA53xQ1AunHG%CNEvR_h#8$1IhpII+E zwdPL{N#qW$O$c1Z;|dz{=3JRtX65&|ivp6(D3;D1Mqnv2*Q$%DN-cUD^bwOa2xtUg zlN3G|1Cb<`d#^cP*Q5y}m-@UU_4Ewh1`%Vn_EH<=axz*iS*n&xvk7%&GFz`o^JiRe zmsAOhY?=13n0(vxOWKl=#Nxes5LQhkt<@Y4<)n<}PpMKf$v{6$#;c3;huI`zm#xjvginJ*C<$&*3wG)Ak*R@|a6tT>R^39UJO2LFXltk%6 z9=YaH%xsc;ml3Q|)M9d&)cyxaP=SFBQXX~2!_IayLA5#v0}<`)Si7TPWTSb*(J<5w z{OMp_LeTt3y^hk@b)do9L^7$cR-Q8UZbP1&!tYIT;?=`%1bMKh+P^tP-eC~ z$OtDgrVrHKuwvHZHEM0R?IaCNejL@O8fJICrXDZy!XzzkQZ>AJASPO9-OxG@szVv5 zPSyZPe|>B$4Xl&Y9CAlp)}}ZNTh4^$OmodU*QhKSD2|f|aL&ZeZSf^5FI?eRE-bY< z&LI0fq+n@IS+bW2z#nkj*(32N_t`u2JRx zda8@@Qr3{Rl!Whnub{mP&sh+ahb%zc_4HDc$0gErZkWPA)reVfZ^ve|bi)^d+=a)k zs-Qm(@+1M(jzo;+qJP^gk7cqV45Pi;BH^<U?2935yc8PGEX}%JAstlBttyB< zYf}(6Hdv`sIw$_}24a447uW?44j)%0dDX}AyZaIk#w+^Em2v#^jR=hkS6JSu?ELS=>qA1K{o00w&)Bl{Vh8PL284t5gUY8!a+| zy6)7JH%*s97LlvYMwtLGR@DKSvK>sTn6NnRdFXjzn2tpC* zXF)J7$sHqam?a2Nh6}8vD92yxdGrNl!wF1H%Yr7?dZhmZqNRD0rbs#;(yfq~U5Q4P zo0Hs%qOB4FgS5`tW6VWTIVV=am5wdHLk@~t>r;`?bV*#_QZ+Wcp3Nc=CueN5)j9E# z-WBqide?0eb&RzJoLJu3+^gu@fx=QoFq)D2FzO|e8{Zje9yEV6j8~6)Hr3Fm*yQ%1 zkz6FKJW?!{ielJJ{$fFqdWD`T^85>WKAC%~|Blc^zI^}_6$5=yAhk^Sb@};GxC0Mq znzsrUQ_nb8$ExtbMDAMlQ2H%JdTHbO+)peCcFam^d%cyo(79kd?^&Z5}MMYRPLjlw06I$g$`(hsW-EI z2e_rUka19`5ho!Sdg*FxxaVn->s`1~5Uk$XVluh)RZ$@KjGZ3*fkKHOtEsRZUE}1E zuRSyYk{54;*IrYbtm$K*{OaofW!+U?V{wW_%eiK$iyksl(_gU6zH59GQ9eSHKZ()P zS`b7er~1|=L1*SgDC30%9BQrOZwV@$Q&w=PV7kRX$Y6OvwIhw-7nUcJ* z%#9hVj_78jXZWzilvb#>%f{|&XdPDT!e{|WVtr3jR;J-@ryT6d8j>RN>!M4T7w)4% zxnP-;4(g~N+9hW!qU!tvikoW}?3}dYZox=GZ?=ACF%%QR=?V+|gN8m30*|FbFB7HJ zR>;Yl*AOlDJ!CHXz+Ne(;1rg31SR;Hm<&%T^*=Pc35#sSC(@on_ber=M8Mg;!II;| zSh^R~)9AKWkN$0RLj#a8rVI7SxZvxARIYmtntO%0;Gu@g!i%$4sx?hsVM}5XhP^d! z=JjP694=caC}DhJxWtFfpo*<%ZrwJVX=&?iU`xLmBu|>R>jT6{(`CDxF3}DCt9K&S zme*ikG^bw!@`Te0BvFMAyZz#orXma#o)@23SOUY&GD|f#${Jr;$YizoK?uz%nq7Qn ztI)SXX?o5+R8+$&`lQO+OD-6if3BiXTnIuCPf3?mkP1E>0%MV(8HgkQ1!Xz%Q$3n! zw;O(y&$;gBpLc%TC$r9^@e(UQuPv14N?dT>d2yc^JNn4fK7U;jRn`v;ZjSO^M@#y$ zMsj;I(%uD;TLeLzIQc;04w zoBm;~ED{E(H|>hc9qJ!WN>i)C;!m8FY4It!%l7Oys@a3BEA)k1Yrx+bAY`_N%3W2f z;NIQqhvseBxMe77)$)P!=3&n$nASf`UMB5CKrh4Ctkf>GgcHX6vTKGn^k1Bn^WXRN z5AoffK1g2`UySIRx4qgS5wU**ZXwC|wT7LxE?aRawyW-3)>n(4c3Hf9$))qI@3$;P z*W2xNt<1$M2hJNB$*VFQkL`b?8Q;wJ1g&@jnSMm;F;(EnGGEEVl!LsbG(jSBZ9ExIMhKom z#KP7AZWjr*k=R*D;>rCF@ok@f+A5@uN}6!!ca4a7J>#Pl(ikt0ucMOeN?}WhV#{}R z_M!t>ZfyOJ8=r1}@f&LRt`3vBMpNTG$R9uV`$b{C35usXI2W}}dpcp}Gd&=M)+KyI zC@sb}_RPtuMGB^3u?{P!eHyGdtDmvbu*&@CP{*zW8~Zo4`vmh(dXhY!~D&3l=<9St2X-|5IbPNroBJtCKY~G&)gDWZ-oSq5(J_ygmSIlm&C9| zdQG>ph60W9+>hxMjA! zrGjL(h9JQhm9*HwXmuSysH8cWOu#-iIlY`ZaP@Fs?kT2j5O1C{wL&>EPDFZ`_p1vnlXlM)q|3;)MsskPtL~@l!P)n^rg=j`znwO&UfUR!q|oq_h=M8+{5Z{ zlfm9osZQv<1Q=Br<`6yx@lvf&PsLk_-qh?HRsTT^@(WJ4$;BQB+c)+PcE*~WF<0M~ zN_)IYUt6STj_*DQ3`>!wsJ=XO&f^mTckKdyE-j?PLps!^EPH1TJT1jt7v0( zZIR^RN?>NEMwrfhMXD~zs5(%r**}?czSY^n%Lm!rr#VRz!>c88Qu6ED329)uzN<6d ztgs9PePi`T^UOFX2!3&U4EJ3+whK|~jJ!B`8wmBx*UJe68B zy1M@n2*G|OFr^KuDC_tjX-aGAeSvs#_}S+p-4%ld5;miRSR}emLm<~!kL>J-X=?gb zVY)M(OzeNCSs91Kw!GvrKlMubgdVYJk((0s)#Kx=?ppgq<{)(rn$wnJo!q_|TlPQX zs4k6)te#Y-j29qD%bHFAYqFj158WZ{Y7Ao{uI_&bawfIQqk~qIC+E!CR15n-%%whW zfabQznA2lB+ggWcmiztl)1Bu2M@A>5gw%exBVq1pdX#G2+h&+(54mPuw&Ynv5dq0L z<`>Mx;wtjZ^3xi;V~i?A-HkStB%OxcP8yrEj5{u!xN$wn3U%Tw#(k}Ec(9yaL2gb( z+sJ#6;GJ0IR~m2%%~3R4F5V1FBn8`#Uf)bUIBpNqk^M9#W#3EW@PXG< zDH>8L9s1r)N((Ynt4{Px`jDy_%yOSB+*7=oPHJl`Q*;C|povt2n@qsOgd!l19D(+L z!t&aKqi;#S`vQu#2HOsaZ<}cmACU7yU?kp>U%vL^N0-fyK;&vC4#v+bz3f-Z`H*}% zB`G>4-H}tOSvjv)ny54LF&Hm%<{**0U`D5xn%SgPAt>l6q9oVJa7m-oFFn>7rw!xW zs3AW*A&{Kb5;~Au%8~jSov3LA+N?Lo&?-J>OlS-h$`JM+$;*}?%5s@gqL$5HuO73W zJiZs~q+wb0sY_wO&>mH15+ZxZ6uq^jKn~fMVhZ1H$Vo!J&ytsIY;q`F3QGHPpgK&F z{;$?^pza^2C26p_-zasUqJ^;b0Sl(AUgVE%LaxPh8=sax=0Q*gJAU@E|g2 zJn3%ha@W?PEO~sjWb!1nBMwNRAXJRjXwm{VVuea0rk)yXHnYi|k~78EoSx22soCem z%$LsQ(3+-BV}Y{K74FRG=|cayx+Cu@M8#%?MVcWNRHy>0#5syNy}T=LINIb3re(uy zqn?v6$B;6^!UXs8+f=)Slw?VE&&*k7972}F_VE@AOwM&o<>-a{~La+=7DCEQB- z;mI+QSd=d?Zx7vx&lg^k>Ij1AW$ak9CNRsKJ118Yhb>$*edB$N>?#e(Leexly<@XI zGZcN?cV>xVh{3p&fPGd^bg%+laf2nHA7)NFD}S;?kk+}I7}n8R1#pvT{Lm1+s0@#i8GAgdW$YFQpXk>vP)rlPI`X}&&s=k zshQG(xKU83i0NRJIJIj=K9@Y>q!+`amMr#c-pebcKAQrIV)DuFGjC=-TRquK6O`=H!lMf1HkPHwH+TJpLHDyFD6%IIt9b9X^tjHV|D!2`Ki0~`g zlDpot{bl&hZmkc5(Zz%GCWOoi9#8I~kL38)MKhu%Oj*C^;-{td4Lq-nWB+*6VI~y~uum`*u5HJmyo6R*t%5(HWcI%Y<*9Ok{*%=L;^OL^8 zgH^(KQ>NAm(wO1W-aVQ|@h;Y+5l=0b*3V{0`TcvO8?|~?M%qlX$YF9Ux~ftgOClyZ z6I%|O#&Dvl@NiMGwOoN*ph~(a_;?DRv`EQvdV$mrzBDC9m5YJHIZM51=9U$Sb7sru zybJ;30)gwX_vI*?t(6KdliR82%&i{h%u@AD(llpHwfU-=tEm`b9%NiP5MOO8CV5e! zV6&?QQs-+j>!~IBXCj;qhSw?VT&!HiRYm|!K$p#7W@906AJPLtflP1nJzZUs&;oMI zykJ!NWsF&rP^xT1R1FP_5nW2g%W!W|Y7V9%iR3rF__cXSRuTE--VAku$*q3|!4Sd# z-c_>YrckFJZRH-0?|+2t#K&PTlm$yNHlMTgl5GNX@CU?9ek-7Z7bbV9 zxjVlhQ1(ck|AVno?4i#`urAJY=e)@{`y*k*_@>Z(G`MXfEJj+4GMFov!!14~R8rwY zf-YbW8$KJ1y%ZYvZ7(DbFL4$OL&{2CVUr<=?wsEi z*(O6MnPd!Mv_9#y)gk#~TB^1GnFR9_&uj(VDT=l9ite}ACWur;%`A*Ne=>98c9#k} z3q^&cOh^2!=@-d7RjEarBxGcMwrHucvmieM3rCIf;j7*Dua`N;AjvAli`i;9Zl(xm zofoFLs|p_WSZpbj2tE0qmsIvhx(|`lPUeg|6(m{d(^#~=YC8}~+IZ1m#1;F>>8+MJ zBXlp_fd~dIh@?F8J}$wuy?axlZvEMH9xCr?4t1B!)J!S}yNa+x16wPPVH&+X^Mo;P zD@@)jY*F+$ZIw9|cjn0)3ww(sRjN_?OCCdd65M7FKqJ zinFmhY&*+3Ot4jR2^f$2NvGXpKTOKwb*HIo8FcZ?Nb*6ORI86p_{WM-*P_jp8rE{*{&;aiFX=Ik!rsv43^XQVmFfvy8QD^}2gHt7+^7&#_AIkw9vi zNaejo$p$`%AidTi9S@DM*xGh^g=u}>>|{V~rpomkhzUWM$RHz0-O+Th(dgfwtHUzk zT%&v?UbvDcSIlK|sq{lxXgRihpzK|Ff)(egg&nNrND9mr+{#QiNx0=mJ=>GB@nEmV zUHYJr7U~A(2=0`lVMrookEf7@$pbb1sSE^A-aJL@O6sOx4cOF!A;dW1#6sDU52!ni zmk?H3WO!FD3s}?+)`*!ZGl?*lUgTfS(8}&1Emn2+ZkSIz?QRgCuWPHVNz3DkLNXbe zT^i2lrL@N^ls+RQ?=G07u&L9W&YL49BT;HD-jqtAmX(3nK6~>@TL`Kl(<&~l7so1i zVEr?1$_&TYmb=yC?)vPrJ4Vx9LO(X_e}LJ5U(DRp3ZJzsXf1vHtz;@IK5elyCl=EE zcM1W8IhB=l8MEr0dtjTlD#_^M_S-eJ7{pc5N$?=?!vC8TG}Lv zv{6ReykuuKhM8YXA9j;M3Q{)r6J=jvZ0y6A;xzDqBDR$(kvmP?$e~h< zld-XA*o8m%0*cdVB54!J~xU9(4%+BVemCpd|s-vbsv|fg5=jqy_YLc1atJm%Rt+mPvYjjlX_|2MC0R{X?J}dS zsruJpXBc&4B3b)Is+5grB+4v{)i}REilaJ?@WGu*qc^#t2ySJ_&nz57>>SD_g_@z1 z*>}Y;L_s_IHeIFe?~126A_rxV`r+#f*UfmarEL+-bCHdXf3R3U1j-h;MSWk))}vs) zd?l3CfY&SDp|f6eyKb^EH7eKDC)j1lt)Laz#4_KP4cdo5QZte-d0#@Kl$sp5N4ZSo zAk9Q6Jhjw+C+T;pQWb6~Mjg!M_#1nq98q}6cAP3cNE{*9agXmo%VaQP&7PZ8;Phw! z?M>T<-KXpm4T2^4gymagF-s7e|+|g!M?Ac7~r9g`8o{ zrR#n46wh>;pTT|jU#F%{mL%;$;ENTn2|25W>K+_wxVr#Kp7 z`kSSG0G4;u$y>|zX?SLShnxhzxfQvq(myVTK^AEXg88gNBbjS9-%QBPgq$Y2)Aez_ zjB}4q?SF``0gm zJe&5N|XL+&I*zA>b@nICmEN=xZm(9S!9XGv_!Zl=5#xeOjt6;wDlhoV`(n9`z zE{`s~#nM)IV&i(&K=wb7+fXZf|C1K+{mdH1g_-LDX>qr-9P0PRMb}-;_^ywHfh}Q=?p9vC`Tl_VFsTr63{ivkoj(=y0^d6Wl7604?$_m6_t@I zYam_6szulO5VKtlhI$h3@c%wTuH;s2$5H$M+sUFVq85f|W@1yY*?D2G-f6#E+3Cme zYL;wMk0}3nxsqXB3Hy?_Psunegi_XR0Jg+-07E_MLqto~Lg0FYxRwom6#?dq(^X7-}9Br~;~et2q10*Xv0*o1_aXQM|ko<{vMa{+zWOffKCOk{}8 zd&X0MxJBBNjDn^a3%=!ra4CiI;cPNPNRgtvcZJ+=q*g{THf+%>7mDNGD=8_hgoj1s zlr(NHvBDdTv@_l#J<5~XWyLXC_RT|_%?=+?)+Pw_)%L3kd62e3)7|<5)#?M)Zgs_d z?J#@2JKd@>B0x@xEU!S!bi$jB1?j?ST+ukg17I$~Nam=SEu#VT0cDwOFd9CB8EK*J zbonrwfcC|(kFwgTY}j(anN;?TIC+}31&5i)kkhG3b{Wg9mH6nR0<|G%>ljS@E;mGT zITbk;n-ybwO*6<+OZ;7OBGkyO zDCy6SKkJ61i&@ZR(LJ^TI6`JT`B`#sX8Q|0!Ty1b{V z2dpq0Wuz_%uV~CeE@MKf>8vr+YZy}#XCS{a%N$qZ3-mYOdQ9R&odS$GgKg-$?>8nE zhhig~i&bzNrr{g*d9MlPu2Gywz@|6|-$yT2!fy493B(MnjzcjRv#~8s!4|k3E8|(z z_kKj(@RoBvsJ`6~!w}9#Vu0})6Hh}=)&fJZ0|sFlG6pjkb>n>07v~|@G#1{6JDl_T zF^v8vj%TnP{ToPOOhN-=s$wf_jj33V`{&cpCqKP29#jfN2YJeB9HU{t|U8g>(LMf;RPDiiiW*CjpScKtt4pq9}9RnNMHwZ^n zAPTiS9z;z|7u0onqAEBU2jdf{&mBh%@OxAxZlk8WtReN+FQRxn-LNHUWL>ZV_C}RB z1MA=@)Z7-Le=1M|u&^1f!Du{;s^|^WnySy;+}Hz|4U>fw*DQ$g+9TY;2^Ov?LEYdp ztc52q0Dr{?@pse>qN%4GgnIHKjK!BxQ}88HJaZNGKvnsW7Hx!ML(~Jus{R^DJJhxr zjvByn)Z7&#*D!lfQ*#nEhrgf(c-QIIY;1q8A!-0|SPr|PD%TTL=`lDA3sCp*eo8~z z=Qsvp73!vuhNDVfAG=~4>H?3U1~A8QG3s-xo&FY7Wp*Kd%y<0I6jbDgrmQ(?aV8FcUQu^HEc}3tz^MQQymt zF@|@KDZ~oe|Ig9TDlJA0U^}{T53+vDF$~5#s2c=wbxlba>PCtD(0(3^S|i1%_rOuq z+Bt`#@iLyr-f_GsF(IBRaDS6aqXb{a$1#hgtQ%fL4eV#sz+99=1FD27VLYmGeXxd$ zHzHQSIgCP6xwN_MO4Jk;qbjfsb^TrFxeyOsTF9gRcjKaaieI#L9a z#N+Bovr&t%82PH%jaqz{Q6+YJp2&ZS*BlmyL3~rHvPG%ZMe>H7wX0zVJMbjKl}qLVDAo04Q8O$#5fGWLUiK- zuhZCyS}cdL8Xk2#jheG-*cWf%P3+#0zXvdhm87ZIi7N4VOvfU+mf6+MS4{dLs$U5qY7`#+3Ec}}=d+bjz8fjCq}lCTM;qUJIOb%Uo+ z?}6p0C)$e|&?46YxE(j5pA$5xH0~`a7T!7GWoh!hNVY zKaU}J7xhGyGwdg5h}Gz~LS3gDYT*4)t9=?igp05iUP3*28ER^(_ox0%Y1Hd)kGwnH zM}GwB1KAjeQ?UxpLhbuSs9muYtKtsS=MNy$YmT5EC}M!UJNjUA`m>RpX?9>DUL8RF z+3Kb-dnE!#V;w9+U2r98bgf<51V>I6_mz{P(1xk!N8ooQ9g4rKlIvD%4!QiK@s6tce#;bA1Qf zVY89;Up^DCHT_)}hF4JEE5ketewel6;;(v4*7JIho`ZFi*1T6vHhd=R8AgaS9F@A*Kt02b-|@Hx?(XVm*d}X*pdDN z<9MlHCQiUtup8DGZ_nij)RQknRqhaKJDPS?@QZWWcU|U>}Mg7${z=`g7 z8+&0F?&ikDs0(d(`X8d6xD>Vh0<-Py+6G(D?~hvL^N_c#*^F)Q7u1wQPqeR>iB0Gi zPNe?2!6r^Lz;`efPvL5;Jjt%iX4Il8MScDjYHq`F?0#d6qMwXfgkzCQn;ED@dJDA( z%P<@xa_!H>d1*A|L}%3gpMb%*1a*T|7>C<12){ud-kim%%yHkzREhq`$C!J(g3;K7 zmBu^A3_y0c*^0B#m2a=9BJ4}wyNJe%G>&6SoLOLx_;oB#e>duVP=egg{~r@;aWm1O z!A|qH?6vVC?JYzL@&lq{KVeDvD+>QRm=zVtenO_+EFh}F9Kv??{||;gQENkQK`qMf z$!((JIUBPNXOc!vpDk{dlN;nJxkEaUO{9dZB=%w7-{1ZhKi89;X6GMiCib& z5*>F*Jb7Q8za5Qf&m$AbzeyBnOInZ}q&fMV{QdZlP6gF}lykNx&LZzP{g#gNu_Ey} z{lD`xosY?BvVw$@V?@U;8~?wk)1CGw*xG3)_@(|soD=u>%xN6Jx}+CLBnQcKl0xns zwVXy89wLvDE@UK$B|ng#h>n}&sJ~_ZyJ0cy|GN3{3we@!O>}H0&yY}3%~u?GA;{_z zoovO%~9aru%v)^u4b zJ3VE!Ng8DxPnzTVy7PXQwJc?yZ)$2ppzm<+x#fI0X}ii>8U16d-}?9RZ62_`ymfd; zzEyeHE7qXlGlP4kxYP4IW3#4PBOe;%DzJ`?h_xDz%;x_mKn2f3{G=dH44&41ErzhI3o^Xc<0D{t}V zzQ>l-39?SVTFZB0#k=KvM^^n%-n#rky!CwX0N=p1hs#@sHjJ}wZ~VquzIkq?o+)wZ oc?UP?sdv6Km#1F1rI~eQOQtV(Yh;B`=c%(iCfk#h%1 löschen" msgid "Reset" msgstr "Zurücksetzen" -#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Page/Profiles.tpl +#: CRM/Twingle/Form/Profile.php js/twingle_shop.js +#: templates/CRM/Twingle/Page/Profiles.tpl msgid "Delete" msgstr "Löschen" @@ -254,6 +490,22 @@ msgstr "Kontakt-Notizen erstellen für" msgid "User Extra Field" msgstr "Benutzer-Extra-Feld" +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl +msgid "Enable Shop Integration" +msgstr "Shop-Integration aktivieren" + +#: CRM/Twingle/Form/Profile.php +msgid "Default Financial Type" +msgstr "Standard-Zuwendungsart" + +#: CRM/Twingle/Form/Profile.php +msgid "Financial Type for top up donations" +msgstr "Zuwendungsart für zusätzliche Spenden" + +#: CRM/Twingle/Form/Profile.php templates/CRM/Twingle/Form/Profile.tpl +msgid "Map Products as Price Fields" +msgstr "Produkte Preisfeldern zuordnen" + #: CRM/Twingle/Form/Profile.php CRM/Twingle/Form/Settings.php msgid "Save" msgstr "Speichern" @@ -262,6 +514,15 @@ msgstr "Speichern" msgid "Warning" msgstr "Warnung" +#: CRM/Twingle/Form/Profile.php +msgid "" +"The required configuration option \"%1\" has no value. Saving the profile " +"might set this option to a possibly unwanted default value." +msgstr "" +"Die erforderliche Konfigurationsoption \"%1\" hat keinen Wert. Das Speichern " +"des Profils könnte diese Option auf einen möglicherweise ungewollten " +"Standardwert setzen." + #: CRM/Twingle/Form/Profile.php msgid "No profile set." msgstr "Kein Profil eingestellt." @@ -314,10 +575,24 @@ msgstr "Status" msgid "Assigned To" msgstr "Zugewiesen an" +#: CRM/Twingle/Form/Settings.php +msgid "Use Twingle Shop Integration" +msgstr "Twingle-Shop-Integration verwenden" + +#: CRM/Twingle/Form/Settings.php +msgid "Twingle Access Key" +msgstr "Twingle-Zugriffsschlüssel" + #: CRM/Twingle/Form/Settings.php msgid "This is required for activity creation" msgstr "Diese Angaben sind erforderlich für das Erstellen von Aktivitäten" +#: CRM/Twingle/Form/Settings.php +msgid "An Access Key is required to enable Twingle Shop Integration" +msgstr "" +"Ein Zugriffsschlüssel ist für die Aktivierung der Twingle-Shop-Integration " +"erforderlich" + #: CRM/Twingle/Form/Settings.php msgid "-select-" msgstr "- auswählen -" @@ -478,6 +753,10 @@ msgstr "" msgid "Invalid format for custom fields." msgstr "Ungültiges Format für benutzerdefinierte Felder." +#: CRM/Twingle/Submission.php +msgid "Invalid format for products." +msgstr "Ungültiges Format für Produkte." + #: CRM/Twingle/Submission.php msgid "campaign_id must be a numeric string. " msgstr "campaign_id muss eine numerische Zeichenkette sein. " @@ -490,6 +769,14 @@ msgstr "Unbekanntes Land %1." msgid "Could not calculate SEPA cycle day from configuration." msgstr "SEPA-Einzugstag konnte nicht aus der Konfiguration berechnet werden." +#: CRM/Twingle/Submission.php +msgid "Could not create line item for product '%1'" +msgstr "Belegzeile für Produkt konnte nicht erstellt werden: %1" + +#: CRM/Twingle/Submission.php +msgid "Could not create line item for donation" +msgstr "Belegzeile für Spende konnte nicht erstellt werden" + #: CRM/Twingle/Tools.php msgid "" "This is a Twingle recurring contribution. It should be terminated through " @@ -508,6 +795,30 @@ msgstr "" "Benutzer beendet. Es ist erforderlich, den zugehörigen Datensatz auch in " "Twingle zu beenden, da die Zuwendung ansonsten weiter eingezogen wird." +#: Civi/Twingle/Shop/ApiCall.php +msgid "Could not find Twingle API token" +msgstr "Twingle-API-Token konnte nicht gefunden werden" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "Call to Twingle API failed. Please check your api token." +msgstr "Aufruf der Twingle-API fehlgeschlagen. Überprüfen Sie Ihren API-Token." + +#: Civi/Twingle/Shop/ApiCall.php +msgid "GET curl failed" +msgstr "GET cURL fehlgeschlagen" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "http status code 404 (not found)" +msgstr "HTTP-Statuscode 404 (not found)" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "https status code 500 (internal error)" +msgstr "HTTP-Statuscode 500 (internal error)" + +#: Civi/Twingle/Shop/ApiCall.php +msgid "Connection not yet established. Use connect() method." +msgstr "Verbindung nch nicht hergestellt. connect()-Methode verwenden." + #: api/v3/TwingleDonation/Cancel.php api/v3/TwingleDonation/Endrecurring.php #: api/v3/TwingleDonation/Submit.php msgid "Project ID" @@ -827,6 +1138,14 @@ msgstr "" "Zusätzliche Informationen für entweder den Kontakt oder die (wiederkehrende) " "Zuwendung." +#: api/v3/TwingleDonation/Submit.php +msgid "Products" +msgstr "Produkte" + +#: api/v3/TwingleDonation/Submit.php +msgid "Products ordered via TwingleShop" +msgstr "Über TwingleShop bestellte Produkte" + #: api/v3/TwingleDonation/Submit.php msgid "Additional remarks for the donation." msgstr "Zusätzliche Anmerkungen für die Zuwendung." @@ -873,6 +1192,285 @@ msgstr "" "Die Nachbearbeitung (postprocessing) der Twingle-Mitgliedschaft ist " "fehlgeschlagen, siehe Protokoll für weitere Informationen." +#: api/v3/TwingleProduct/Create.php api/v3/TwingleProduct/Delete.php +#: api/v3/TwingleProduct/Get.php +msgid "TwingleProduct ID" +msgstr "TwingleProduct-ID" + +#: api/v3/TwingleProduct/Create.php +msgid "The TwingleProduct ID in the database" +msgstr "Die TwingleProduct-ID in der Datenbank" + +#: api/v3/TwingleProduct/Create.php +msgid "Twingle ID" +msgstr "Twingle-ID" + +#: api/v3/TwingleProduct/Create.php +msgid "External product ID in Twingle database" +msgstr "Externe Produkt-ID in der Twingle-Datenbank" + +#: api/v3/TwingleProduct/Create.php +msgid "ID of the corresponding Twingle Shop" +msgstr "ID des zugehlrigen Twingle-Shops" + +#: api/v3/TwingleProduct/Create.php +msgid "Product Name" +msgstr "Produktname" + +#: api/v3/TwingleProduct/Create.php +msgid "Name of the product" +msgstr "Name des Produkts" + +#: api/v3/TwingleProduct/Create.php +msgid "Is active?" +msgstr "Status" + +#: api/v3/TwingleProduct/Create.php +msgid "Is the product active?" +msgstr "Ob das Produkt aktiviert ist" + +#: api/v3/TwingleProduct/Create.php +msgid "Product Description" +msgstr "Produktbeschreibung" + +#: api/v3/TwingleProduct/Create.php +msgid "Short description of the product" +msgstr "Kurzbeschreibung des Produkts" + +#: api/v3/TwingleProduct/Create.php +msgid "Product Price" +msgstr "Produkt-Preis" + +#: api/v3/TwingleProduct/Create.php +msgid "Price of the product" +msgstr "Preis des Produkts" + +#: api/v3/TwingleProduct/Create.php +msgid "Sort" +msgstr "Sortierung" + +#: api/v3/TwingleProduct/Create.php +msgid "Sort order of the product" +msgstr "Sortierungsreihenfolge des Produkts" + +#: api/v3/TwingleProduct/Create.php api/v3/TwingleShop/Create.php +msgid "Financial Type ID" +msgstr "Zuwendungsart" + +#: api/v3/TwingleProduct/Create.php +msgid "ID of the financial type of the product" +msgstr "ID der Zuwendungsart des Produkts" + +#: api/v3/TwingleProduct/Create.php +msgid "FK to TwingleShop" +msgstr "Fremdschlüssel zu TwingleShop" + +#: api/v3/TwingleProduct/Create.php +msgid "Twingle timestamp" +msgstr "Twingle-Zeitstempel" + +#: api/v3/TwingleProduct/Create.php +msgid "Timestamp of last update in Twingle db" +msgstr "Zeitstempel der letzten Aktualisierung in der Twingle-Datenbank" + +#: api/v3/TwingleProduct/Create.php +msgid "FK to PriceField" +msgstr "Fremdschlüssel zu PriceField" + +#: api/v3/TwingleProduct/Delete.php api/v3/TwingleProduct/Get.php +msgid "The TwingleProduct ID in CiviCRM" +msgstr "Die TwingleProduct-ID in CiviCRM" + +#: api/v3/TwingleProduct/Delete.php api/v3/TwingleProduct/Get.php +msgid "External TwingleProduct ID" +msgstr "Externe TwingleProduct-ID" + +#: api/v3/TwingleProduct/Delete.php api/v3/TwingleProduct/Get.php +msgid "Twingle's ID of the product" +msgstr "Twingles ID des Produkts" + +#: api/v3/TwingleProduct/Delete.php +msgid "TwingleProduct could not be deleted." +msgstr "TwingleProduct konnte nicht gelöscht werden." + +#: api/v3/TwingleProduct/Get.php +msgid "FK to civicrm_price_field" +msgstr "Fremdschlüssel zu civicrm_price_field" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Delete.php +#: api/v3/TwingleShop/Get.php +msgid "TwingleShop ID" +msgstr "TwingleShop-ID" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Delete.php +#: api/v3/TwingleShop/Get.php +msgid "The TwingleShop ID in CiviCRM" +msgstr "Die TwingleShop-ID in CiviCRM" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Create.php +#: api/v3/TwingleShop/Delete.php api/v3/TwingleShop/Get.php +msgid "Twingle project identifier" +msgstr "Twingle-Projekt-Identifikator" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Create.php +#: api/v3/TwingleShop/Get.php +msgid "Numerical Project Identifier" +msgstr "Numerischer Projekt-Identifikator" + +#: api/v3/TwingleProduct/Get.php api/v3/TwingleShop/Get.php +msgid "Twingle numerical project identifier" +msgstr "Twingles numerischer Projekt-Identifikator" + +#: api/v3/TwingleShop/Create.php +msgid "Numerical Twingle project identifier" +msgstr "Numerischer Twingle-Projekt-Identifikator" + +#: api/v3/TwingleShop/Create.php +msgid "Shop Name" +msgstr "Shop-Name" + +#: api/v3/TwingleShop/Create.php +msgid "Name of the shop" +msgstr "Name des Shops" + +#: api/v3/TwingleShop/Create.php +msgid "FK to civicrm_financial_type" +msgstr "ZuwendungsartFremdschlüssel zu civicrm_financial_type" + +#: api/v3/TwingleShop/Delete.php +msgid "TwingleShop could not be found." +msgstr "TwingleShop konnte nicht gefunden werden." + +#: api/v3/TwingleShop/Delete.php +msgid "TwingleShop could not be deleted." +msgstr "TwingleShop konnte nicht gelöscht werden." + +#: api/v3/TwingleShop/Fetch.php +msgid "Project Identifiers" +msgstr "Projekt-Identifikatoren" + +#: api/v3/TwingleShop/Fetch.php +msgid "Comma separated list of Twingle project identifiers." +msgstr "Kommaseparierte Liste von Twingle-Projekt-Identifikatoren." + +#: api/v3/TwingleShop/Get.php +msgid "Name of the TwingleShop" +msgstr "Name des TwingleShop" + +#: api/v3/TwingleShop/Get.php +msgid "FK to civicrm_price_set" +msgstr "Fremdschlüssel zu civicrm_price_set" + +#: js/twingle_shop.js +msgid "Could not fetch products" +msgstr "Produkte konnten nicht abgerufen werden" + +#: js/twingle_shop.js +msgid "Could not fetch products. Please check your Twingle API key." +msgstr "" +"Produkte konnten nicht abgerufen werden. Überprüfen Sie Ihren Twingle-API-" +"Schlüssel." + +#: js/twingle_shop.js +msgid "Create" +msgstr "Erstellen" + +#: js/twingle_shop.js +msgid "Update" +msgstr "Bearbeiten" + +#: js/twingle_shop.js +msgid "Could not create Price Field for this product" +msgstr "Preisfeld für dieses Produkt konnte nicht erstellt werden" + +#: js/twingle_shop.js +msgid "Delete Price Field" +msgstr "Preisfeld löschen" + +#: js/twingle_shop.js +msgid "" +"Are you sure you want to delete the price field associated with this product?" +msgstr "Möchten Sie wirklich das diesem Produkt zugehörige Preisfeld löschen?" + +#: js/twingle_shop.js +msgid "Could not delete Price Field" +msgstr "Preisfeld konnte nicht gelöscht werden" + +#: js/twingle_shop.js +msgid "The Price Field was deleted successfully." +msgstr "Das Preisfeld wurde erfolgreich gelöscht." + +#: js/twingle_shop.js +msgid "Price Field deleted" +msgstr "Preisfeld gelöscht" + +#: js/twingle_shop.js +msgid "select financial type" +msgstr "Zuwendungsart auswählen" + +#: js/twingle_shop.js +msgid "Product" +msgstr "Produkt" + +#: js/twingle_shop.js +msgid "Financial Type" +msgstr "Zuwendungsart" + +#: js/twingle_shop.js +msgid "Price Field" +msgstr "Preisfeld" + +#: js/twingle_shop.js +msgid "Create Price Set" +msgstr "Preisschema erstellen" + +#: js/twingle_shop.js +msgid "Update Price Set" +msgstr "Preisschema bearbeiten" + +#: js/twingle_shop.js +msgid "Delete Price Set" +msgstr "Preisschema löschen" + +#: js/twingle_shop.js +msgid "Could not create Twingle Shop" +msgstr "Twingle-Shop konnte nicht erstellt werden" + +#: js/twingle_shop.js +msgid "The Price Set was created successfully." +msgstr "Das Preisschema wurde erfolgreich erstellt." + +#: js/twingle_shop.js +msgid "Price Field created" +msgstr "Preisfeld erstellt" + +#: js/twingle_shop.js +msgid "Could not create TwingleShop" +msgstr "TwingleShop konnte nicht erstellt werden" + +#: js/twingle_shop.js +msgid "" +"Are you sure you want to delete the price set associated with this Twingle " +"Shop?" +msgstr "" +"Möchten Sie wirklich das diesem Twingle-Shop zugehörige Preisschema löschen?" + +#: js/twingle_shop.js +msgid "Could not delete Twingle Shop" +msgstr "Twingle-Shop konnte nicht gelöscht werden" + +#: js/twingle_shop.js +msgid "The Price Set was deleted successfully." +msgstr "Das Preisschema wurde erfolgreiche gelöscht." + +#: js/twingle_shop.js +msgid "Price Set deleted" +msgstr "Preisschema gelöscht" + +#: js/twingle_shop.js +msgid "Could not update Twingle Shop" +msgstr "Twingle-Shop konnte nicht aktualisiert werden" + #: managed/Navigation__twingle_configuration.mgd.php msgid "Twingle API Configuration" msgstr "Twingle-API-Konfiguration" @@ -1099,6 +1697,37 @@ msgstr "" "

Tipp: Sie können diese Felder im TwingleMANAGER aktivieren oder " "deaktivieren.

" +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"Enable the processing of orders via Twingle Shop for this profile. The " +"ordered products will then appear as line items in the contribution." +msgstr "" +"Aktiviert die Verarbeitung von Bestellungen über den Twingle-Shop für dieses " +"Profil. Die bestellten Produkte werden als Belegzeilen der Zuwendung " +"erscheinen." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"If this option is enabled, all Twingle Shop products corresponding to the " +"specified project IDs will be retrieved from Twingle and mapped as price " +"sets and price fields. Each Twingle Shop is mapped as a price set with its " +"products as price fields." +msgstr "" +"Wenn diese Option aktiviert ist, werden alle zur angegebenen Projekt-ID " +"gehörenden Twingle-Shop-Produkte von Twingle abgerufen und als Preisschemata " +"und Preisfelder zugeordnet. Jeder Twingle-Shop wird als ein Preisschema mit " +"seinen Produkten als Preisfelder zugeordnet." + +#: templates/CRM/Twingle/Form/Profile.hlp +msgid "" +"This allows you to manually create contributions with the same line items " +"for phone orders, for example, as would be the case for orders placed " +"through the Twingle Shop." +msgstr "" +"Dies ermöglicht das manuelle Erstellen von Zuwendungen mit den gleichen " +"Belegzeilen wie bei Bestellungen über den Twingle-Shop, z. B. für " +"telefonische Bestellungen." + #: templates/CRM/Twingle/Form/Profile.tpl msgid "General settings" msgstr "Allgemeine Einstellungen" @@ -1139,6 +1768,11 @@ msgstr "Double-Opt-In für Newsletter" msgid "Membership Postprocessing" msgstr "Mitgliedschafts--Nachbearbeitung (Postprocessing)" +#: templates/CRM/Twingle/Form/Profile.tpl +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "Shop Integration" +msgstr "Shop-Integration" + #: templates/CRM/Twingle/Form/Profile.tpl msgid "Are you sure you want to reset the default profile?" msgstr "Möchten Sie wirklich das Standard-Profil zurücksetzen?" @@ -1188,6 +1822,20 @@ msgstr "" "Transaktions-ID voranzustellen, um Kollisionen mit anderen Transaktions-IDs " "auszuschließen." +#: templates/CRM/Twingle/Form/Settings.hlp +msgid "" +"If you enable Twingle Shop integration, you can configure Twingle API " +"profiles to include products ordered through Twingle Shop as line items in " +"the created contribution." +msgstr "" +"Mit aktivierter Twingle-Shop-Integration können Twingle-API-Profile so " +"konfiguriert werden, dass über den Twingle-Shop bestellte Produkte als " +"Belegzeilen in der erstellten Zuwendung enthalten sind." + +#: templates/CRM/Twingle/Form/Settings.hlp +msgid "Enter your twingle API access key." +msgstr "Geben Sie ihren Zugriffsschlüssel für die Twingle-API ein." + #: templates/CRM/Twingle/Page/Configuration.tpl msgid "Profiles" msgstr "Profile" @@ -1224,6 +1872,14 @@ msgstr "Zuletzt verwendet" msgid "Operations" msgstr "Operationen" +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "enabled" +msgstr "aktiviert" + +#: templates/CRM/Twingle/Page/Profiles.tpl +msgid "disabled" +msgstr "deaktiviert" + #: templates/CRM/Twingle/Page/Profiles.tpl msgid "Edit profile %1" msgstr "Profil %1 bearbeiten" From eaf9d531696170ae3e28e339c360d3aeef98d01a Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 1 Oct 2024 14:27:53 +0200 Subject: [PATCH 37/43] Version 1.5-beta3 --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index c019d80..099915c 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - - 1.5-dev - dev + 2024-010-01 + 1.5-beta3 + beta 5.58 From 82952a01623b1127c7268352a06cbc543487c402 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Tue, 1 Oct 2024 14:28:06 +0200 Subject: [PATCH 38/43] Back to 1.5-dev --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index 099915c..c019d80 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - 2024-010-01 - 1.5-beta3 - beta + + 1.5-dev + dev 5.58 From c7c766d926bc3475b0e119a6ff9b9a283d2bc8a8 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Wed, 9 Oct 2024 12:39:57 +0200 Subject: [PATCH 39/43] Fix BAO class namespace issues --- CRM/Twingle/BAO/TwingleShop.php | 2 +- CRM/Twingle/Submission.php | 3 +-- api/v3/TwingleProduct/Create.php | 3 +-- api/v3/TwingleProduct/Delete.php | 3 +-- api/v3/TwingleProduct/Get.php | 5 ++--- api/v3/TwingleShop/Create.php | 3 +-- api/v3/TwingleShop/Delete.php | 6 +++--- api/v3/TwingleShop/Fetch.php | 5 ++--- api/v3/TwingleShop/Get.php | 4 ++-- twingle.php | 4 ++-- 10 files changed, 16 insertions(+), 22 deletions(-) diff --git a/CRM/Twingle/BAO/TwingleShop.php b/CRM/Twingle/BAO/TwingleShop.php index a4b006c..27ae3b8 100644 --- a/CRM/Twingle/BAO/TwingleShop.php +++ b/CRM/Twingle/BAO/TwingleShop.php @@ -294,7 +294,7 @@ class CRM_Twingle_BAO_TwingleShop extends CRM_Twingle_DAO_TwingleShop { /** * Get associated products. * - * @return array[Civi\Twingle\Shop\BAO\TwingleProduct] + * @return list * @throws \Civi\Core\Exception\DBQueryException * @throws \Civi\Twingle\Shop\Exceptions\ProductException */ diff --git a/CRM/Twingle/Submission.php b/CRM/Twingle/Submission.php index 2c3e85d..1955d18 100644 --- a/CRM/Twingle/Submission.php +++ b/CRM/Twingle/Submission.php @@ -18,7 +18,6 @@ declare(strict_types = 1); use CRM_Twingle_ExtensionUtil as E; use Civi\Twingle\Exceptions\BaseException; use Civi\Twingle\Shop\Exceptions\LineItemException; -use Civi\Twingle\Shop\BAO\TwingleProduct; class CRM_Twingle_Submission { @@ -503,7 +502,7 @@ class CRM_Twingle_Submission { // Try to find the TwingleProduct with its corresponding PriceField // for this product try { - $price_field = TwingleProduct::findByExternalId($product['id']); + $price_field = CRM_Twingle_BAO_TwingleProduct::findByExternalId($product['id']); } catch (Exception $e) { Civi::log()->error(E::LONG_NAME . diff --git a/api/v3/TwingleProduct/Create.php b/api/v3/TwingleProduct/Create.php index 36bb48a..2490557 100644 --- a/api/v3/TwingleProduct/Create.php +++ b/api/v3/TwingleProduct/Create.php @@ -2,7 +2,6 @@ use Civi\Twingle\Shop\Exceptions\ProductException; use CRM_Twingle_ExtensionUtil as E; -use Civi\Twingle\Shop\BAO\TwingleProduct; /** * TwingleProduct.Create API specification (optional) @@ -121,7 +120,7 @@ function civicrm_api3_twingle_product_Create($params): array { try { // Create TwingleProduct and load params - $product = new TwingleProduct(); + $product = new CRM_Twingle_BAO_TwingleProduct(); $product->load($params); // Save TwingleProduct diff --git a/api/v3/TwingleProduct/Delete.php b/api/v3/TwingleProduct/Delete.php index 60c9591..7c675ab 100644 --- a/api/v3/TwingleProduct/Delete.php +++ b/api/v3/TwingleProduct/Delete.php @@ -1,7 +1,6 @@ delete(); diff --git a/api/v3/TwingleProduct/Get.php b/api/v3/TwingleProduct/Get.php index a7f64ca..9544678 100644 --- a/api/v3/TwingleProduct/Get.php +++ b/api/v3/TwingleProduct/Get.php @@ -1,7 +1,6 @@ getMessage(), [ diff --git a/api/v3/TwingleShop/Create.php b/api/v3/TwingleShop/Create.php index ebb34f4..ab858ff 100644 --- a/api/v3/TwingleShop/Create.php +++ b/api/v3/TwingleShop/Create.php @@ -1,6 +1,5 @@ load($params); // Save TwingleShop diff --git a/api/v3/TwingleShop/Delete.php b/api/v3/TwingleShop/Delete.php index d1f3323..cc851a7 100644 --- a/api/v3/TwingleShop/Delete.php +++ b/api/v3/TwingleShop/Delete.php @@ -1,6 +1,6 @@ deleteByConstraint(); if ($result) { return civicrm_api3_create_success(1, $params, 'TwingleShop', 'Delete'); diff --git a/api/v3/TwingleShop/Fetch.php b/api/v3/TwingleShop/Fetch.php index 961f42b..9c5d3e3 100644 --- a/api/v3/TwingleShop/Fetch.php +++ b/api/v3/TwingleShop/Fetch.php @@ -1,7 +1,6 @@ fetchProducts(); $returnValues[$projectId] = []; $returnValues[$projectId] += $shop->getAttributes(); @@ -63,7 +62,7 @@ function civicrm_api3_twingle_shop_Fetch($params) { return $product->getAttributes(); }, $products); } - catch (ShopException|ApiCallError|ProductException $e) { + catch (ShopException | ApiCallError | ProductException $e) { // If this project identifier doesn't belong to a project of type // 'shop', just skip it if ($e->getErrorCode() == ShopException::ERROR_CODE_NOT_A_SHOP) { diff --git a/api/v3/TwingleShop/Get.php b/api/v3/TwingleShop/Get.php index 2915212..9309e04 100644 --- a/api/v3/TwingleShop/Get.php +++ b/api/v3/TwingleShop/Get.php @@ -1,6 +1,6 @@ getMessage(), [ diff --git a/twingle.php b/twingle.php index c00ac35..9c20db1 100644 --- a/twingle.php +++ b/twingle.php @@ -17,7 +17,7 @@ function twingle_civicrm_pre($op, $objectName, $id, &$params) { // Create/delete PriceField and PriceFieldValue for TwingleProduct elseif ($objectName == 'TwingleProduct') { - $twingle_product = new \Civi\Twingle\Shop\BAO\TwingleProduct(); + $twingle_product = new CRM_Twingle_BAO_TwingleProduct(); $twingle_product->load($params); if ($op == 'create' || $op == 'edit') { $twingle_product->createPriceField(); @@ -30,7 +30,7 @@ function twingle_civicrm_pre($op, $objectName, $id, &$params) { // Create PriceSet for TwingleShop elseif ($objectName == 'TwingleShop' && ($op == 'create' || $op == 'edit')) { - $twingle_shop = new \Civi\Twingle\Shop\BAO\TwingleShop(); + $twingle_shop = new CRM_Twingle_BAO_TwingleShop(); $twingle_shop->load($params); $twingle_shop->createPriceSet(); $params = $twingle_shop->getAttributes(); From 2ee06faf34bb7ccdd40e066770d175ac0f7cbba5 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 23 Jan 2025 15:17:54 +0100 Subject: [PATCH 40/43] Version 1.5-beta4 --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index c019d80..56b4ef3 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - - 1.5-dev - dev + 2025-01-23 + 1.5-beta4 + beta 5.58 From c8a577b6517807a7b273e2691454fa9f597dd366 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 23 Jan 2025 15:18:14 +0100 Subject: [PATCH 41/43] Back to dev (1.5-dev) --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index 56b4ef3..c019d80 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - 2025-01-23 - 1.5-beta4 - beta + + 1.5-dev + dev 5.58 From 21f29ce169ffe00eebab2daf36dc68b9533fc1c7 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Fri, 21 Feb 2025 13:13:44 +0100 Subject: [PATCH 42/43] Version 1.5.0 --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index c019d80..1e7baa0 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - - 1.5-dev - dev + 2025-02-21 + 1.5.0 + stable 5.58 From b8f44d962de634a777bf85fe25300101c66788cd Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Fri, 21 Feb 2025 13:14:04 +0100 Subject: [PATCH 43/43] Back to dev (1.6-dev) --- info.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.xml b/info.xml index 1e7baa0..edfa3fb 100644 --- a/info.xml +++ b/info.xml @@ -14,9 +14,9 @@ https://github.com/systopia/de.systopia.twingle/issues http://www.gnu.org/licenses/agpl-3.0.html - 2025-02-21 - 1.5.0 - stable + + 1.6-dev + dev 5.58