diff --git a/cleverpush-api.php b/cleverpush-api.php index 5236cab..9c25a04 100644 --- a/cleverpush-api.php +++ b/cleverpush-api.php @@ -85,6 +85,27 @@ public static function send_notification($title, $body, $url, $options = array() return CleverPush_Api::request('/notification/send', $params); } + public static function delete_notification($notification_id) + { + $channel_id = get_option('cleverpush_channel_id'); + $api_key_private = get_option('cleverpush_apikey_private'); + + if (empty($channel_id) || empty($notification_id) || empty($api_key_private)) { + return null; + } + + return wp_remote_request( + CLEVERPUSH_API_ENDPOINT . '/channel/' . rawurlencode($channel_id) . '/notification/' . rawurlencode($notification_id), + array( + 'method' => 'DELETE', + 'timeout' => 20, // phpcs:ignore + 'headers' => array( + 'authorization' => $api_key_private + ) + ) + ); + } + public static function update_channel($channel_id, $params = array()) { if (empty($channel_id)) { diff --git a/cleverpush.php b/cleverpush.php index f7ee2da..3751094 100644 --- a/cleverpush.php +++ b/cleverpush.php @@ -4,7 +4,7 @@ Plugin URI: https://cleverpush.com Description: Send push notifications to your users right through your website. Visit CleverPush for more details. Author: CleverPush -Version: 1.9.8 +Version: 1.9.9 Author URI: https://cleverpush.com Text Domain: cleverpush Domain Path: /languages @@ -763,11 +763,46 @@ public function metabox($post) cpCheckbox.addEventListener('change', function (e) { cpContent.style.display = e.target.checked ? 'block' : 'none'; - if (!e.target.checked && cpScheduledAtPicker) { - cpScheduledAtPicker.value = ''; + if (!e.target.checked) { + if (cpScheduledAtPicker) { + cpScheduledAtPicker.value = ''; + } + if (cpScheduledAtInput) { + cpScheduledAtInput.value = ''; + } } }); + var syncScheduledAt = function () {}; + if (cpScheduledAtInput && cpScheduledAtPicker) { + var padDatePart = function (value) { + return (value < 10 ? '0' : '') + value; + }; + var getLocalDateString = function (date) { + return date.getFullYear() + '-' + padDatePart(date.getMonth() + 1) + '-' + padDatePart(date.getDate()) + 'T' + padDatePart(date.getHours()) + ':' + padDatePart(date.getMinutes()); + }; + syncScheduledAt = function () { + if (!cpScheduledAtPicker.value) { + cpScheduledAtInput.value = ''; + return; + } + var parsed = new Date(cpScheduledAtPicker.value); + if (!isNaN(parsed.getTime())) { + cpScheduledAtInput.value = parsed.toISOString(); + } + }; + cpScheduledAtPicker.min = getLocalDateString(new Date()); + if (cpScheduledAtInput.value && new Date(cpScheduledAtInput.value) > new Date()) { + cpScheduledAtPicker.value = getLocalDateString(new Date(cpScheduledAtInput.value)); + } + cpScheduledAtPicker.addEventListener('input', syncScheduledAt); + cpScheduledAtPicker.addEventListener('change', syncScheduledAt); + var postForm = document.getElementById('post'); + if (postForm) { + postForm.addEventListener('submit', syncScheduledAt); + } + } + var initCleverPush = function () { if (typeof wp !== 'undefined' && wp.data && wp.data.subscribe && wp.data.select) { var hasNotice = false; @@ -790,6 +825,10 @@ public function metabox($post) var isPreviewingPost = coreEditor.isPreviewingPost(); var postStatus = coreEditor.getEditedPostAttribute('status'); + if (isSavingPost && !wasSavingPost) { + syncScheduledAt(); + } + // Save metaboxes on save completion, except for autosaves that are not a post preview. var shouldTriggerTemplateNotice = ( (wasSavingPost && !isSavingPost && !wasAutosavingPost) || @@ -971,23 +1010,6 @@ public function metabox($post) } } - if (cpScheduledAtInput && cpScheduledAtPicker) { - var getLocalDateString = function(date) { - return date.getFullYear() + '-' + ((date.getMonth() + 1) + '').padStart(2, '0') + '-' + (date.getDate() + '') + "T" + (date.getHours() + '').padStart(2, '0') + ":" + (date.getMinutes() + '').padStart(2, '0') - }; - var date = new Date(); - cpScheduledAtPicker.min = getLocalDateString(date); - if (cpScheduledAtInput.value && new Date(cpScheduledAtInput.value) > new Date()) { - cpScheduledAtPicker.value = getLocalDateString(new Date(cpScheduledAtInput.value)); - } - cpScheduledAtPicker.addEventListener('change', function() { - if (!cpScheduledAtPicker.value) { - cpScheduledAtInput.value = ''; - return; - } - cpScheduledAtInput.value = new Date(cpScheduledAtPicker.value).toISOString(); - }); - } } } }; @@ -1126,16 +1148,18 @@ public function publish_post($post_id) return; } - if (!current_user_can('cleverpush_send')) { + $saved_send = get_post_meta($post_id, 'cleverpush_send_notification', true); + $publishing_via_cron = (function_exists('wp_doing_cron') && wp_doing_cron()) || (defined('DOING_CRON') && DOING_CRON); + if (!current_user_can('cleverpush_send') && !($publishing_via_cron && $saved_send)) { return; } - if (isset($_POST['cleverpush_metabox_form_data_available']) ? !isset($_POST['cleverpush_send_notification']) : !get_post_meta($post_id, 'cleverpush_send_notification', true)) { + if (isset($_POST['cleverpush_metabox_form_data_available']) ? !isset($_POST['cleverpush_send_notification']) : !$saved_send) { return; } if (get_post_meta($post_id, 'cleverpush_notification_sent', true)) { - $notification_sent_at = get_post_meta(get_the_ID(), 'cleverpush_notification_sent_at', true); + $notification_sent_at = get_post_meta($post_id, 'cleverpush_notification_sent_at', true); if (!empty($notification_sent_at) && (time() - $notification_sent_at) < 60) { return; } @@ -1145,15 +1169,28 @@ public function publish_post($post_id) if (get_option('cleverpush_notification_title_required') == 'on') { $title = null; } - $text = !empty(get_the_excerpt()) ? html_entity_decode(get_the_excerpt()) : ''; + $text = get_the_excerpt($post_id); + $text = !empty($text) ? html_entity_decode($text) : ''; $url = get_permalink($post_id); + $metabox_submitted = isset($_POST['cleverpush_metabox_form_data_available']); if (!empty($_POST['cleverpush_title'])) { $title = sanitize_text_field(wp_unslash($_POST['cleverpush_title'])); $text = ''; + } elseif (!$metabox_submitted) { + $saved_title = get_post_meta($post_id, 'cleverpush_title', true); + if (!empty($saved_title)) { + $title = $saved_title; + $text = ''; + } } if (!empty($_POST['cleverpush_text'])) { $text = sanitize_text_field(wp_unslash($_POST['cleverpush_text'])); + } elseif (!$metabox_submitted) { + $saved_text = get_post_meta($post_id, 'cleverpush_text', true); + if (!empty($saved_text)) { + $text = $saved_text; + } } if (empty($title)) { @@ -1170,13 +1207,19 @@ public function publish_post($post_id) if (isset($_POST['cleverpush_use_tags']) && $_POST['cleverpush_use_tags'] == '1' && !empty($_POST['cleverpush_tags'])) { $options['tags'] = array_map('sanitize_text_field', $_POST['cleverpush_tags']); } - $thumbnail_url = get_the_post_thumbnail_url(); + $thumbnail_url = get_the_post_thumbnail_url($post_id); if (!empty($thumbnail_url)) { $options['mediaUrl'] = $thumbnail_url; } if (!empty($_POST['cleverpush_scheduled_at'])) { $options['scheduledAt'] = sanitize_text_field(wp_unslash($_POST['cleverpush_scheduled_at'])); + } else { + $saved_scheduled_at = get_post_meta($post_id, 'cleverpush_scheduled_at', true); + $saved_timestamp = !empty($saved_scheduled_at) ? strtotime($saved_scheduled_at) : false; + if ($saved_timestamp && $saved_timestamp > time()) { + $options['scheduledAt'] = $saved_scheduled_at; + } } delete_post_meta($post_id, 'cleverpush_send_notification'); @@ -1187,6 +1230,7 @@ public function publish_post($post_id) update_option('cleverpush_notification_error', null); update_post_meta($post_id, 'cleverpush_notification_sent', true); update_post_meta($post_id, 'cleverpush_notification_sent_at', time()); + $this->delete_draft_notification($post_id); } catch (Exception $ex) { update_option('cleverpush_notification_result', array('status' => 'error', 'message' => $ex->getMessage() )); @@ -1215,10 +1259,12 @@ public function save_post($post_id, $post) if (isset($_POST['cleverpush_text'])) { update_post_meta($post_id, 'cleverpush_text', sanitize_text_field($_POST['cleverpush_text'])); } - if (isset($_POST['cleverpush_scheduled_at'])) { - update_post_meta($post_id, 'cleverpush_scheduled_at', sanitize_text_field($_POST['cleverpush_scheduled_at'])); + if (isset($_POST['cleverpush_metabox_form_data_available']) && isset($_POST['cleverpush_scheduled_at'])) { + update_post_meta($post_id, 'cleverpush_scheduled_at', sanitize_text_field(wp_unslash($_POST['cleverpush_scheduled_at']))); } + $this->sync_draft_notification($post_id); + if (!empty($_POST['cleverpush_story_id'])) { if (isset($_POST['clear_cache']) && !empty($_POST['cleverpush_story_id'])) { delete_transient('cleverpush_story_' . sanitize_text_field($_POST['cleverpush_story_id']) . '_content'); @@ -1245,6 +1291,86 @@ public function save_post($post_id, $post) } } + public function sync_draft_notification($post_id) + { + if ((defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) || wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) { + return; + } + + if (!isset($_POST['cleverpush_metabox_form_data_available'])) { + return; + } + + $status = get_post_status($post_id); + if (!in_array($status, array('draft', 'future', 'pending'), true)) { + return; + } + + if (!isset($_POST['cleverpush_send_notification'])) { + $this->delete_draft_notification($post_id); + return; + } + + $title = html_entity_decode(get_the_title($post_id)); + if (!empty($_POST['cleverpush_title'])) { + $title = sanitize_text_field(wp_unslash($_POST['cleverpush_title'])); + } + $text = ''; + if (!empty($_POST['cleverpush_text'])) { + $text = sanitize_text_field(wp_unslash($_POST['cleverpush_text'])); + } + if (empty($title)) { + return; + } + + $scheduled_at = get_post_meta($post_id, 'cleverpush_scheduled_at', true); + $scheduled_timestamp = !empty($scheduled_at) ? strtotime($scheduled_at) : false; + if (!$scheduled_timestamp || $scheduled_timestamp <= time()) { + $scheduled_at = ''; + } + + $fingerprint = md5($title . '|' . $text . '|' . $scheduled_at); + $existing_id = get_post_meta($post_id, 'cleverpush_draft_notification_id', true); + $existing_fingerprint = get_post_meta($post_id, 'cleverpush_draft_notification_hash', true); + if (!empty($existing_id) && $existing_fingerprint === $fingerprint) { + return; + } + + if (!empty($existing_id)) { + $this->delete_draft_notification($post_id); + } + + $options = array('status' => 'draft'); + if (!empty($scheduled_at)) { + $options['scheduledAt'] = $scheduled_at; + } + + try { + $response = CleverPush_Api::send_notification($title, $text, get_permalink($post_id), $options); + if (empty($response) || is_wp_error($response)) { + return; + } + $body = json_decode(wp_remote_retrieve_body($response)); + if (empty($body->notificationId)) { + return; + } + update_post_meta($post_id, 'cleverpush_draft_notification_id', sanitize_text_field($body->notificationId)); + update_post_meta($post_id, 'cleverpush_draft_notification_hash', $fingerprint); + } catch (Exception $ex) { + update_option('cleverpush_notification_error', $ex->getMessage()); + } + } + + public function delete_draft_notification($post_id) + { + $notification_id = get_post_meta($post_id, 'cleverpush_draft_notification_id', true); + if (!empty($notification_id)) { + CleverPush_Api::delete_notification($notification_id); + } + delete_post_meta($post_id, 'cleverpush_draft_notification_id'); + delete_post_meta($post_id, 'cleverpush_draft_notification_hash'); + } + public function show_public_preview( $query ) { if ($query->is_main_query() diff --git a/readme.txt b/readme.txt index e6fc754..22b7fd8 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Plugin URI: https://cleverpush.com Tags: push notifications, web push, browser notifications, woocommerce Requires at least: 2.7 Tested up to: 6.7.2 -Stable tag: 1.9.8 +Stable tag: 1.9.9 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -34,6 +34,11 @@ If you are using any cache plugin, also be sure, to empty your cache. == ChangeLog == += 1.9.9 = +* Keep the optional notification schedule after saving a post +* Send that scheduled push when WordPress publishes the post, including a scheduled post +* Show an unpublished scheduled post as a draft in CleverPush + = 1.9.8 = * Fixed issue with capabilities_version