Skip to content

Commit d64f717

Browse files
[RELEASE] Forms fixes
2 parents 399738a + c940767 commit d64f717

6 files changed

Lines changed: 117 additions & 42 deletions

File tree

wordpress/wp-content/themes/les-verts/lib/form/include/CrmQueueItem.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,6 @@ public function last_attempt_seconds_ago() {
106106
return null;
107107
}
108108

109-
$now = new DateTime();
110-
111-
return $now->diff( $this->last_attempt )->s;
109+
return (new DateTime())->getTimestamp() - $this->last_attempt->getTimestamp();
112110
}
113111
}

wordpress/wp-content/themes/les-verts/lib/form/include/MailchimpSaver.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ class MailchimpSaver {
1616
* @throws \Exception On API error
1717
*/
1818
public static function send_to_mailchimp($data) {
19+
$data = self::remove_interest_fields($data);
1920

20-
// Send to Mailchimp service
2121
$response = wp_remote_post(
2222
MAILCHIMP_SERVICE_ENDPOINT,
2323
[
@@ -30,16 +30,37 @@ public static function send_to_mailchimp($data) {
3030
);
3131

3232
if (is_wp_error($response)) {
33+
Util::debug_log("msg=Mailchimp API error: " . $response->get_error_message());
3334
throw new \Exception('Mailchimp API error: ' . $response->get_error_message());
3435
}
3536

3637
$status_code = wp_remote_retrieve_response_code($response);
3738
if ($status_code < 200 || $status_code >= 300) {
39+
Util::debug_log("msg=Mailchimp API error: HTTP " . $status_code . " - " . wp_remote_retrieve_response_message($response));
3840
throw new \Exception('Mailchimp API error: HTTP ' . $status_code . ' - ' . wp_remote_retrieve_response_message($response));
3941
}
4042
}
4143

4244
public static function has_mailchimp_api_key() {
4345
return defined('MAILCHIMP_SERVICE_ENDPOINT') && MAILCHIMP_SERVICE_ENDPOINT;
4446
}
47+
48+
/**
49+
* Remove any interest fields from the data array since they can cause validation errors
50+
* This is a workaround that works for now. If we ever need interest fields in the direct
51+
* Mailchimp API call, this functionality can be extended.
52+
*
53+
* @param array $data Form data
54+
* @return array Cleaned data without interest fields
55+
*/
56+
private static function remove_interest_fields($data)
57+
{
58+
foreach ($data as $key => $value) {
59+
if (stripos($key, 'interest') !== false) {
60+
unset($data[$key]);
61+
}
62+
}
63+
64+
return $data;
65+
}
4566
}

wordpress/wp-content/themes/les-verts/lib/form/include/QueueDao.php

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,37 @@ public function push_if_not_in_queue( $item ) {
5959
$this->_push( $item, false );
6060
}
6161

62+
/**
63+
* Update an existing item in the queue by submission ID and move it to the end of the queue
64+
*
65+
* @param CrmQueueItem $updated_item The item with updated values
66+
* @return bool True if item was found and updated, false otherwise
67+
* @throws Exception
68+
*/
69+
public function update_and_move_to_end(CrmQueueItem $updated_item) {
70+
$this->lock();
71+
$queue = $this->get_all();
72+
$found = false;
73+
$new_queue = [];
74+
75+
foreach ($queue as $item) {
76+
if (!$found && $item instanceof CrmQueueItem &&
77+
$item->get_submission_id() === $updated_item->get_submission_id()) {
78+
$found = true; // Skip the old instance
79+
} else {
80+
$new_queue[] = $item;
81+
}
82+
}
83+
84+
if ($found) {
85+
$new_queue[] = $updated_item;
86+
$this->save($new_queue);
87+
}
88+
89+
$this->unlock();
90+
return $found;
91+
}
92+
6293
/**
6394
* Return the first item from the queue and remove it
6495
*
@@ -100,6 +131,7 @@ public function has_items(): bool {
100131
* @return array
101132
*/
102133
public function get_all() {
134+
wp_cache_delete( $this->key, 'options' );
103135
return get_option( $this->key, array() );
104136
}
105137

@@ -217,13 +249,21 @@ private function _push( $item, $allow_duplicates ) {
217249
*
218250
* @return bool
219251
*/
220-
private function contains( $all_items, $item ) {
221-
if ( is_scalar( $item ) ) {
222-
// strict: true => prevent type coercion
223-
return in_array( $item, $all_items, true );
252+
private function contains($all_items, $item) {
253+
if ($item instanceof CrmQueueItem) {
254+
foreach ($all_items as $queue_item) {
255+
if ($queue_item instanceof CrmQueueItem &&
256+
$queue_item->get_submission_id() === $item->get_submission_id()) {
257+
return true;
258+
}
259+
}
260+
return false;
224261
}
225262

226-
// strict: false => compare objects / arrays by value
227-
return in_array( $item, $all_items, false );
263+
// Original comparison for non-CrmQueueItems
264+
if (is_scalar($item)) {
265+
return in_array($item, $all_items, true);
266+
}
267+
return in_array($item, $all_items, false);
228268
}
229269
}

wordpress/wp-content/themes/les-verts/lib/form/include/SyncEnqueuer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ public function __construct($submission_id, $form_id = null) {
8585
*/
8686
public function add_to_queue($data) {
8787
$this->data = $data;
88-
$queue = new QueueDao(self::QUEUE_KEY);
8988

9089
// Process the data
9190
$this->apply_filtered_data();
9291

9392
if ( ! empty( $this->data ) ) {
9493
unset($this->data['_meta_']);
9594
$item = new CrmQueueItem( $this->data, $this->submission );
96-
$queue->push_if_not_in_queue( $item );
95+
$queue = new QueueDao(self::QUEUE_KEY);
96+
$queue->push_if_not_in_queue( $item );
9797
}
9898

9999
// If SUPT_FORM_ASYNC is false, process the sync queue immediately (for debugging)

wordpress/wp-content/themes/les-verts/lib/form/include/SyncProcessor.php

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ class SyncProcessor {
2222
const CRON_LOCK_TIMEOUT = 9 * MINUTE_IN_SECONDS;
2323

2424
const MSG_ITEM_NO_DATA = "Item has no data";
25+
const SUBJECT_ITEM_MAX_ATTEMPTS = "SyncProcessor: Max attempts reached";
2526
const MSG_ITEM_MAX_ATTEMPTS = "Item was processed more than " . self::MAX_SAVE_ATTEMPTS . " times. " .
26-
"Has been removed from the queue.";
27+
"Has been removed from the queue.";
2728

2829
private CrmDao $crm_dao;
2930
private QueueDao $queue;
@@ -40,16 +41,18 @@ public static function process_queue() {
4041
$processor = new self();
4142

4243
if (!$processor->can_sync_to_crm && !$processor->can_sync_to_mailchimp) {
43-
Util::remove_cron( self::CRON_HOOK_CRM_MC_SAVE );
44+
Util::remove_cron(self::CRON_HOOK_CRM_MC_SAVE);
45+
Util::debug_log("msg=No CRM or Mailchimp API key found, removing cron job");
4446
return;
4547
}
4648

4749
if (!$processor->acquire_lock()) {
50+
Util::debug_log("msg=Sync processor already running, skipping this execution");
4851
return;
4952
}
5053

5154
try {
52-
$processor->queue = new QueueDao(SyncEnqueuer::QUEUE_KEY);
55+
$processor->queue = self::get_queue();
5356
$items = array_slice($processor->queue->get_all(), 0, self::MAX_BATCH_SIZE);
5457

5558
if (empty($items)) {
@@ -63,36 +66,40 @@ public static function process_queue() {
6366
$first_item = $items[0] ?? null;
6467
$submission = ($first_item instanceof CrmQueueItem) ? $first_item->get_submission_id() : "(submission id not found)";
6568
CrmSaver::send_permanent_error_notification($submission, $e->getMessage());
69+
Util::debug_log("submissionId={$submission} msg=Failed to create CRM DAO. Skipping.");
6670
return;
6771
}
6872
}
6973

7074
/** @var CrmQueueItem $item */
7175
foreach ($items as $item) {
72-
if($processor->too_many_attempts($item)) {
76+
if ($processor->too_many_attempts($item) || $processor->too_recent_attempt($item)) {
7377
continue;
7478
}
7579

7680
if (!$item->has_data()) {
7781
Util::report_form_error('sync queue process', $item, new \Exception(self::MSG_ITEM_NO_DATA), null);
78-
$processor->update_queue($item, true);
82+
$processor->remove_from_queue($item);
7983
continue;
8084
}
8185

8286
try {
83-
$processed = $processor->process_item($item);
84-
$processor->update_queue($item, $processed);
85-
}
86-
catch (CrmMaxSyncsException $e) {
87-
Util::debug_log("submissionId={$item->get_submission_id()} msg=Too many CRM syncs per run. Ending this run.");
87+
if ($processor->process_item($item)) {
88+
$processor->remove_from_queue($item);
89+
} else {
90+
$processor->update_queue($item);
91+
}
92+
} catch (CrmMaxSyncsException $e) {
93+
Util::debug_log("submissionId=" . $item->get_submission_id() . " msg=Too many CRM syncs per run. Ending this run.");
8894
return;
8995
} catch (\Exception $e) {
9096
Util::report_form_error('sync queue process', $item, $e, null);
91-
$processor->update_queue($item, false);
97+
$processor->update_queue($item);
9298
}
9399
}
94100

95101
$processor->schedule_next_batch();
102+
Util::debug_log("msg=Processed batch of {$processor->crm_sync_count} CRM syncs");
96103
} finally {
97104
$processor->release_lock();
98105
}
@@ -161,13 +168,30 @@ private function restructure_field_data(array $crm_field_data_objects): array {
161168
*/
162169
private function too_many_attempts(CrmQueueItem $item) {
163170
if ( $item->get_attempts() >= self::MAX_SAVE_ATTEMPTS ) {
164-
Util::debug_log( "submissionId={$item->get_submission_id()} msg=Too many attempts. Skipping." );
171+
$this->remove_from_queue($item);
172+
Util::send_mail_to_admin(
173+
self::SUBJECT_ITEM_MAX_ATTEMPTS . "id=" . $item->get_submission_id(),
174+
self::MSG_ITEM_MAX_ATTEMPTS . "\n\n" . json_encode($item->get_data())
175+
);
176+
Util::debug_log("submissionId=" . $item->get_submission_id() . " msg=Too many attempts. Removed from queue.");
165177
return true;
166178
}
167179

168-
if ( $item->last_attempt_seconds_ago() &&
169-
$item->last_attempt_seconds_ago() < self::MIN_ATTEMPT_TIMEOUT ) {
170-
Util::debug_log( "submissionId={$item->get_submission_id()} msg=Last attempt only {$item->last_attempt_seconds_ago()} seconds ago. Skipping." );
180+
return false;
181+
}
182+
183+
/**
184+
* Check if the item should be skipped because it was processed too recently
185+
*
186+
* @param CrmQueueItem $item The item to check
187+
* @return bool True if the item should be skipped, false otherwise
188+
*/
189+
private function too_recent_attempt(CrmQueueItem $item) {
190+
if (
191+
$item->last_attempt_seconds_ago() &&
192+
$item->last_attempt_seconds_ago() < self::MIN_ATTEMPT_TIMEOUT
193+
) {
194+
Util::debug_log("submissionId={$item->get_submission_id()} msg=Last attempt only {$item->last_attempt_seconds_ago()} seconds ago. Skipping.");
171195
return true;
172196
}
173197

@@ -192,7 +216,7 @@ private function process_item(CrmQueueItem $item): bool {
192216
if ($this->can_sync_to_crm) {
193217
$match = $this->crm_dao->match($crm_field_data_objects);
194218
if ($this->is_valid_crm_match($match) || $new_contacts_to_crm) {
195-
if($this->crm_sync_count >= self::CRM_MAX_SYNCS_PER_RUN) {
219+
if ($this->crm_sync_count >= self::CRM_MAX_SYNCS_PER_RUN) {
196220
throw new CrmMaxSyncsException();
197221
}
198222
$this->crm_sync_count++;
@@ -237,19 +261,11 @@ private function is_valid_crm_match(array $match): bool {
237261
* Update the queue based on processing result
238262
*
239263
* @param CrmQueueItem $item Queue item that was processed
240-
* @param bool $processed Whether the item was successfully processed
241264
*/
242-
private function update_queue(CrmQueueItem $item, bool $remove): void {
243-
if ($remove) {
244-
$this->remove_from_queue($item);
245-
} else if ($item->get_attempts() >= self::MAX_SAVE_ATTEMPTS) {
246-
$this->remove_from_queue($item);
247-
Util::report_form_error('item max attempts', $item, new \Exception(self::MSG_ITEM_MAX_ATTEMPTS), null);
248-
} else {
249-
$item->add_attempt();
250-
$this->queue->push_if_not_in_queue($item);
251-
Util::debug_log("submissionId={$item->get_submission_id()} msg=Processing failed, will retry. attempts={$item->get_attempts()}");
252-
}
265+
private function update_queue(CrmQueueItem $item): void {
266+
$item->add_attempt();
267+
$this->queue->update_and_move_to_end($item);
268+
Util::debug_log("submissionId={$item->get_submission_id()} msg=Processing failed, will retry. attempts={$item->get_attempts()}");
253269
}
254270

255271
/**
@@ -258,7 +274,7 @@ private function update_queue(CrmQueueItem $item, bool $remove): void {
258274
* @param CrmQueueItem $item The item to remove
259275
*/
260276
private function remove_from_queue(CrmQueueItem $item): void {
261-
$this->queue->filter(function($q_item) use ($item) {
277+
$this->queue->filter(function ($q_item) use ($item) {
262278
return $q_item->get_submission_id() !== $item->get_submission_id();
263279
});
264280
}

wordpress/wp-content/themes/les-verts/style.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Theme Name: Les Verts
33
* Description: Custom theme for the GREENS of Switzerland. Designed by superhuit.ch, built by gruene.ch on top of superhuit's stack.
44
* Author: superhuit.ch & gruene.ch
5-
* Version: 0.40.0
5+
* Version: 0.40.1
66
* Requires PHP: 7.4
77
* Requires at least: 5.0
88
* Theme URI: https://github.com/grueneschweiz/2018.gruene.ch/

0 commit comments

Comments
 (0)