Plugin Directory

Changeset 3460927


Ignore:
Timestamp:
02/13/2026 04:57:35 PM (4 days ago)
Author:
dimensionmedia
Message:

Update To v1.8.9.5

Location:
charitable/trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • charitable/trunk/CHANGELOG.md

    r3455474 r3460927  
     1# 1.8.9.5
     2* FIX: Added additional checks to resolved issue with Stripe webhook that was causing a fatal error a certain scenario.
     3* FIX: Allow more international characters into campaign title fields in campaign visual builder.
     4* IMPROVED: Email shortcode processing with recursion prevention for better reliability.
     5* IMPROVED: Added additional enhanced email error logging and diagnostics.
     6
    17# 1.8.9.4
    28* FIX: Resolved seemingly missing social media icons in some campaign builder template previews in certain scenarios.
  • charitable/trunk/assets/js/campaign-builder/admin-builder.js

    r3453160 r3460927  
    21042104
    21052105            // Clean the string.
    2106             title = title.replace(/[^a-z0-9 _.,!"'/$]/gi, '');
     2106            title = title.replace(/[^\w\s\u00C0-\u024F\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF _.,!"()'\/$[\]:@#%-]/gi, '');
    21072107
    21082108            // Should we allow a blank title even temp?
  • charitable/trunk/assets/js/campaign-builder/utils.js

    r2987535 r3460927  
    2424        santitizeTitle: function ( stringValue ) {
    2525
    26             return stringValue.replace(/[^a-z0-9 _.,!"()'/$[]:]/gi, '');
     26            return stringValue.replace(/[^\w\s\u00C0-\u024F\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF _.,!"()'\/$[\]:@#%-]/gi, '');
    2727
    2828        },
     
    4040        santitizeTextInput: function ( stringValue ) {
    4141
    42             return stringValue.replace(/[^a-z0-9 _.,!"()'/$[]:-]/gi, '');
     42            return stringValue.replace(/[^\w\s\u00C0-\u024F\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF _.,!"()'\/$[\]:@#%-]/gi, '');
    4343
    4444        },
  • charitable/trunk/charitable.php

    r3455474 r3460927  
    44 * Plugin URI: https://www.wpcharitable.com
    55 * Description: The best WordPress donation plugin. Fundraising with recurring donations, and powerful features to help you raise more money online.
    6  * Version: 1.8.9.4
     6 * Version: 1.8.9.5
    77 * Author: Charitable Donations & Fundraising Team
    88 * Author URI: https://wpcharitable.com
    99 * Requires at least: 5.0
    10  * Stable tag: 1.8.9.4
     10 * Stable tag: 1.8.9.5
    1111 * License: GPLv2 or later
    1212 * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
     
    4040
    4141        /* Plugin version. */
    42         const VERSION = '1.8.9.4';
     42        const VERSION = '1.8.9.5';
    4343
    4444        /* Version of database schema. */
  • charitable/trunk/i18n/languages/charitable.pot

    r3453160 r3460927  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Charitable 1.8.9.2\n"
     5"Project-Id-Version: Charitable 1.8.9.5\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/charitable\n"
    77"Last-Translator: Charitable Team\n"
     
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-02-02T22:56:07+00:00\n"
     12"POT-Creation-Date: 2026-02-13T00:21:50+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.10.0\n"
     
    53555355
    53565356#: includes/admin/campaign-builder/campaign-congrats-wizard/popup.php:38
    5357 #: includes/admin/onboarding/class-charitable-setup.php:1874
     5357#: includes/admin/onboarding/class-charitable-setup.php:1879
    53585358msgid "Congratulations!"
    53595359msgstr ""
     
    1083010830msgstr ""
    1083110831
    10832 #: includes/admin/onboarding/class-charitable-setup.php:461
    10833 #: includes/admin/onboarding/class-charitable-setup.php:462
     10832#: includes/admin/onboarding/class-charitable-setup.php:466
     10833#: includes/admin/onboarding/class-charitable-setup.php:467
    1083410834msgid "Setup Charitable"
    1083510835msgstr ""
    1083610836
    10837 #: includes/admin/onboarding/class-charitable-setup.php:1864
     10837#: includes/admin/onboarding/class-charitable-setup.php:1869
    1083810838msgid "Starting..."
    1083910839msgstr ""
    1084010840
    10841 #: includes/admin/onboarding/class-charitable-setup.php:1865
     10841#: includes/admin/onboarding/class-charitable-setup.php:1870
    1084210842msgid "Setting Up General Settings..."
    1084310843msgstr ""
    1084410844
    10845 #: includes/admin/onboarding/class-charitable-setup.php:1866
     10845#: includes/admin/onboarding/class-charitable-setup.php:1871
    1084610846msgid "Updating Plugins..."
    1084710847msgstr ""
    1084810848
    10849 #: includes/admin/onboarding/class-charitable-setup.php:1867
     10849#: includes/admin/onboarding/class-charitable-setup.php:1872
    1085010850msgid "Installing Plugins..."
    1085110851msgstr ""
    1085210852
    10853 #: includes/admin/onboarding/class-charitable-setup.php:1868
     10853#: includes/admin/onboarding/class-charitable-setup.php:1873
    1085410854msgid "Activating Plugins..."
    1085510855msgstr ""
    1085610856
    10857 #: includes/admin/onboarding/class-charitable-setup.php:1869
    10858 #: includes/admin/onboarding/class-charitable-setup.php:1870
     10857#: includes/admin/onboarding/class-charitable-setup.php:1874
     10858#: includes/admin/onboarding/class-charitable-setup.php:1875
    1085910859msgid "Setting Up Features..."
    1086010860msgstr ""
    1086110861
    10862 #: includes/admin/onboarding/class-charitable-setup.php:1871
     10862#: includes/admin/onboarding/class-charitable-setup.php:1876
    1086310863msgid "Setting Up Your First Campaign..."
    1086410864msgstr ""
    1086510865
    10866 #: includes/admin/onboarding/class-charitable-setup.php:1872
     10866#: includes/admin/onboarding/class-charitable-setup.php:1877
    1086710867msgid "Setting Up Payment Methods..."
    1086810868msgstr ""
    1086910869
    10870 #: includes/admin/onboarding/class-charitable-setup.php:1873
     10870#: includes/admin/onboarding/class-charitable-setup.php:1878
    1087110871msgid "Almost done!"
    1087210872msgstr ""
    1087310873
    10874 #: includes/admin/onboarding/class-charitable-setup.php:1878
     10874#: includes/admin/onboarding/class-charitable-setup.php:1883
    1087510875msgid "Updating country, currency, and related settings for you."
    1087610876msgstr ""
    1087710877
    10878 #: includes/admin/onboarding/class-charitable-setup.php:1879
     10878#: includes/admin/onboarding/class-charitable-setup.php:1884
    1087910879msgid "Checking plugins..."
    1088010880msgstr ""
    1088110881
    10882 #: includes/admin/onboarding/class-charitable-setup.php:1882
    10883 #: includes/admin/onboarding/class-charitable-setup.php:1883
     10882#: includes/admin/onboarding/class-charitable-setup.php:1887
     10883#: includes/admin/onboarding/class-charitable-setup.php:1888
    1088410884msgid "Checking license and site information..."
    1088510885msgstr ""
    1088610886
    10887 #: includes/admin/onboarding/class-charitable-setup.php:1884
     10887#: includes/admin/onboarding/class-charitable-setup.php:1889
    1088810888msgid "Creating draft..."
    1088910889msgstr ""
    1089010890
    10891 #: includes/admin/onboarding/class-charitable-setup.php:1885
     10891#: includes/admin/onboarding/class-charitable-setup.php:1890
    1089210892msgid "Checking payment methods..."
    1089310893msgstr ""
    1089410894
    10895 #: includes/admin/onboarding/class-charitable-setup.php:1886
     10895#: includes/admin/onboarding/class-charitable-setup.php:1891
    1089610896msgid "Finishing things..."
    1089710897msgstr ""
    1089810898
    10899 #: includes/admin/onboarding/class-charitable-setup.php:1887
     10899#: includes/admin/onboarding/class-charitable-setup.php:1892
    1090010900msgid "Stripe offers a seamless and secure payment experience for both you and your donors."
    1090110901msgstr ""
    1090210902
    10903 #: includes/admin/onboarding/class-charitable-setup.php:1888
     10903#: includes/admin/onboarding/class-charitable-setup.php:1893
    1090410904msgid "Your Charitable install has been setup and is ready for you."
    1090510905msgstr ""
    1090610906
    1090710907#. translators: %1$s: List of requested features
    10908 #: includes/admin/onboarding/class-charitable-setup.php:1902
     10908#: includes/admin/onboarding/class-charitable-setup.php:1907
    1090910909msgid "In order to install and activate some of the features you requested %1$s you need to activate your PRO license after setup is complete."
    1091010910msgstr ""
    1091110911
    10912 #: includes/admin/onboarding/class-charitable-setup.php:1903
     10912#: includes/admin/onboarding/class-charitable-setup.php:1908
    1091310913msgid "Activating license and installing addons..."
    1091410914msgstr ""
    1091510915
    10916 #: includes/admin/onboarding/class-charitable-setup.php:1908
     10916#: includes/admin/onboarding/class-charitable-setup.php:1913
    1091710917msgid "Skip setting up Stripe"
    1091810918msgstr ""
     
    1701517015
    1701617016#: includes/gateways/class-charitable-gateway-square.php:2402
    17017 #: includes/gateways/class-charitable-gateway-stripe-am.php:1120
     17017#: includes/gateways/class-charitable-gateway-stripe-am.php:1163
    1701817018msgid "Unknown error."
    1701917019msgstr ""
     
    1723717237
    1723817238#: includes/gateways/class-charitable-gateway-stripe-am.php:841
    17239 #: includes/gateways/class-charitable-gateway-stripe-am.php:1532
     17239#: includes/gateways/class-charitable-gateway-stripe-am.php:1579
    1724017240msgid "Missing keys for Stripe payment gateway. Unable to proceed with payment."
    1724117241msgstr ""
     
    1725517255msgstr ""
    1725617256
    17257 #: includes/gateways/class-charitable-gateway-stripe-am.php:1114
     17257#: includes/gateways/class-charitable-gateway-stripe-am.php:1102
     17258msgid "Cancellation failed: No gateway subscription ID found."
     17259msgstr ""
     17260
     17261#: includes/gateways/class-charitable-gateway-stripe-am.php:1122
     17262msgid "Cancellation failed: No Stripe API key configured."
     17263msgstr ""
     17264
     17265#: includes/gateways/class-charitable-gateway-stripe-am.php:1149
     17266msgid "Retrieved subscription from Stripe - Current status: %s"
     17267msgstr ""
     17268
     17269#: includes/gateways/class-charitable-gateway-stripe-am.php:1157
    1725817270msgid "Subscription cancelled in Stripe."
    1725917271msgstr ""
    1726017272
    1726117273#. translators: %s: error message
    17262 #: includes/gateways/class-charitable-gateway-stripe-am.php:1125
     17274#: includes/gateways/class-charitable-gateway-stripe-am.php:1168
    1726317275msgid "Stripe cancellation failed: %s"
    1726417276msgstr ""
    1726517277
    17266 #: includes/gateways/class-charitable-gateway-stripe-am.php:1268
    17267 #: includes/gateways/class-charitable-gateway-stripe-am.php:1369
     17278#: includes/gateways/class-charitable-gateway-stripe-am.php:1315
     17279#: includes/gateways/class-charitable-gateway-stripe-am.php:1416
    1726817280#: includes/stripe/abstracts/class-charitable-stripe-gateway-processor.php:484
    1726917281#: includes/stripe/abstracts/class-charitable-stripe-gateway-processor.php:1354
     
    1727617288msgstr ""
    1727717289
    17278 #: includes/gateways/class-charitable-gateway-stripe-am.php:1351
     17290#: includes/gateways/class-charitable-gateway-stripe-am.php:1398
    1727917291msgid "Donor for"
    1728017292msgstr ""
    1728117293
    17282 #: includes/gateways/class-charitable-gateway-stripe-am.php:1412
     17294#: includes/gateways/class-charitable-gateway-stripe-am.php:1459
    1728317295msgid "Missing credit card details. Unable to proceed with payment."
    1728417296msgstr ""
     
    1751717529msgstr ""
    1751817530
    17519 #: includes/shortcodes/class-charitable-email-shortcode.php:107
     17531#: includes/shortcodes/class-charitable-email-shortcode.php:126
    1752017532msgid "[charitable_email] cannot be called until a class instance has been created."
     17533msgstr ""
     17534
     17535#: includes/shortcodes/class-charitable-email-shortcode.php:241
     17536msgid "Payment instructions will be provided separately."
     17537msgstr ""
     17538
     17539#: includes/shortcodes/class-charitable-email-shortcode.php:242
     17540msgid "Donation summary unavailable."
     17541msgstr ""
     17542
     17543#: includes/shortcodes/class-charitable-email-shortcode.php:243
     17544msgid "Donor information unavailable."
     17545msgstr ""
     17546
     17547#: includes/shortcodes/class-charitable-email-shortcode.php:244
     17548msgid "Email unavailable."
     17549msgstr ""
     17550
     17551#: includes/shortcodes/class-charitable-email-shortcode.php:246
     17552msgid "Campaign information unavailable."
     17553msgstr ""
     17554
     17555#: includes/shortcodes/class-charitable-email-shortcode.php:250
     17556msgid "Content temporarily unavailable."
    1752117557msgstr ""
    1752217558
     
    1782017856
    1782117857#: includes/square/gateway/class-charitable-square-webhook-processor.php:176
    17822 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:140
     17858#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:141
    1782317859msgid "Webhook processed."
    1782417860msgstr ""
    1782517861
    1782617862#: includes/square/gateway/class-charitable-square-webhook-processor.php:185
    17827 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:151
    1782817863msgid "Error while retrieving event."
    1782917864msgstr ""
     
    1788517920#: includes/square/gateway/class-charitable-square-webhook-processor.php:1015
    1788617921#: includes/square/gateway/class-charitable-square-webhook-processor.php:1028
    17887 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:400
     17922#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:411
    1788817923msgid "Donation Webhook: Refund processed"
    1788917924msgstr ""
     
    1789517930#: includes/square/gateway/class-charitable-square-webhook-processor.php:1041
    1789617931#: includes/square/gateway/class-charitable-square-webhook-processor.php:1103
    17897 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:470
    17898 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:758
    17899 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:787
    17900 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:821
    17901 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:888
    17902 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:918
     17932#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:481
     17933#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:769
     17934#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:798
     17935#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:832
     17936#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:912
     17937#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:1001
    1790317938msgid "Subscription Webhook: Unable to process without Charitable Recurring extension."
    1790417939msgstr ""
    1790517940
    1790617941#: includes/square/gateway/class-charitable-square-webhook-processor.php:1064
    17907 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:765
    17908 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:800
    17909 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:828
    17910 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:895
    17911 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:925
     17942#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:776
     17943#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:811
     17944#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:839
     17945#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:937
     17946#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:1025
    1791217947msgid "Subscription Webhook: Missing subscription"
    1791317948msgstr ""
     
    1791517950#: includes/square/gateway/class-charitable-square-webhook-processor.php:1088
    1791617951#: includes/square/gateway/class-charitable-square-webhook-processor.php:1222
    17917 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:905
     17952#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:975
    1791817953msgid "Subscription Webhook: Recurring donation updated"
    1791917954msgstr ""
     
    1792917964#. translators: %d: threshold
    1793017965#: includes/square/gateway/class-charitable-square-webhook-processor.php:1310
    17931 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:1014
     17966#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:1165
    1793217967msgid "The payment intent has been cancelled after %d failed payment attempts."
    1793317968msgstr ""
     
    1810718142msgstr ""
    1810818143
    18109 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:135
     18144#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:136
    1811018145msgid "Test webhook successfully received."
    1811118146msgstr ""
    1811218147
    18113 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:371
    18114 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:420
    18115 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:558
     18148#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:162
     18149msgid "Error while processing webhook."
     18150msgstr ""
     18151
     18152#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:382
     18153#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:431
     18154#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:569
    1811618155msgid "Donation Webhook: Missing donation ID"
    1811718156msgstr ""
    1811818157
    18119 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:383
     18158#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:394
    1812018159msgid "Donation Webhook: Refund donation ID not valid"
    1812118160msgstr ""
    1812218161
    18123 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:395
     18162#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:406
    1812418163msgid "Donation Webhook: Charge ID does not match donation reference on this site"
    1812518164msgstr ""
    1812618165
    18127 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:398
     18166#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:409
    1812818167msgid "Donation refunded from the Stripe dashboard."
    1812918168msgstr ""
    1813018169
    18131 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:426
    18132 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:570
    18133 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:662
     18170#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:437
     18171#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:581
     18172#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:673
    1813418173msgid "Donation Webhook: Donation ID not valid"
    1813518174msgstr ""
    1813618175
    18137 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:438
    18138 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:580
     18176#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:449
     18177#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:591
    1813918178msgid "Donation Webhook: Payment Intent does not match donation reference on this site"
    1814018179msgstr ""
    1814118180
    18142 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:457
     18181#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:468
    1814318182msgid "Donation Webhook: Donation marked as Failed"
    1814418183msgstr ""
    1814518184
    18146 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:482
     18185#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:493
    1814718186msgid "Donation Webhook: Unable to retrieve invoice for failed payment intent."
    1814818187msgstr ""
    1814918188
    18150 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:488
     18189#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:499
    1815118190msgid "Donation Webhook: No matching subscription found for invoice with failed payment intent."
    1815218191msgstr ""
    1815318192
    18154 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:501
     18193#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:512
    1815518194msgid "Donation Webhook: Recurring donation for payment intent marked as cancelled."
    1815618195msgstr ""
    1815718196
    18158 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:504
     18197#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:515
    1815918198msgid "Donation Webhook: Recurring donation for payment intent marked as pending cancellation."
    1816018199msgstr ""
    1816118200
    18162 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:530
     18201#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:541
    1816318202msgid "Donation Webhook: Recurring donation and initial payment for payment intent marked as failed"
    1816418203msgstr ""
    1816518204
    18166 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:628
     18205#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:639
    1816718206msgid "Donation Webhook: Donation marked as Paid"
    1816818207msgstr ""
    1816918208
    18170 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:657
     18209#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:668
    1817118210msgid "Donation Webhook: Session id does not match donation reference on this site"
    1817218211msgstr ""
    1817318212
    18174 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:672
     18213#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:683
    1817518214msgid "Donation Webhook: Missing payment intent"
    1817618215msgstr ""
    1817718216
    18178 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:689
     18217#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:700
    1817918218msgid "Session Webhook: Donation updated with Payment Intent data"
    1818018219msgstr ""
    1818118220
    18182 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:706
     18221#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:717
    1818318222msgid "Session Webhook: Missing subscription"
    1818418223msgstr ""
    1818518224
    18186 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:715
     18225#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:726
    1818718226msgid "Session Webhook: Invalid subscription"
    1818818227msgstr ""
    1818918228
    18190 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:740
     18229#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:751
    1819118230msgid "Unable to set cancel time for subscription."
    1819218231msgstr ""
    1819318232
    18194 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:745
     18233#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:756
    1819518234msgid "Session Webhook: Donation and subscription updated with session data"
    1819618235msgstr ""
    1819718236
    18198 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:774
     18237#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:785
    1819918238msgid "Subscription Webhook: Invoice created"
    1820018239msgstr ""
    1820118240
    1820218241#. translators: %s is the invoice status.
    18203 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:794
     18242#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:805
    1820418243msgid "Subscription Webhook: Not processing invoice with a status of %s."
    1820518244msgstr ""
    1820618245
    18207 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:808
     18246#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:819
    1820818247msgid "Subscription Webhook: Invoice payment failed"
    1820918248msgstr ""
    1821018249
    18211 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:840
     18250#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:851
    1821218251msgid "Subscription Webhook: Renewal has already been added"
    1821318252msgstr ""
    1821418253
    18215 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:872
     18254#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:883
    1821618255msgid "Unable to save donation ID to Stripe charge metadata."
    1821718256msgstr ""
    1821818257
    18219 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:875
     18258#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:886
    1822018259msgid "Subscription Webhook: Payment complete"
    1822118260msgstr ""
    1822218261
    18223 #: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:932
     18262#: includes/stripe/gateway/class-charitable-stripe-webhook-processor.php:1049
    1822418263msgid "Subscription Webhook: Recurring donation cancelled"
    1822518264msgstr ""
  • charitable/trunk/includes/abstracts/abstract-class-charitable-email.php

    r3453160 r3460927  
    962962         *
    963963         * @since  1.8.9.2
     964         * @since  1.8.9.5 Enhanced with detailed diagnostic context for recipient extraction failures.
    964965         *
    965966         * @return string|false Email recipient or false on error.
     
    969970                $recipient = $this->get_recipient();
    970971
    971                 if ( empty( $recipient ) || ! is_email( $recipient ) ) {
    972                     $this->log_email_error( 'invalid_recipient', 'Recipient email is empty or invalid: ' . $recipient );
     972                if ( empty( $recipient ) || ! $this->is_valid_recipient( $recipient ) ) {
     973                    // Phase 1: Enhanced diagnostics - minimal overhead, only on errors
     974                    $diagnostic_context = array(
     975                        'recipient_value' => $recipient,
     976                        'recipient_type' => gettype( $recipient ),
     977                        'recipient_length' => strlen( (string) $recipient )
     978                    );
     979
     980                    // Add donation context if available (safe checks)
     981                    if ( is_object( $this->donation ) && method_exists( $this->donation, 'get_donor_id' ) ) {
     982                        $donor_id = $this->donation->get_donor_id();
     983                        $diagnostic_context['donation_id'] = method_exists( $this->donation, 'get_donation_id' ) ? $this->donation->get_donation_id() : 'unknown';
     984                        $diagnostic_context['donor_id'] = $donor_id;
     985                        $diagnostic_context['donor_id_type'] = gettype( $donor_id );
     986
     987                        // Check campaign donations count (common failure point)
     988                        if ( method_exists( $this->donation, 'get_campaign_donations' ) ) {
     989                            $campaign_donations = $this->donation->get_campaign_donations();
     990                            $diagnostic_context['campaign_donations_count'] = is_array( $campaign_donations ) ? count( $campaign_donations ) : 'not_array';
     991                        }
     992
     993                        // Check donor object validity if donor_id exists
     994                        if ( ! empty( $donor_id ) && $donor_id !== false && method_exists( $this->donation, 'get_donor' ) ) {
     995                            $donor = $this->donation->get_donor();
     996                            $diagnostic_context['donor_object_type'] = gettype( $donor );
     997                            $diagnostic_context['donor_object_class'] = is_object( $donor ) ? get_class( $donor ) : 'not_object';
     998                        }
     999                    } else {
     1000                        $diagnostic_context['donation_object_type'] = gettype( $this->donation );
     1001                    }
     1002
     1003                    $this->log_email_error( 'invalid_recipient',
     1004                        'Recipient email is empty or invalid - Enhanced diagnosis: ' . wp_json_encode( $diagnostic_context ) );
    9731005                    return false;
    9741006                }
     
    10201052         *
    10211053         * @since  1.8.9.2
     1054         * @since  1.8.9.5 Enhanced with detailed diagnostic context for email build failures.
    10221055         *
    10231056         * @return string|false Email body or false on error.
     
    10461079                    restore_error_handler();
    10471080                }
    1048                 $this->log_email_error( 'email_build_failed', $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() );
     1081
     1082                // Phase 1: Enhanced email build diagnostics
     1083                $build_context = array(
     1084                    'email_class' => get_class( $this ),
     1085                    'email_id' => method_exists( $this, 'get_email_id' ) ? $this->get_email_id() : 'unknown',
     1086                    'error_message' => $e->getMessage(),
     1087                    'error_file' => basename( $e->getFile() ),
     1088                    'error_line' => $e->getLine(),
     1089                    'ob_level' => ob_get_level()
     1090                );
     1091
     1092                // Add donation context for template building
     1093                if ( is_object( $this->donation ) ) {
     1094                    $build_context['has_donation_data'] = true;
     1095                    $build_context['donation_id'] = method_exists( $this->donation, 'get_donation_id' ) ? $this->donation->get_donation_id() : 'unknown';
     1096                } else {
     1097                    $build_context['has_donation_data'] = false;
     1098                }
     1099
     1100                // Check template availability
     1101                $template_dir = CHARITABLE_DIRECTORY_PATH . 'templates/emails/';
     1102                $build_context['templates_exist'] = array(
     1103                    'header' => file_exists( $template_dir . 'header.php' ),
     1104                    'body' => file_exists( $template_dir . 'body.php' ),
     1105                    'footer' => file_exists( $template_dir . 'footer.php' )
     1106                );
     1107
     1108                $this->log_email_error( 'email_build_failed',
     1109                    'Email template build failed - Enhanced diagnosis: ' . wp_json_encode( $build_context ) );
    10491110                return false;
    10501111            }
     
    11251186            return charitable_log_form_error( 'email_failure', $error_details, $context );
    11261187        }
     1188
     1189        /**
     1190         * Check if recipient string is valid (handles single emails and comma-separated lists).
     1191         *
     1192         * @since  1.8.9.5
     1193         *
     1194         * @param  string $recipient The recipient string to validate.
     1195         * @return boolean True if valid, false otherwise.
     1196         */
     1197        private function is_valid_recipient( $recipient ) {
     1198            if ( empty( $recipient ) ) {
     1199                return false;
     1200            }
     1201
     1202            // If it's a single email, use is_email()
     1203            if ( strpos( $recipient, ',' ) === false ) {
     1204                return is_email( trim( $recipient ) );
     1205            }
     1206
     1207            // Handle comma-separated emails
     1208            $emails = explode( ',', $recipient );
     1209            foreach ( $emails as $email ) {
     1210                $email = trim( $email );
     1211                if ( empty( $email ) || ! is_email( $email ) ) {
     1212                    return false;
     1213                }
     1214            }
     1215
     1216            return true;
     1217        }
    11271218    }
    11281219
  • charitable/trunk/includes/admin/tools/class-charitable-tools-email-diagnostics.php

    r3453160 r3460927  
    365365                                }
    366366
     367                                // Phase 1 Integration: Test recipient extraction with real donation data
     368                                $email_test['recipient_extraction'] = $this->test_recipient_extraction_for_email( $email_instance, $email_id );
     369
    367370                                $successful_accesses++;
    368371                            } catch ( Exception $e ) {
     
    393396                $result['average_time'] = round( $total_time / count( $this->priority_emails ), 1 );
    394397
     398                // Phase 1 Integration: Count recipient extraction failures
     399                $recipient_failures = 0;
     400                foreach ( $result['email_tests'] as $email_test ) {
     401                    if ( isset( $email_test['recipient_extraction']['tested'] ) &&
     402                         $email_test['recipient_extraction']['tested'] &&
     403                         ! $email_test['recipient_extraction']['success'] ) {
     404                        $recipient_failures++;
     405                    }
     406                }
     407                $result['details']['recipient_extraction_failures'] = $recipient_failures;
     408
    395409                // Determine status and score
    396410                if ( $successful_accesses === count( $this->priority_emails ) ) {
    397411                    $result['status'] = 'success';
    398412                    $result['score'] = 20;
     413
     414                    // Reduce score for recipient extraction failures
     415                    if ( $recipient_failures > 0 ) {
     416                        $result['score'] -= ( $recipient_failures * 3 ); // -3 points per failure
     417                        $result['status'] = 'partial';
     418                        $result['details']['recipient_warning'] = "Recipient extraction failing for {$recipient_failures} email(s)";
     419                    }
    399420                } elseif ( $successful_accesses > 0 ) {
    400421                    $result['status'] = 'partial';
    401422                    $result['score'] = round( ( $successful_accesses / count( $this->priority_emails ) ) * 20 );
     423
     424                    // Additional penalty for recipient failures
     425                    if ( $recipient_failures > 0 ) {
     426                        $result['score'] -= ( $recipient_failures * 2 );
     427                    }
    402428                }
    403429
     
    920946         *
    921947         * @since 1.8.9.2
     948         * @since 1.8.9.5 Enhanced to connect with Phase 1 error logging improvements.
    922949         *
    923950         * @return int Number of recent email errors.
    924951         */
    925952        private function count_recent_email_errors() {
    926             // This would typically check error logs or database for email failures
    927             // For now, return 0 as we don't have a built-in error tracking system
    928             // This could be enhanced to check wp_mail failures or charitable-specific logs
    929             return 0;
     953            $error_count = 0;
     954
     955            try {
     956                // Connect to Phase 1 enhanced error logging via activities table
     957                if ( function_exists( 'charitable_get_table' ) ) {
     958                    $activities_table = charitable_get_table( 'charitable_activities' );
     959                    if ( $activities_table ) {
     960                        global $wpdb;
     961                        $table_name = $activities_table->get_table_name();
     962                        $seven_days_ago = date( 'Y-m-d H:i:s', strtotime( '-7 days' ) );
     963
     964                        // Count email failures from the last 7 days
     965                        $error_count = (int) $wpdb->get_var( $wpdb->prepare(
     966                            "SELECT COUNT(*) FROM {$table_name}
     967                             WHERE type = 'form_error'
     968                             AND error_type = 'email_failure'
     969                             AND timestamp >= %s",
     970                            $seven_days_ago
     971                        ) );
     972                    }
     973                }
     974            } catch ( Exception $e ) {
     975                // Fail silently for diagnostics - don't break the diagnostic system
     976                error_log( 'Charitable Email Diagnostics: Error counting recent email errors - ' . $e->getMessage() );
     977            }
     978
     979            return $error_count;
     980        }
     981
     982        /**
     983         * Test recipient extraction for a specific email with real donation data.
     984         *
     985         * @since 1.8.9.5 Phase 1 Integration
     986         *
     987         * @param object $email_instance Email instance to test.
     988         * @param string $email_id Email ID being tested.
     989         * @return array Test results.
     990         */
     991        private function test_recipient_extraction_for_email( $email_instance, $email_id ) {
     992            $extraction_test = array(
     993                'tested' => false,
     994                'success' => false,
     995                'recipient_valid' => false,
     996                'recipient_value' => '',
     997                'donation_context' => 'none',
     998                'error' => null
     999            );
     1000
     1001            try {
     1002                // Skip recipient extraction test for emails that don't need donations
     1003                $non_donation_emails = array( 'password_reset', 'email_verification' );
     1004                if ( in_array( $email_id, $non_donation_emails ) ) {
     1005                    $extraction_test['tested'] = true;
     1006                    $extraction_test['success'] = true;
     1007                    $extraction_test['recipient_value'] = 'N/A - No donation required';
     1008                    return $extraction_test;
     1009                }
     1010
     1011                // Find a recent completed donation for testing
     1012                $recent_donations = get_posts( array(
     1013                    'post_type' => 'donation',
     1014                    'post_status' => 'charitable-completed',
     1015                    'posts_per_page' => 1,
     1016                    'orderby' => 'date',
     1017                    'order' => 'DESC'
     1018                ) );
     1019
     1020                if ( empty( $recent_donations ) ) {
     1021                    $extraction_test['donation_context'] = 'no_donations';
     1022                    $extraction_test['error'] = 'No completed donations available for testing';
     1023                    return $extraction_test;
     1024                }
     1025
     1026                $donation = charitable_get_donation( $recent_donations[0]->ID );
     1027                if ( ! $donation ) {
     1028                    $extraction_test['donation_context'] = 'invalid_donation';
     1029                    $extraction_test['error'] = 'Could not load donation object';
     1030                    return $extraction_test;
     1031                }
     1032
     1033                $extraction_test['donation_context'] = 'donation_' . $donation->get_donation_id();
     1034                $extraction_test['tested'] = true;
     1035
     1036                // Create new email instance with donation context
     1037                $email_class = get_class( $email_instance );
     1038                $test_email = new $email_class( array( 'donation' => $donation ) );
     1039
     1040                // Test recipient extraction
     1041                if ( method_exists( $test_email, 'get_recipient' ) ) {
     1042                    $recipient = $test_email->get_recipient();
     1043                    $extraction_test['recipient_value'] = $recipient;
     1044                    $extraction_test['recipient_valid'] = is_email( $recipient );
     1045                    $extraction_test['success'] = ! empty( $recipient ) && is_email( $recipient );
     1046                } else {
     1047                    $extraction_test['error'] = 'get_recipient method not available';
     1048                }
     1049
     1050            } catch ( Exception $e ) {
     1051                $extraction_test['error'] = $e->getMessage();
     1052                $extraction_test['exception_file'] = basename( $e->getFile() );
     1053                $extraction_test['exception_line'] = $e->getLine();
     1054            }
     1055
     1056            return $extraction_test;
    9301057        }
    9311058
  • charitable/trunk/includes/emails/class-charitable-email-offline-donation-receipt.php

    r3328339 r3460927  
    135135            }
    136136
    137             return true;
     137            return $sent;
    138138        }
    139139
  • charitable/trunk/includes/gateways/class-charitable-gateway-stripe-am.php

    r3453160 r3460927  
    10901090         */
    10911091        public static function cancel_subscription( $cancelled, Charitable_Recurring_Donation $donation ) {
     1092            // Enhanced logging for cancellation debugging
     1093            if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1094                error_log( sprintf(
     1095                    'CHARITABLE_STRIPE_CANCEL_DEBUG: Starting cancellation for donation #%d',
     1096                    $donation->get_donation_id()
     1097                ) );
     1098            }
     1099
    10921100            $subscription_id = $donation->get_gateway_subscription_id();
    10931101
    10941102            if ( ! $subscription_id ) {
     1103                if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1104                    error_log( 'CHARITABLE_STRIPE_CANCEL_DEBUG: No subscription ID found - cancellation aborted' );
     1105                }
     1106                $donation->log()->add( __( 'Cancellation failed: No gateway subscription ID found.', 'charitable' ) );
    10951107                return false;
     1108            }
     1109
     1110            if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1111                error_log( sprintf(
     1112                    'CHARITABLE_STRIPE_CANCEL_DEBUG: Attempting to cancel subscription %s',
     1113                    $subscription_id
     1114                ) );
    10961115            }
    10971116
     
    11031122
    11041123            if ( ! $api_key ) {
     1124                if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1125                    error_log( sprintf(
     1126                        'CHARITABLE_STRIPE_CANCEL_DEBUG: No API key found for mode %s - cancellation aborted',
     1127                        $donation->get_test_mode( false ) ? 'test' : 'live'
     1128                    ) );
     1129                }
     1130                $donation->log()->add( __( 'Cancellation failed: No Stripe API key configured.', 'charitable' ) );
    11051131                return false;
    11061132            }
    11071133
     1134            if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1135                error_log( sprintf(
     1136                    'CHARITABLE_STRIPE_CANCEL_DEBUG: Using %s mode, account: %s',
     1137                    $donation->get_test_mode( false ) ? 'test' : 'live',
     1138                    $account ? $account : 'none'
     1139                ) );
     1140            }
     1141
    11081142            $gateway->setup_api( $api_key );
    11091143
    11101144            try {
     1145                if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1146                    error_log( sprintf(
     1147                        'CHARITABLE_STRIPE_CANCEL_DEBUG: Retrieving subscription %s from Stripe',
     1148                        $subscription_id
     1149                    ) );
     1150                }
     1151
    11111152                $subscription = \Stripe\Subscription::retrieve( $subscription_id, $options );
     1153
     1154                if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1155                    error_log( sprintf(
     1156                        'CHARITABLE_STRIPE_CANCEL_DEBUG: Retrieved subscription status: %s',
     1157                        $subscription->status
     1158                    ) );
     1159                }
     1160
     1161                // Log current subscription state before cancellation
     1162                $donation->log()->add( sprintf(
     1163                    __( 'Retrieved subscription from Stripe - Current status: %s', 'charitable' ),
     1164                    $subscription->status
     1165                ) );
     1166
    11121167                $subscription->cancel( null, $options );
     1168
     1169                if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1170                    error_log( 'CHARITABLE_STRIPE_CANCEL_DEBUG: Stripe cancellation API call completed successfully' );
     1171                }
    11131172
    11141173                $donation->log()->add( __( 'Subscription cancelled in Stripe.', 'charitable' ) );
     
    11311190
    11321191            } finally {
     1192                if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
     1193                    error_log( sprintf(
     1194                        'CHARITABLE_STRIPE_CANCEL_DEBUG: Cancellation result: %s',
     1195                        $cancelled ? 'SUCCESS' : 'FAILED'
     1196                    ) );
     1197                }
    11331198                return $cancelled;
    11341199            }
  • charitable/trunk/includes/shortcodes/class-charitable-email-shortcode.php

    r2987535 r3460927  
    99 * @since     1.5.0
    1010 * @version   1.5.0
     11 * @version   1.8.9.5 Added recursion prevention for email shortcodes.
    1112 */
    1213
     
    4243         */
    4344        private $fields;
     45
     46        /**
     47         * Processing stack to track active shortcodes and prevent recursion.
     48         *
     49         * @since 1.8.9.5
     50         *
     51         * @var   array
     52         */
     53        private static $processing_stack = array();
     54
     55        /**
     56         * Processing depth counter for recursion detection.
     57         *
     58         * @since 1.8.9.5
     59         *
     60         * @var   int
     61         */
     62        private static $processing_depth = 0;
    4463
    4564        /**
     
    122141            }
    123142
    124             return self::$instance->get( $args['show'], $args );
     143            // Recursion prevention - Added in 1.8.9.5
     144            $recursion_result = self::check_recursion( $args['show'], $args );
     145            if ( false !== $recursion_result ) {
     146                return $recursion_result;
     147            }
     148
     149            // Add to processing stack
     150            self::push_processing_stack( $args['show'] );
     151
     152            // Process the shortcode
     153            $result = self::$instance->get( $args['show'], $args );
     154
     155            // Remove from processing stack
     156            self::pop_processing_stack( $args['show'] );
     157
     158            return $result;
    125159        }
    126160
     
    138172            return $value;
    139173        }
     174
     175        /**
     176         * Check for recursion and return fallback content if detected.
     177         *
     178         * @since 1.8.9.5
     179         *
     180         * @param  string $show The field to show.
     181         * @param  array  $args Mixed arguments.
     182         * @return string|false False if no recursion, fallback content if recursion detected.
     183         */
     184        private static function check_recursion( $show, $args ) {
     185            // Check depth limit
     186            $max_depth = apply_filters( 'charitable_email_recursion_depth_limit', 3 );
     187            if ( self::$processing_depth >= $max_depth ) {
     188                self::log_recursion_attempt( 'depth', $show, self::$processing_depth, $max_depth );
     189                return self::get_context_specific_fallback( $show, 'depth_limit' );
     190            }
     191
     192            // Check for direct recursion (same field already being processed)
     193            $stack_key = $show;
     194            if ( in_array( $stack_key, self::$processing_stack, true ) ) {
     195                self::log_recursion_attempt( 'circular', $show, count( self::$processing_stack ), $max_depth );
     196                return self::get_context_specific_fallback( $show, 'circular_reference' );
     197            }
     198
     199            return false; // No recursion detected
     200        }
     201
     202        /**
     203         * Add a field to the processing stack.
     204         *
     205         * @since 1.8.9.5
     206         *
     207         * @param string $show The field being processed.
     208         */
     209        private static function push_processing_stack( $show ) {
     210            self::$processing_stack[] = $show;
     211            self::$processing_depth++;
     212        }
     213
     214        /**
     215         * Remove a field from the processing stack.
     216         *
     217         * @since 1.8.9.5
     218         *
     219         * @param string $show The field being processed.
     220         */
     221        private static function pop_processing_stack( $show ) {
     222            $index = array_search( $show, self::$processing_stack, true );
     223            if ( false !== $index ) {
     224                unset( self::$processing_stack[ $index ] );
     225                self::$processing_stack = array_values( self::$processing_stack ); // Re-index array
     226            }
     227            self::$processing_depth = max( 0, self::$processing_depth - 1 );
     228        }
     229
     230        /**
     231         * Get context-specific fallback content when recursion is detected.
     232         *
     233         * @since 1.8.9.5
     234         *
     235         * @param  string $show      The field that caused recursion.
     236         * @param  string $reason    The reason for fallback (depth_limit, circular_reference).
     237         * @return string           Fallback content.
     238         */
     239        private static function get_context_specific_fallback( $show, $reason ) {
     240            $fallbacks = array(
     241                'offline_instructions' => __( 'Payment instructions will be provided separately.', 'charitable' ),
     242                'donation_summary'     => __( 'Donation summary unavailable.', 'charitable' ),
     243                'donor'                => __( 'Donor information unavailable.', 'charitable' ),
     244                'donor_email'          => __( 'Email unavailable.', 'charitable' ),
     245                'site_name'            => get_bloginfo( 'name' ),
     246                'campaign_title'       => __( 'Campaign information unavailable.', 'charitable' ),
     247            );
     248
     249            // Get specific fallback or generic one
     250            $fallback = isset( $fallbacks[ $show ] ) ? $fallbacks[ $show ] : __( 'Content temporarily unavailable.', 'charitable' );
     251
     252            /**
     253             * Filter the fallback content for recursive email shortcodes.
     254             *
     255             * @since 1.8.9.5
     256             *
     257             * @param string $fallback The default fallback content.
     258             * @param string $show     The field that caused recursion.
     259             * @param string $reason   The reason for fallback.
     260             */
     261            return apply_filters( 'charitable_email_shortcode_recursion_fallback', $fallback, $show, $reason );
     262        }
     263
     264        /**
     265         * Log recursion attempts for debugging.
     266         *
     267         * @since 1.8.9.5
     268         *
     269         * @param string $type      Type of recursion (circular, depth).
     270         * @param string $show      The field that caused recursion.
     271         * @param int    $current   Current depth/stack size.
     272         * @param int    $limit     The configured limit.
     273         */
     274        private static function log_recursion_attempt( $type, $show, $current, $limit ) {
     275            // Only log if both CHARITABLE_DEBUG and CHARITABLE_DEBUG_EMAILS are enabled
     276            if ( ! charitable_is_debug() || ! defined( 'CHARITABLE_DEBUG_EMAILS' ) || ! CHARITABLE_DEBUG_EMAILS ) {
     277                return;
     278            }
     279
     280            $message = sprintf(
     281                '[CHARITABLE_EMAIL_DEBUG] Recursion prevented: %s recursion detected for shortcode [charitable_email show="%s"]. Current: %d, Limit: %d, Stack: [%s]',
     282                $type,
     283                $show,
     284                $current,
     285                $limit,
     286                implode( ', ', self::$processing_stack )
     287            );
     288
     289            error_log( $message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     290        }
    140291    }
    141292
  • charitable/trunk/includes/stripe/gateway/class-charitable-stripe-webhook-processor.php

    r3453160 r3460927  
    122122         *
    123123         * @since  1.3.0
     124         * @since  1.8.9.5 Fixed fatal error when ErrorException is thrown by using type-safe exception handling.
    124125         *
    125126         * @return void
     
    141142
    142143            } catch ( Exception $e ) {
    143                 $body = $e->getJsonBody();
    144                 // phpcs:disable
    145                 if ( charitable_is_debug() ) {
    146                     error_log( $body['error']['message'] );
    147                 }
    148                 // phpcs:enable
     144                // Handle Stripe API exceptions that have getJsonBody method
     145                if ( method_exists( $e, 'getJsonBody' ) ) {
     146                    $body = $e->getJsonBody();
     147                    // phpcs:disable
     148                    if ( charitable_is_debug() ) {
     149                        error_log( 'Stripe API Error: ' . $body['error']['message'] );
     150                    }
     151                    // phpcs:enable
     152                } else {
     153                    // Handle generic PHP exceptions (ErrorException, etc.)
     154                    // phpcs:disable
     155                    if ( charitable_is_debug() ) {
     156                        error_log( 'Webhook Processing Error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() );
     157                    }
     158                    // phpcs:enable
     159                }
     160
    149161                status_header( 500 );
    150 
    151                 die( __( 'Error while retrieving event.', 'charitable' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     162                die( __( 'Error while processing webhook.', 'charitable' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    152163            }//end try
    153164        }
  • charitable/trunk/readme.txt

    r3455615 r3460927  
    55Tested up to: 6.9.1
    66Requires PHP: 7.2
    7 Stable tag: 1.8.9.4
     7Stable tag: 1.8.9.5
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    269269== Changelog ==
    270270
     271= Donation Form & Fundraising Campaigns v1.8.9.5 =
     272* FIX: Added additional checks to resolved issue with Stripe webhook that was causing a fatal error a certain scenario.
     273* FIX: Allow more international characters into campaign title fields in campaign visual builder.
     274* IMPROVED: Email shortcode processing with recursion prevention for better reliability.
     275* IMPROVED: Added additional enhanced email error logging and diagnostics.
     276
    271277= Donation Form & Fundraising Campaigns v1.8.9.4 =
    272278* FIX: Resolved seemingly missing social media icons in some campaign builder template previews in certain scenarios.
Note: See TracChangeset for help on using the changeset viewer.