diff --git a/README.txt b/README.txt index d59356309..b7127ff2d 100644 --- a/README.txt +++ b/README.txt @@ -353,8 +353,9 @@ You can purchase a premium plan for access to support and our premium extensions * IMPROVED Funnel step flow UI. * ADDED `{substr}` replacement code. * ADDED Nested meta key support for meta replacement codes using dot notation. Example `{meta.custom_object.some_key}`. -* ADDED WPCLI functions for managing licenses. +* ADDED WP-CLI functions for managing licenses. * FIXED Small UI issue for tooltips not showing as connected to their arrows. +* FIXED Broadcasts sometimes not moving from `sending` to `sent` after all events have completed. = 3.7.0.2 (2024-10-16) = * ADDED `{andList}`, `{orList}`, `{ol}`, and `{ul}` formatting replacement codes. @@ -509,189 +510,6 @@ You can purchase a premium plan for access to support and our premium extensions * FIXED Issue with the broadcast schedule lock not being reset, resulting in broadcast scheduling hanging up sometimes. * FIXED Unable to cancel pending broadcasts if no events have been scheduled yet. -= 3.4.3 (2024-05-29) = -* ADDED Asset editing lock. Emails and funnels will now be "locked" while editing. Similar to WordPress posts. -* ADDED Rest API Playground! You can now see proper API documentation, as well as test the API based on your website data from within the browser. -* FIXED 2 non-severe (authentication required) vulnerabilities responsibly disclosed to us by Patchstack. - -= 3.4.2.3 (2024-05-15) = -* ADDED Option in the custom properties editor to move fields between property groups. -* FIXED Bug with birthday filter caused birthdays during non-leap years to be excluded when using the 'today' range. - -= 3.4.2.2 (2024-05-08) = -* FIXED Sequential page views error out. - -= 3.4.2.1 (2024-05-08) = -* HOT FIX Renaming IP address column not working in MySQL 5.6 - -= 3.4.2 (2024-05-08) = -* IMPROVED Optimized indexes for activity and events tables for faster reporting. -* IMPROVED Optimized more queries for faster reporting. -* TWEAKED IP Addresses will now be stored in binary rather than plain text. -* FIXED Activity not saving when visitor accesses the site via IPv6. -* FIXED Guided setup notice not dismissing. -* FIXED Broken premium link in checklist items. - -= 3.4.1 (2024-04-30) = -* ADDED Begin tracking user agents and IP address with activity and page visits -* ADDED Set a delay to ignore clicks and opens in the email settings. This will mitigate bot activity inflating reports. -* FIXED No path to guided setup after WP 6.5 update. -* FIXED Replaced legacy SMS broadcast scheduler with the new on the add screen from the SMS page. Requires updating the SMS addon. -* FIXED Deprecated function notices coming from funnels. - -= 3.4 (2024-04-25) = -* IMPROVED New unsubscribe features! - * Instead of link options there is now an unsubscribe form in the preferences center. - * The form collects standard reasons for unsubscribing, as well as optional written feedback. - * Responses are compiled into the Unsubscribe Reasons chart in the contacts report. - * Unsubscribe reasons and feedback can be viewed in the activity timeline, contacts list, and exported. - * For users of the one-click unsubscribe feature, the option to offer feedback is provided after they've unsubscribed. - * Go to `https://yoursite.com/gh/preferences/manage/` to see what subscribers will see. -* ADDED Campaigns management in the admin. Edit, delete, etc... -* ADDED You can now create public campaign archives. - * Visit `https://yoursite.com/gh/campaigns/`. - * All public campaigns will be listed. - * Campaign archives can be searched by subject and content. -* ADDED Shortcode to embed email content into posts. -* ADDED UTM Parameters support in the email editor. -* FIXED Contact activity timeline sometimes not loading. -* FIXED Missing strings in the POT file. - -= 3.3.3.2 (2024-04-17) = -* FIXED Some activity not preloading correctly in the contact activity timeline -* FIXED Email settings disappear after changing campaigns and not saving. - -= 3.3.3.1 (2024-04-17) = -* FIXED Fatal error in the contact list view for lower permission users. - -= 3.3.3 (2024-04-15) = -* ADDED Column preset buttons for easier column management in the contacts list. -* ADDED A better way to handle all the different mailer handlers with the `gh_mail()` function. -* ADDED Some WP CLI tooling which will be improved on and documented at a later date. -* ADDED Tools to generate fake data for funnels, broadcasts, and contacts. -* TWEAKED Funnel timers will calculate delay time based on the enqueued time rather than the current time. -* TWEAKED No longer use `p` style for `a` tags in the email editor. -* TWEAKED Replacement codes for custom date fields will output `Y-m-d` or `Y-m-d H:i:s` format by default so that they are easier to use in other functions. You can use the `{date}` or `{local_date}` replacement codes to format your dates. -* TWEAKED Column checkbox toggle order is now more consistent. -* FIXED Emails not preloading in contact activity timeline. -* FIXED Removed a bunch of unused files and code. -* FIXED Notes not being added from funnels when the content is exactly the same as a previous note. -* FIXED WYSIWYG Editor in text block gets large when editing CSS. -* FIXED Fatal error in PHP 8+ if no log ID in Email_Logger. - -= 3.3.2 (2024-03-05) = -* ADDED 4x per day the queue will be "cleaned" and events that failed to process within the last 6 hours will be fixed. -* ADDED Activity for when a contact is imported from a CSV file. -* IMPROVED using `_` to prefix meta for replacements like `{_meta_key}` is no longer required. You can simply do `{meta_key}` even if it's not a registered custom field. -* TWEAKED Meta that can be serialized, but is not associated with a custom field and thus has no pretty output, will be output as JSON encoded. -* TWEAKED The replacements cache group has been changed from `replacements` to `groundhogg\replacements`. -* TWEAKED Combined several cron task hooks into one. -* TWEAKED Funnel conversion activity in timeline now has proper UI. -* TWEAKED Optimized how emails are preloaded for email activity in the timeline. -* FIXED Export contacts BG task causing error in tasks log. -* FIXED HTML email template previews not working. -* FIXED DB error when passing consent attributes to the contact update method. -* FIXED Changed all button types in the filters component to `button` to prevent form submission in custom post types. - -= 3.3.1 (2024-02-27) = -* ADDED New background tasks tab in the logs page. -* ADDED New Age filter! -* ADDED New "Day Of" date range for date filters. -* FIXED Background tasks not always scheduling correctly, moved to using a dedicated table instead of using internal WP Cron. -* FIXED The `Empty` comparison for meta filters was not respecting `NULL` values. -* FIXED Meta filters with keys containing `-` were not working. -* FIXED Is/Is Not comparison for the birthday filter not working. -* FIXED Email not loading after duplicating an email step in a funnel. -* FIXED Only show active funnels in **Add to funnel** tool in the contact record. - -= 3.3 (2024-02-15) = -* ADDED Alternate email testing methods. *Design* for inbox compatibility testing or *Functional* to simulate a real email send. -* ADDED **Last/Next 14 days** and **Last/Next X days** ranges for date filters. -* ADDED Setting to enable sending unsubscribe notifications to a custom email address. -* ADDED Filters for the List-Unsubscribe header so it can be hijacked by custom implementations. -* TWEAKED Made the text block editor inline rather than a modal. -* TWEAKED Made the editor replacements button an MCE toolbar widget. -* TWEAKED Moved the email editor block inspector and inspector toolbar to the right of the email content. -* TWEAKED Email previews in the editor will ignore conditional blocks and show all content. -* TWEAKED Bounce inbox IMAP settings are hidden if using API based outgoing email service. -* IMPROVED UI of sending email templates from the contact record. -* IMPROVED UI of adding a contact to a funnel from the contact record. -* IMPROVED Fallback event and contact tracking when tracking cookie is not working. -* IMPROVED The One-Click List-Unsubscribe unsubscribe endpoint requires a token. -* IMPROVED The One-Click List-Unsubscribe endpoint track the unsubscribe to the associated funnel or broadcast. -* IMPROVED The manage preferences email now uses HTML links and offers a direct unsubscribe link. -* FIXED If more than 25 emails in a funnel they were not all preloading. -* FIXED List-Unsubscribe header not working in iCloud because iCloud does not decode it properly. - -= 3.2.3.3 (2024-02-13) = -* FIXED Uninstall function was not deleting all options. -* FIXED Need to flush cache after using the reset tool. - -= 3.2.3.2 (2024-02-13) = -* ADDED Checkboxes settings field type. -* ADDED Additional date format helper functions to the DateTimeHelper class. -* FIXED Coalesce `null` meta values to the empty string `''` for `NOT IN` comparisons. -* FIXED Newlines in CSV cells breaking imports. -* FIXED Search for a contact by email not returning results on email equality. -* FIXED Force data to save as serialized when mapping to a checkbox list or multi-select custom field. - -= 3.2.3.1 (2024-02-05) = -* TWEAKED The export file headers are added to the CSV before the background process starts rather than during. -* TWEAKED Funnels will sync step status with every update instead of only during a status change. -* FIXED When adding new steps to an active funnel the step status is `inactive` when it should be `active`. -* FIXED Initiating an export from the tools page causing fatal error. - -= 3.2.3 (2024-02-03) = -* ADDED Notifications when background tasks are complete. -* IMPROVED The background task system to be more reliable. -* TWEAKED Bulk deleting, updating, importing, and exporting contacts will be handled as background tasks. -* FIXED Guided setup screen blank. - -= 3.2.2.2 (2024-02-01) = -* HOT FIX User info updating incorrect contact records. -* TWEAKED Optimized the recount tag associations function to perform in a single query. -* FIXED If filters aren't registered the filters don't work. Now shows a broken filter message instead. - -= 3.2.2.1 (2024-01-31) = -* FIXED Funnel history filters not working if in multiple OR conditions. - -= 3.2.2 (2024-01-31) = -* ADDED Background task for completing benchmarks. -* ADDED Tool to re-sync user IDs based on email address equality. -* ADDED If user email address and contact email address are not equal show an button to unlink the user from the contact in the user info card. -* TWEAKED The limits exceeded calculation for background tasks. -* FIXED Default date range for future date filters didn't exist. -* FIXED Caching for the `get_contactdata()` was not working causing slowness when checking contact marketability. -* FIXED New filters not setting between before date to EOD. -* FIXED Refresh button in contact activity not working because our caching was too good. -* IMPROVED Hardened the auto-login link functionality. - * If the {auto_login_link} is detected in the email, the `to` address of that email will be forcibly changed to the associated user's email address if it's different than the contact's. - * Changing of the `user_id` or `email` of a contact will invalidate previously issued login links. - -= 3.2.1 (2024-01-31) = -* TWEAKED Further optimized activity and event based filters. - * Setting `count equals 0` will now return results similar to using the inverse `count at least 1` in the exclude group. -* TWEAKED Delayed localization of gutenberg block js assets. -* TWEAKED Filters can now be registered directly from custom properties. This will be helpful for addons! -* TWEAKED Reduced number of queries for the views on the contacts table screen. -* FIXED `count()` not always returning a correct number because `FOUND_ROWS()` was not cached. -* FIXED Broadcast search not working. - -= 3.2 (2024-01-29) = -* ADDED New under the hood query improvements to help create more complex and performant queries. -* ADDED Color coding, sorting, and pagination to performance report tables. -* TWEAKED The funnel reports now shows *Added* and *Active* as separate metrics. -* TWEAKED The conversion rate of a funnel will be calculated with the *Active* metric and not *Added* as it was previously. This may reduce your funnel conversion rates. -* TWEAKED Date ranges for custom fields are now more flexible. -* TWEAKED Refactored many of the reports to be more performant. -* TWEAKED Refactored many of the search filters to be more performant. -* TWEAKED Funnel editor will preload email and tag assets before being editable. -* FIXED Custom fields built with the legacy custom fields builder not showing up in replacement codes -* FIXED using the `Between` comparison in date filters not working. -* FIXED Funnel campaigns not saving in PHP 8.0+ -* FIXED Selecting posts by ID not working when using a custom post type in the email posts block. -* FIXED Unable to scroll the email templates. - -Logs older than 2024 have been removed due to a new 5000 word changelog size restriction. Groundhogg has been in active development since 2018! +Logs older than June 1st 2024 have been removed due to a 5000 word changelog size restriction. Groundhogg has been in active development since 2018! To see older logs you can view them on [github](https://github.com/groundhoggwp/groundhogg/README.txt). diff --git a/api/v4/broadcasts-api.php b/api/v4/broadcasts-api.php index 59882582f..8edb5ea3b 100644 --- a/api/v4/broadcasts-api.php +++ b/api/v4/broadcasts-api.php @@ -2,7 +2,6 @@ namespace Groundhogg\Api\V4; -use Elementor\Tracker; use Groundhogg\Broadcast; use Groundhogg\Campaign; use Groundhogg\Email; @@ -12,7 +11,6 @@ use WP_REST_Response; use WP_REST_Server; use function Groundhogg\create_object_from_type; -use function Groundhogg\db; use function Groundhogg\get_db; // Exit if accessed directly @@ -94,7 +92,7 @@ public function create( WP_REST_Request $request ) { $meta['batch_interval'] = sanitize_text_field( $request->get_param( 'batch_interval' ) ); $meta['batch_interval_length'] = absint( $request->get_param( 'batch_interval_length' ) ); $meta['batch_amount'] = absint( $request->get_param( 'batch_amount' ) ); - $meta['batch_delay'] = 0; // initialize batch offset at 0 + $meta['batch_delay'] = 0; // initialize batch offset at 0 } if ( $date->getTimestamp() < time() ) { @@ -165,7 +163,7 @@ public function create( WP_REST_Request $request ) { $tracker = new Micro_Time_Tracker(); // schedule 5 seconds worth. Should cover most small broadcasts - while ( $broadcast->is_pending() && $tracker->time_elapsed() < 5 ){ + while ( $broadcast->is_pending() && $tracker->time_elapsed() < 5 ) { $broadcast->enqueue_batch(); } } @@ -173,6 +171,11 @@ public function create( WP_REST_Request $request ) { // If the broadcast is still pending, create a background task $broadcast->maybe_schedule_in_background(); + wp_send_json( [ + 'status' => 'success', + 'item' => $broadcast + ] ); + return self::SUCCESS_RESPONSE( [ 'item' => $broadcast ] ); diff --git a/includes/classes/broadcast.php b/includes/classes/broadcast.php index 607ed43d6..4831b5f12 100644 --- a/includes/classes/broadcast.php +++ b/includes/classes/broadcast.php @@ -8,6 +8,7 @@ use Groundhogg\DB\Broadcast_Meta; use Groundhogg\DB\Broadcasts; use Groundhogg\DB\Query\Table_Query; +use Groundhogg\Utils\DateTimeHelper; use Groundhogg\Utils\Micro_Time_Tracker; use GroundhoggSMS\Classes\SMS; @@ -273,11 +274,12 @@ public function maybe_schedule_in_background() { return false; } - if ( $this->get_meta( 'segment_type' ) === 'dynamic' ) { - $added = Background_Tasks::add( new Schedule_Broadcast( $this->get_id() ), $this->get_send_time() ); - } else { - $added = Background_Tasks::schedule_pending_broadcast( $this->get_id() ); - } + $added = Background_Tasks::add( + new Schedule_Broadcast( $this->get_id() ), + $this->get_meta( 'segment_type' ) === 'dynamic' + ? $this->get_send_time() + : time() + ); if ( is_wp_error( $added ) ) { return $added; @@ -372,7 +374,7 @@ public function cancel() { */ public function is_schedulable() { return ( $this->status_is( 'pending' ) - || $this->status_is( 'sending' ) ) && ! $this->is_scheduled(); + || $this->status_is( 'sending' ) ) && ! $this->is_scheduled(); } /** @@ -409,15 +411,16 @@ public function enqueue_batch() { $query['offset'] = $offset; $query['found_rows'] = true; + $c_query = new Contact_Query( $query ); + $c_query->setOrderby( [ 'ID', 'ASC' ] )->setGroupby( 'ID' ); + $contacts = $c_query->query( null, true ); + $total = $c_query->found_items; + $batch_interval = $this->get_meta( 'batch_interval' ); $batch_interval_length = absint( $this->get_meta( 'batch_interval_length' ) ); $batch_amount = absint( $this->get_meta( 'batch_amount' ) ) ?: 100; $batch_delay = absint( $this->get_meta( 'batch_delay' ) ) ?: 0; - $c_query = new Contact_Query( $query ); - $contacts = $c_query->query( null, true ); - $total = $c_query->found_items; - // No contacts to send to? if ( $total === 0 ) { return false; @@ -555,7 +558,7 @@ public function maybe_set_status_to_sent() { } // We're not done sending events yet - if ( $this->has_pending_events() ){ + if ( $this->has_pending_events() ) { return false; } @@ -569,6 +572,28 @@ public function maybe_set_status_to_sent() { return true; } + /** + * Transition broadcasts from sending to sent + * + * @throws DB\Query\FilterException + * @return void + */ + public static function transition_from_sending_to_sent() { + + $sending = db()->broadcasts->query( [ + 'status' => 'sending' + ] ); + + if ( empty( $sending ) ) { + return; + } + + foreach ( $sending as $broadcast ) { + $broadcast = new Broadcast( $broadcast ); + $broadcast->maybe_set_status_to_sent(); + } + } + /** * Send the associated object to the given contact * @@ -618,7 +643,7 @@ public function run( $contact, $event = null ) { if ( ! $this->status_is( 'sending' ) ) { // directly from pending - if ( $this->is_pending() ){ + if ( $this->is_pending() ) { // set the status to schedule first to run any hooks temporarily $this->update( [ 'status' => 'scheduled' ] ); } @@ -627,9 +652,8 @@ public function run( $contact, $event = null ) { $this->update( [ 'status' => 'sending' ] ); } - if ( ! self::$sent_hook_set ){ - // set a hook after the event queue has finished processing to see if the broadcast is done sending. - add_action( 'groundhogg/event_queue/after_process', [ $this, 'maybe_set_status_to_sent' ] ); + if ( ! self::$sent_hook_set ) { + add_action( 'groundhogg/queue/processed_events', [ __CLASS__, 'transition_from_sending_to_sent' ] ); self::$sent_hook_set = true; } @@ -754,9 +778,12 @@ public function get_report_data( $unused = 0 ) { * @return array */ public function get_as_array() { + + $date = new DateTimeHelper( $this->get_send_time() ); + return array_merge( parent::get_as_array(), [ 'object' => $this->get_object(), - 'date_sent_pretty' => format_date( convert_to_local_time( $this->get_send_time() ) ) + 'date_sent_pretty' => $date->wpDateTimeFormat() ] ); } diff --git a/includes/cleanup-actions.php b/includes/cleanup-actions.php index 29d2d983d..805491a93 100644 --- a/includes/cleanup-actions.php +++ b/includes/cleanup-actions.php @@ -14,8 +14,23 @@ public function __construct() { add_action( 'groundhogg/cleanup', [ $this, 'fix_unprocessed_tasks' ] ); add_action( 'groundhogg/cleanup', [ $this, 'delete_expired_permission_keys' ] ); add_action( 'groundhogg/cleanup', [ $this, 'purge_email_logs' ] ); + add_action( 'groundhogg/cleanup', [ $this, 'handle_sent_broadcasts' ] ); } + /** + * Fix broadcasts that might have their status stuck in 'sending' + * + * @return void + */ + public function handle_sent_broadcasts() { + Broadcast::transition_from_sending_to_sent(); + } + + /** + * Schedules the cron event for WordPress + * + * @return void + */ public function schedule_event() { if ( wp_next_scheduled( 'groundhogg/cleanup' ) ) { return; diff --git a/includes/contact-query.php b/includes/contact-query.php index 32b5dfae3..aa415290c 100644 --- a/includes/contact-query.php +++ b/includes/contact-query.php @@ -1599,12 +1599,12 @@ protected static function set_where_conditions( $query_vars, Where $where ) { break; case 'marketable': - if ( $value === true || $value === 'yes' ) { + if ( $value === true || $value === 'yes' || $value == 1 ) { self::marketable( $where ); break; } - if ( $value === false || $value === 'no' ) { + if ( $value === false || $value === 'no' || $value == 0 ) { self::not_marketable( $where ); break; }