From 4a1d81a507db977c2825dbc7e44bc878824ae61f Mon Sep 17 00:00:00 2001 From: Soare Robert Daniel Date: Mon, 24 Nov 2025 19:33:54 +0200 Subject: [PATCH 01/11] fix: update ThemeIsle link domain in sale URL (#1068) --- includes/admin/class-rop-admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/class-rop-admin.php b/includes/admin/class-rop-admin.php index 0ec3f9bf..3b937035 100644 --- a/includes/admin/class-rop-admin.php +++ b/includes/admin/class-rop-admin.php @@ -1933,7 +1933,7 @@ public static function add_black_friday_data( $configs ) { $config['message'] = sprintf( $message_template, '', $discount, '', $product_label ); $config['sale_url'] = add_query_arg( $url_params, - tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/rs-bf', 'bfcm', 'revive' ) ) + tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/rs-bf', 'bfcm', 'revive' ) ) ); $configs[ ROP_PRODUCT_SLUG ] = $config; From c098832a11aee0b732fa5129d01577083760b1e0 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Mon, 29 Dec 2025 18:52:20 +0530 Subject: [PATCH 02/11] create hashtags on bluesky --- .../admin/helpers/class-rop-bluesky-api.php | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/includes/admin/helpers/class-rop-bluesky-api.php b/includes/admin/helpers/class-rop-bluesky-api.php index da2cd566..34a1a304 100644 --- a/includes/admin/helpers/class-rop-bluesky-api.php +++ b/includes/admin/helpers/class-rop-bluesky-api.php @@ -290,12 +290,19 @@ public function create_post( $did, $post, $post_type, $hashtags, $access_token = $now = gmdate( 'Y-m-d\TH:i:s\Z' ); + $text = $post['content'] . $hashtags; + $record = array( '$type' => 'app.bsky.feed.post', - 'text' => $post['content'] . $hashtags, + 'text' => $text, 'createdAt' => $now, ); + $facets = $this->generate_facets( $text ); + if ( ! empty( $facets ) ) { + $record['facets'] = $facets; + } + if ( $post_type === 'link' && isset( $post['post_url'] ) && ! empty( $post['post_url'] ) ) { $record['embed'] = array( '$type' => 'app.bsky.embed.external', @@ -369,6 +376,41 @@ public function create_post( $did, $post, $post_type, $hashtags, $access_token = } } + /** + * Generate facets for hashtags. + * + * @param string $text The text to parse. + * + * @return mixed|array + */ + private function generate_facets( $text ) { + $facets = array(); + preg_match_all( '/#\S+/', $text, $matches, PREG_OFFSET_CAPTURE ); + + if ( ! empty( $matches[0] ) ) { + foreach ( $matches[0] as $match ) { + $hashtag = $match[0]; + $start = $match[1]; + $end = $start + strlen( $hashtag ); + + $facets[] = array( + 'index' => array( + 'byteStart' => $start, + 'byteEnd' => $end, + ), + 'features' => array( + array( + '$type' => 'app.bsky.richtext.facet#tag', + 'tag' => ltrim( $hashtag, '#' ), + ), + ), + ); + } + } + + return $facets; + } + /** * Fetch Website Card embeds. * From 1d099cb0b1ac9ac7d0c2c04b8f86b35f8a6ed308 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Tue, 30 Dec 2025 16:05:32 +0530 Subject: [PATCH 03/11] fix: handle multiple GMB accounts for post sharing --- .../admin/services/class-rop-gmb-service.php | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/includes/admin/services/class-rop-gmb-service.php b/includes/admin/services/class-rop-gmb-service.php index 64cf12e6..ca742c1a 100644 --- a/includes/admin/services/class-rop-gmb-service.php +++ b/includes/admin/services/class-rop-gmb-service.php @@ -210,13 +210,11 @@ public function add_account_with_app( $accounts_data ) { * * @since 8.5.9 * @access private + * @param array $arguments Arguments needed by the method. * * @return array */ - private function gmb_refresh_access_token() { - - $rop_data = get_option( 'rop_data' ); - $rop_services_data = $rop_data['services']; + private function gmb_refresh_access_token( $arguments ) { $id = ''; $access_token = ''; @@ -225,26 +223,51 @@ private function gmb_refresh_access_token() { $gmb_service_id = ''; $refresh_token = ''; + $credentials = isset( $arguments['credentials'] ) ? $arguments['credentials'] : array(); + + if ( ! empty( $credentials ) ) { + $created = isset( $credentials['created'] ) ? $credentials['created'] : ''; + $expires_in = isset( $credentials['expires_in'] ) ? $credentials['expires_in'] : ''; + $access_token = isset( $credentials['access_token'] ) ? $credentials['access_token'] : ''; + + // Check if access token will expire in next 30 seconds. + $expired = ( $created + ( $expires_in - 30 ) ) < time(); + + // If it's not expired then return current access token in DB + if ( ! $expired ) { + // Add an expires_in value to prevent Google Client PHP notice for undefined expires_in index + $access_token = array( 'access_token' => $access_token, 'expires_in' => $expires_in ); + return $access_token; + } + } + + $rop_data = get_option( 'rop_data' ); + $rop_services_data = isset( $rop_data['services'] ) ? $rop_data['services'] : array(); + $account_id = isset( $arguments['id'] ) ? $arguments['id'] : ''; + foreach ( $rop_services_data as $service => $service_data ) { if ( $service_data['service'] === 'gmb' ) { - $id = $service_data['id']; - $access_token = $service_data['credentials']['access_token']; - $created = $service_data['credentials']['created']; - $expires_in = $service_data['credentials']['expires_in']; - $gmb_service_id = $service; - $refresh_token = $service_data['credentials']['refresh_token']; - break; + foreach ( $service_data['available_accounts'] as $account ) { + if ( $account_id === $account['id'] ) { + $id = $service_data['id']; + $access_token = $service_data['credentials']['access_token']; + $created = $service_data['credentials']['created']; + $expires_in = $service_data['credentials']['expires_in']; + $gmb_service_id = $service; + $refresh_token = $service_data['credentials']['refresh_token']; + break; + } + } } } - // $created = '1593273390'; // Check if access token will expire in next 30 seconds. $expired = ( $created + ( $expires_in - 30 ) ) < time(); // If it's not expired then return current access token in DB if ( ! $expired ) { // Add an expires_in value to prevent Google Client PHP notice for undefined expires_in index - $access_token = array('access_token' => $access_token, 'expires_in' => $expires_in); + $access_token = array( 'access_token' => $access_token, 'expires_in' => $expires_in ); return $access_token; } @@ -488,7 +511,7 @@ public function share( $post_details, $args = array() ) { } } - $access_token = $this->gmb_refresh_access_token(); + $access_token = $this->gmb_refresh_access_token( $args ); $client->setAccessToken( $access_token ); $client->setApiFormatV2( true ); From b5a4bd4e3ff3176220d02ae588ac5b1c8ca402ba Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Tue, 30 Dec 2025 18:51:32 +0530 Subject: [PATCH 04/11] feat: add cleanup records --- .../abstract/class-rop-model-abstract.php | 2 +- includes/admin/class-rop-rest-api.php | 14 +++ .../admin/models/class-rop-services-model.php | 23 ++++ includes/class-rop-i18n.php | 4 + vue/src/models/rop_store.js | 3 + vue/src/vue-elements/accounts-tab-panel.vue | 102 +++++++++++++++++- 6 files changed, 145 insertions(+), 3 deletions(-) diff --git a/includes/admin/abstract/class-rop-model-abstract.php b/includes/admin/abstract/class-rop-model-abstract.php index ea9a4718..f3a8238d 100644 --- a/includes/admin/abstract/class-rop-model-abstract.php +++ b/includes/admin/abstract/class-rop-model-abstract.php @@ -92,7 +92,7 @@ protected function set( $key, $value = '', $refresh = false ) { $this->data[ $key ] = apply_filters( 'rop_set_key_' . $key, $value ); - return update_option( $this->namespace, $this->data ); + return update_option( $this->namespace, $this->data, false ); } diff --git a/includes/admin/class-rop-rest-api.php b/includes/admin/class-rop-rest-api.php index c90649aa..e9d11892 100644 --- a/includes/admin/class-rop-rest-api.php +++ b/includes/admin/class-rop-rest-api.php @@ -1642,6 +1642,20 @@ private function add_account_bluesky( $data ) { return $this->response->to_array(); } + /** + * API method called to cleanup services. + * + * @access private + * @return array + */ + public function cleanup_accounts() { + $model = new Rop_Services_Model(); + $this->response->set_code( '200' ) + ->set_data( $model->cleanup_accounts() ); + + return $this->response->to_array(); + } + /** * Share API method. * diff --git a/includes/admin/models/class-rop-services-model.php b/includes/admin/models/class-rop-services-model.php index 0bb8a3f7..a0201668 100644 --- a/includes/admin/models/class-rop-services-model.php +++ b/includes/admin/models/class-rop-services-model.php @@ -37,6 +37,13 @@ class Rop_Services_Model extends Rop_Model_Abstract { */ private $accounts_namespace = 'active_accounts'; + /** + * Cleanup account count. + * + * @access private + * @var int $cleanup_account_count + */ + private $cleanup_account_count = 1000; /** * Utility method to clear authenticated services. @@ -520,4 +527,20 @@ public function find_account( $account_id ) { return false; } + /** + * Utility method to cleanup authenticated services. + * + * @access public + * @return array + */ + public function cleanup_accounts() { + $services = $this->get( $this->services_namespace ); + + if ( count( $services ) > $this->cleanup_account_count ) { + $services = array_slice( $services, -( $this->cleanup_account_count ), null, true ); + $this->set( $this->services_namespace, $services ); + } + + return $services; + } } diff --git a/includes/class-rop-i18n.php b/includes/class-rop-i18n.php index 338b1f06..74323f09 100644 --- a/includes/class-rop-i18n.php +++ b/includes/class-rop-i18n.php @@ -139,6 +139,10 @@ public static function get_labels( $key = '' ) { 'upsell_bz_service_body' => __( 'We\'re sorry, %1$s is not available on your plan. Please upgrade to the business plan to unlock all these features and get more traffic.', 'tweet-old-post' ), 'search_account' => __( 'Search account', 'tweet-old-post' ), 'no_account_found' => __( 'No account found for search', 'tweet-old-post' ), + 'cleanup_cta' => __( 'Cleanup accounts', 'tweet-old-post' ), + 'cleanup_title' => __( 'Clean Up Accounts', 'tweet-old-post' ), + 'cleanup_description' => __( 'This will remove older account records and keep only the most recent 1,000 accounts. This helps improve performance and reduce stored data.', 'tweet-old-post' ), + 'cleanup_now' => __( 'Cleanup now', 'tweet-old-post' ), ), 'settings' => array( 'yes_text' => __( 'Yes', 'tweet-old-post' ), diff --git a/vue/src/models/rop_store.js b/vue/src/models/rop_store.js index a8226267..81ff218b 100644 --- a/vue/src/models/rop_store.js +++ b/vue/src/models/rop_store.js @@ -270,6 +270,9 @@ export default new Vuex.Store({ case 'toggle_tracking': break + case 'cleanup_accounts': + state.authenticatedServices = stateData; + break; default: Vue.$log.error('No state request for ', requestName); } diff --git a/vue/src/vue-elements/accounts-tab-panel.vue b/vue/src/vue-elements/accounts-tab-panel.vue index ad2cb37b..8eb00b26 100644 --- a/vue/src/vue-elements/accounts-tab-panel.vue +++ b/vue/src/vue-elements/accounts-tab-panel.vue @@ -89,6 +89,20 @@
+
+ @@ -142,7 +182,8 @@ labels: this.$store.state.labels.accounts, upsell_link: ropApiSettings.upsell_link, pro_installed: ropApiSettings.pro_installed, - postTimeout: '' + postTimeout: '', + cleanupModal: false, } }, computed: { @@ -189,7 +230,12 @@ }, hasActiveAccountsLimitation: function () { return !this.pro_installed && this.accountsCount >= 2 && this.checkLicense ; - } + }, + cleanupModalClass: function () { + return { + 'active': this.cleanupModal === true + } + }, }, mounted: function () { if (0 === this.is_preloading) { @@ -228,6 +274,33 @@ Vue.$log.error('Got nothing from server. Prompt user to check internet connection and try again', error) }) }, + openCleanupModal: function() { + this.cleanupModal = true; + }, + closeCleanupModal: function() { + this.cleanupModal = false; + }, + cleanupAccount: function() { + if (this.is_loading) { + this.$log.warn('Request in progress...Bail'); + return; + } + this.is_loading = true; + this.$store.dispatch('fetchAJAXPromise', { + req: 'cleanup_accounts', + data: {} + }).then(response => { + this.is_loading = false; + this.cleanupModal = false; + if (this.$parent.start_status === true) { + // Stop sharing process if enabled. + this.$parent.togglePosting(); + } + }, error => { + this.is_loading = false; + Vue.$log.error('Got nothing from server. Prompt user to check internet connection and try again', error) + }) + }, filteredAccounts: function(accounts) { const result = {}; const query = this.searchAccount?.toLowerCase() || ''; @@ -273,6 +346,31 @@ margin-bottom: 15px; } + #rop_core .rop-cleanup-modal .modal-container{ + max-width: 500px; + padding: 25px; + + .modal-title, .modal-footer{ + text-align: center; + } + .btn-success{ + border:none; + background-color:#00a32a; + color: #fff; + padding: 0.5rem 1rem; + height: auto; + display: inline; + } + .btn-success:hover{ + background-color:#009528; + } + .modal-body{ + font-size: 0.7rem; + margin: 10px 30px; + padding: 0px; + } + } + @media ( max-width: 600px ) { #rop_core .panel-body .text-gray { margin-bottom: 10px; From 6a7a352493b02c670b9a88c4f234000a56c6d9fd Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Tue, 13 Jan 2026 17:48:46 +0530 Subject: [PATCH 05/11] feat: add cleanup logs --- includes/admin/class-rop-logger.php | 29 ++++- includes/admin/class-rop-rest-api.php | 8 +- .../admin/models/class-rop-services-model.php | 25 ----- includes/class-rop-i18n.php | 11 +- vue/src/models/rop_store.js | 5 +- vue/src/vue-elements/accounts-tab-panel.vue | 98 ----------------- vue/src/vue-elements/logs-tab-panel.vue | 100 +++++++++++++++++- 7 files changed, 140 insertions(+), 136 deletions(-) diff --git a/includes/admin/class-rop-logger.php b/includes/admin/class-rop-logger.php index 209fc69d..63f40c4c 100644 --- a/includes/admin/class-rop-logger.php +++ b/includes/admin/class-rop-logger.php @@ -29,6 +29,14 @@ class Rop_Logger { */ private $stream; + /** + * Holds the log namespace. + * + * @access private + * @var string $log_namespace Defaults 'rop_logs'. + */ + private $log_namespace = 'rop_logs'; + /** * Rop_Logger constructor. * Instantiate the Logger with specified formatter and stream. @@ -38,7 +46,7 @@ class Rop_Logger { */ public function __construct() { - $this->stream = new Rop_Log_Handler( 'rop_logs' ); + $this->stream = new Rop_Log_Handler( $this->log_namespace ); } @@ -230,4 +238,23 @@ public function is_status_error_necessary( $logs = array() ) { } + /** + * Utility method to cleanup authenticated services. + * + * @access public + * + * @param int $cleanup_log_count Cleanup the log count. + * @return array + */ + public function cleanup_logs( $cleanup_log_count = 1000 ) { + $logs = $this->stream->get_logs(); + + if ( count( $logs ) > $cleanup_log_count ) { + $logs = array_slice( $logs, 0, $cleanup_log_count ); + + update_option( $this->log_namespace, array_reverse( $logs ), false ); + } + + return $logs; + } } diff --git a/includes/admin/class-rop-rest-api.php b/includes/admin/class-rop-rest-api.php index e9d11892..85dac17c 100644 --- a/includes/admin/class-rop-rest-api.php +++ b/includes/admin/class-rop-rest-api.php @@ -1643,15 +1643,15 @@ private function add_account_bluesky( $data ) { } /** - * API method called to cleanup services. + * API method called to cleanup logs. * * @access private * @return array */ - public function cleanup_accounts() { - $model = new Rop_Services_Model(); + private function cleanup_logs() { + $model = new Rop_Logger(); $this->response->set_code( '200' ) - ->set_data( $model->cleanup_accounts() ); + ->set_data( $model->cleanup_logs() ); return $this->response->to_array(); } diff --git a/includes/admin/models/class-rop-services-model.php b/includes/admin/models/class-rop-services-model.php index a0201668..2c589557 100644 --- a/includes/admin/models/class-rop-services-model.php +++ b/includes/admin/models/class-rop-services-model.php @@ -37,14 +37,6 @@ class Rop_Services_Model extends Rop_Model_Abstract { */ private $accounts_namespace = 'active_accounts'; - /** - * Cleanup account count. - * - * @access private - * @var int $cleanup_account_count - */ - private $cleanup_account_count = 1000; - /** * Utility method to clear authenticated services. * @@ -526,21 +518,4 @@ public function find_account( $account_id ) { return false; } - - /** - * Utility method to cleanup authenticated services. - * - * @access public - * @return array - */ - public function cleanup_accounts() { - $services = $this->get( $this->services_namespace ); - - if ( count( $services ) > $this->cleanup_account_count ) { - $services = array_slice( $services, -( $this->cleanup_account_count ), null, true ); - $this->set( $this->services_namespace, $services ); - } - - return $services; - } } diff --git a/includes/class-rop-i18n.php b/includes/class-rop-i18n.php index 74323f09..1ce36331 100644 --- a/includes/class-rop-i18n.php +++ b/includes/class-rop-i18n.php @@ -139,10 +139,6 @@ public static function get_labels( $key = '' ) { 'upsell_bz_service_body' => __( 'We\'re sorry, %1$s is not available on your plan. Please upgrade to the business plan to unlock all these features and get more traffic.', 'tweet-old-post' ), 'search_account' => __( 'Search account', 'tweet-old-post' ), 'no_account_found' => __( 'No account found for search', 'tweet-old-post' ), - 'cleanup_cta' => __( 'Cleanup accounts', 'tweet-old-post' ), - 'cleanup_title' => __( 'Clean Up Accounts', 'tweet-old-post' ), - 'cleanup_description' => __( 'This will remove older account records and keep only the most recent 1,000 accounts. This helps improve performance and reduce stored data.', 'tweet-old-post' ), - 'cleanup_now' => __( 'Cleanup now', 'tweet-old-post' ), ), 'settings' => array( 'yes_text' => __( 'Yes', 'tweet-old-post' ), @@ -363,6 +359,13 @@ public static function get_labels( $key = '' ) { 'clear_btn' => __( 'Clear logs', 'tweet-old-post' ), 'no_logs' => __( 'No recent logs!', 'tweet-old-post' ), 'export_btn' => __( 'Export logs', 'tweet-old-post' ), + 'cleanup' => array( + 'cta' => __( 'Cleanup logs', 'tweet-old-post' ), + 'title' => __( 'Cleanup logs', 'tweet-old-post' ), + 'description' => __( 'This will remove older logs and keep only the most recent 1,000 logs. This helps improve performance and reduce stored data.', 'tweet-old-post' ), + 'btn' => __( 'Cleanup now', 'tweet-old-post' ), + ) + ), 'general' => array( 'plugin_name' => __( 'Revive Social', 'tweet-old-post' ), diff --git a/vue/src/models/rop_store.js b/vue/src/models/rop_store.js index 81ff218b..1d7b4748 100644 --- a/vue/src/models/rop_store.js +++ b/vue/src/models/rop_store.js @@ -270,8 +270,9 @@ export default new Vuex.Store({ case 'toggle_tracking': break - case 'cleanup_accounts': - state.authenticatedServices = stateData; + case 'cleanup_logs': + state.page.logs = stateData; + state.cron_status.logs_number = stateData.length || 0; break; default: Vue.$log.error('No state request for ', requestName); diff --git a/vue/src/vue-elements/accounts-tab-panel.vue b/vue/src/vue-elements/accounts-tab-panel.vue index 8eb00b26..85835194 100644 --- a/vue/src/vue-elements/accounts-tab-panel.vue +++ b/vue/src/vue-elements/accounts-tab-panel.vue @@ -89,20 +89,6 @@
-
- @@ -183,7 +143,6 @@ upsell_link: ropApiSettings.upsell_link, pro_installed: ropApiSettings.pro_installed, postTimeout: '', - cleanupModal: false, } }, computed: { @@ -231,11 +190,6 @@ hasActiveAccountsLimitation: function () { return !this.pro_installed && this.accountsCount >= 2 && this.checkLicense ; }, - cleanupModalClass: function () { - return { - 'active': this.cleanupModal === true - } - }, }, mounted: function () { if (0 === this.is_preloading) { @@ -274,33 +228,6 @@ Vue.$log.error('Got nothing from server. Prompt user to check internet connection and try again', error) }) }, - openCleanupModal: function() { - this.cleanupModal = true; - }, - closeCleanupModal: function() { - this.cleanupModal = false; - }, - cleanupAccount: function() { - if (this.is_loading) { - this.$log.warn('Request in progress...Bail'); - return; - } - this.is_loading = true; - this.$store.dispatch('fetchAJAXPromise', { - req: 'cleanup_accounts', - data: {} - }).then(response => { - this.is_loading = false; - this.cleanupModal = false; - if (this.$parent.start_status === true) { - // Stop sharing process if enabled. - this.$parent.togglePosting(); - } - }, error => { - this.is_loading = false; - Vue.$log.error('Got nothing from server. Prompt user to check internet connection and try again', error) - }) - }, filteredAccounts: function(accounts) { const result = {}; const query = this.searchAccount?.toLowerCase() || ''; @@ -346,31 +273,6 @@ margin-bottom: 15px; } - #rop_core .rop-cleanup-modal .modal-container{ - max-width: 500px; - padding: 25px; - - .modal-title, .modal-footer{ - text-align: center; - } - .btn-success{ - border:none; - background-color:#00a32a; - color: #fff; - padding: 0.5rem 1rem; - height: auto; - display: inline; - } - .btn-success:hover{ - background-color:#009528; - } - .modal-body{ - font-size: 0.7rem; - margin: 10px 30px; - padding: 0px; - } - } - @media ( max-width: 600px ) { #rop_core .panel-body .text-gray { margin-bottom: 10px; diff --git a/vue/src/vue-elements/logs-tab-panel.vue b/vue/src/vue-elements/logs-tab-panel.vue index 2f8cd1de..720898bb 100644 --- a/vue/src/vue-elements/logs-tab-panel.vue +++ b/vue/src/vue-elements/logs-tab-panel.vue @@ -29,6 +29,21 @@ /> {{ labels.clear_btn }} +
@@ -70,6 +85,32 @@
+ @@ -86,6 +127,7 @@ is_loading: false, labels: this.$store.state.labels.logs, upsell_link: ropApiSettings.upsell_link, + cleanupModal: false, } }, computed: { @@ -95,6 +137,11 @@ logs_no: function () { return this.$store.state.cron_status.logs_number; }, + cleanupModalClass: function() { + return { + 'active': true === this.cleanupModal + } + } }, watch: { logs_no: function () { @@ -153,8 +200,34 @@ document.body.appendChild(element); element.click(); document.body.removeChild(element); - } - + }, + openCleanupModal() { + this.cleanupModal = true; + }, + closeCleanupModal() { + this.cleanupModal = false; + }, + cleanupLogs: function() { + if (this.is_loading) { + this.$log.warn('Request in progress...Bail'); + return; + } + this.is_loading = true; + this.$store.dispatch('fetchAJAXPromise', { + req: 'cleanup_logs', + data: {} + }).then(response => { + this.is_loading = false; + this.cleanupModal = false; + if (this.$parent.start_status === true) { + // Stop sharing process if enabled. + this.$parent.togglePosting(); + } + }, error => { + this.is_loading = false; + Vue.$log.error('Got nothing from server. Prompt user to check internet connection and try again', error) + }) + }, }, } @@ -208,4 +281,27 @@ background-color: #FBE8E8; } } + #rop_core .rop-cleanup-modal .modal-container{ + max-width: 500px; + padding: 25px; + .modal-title, .modal-footer{ + text-align: center; + } + .btn-success{ + border:none; + background-color:#00a32a; + color: #fff; + padding: 0.5rem 1rem; + height: auto; + display: inline; + } + .btn-success:hover{ + background-color:#009528; + } + .modal-body{ + font-size: 0.7rem; + margin: 10px 30px; + padding: 0px; + } + } From 9253d5d48e59f5eb60648ead0ceb3e2f51f254f3 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Tue, 13 Jan 2026 17:52:36 +0530 Subject: [PATCH 06/11] fix: phpcs --- includes/class-rop-i18n.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/includes/class-rop-i18n.php b/includes/class-rop-i18n.php index 1ce36331..be8ac60b 100644 --- a/includes/class-rop-i18n.php +++ b/includes/class-rop-i18n.php @@ -364,8 +364,7 @@ public static function get_labels( $key = '' ) { 'title' => __( 'Cleanup logs', 'tweet-old-post' ), 'description' => __( 'This will remove older logs and keep only the most recent 1,000 logs. This helps improve performance and reduce stored data.', 'tweet-old-post' ), 'btn' => __( 'Cleanup now', 'tweet-old-post' ), - ) - + ), ), 'general' => array( 'plugin_name' => __( 'Revive Social', 'tweet-old-post' ), From c90f11a245ea71d483e2211bb61aa97a0261bc2a Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Fri, 16 Jan 2026 11:04:01 +0530 Subject: [PATCH 07/11] fix: formatting --- vue/src/vue-elements/logs-tab-panel.vue | 120 ++++++++++++------------ 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/vue/src/vue-elements/logs-tab-panel.vue b/vue/src/vue-elements/logs-tab-panel.vue index 720898bb..665cc8a1 100644 --- a/vue/src/vue-elements/logs-tab-panel.vue +++ b/vue/src/vue-elements/logs-tab-panel.vue @@ -105,9 +105,11 @@ @@ -127,7 +129,7 @@ is_loading: false, labels: this.$store.state.labels.logs, upsell_link: ropApiSettings.upsell_link, - cleanupModal: false, + cleanupModal: false, } }, computed: { @@ -137,11 +139,11 @@ logs_no: function () { return this.$store.state.cron_status.logs_number; }, - cleanupModalClass: function() { - return { - 'active': true === this.cleanupModal - } - } + cleanupModalClass: function() { + return { + 'active': true === this.cleanupModal + } + } }, watch: { logs_no: function () { @@ -201,33 +203,33 @@ element.click(); document.body.removeChild(element); }, - openCleanupModal() { - this.cleanupModal = true; - }, - closeCleanupModal() { - this.cleanupModal = false; - }, - cleanupLogs: function() { - if (this.is_loading) { - this.$log.warn('Request in progress...Bail'); - return; - } - this.is_loading = true; - this.$store.dispatch('fetchAJAXPromise', { - req: 'cleanup_logs', - data: {} - }).then(response => { - this.is_loading = false; - this.cleanupModal = false; - if (this.$parent.start_status === true) { - // Stop sharing process if enabled. - this.$parent.togglePosting(); - } - }, error => { - this.is_loading = false; - Vue.$log.error('Got nothing from server. Prompt user to check internet connection and try again', error) - }) - }, + openCleanupModal() { + this.cleanupModal = true; + }, + closeCleanupModal() { + this.cleanupModal = false; + }, + cleanupLogs: function() { + if (this.is_loading) { + this.$log.warn('Request in progress...Bail'); + return; + } + this.is_loading = true; + this.$store.dispatch('fetchAJAXPromise', { + req: 'cleanup_logs', + data: {} + }).then(response => { + this.is_loading = false; + this.cleanupModal = false; + if (this.$parent.start_status === true) { + // Stop sharing process if enabled. + this.$parent.togglePosting(); + } + }, error => { + this.is_loading = false; + Vue.$log.error('Got nothing from server. Prompt user to check internet connection and try again', error) + }) + }, }, } @@ -281,27 +283,27 @@ background-color: #FBE8E8; } } - #rop_core .rop-cleanup-modal .modal-container{ - max-width: 500px; - padding: 25px; - .modal-title, .modal-footer{ - text-align: center; - } - .btn-success{ - border:none; - background-color:#00a32a; - color: #fff; - padding: 0.5rem 1rem; - height: auto; - display: inline; - } - .btn-success:hover{ - background-color:#009528; - } - .modal-body{ - font-size: 0.7rem; - margin: 10px 30px; - padding: 0px; - } - } + #rop_core .rop-cleanup-modal .modal-container{ + max-width: 500px; + padding: 25px; + .modal-title, .modal-footer{ + text-align: center; + } + .btn-success{ + border:none; + background-color:#00a32a; + color: #fff; + padding: 0.5rem 1rem; + height: auto; + display: inline; + } + .btn-success:hover{ + background-color:#009528; + } + .modal-body{ + font-size: 0.7rem; + margin: 10px 30px; + padding: 0px; + } + } From 17c2e847ce330c45a4489e0afa7926d1ae3ae95d Mon Sep 17 00:00:00 2001 From: Soare Robert Daniel Date: Wed, 25 Feb 2026 16:52:08 +0200 Subject: [PATCH 08/11] dev: add AGENTS.md (#1074) --- .distignore | 3 +- AGENTS.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/.distignore b/.distignore index 832ab962..8907a00e 100644 --- a/.distignore +++ b/.distignore @@ -32,4 +32,5 @@ vue webpack.config.js docker-compose.yml phpstan.neon -phpstan-baseline.neon \ No newline at end of file +phpstan-baseline.neon +AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..68a787df --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,106 @@ +# Agent Workflow + +## Project Overview + +**Revive Old Posts (Revive Social)** — a WordPress plugin that automatically shares WordPress posts to social networks (Facebook, X/Twitter, LinkedIn, Instagram, Telegram, Mastodon, BlueSky, VK, Pinterest, TikTok, etc.) with scheduling and automation. + +- **Text Domain:** `tweet-old-post` +- **Main Plugin File:** `tweet-old-post.php` +- **Pro Companion Plugin:** `tweet-old-post-pro` (separate repo) + +## Build & Development Commands + +```bash +# Install dependencies +npm ci +composer install + +# Build assets (Vue dashboard + React sharing panel) +npm run build # Production webpack build (Vue) +npm run sharing # Production wp-scripts build (React) + +# Development with watch +npm run dev # Webpack watch mode (Vue) +npm run sharing-dev # wp-scripts dev mode (React) + +# Linting +composer run lint # PHPCS (WordPress-Core standard) +composer run format # Auto-fix PHP code style +npm run lint # ESLint for Vue + React files +npm run format # ESLint auto-fix + +# Testing +composer run test # PHPUnit (all suites) +./vendor/bin/phpunit tests/test-content.php # Single test file +composer run phpstan # Static analysis (level 6) + +# E2E Testing (requires wp-env) +npm run wp-env start # Start WordPress environment +npm run test:e2e:playwright # Run Playwright E2E tests +npm run test:e2e:playwright:ui # E2E with Playwright UI mode + +# Distribution +npm run dist # Create distribution ZIP archive +``` + +## Architecture + +### Entry Point & Autoloading + +`tweet-old-post.php` defines constants (prefixed `ROP_`), registers activation/deactivation hooks, sets up a custom autoloader (`class-rop-autoloader.php`), and calls `run_rop()` which instantiates the `Rop` core class. + +The autoloader scans `includes/` recursively, matching classes by the `Rop` namespace prefix. Class files follow the convention `class-{slug-case-name}.php`. + +### Core Class Hierarchy + +- **`Rop`** (`includes/class-rop.php`) — Core plugin class. Loads dependencies, sets up i18n, defines admin hooks. +- **`Rop_Loader`** (`includes/class-rop-loader.php`) — Central hook registration system. Actions/filters are queued then bulk-registered. +- **`Rop_Admin`** (`includes/admin/class-rop-admin.php`) — Admin UI, script/style enqueuing, menu registration. ~2,000 lines. +- **`Rop_Rest_Api`** (`includes/admin/class-rop-rest-api.php`) — REST endpoints at `tweet-old-post/v8/api` and `tweet-old-post/v8/share/{id}`. Requires `manage_options` capability. ~1,700 lines. + +### Service Layer (Social Networks) + +`includes/admin/services/` — Each social network has a service class extending `Rop_Services_Abstract` (Strategy pattern): +- Key methods: `get_service_credentials()`, `login()`, `publish()`, `get_account()` +- Services: Twitter, Facebook, LinkedIn, Mastodon, BlueSky, Telegram, VK, Pinterest, Tumblr, GMB, Webhook + +### Models (Data Layer) + +`includes/admin/models/` — Settings, services, queue, scheduler, post format, post selector, URL shorteners. Models store data in WordPress options. + +### Helpers + +`includes/admin/helpers/` — Content manipulation, post formatting, cron scheduling, DB migrations, logging, custom API clients (Telegram, BlueSky). + +### URL Shorteners + +`includes/admin/shortners/` — Each shortener extends `Rop_Url_Shortner_Abstract` (Bitly, Firebase, Rebrandly, is.gd, ow.ly, rviv.ly). + +### Frontend (Two Systems) + +1. **Vue 2 Dashboard** (legacy) — Built via `webpack.config.js`. Entry points in `vue/src/` → output to `assets/js/build/`. Uses Vuex for state, vue-resource for HTTP. +2. **React Components** (new) — Built via `webpack.sharing.config.js` using `@wordpress/scripts`. Source in `src/` → output to `assets/js/react/build/`. Used for instant/manual sharing in the block editor. + +### Cron System + +`cron-system/` — Alternative remote cron implementation (`RopCronSystem\Rop_Cron_Core`). Activated when `ROP_CRON_ALTERNATIVE` is true (controlled by `rop_use_remote_cron` option). + +### External Auth + +Social network authentication goes through `ROP_AUTH_APP_URL` (`https://app.revive.social`) with per-service paths (`/fb_auth`, `/tw_auth`, `/li_auth`, etc.). + +## Coding Standards + +- **PHP:** WordPress-Core via PHPCS (`phpcs.xml`) with many naming convention rules relaxed — camelCase variables/methods are allowed throughout the codebase +- **JS (Vue):** `plugin:vue/recommended` with babel-eslint parser +- **JS (React):** `@wordpress/eslint-plugin/recommended` with text domain enforced as `tweet-old-post` +- **Static Analysis:** PHPStan level 6 with WordPress extension and baseline (`phpstan-baseline.neon`) + +## Testing Structure + +PHPUnit test suites are defined in `phpunit.xml` with individual files in `tests/`: +- `test-plugin.php`, `test-accounts.php`, `test-content.php`, `test-logger.php`, `test-post-format.php`, `test-queue.php`, `test-scheduler.php`, `test-selector.php` + +E2E tests use Playwright with `@wordpress/e2e-test-utils-playwright`. Specs live in `tests/e2e/specs/`. Config at `tests/e2e/playwright.config.js`. + +PHPUnit bootstrap (`tests/bootstrap.php`) requires WordPress test suite via `WP_TESTS_DIR` env var. From 5590da16829837eac2e8b35f9132beee158f1f26 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Mon, 16 Mar 2026 20:02:23 +0530 Subject: [PATCH 09/11] fix: update sharing status label --- vue/src/vue-elements/reusables/status-box.vue | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vue/src/vue-elements/reusables/status-box.vue b/vue/src/vue-elements/reusables/status-box.vue index 517d1a45..20d975b5 100644 --- a/vue/src/vue-elements/reusables/status-box.vue +++ b/vue/src/vue-elements/reusables/status-box.vue @@ -56,11 +56,12 @@ export default { required: true } }, - data() { - const [ title, description ] = this.label.split( ':' ) - return { - title, - description + computed: { + title() { + return this.label.split(':')[0] + }, + description() { + return this.label.split(':')[1] } } } From 1b4d89f2d828d50a44b0e53e61fea6d20362e43e Mon Sep 17 00:00:00 2001 From: Soare Robert Daniel Date: Fri, 3 Apr 2026 15:21:22 +0300 Subject: [PATCH 10/11] refactor: update Black Friday labels (#1076) --- includes/admin/class-rop-admin.php | 50 +++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/includes/admin/class-rop-admin.php b/includes/admin/class-rop-admin.php index 3b937035..013e5baa 100644 --- a/includes/admin/class-rop-admin.php +++ b/includes/admin/class-rop-admin.php @@ -1852,6 +1852,12 @@ public function get_survey_metadata() { * @return array */ public function rop_upgrade_to_pro_plugin_action( $actions, $plugin_file ) { + + $is_black_friday = apply_filters( 'themeisle_sdk_is_black_friday_sale', false ); + if ( $is_black_friday ) { + return $actions; + } + $global_settings = new \Rop_Global_Settings(); $actions['settings'] = '' . __( 'Settings', 'tweet-old-post' ) . ''; if ( $global_settings->license_type() < 1 ) { @@ -1908,29 +1914,51 @@ public function get_languages() { public static function add_black_friday_data( $configs ) { $config = $configs['default']; - // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name. - $message_template = __( 'Our biggest sale of the year: %1$sup to %2$s OFF%3$s on %4$s. Don\'t miss this limited-time offer.', 'tweet-old-post' ); - $product_label = __( 'Revive Social', 'tweet-old-post' ); - $discount = '50%'; + $message = __( 'Custom schedules, multiple accounts, analytics. Automate your social sharing properly. Exclusively for existing Revive Social users.', 'tweet-old-post' ); + $cta_label = __( 'Get Revive Social Pro', 'tweet-old-post' ); $plan = apply_filters( 'product_rop_license_plan', 0 ); $license = apply_filters( 'product_rop_license_key', false ); - $is_pro = 0 < $plan; + $status = apply_filters( 'product_rop_license_status', false ); + $is_pro = 'valid' === $status; + $is_expired = 'expired' === $status || 'active-expired' === $status; + + $pro_product_slug = defined( 'ROP_PRO_BASEFILE' ) ? basename( dirname( ROP_PRO_BASEFILE ) ) : ''; + + if ( $is_pro || $is_expired ) { + $config['plugin_meta_targets'] = array( $pro_product_slug ); + } if ( $is_pro ) { - // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name. - $message_template = __( 'Get %1$sup to %2$s off%3$s when you upgrade your %4$s plan or renew early.', 'tweet-old-post' ); - $product_label = __( 'Revive Social Pro', 'tweet-old-post' ); - $discount = '20%'; + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - up to %s off', 'tweet-old-post' ), '30%' ); + // translators: %1$s - discount, %2$s - discount. + $message = sprintf( __( 'Upgrade your Revive Social Pro plan: %1$s off this week. Already on the plan you need? Renew early and save up to %2$s.', 'tweet-old-post' ), '30%', '20%' ); + $cta_label = __( 'See your options', 'tweet-old-post' ); + } elseif ( $is_expired ) { + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'tweet-old-post' ), '50%' ); + // translators: %s - discount. + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'tweet-old-post' ), '50%' ); + $message = __( 'Your Revive Social Pro features are still here, just locked. Renew at a reduced rate this week.', 'tweet-old-post' ); + $cta_label = __( 'Reactivate now', 'tweet-old-post' ); + } else { + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'tweet-old-post' ), '50%' ); + // translators: %s - discount. + $config['title'] = sprintf( __( 'Revive Social Pro: %s off this week', 'tweet-old-post' ), '60%' ); + // translators: %s - discount. + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'tweet-old-post' ), '60%' ); } - $product_label = sprintf( '%s', $product_label ); $url_params = array( 'utm_term' => $is_pro ? 'plan-' . $plan : 'free', 'lkey' => ! empty( $license ) ? $license : false, + 'expired' => $is_expired ? '1' : false, ); - $config['message'] = sprintf( $message_template, '', $discount, '', $product_label ); + $config['message'] = $message; + $config['cta_label'] = $cta_label; $config['sale_url'] = add_query_arg( $url_params, tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/rs-bf', 'bfcm', 'revive' ) ) From 5d8f08b2ae623fbdc9bd224482d66daa3037494e Mon Sep 17 00:00:00 2001 From: vytisbulkevicius Date: Fri, 3 Apr 2026 16:24:51 +0300 Subject: [PATCH 11/11] chore(deps): bump codeinwp/themeisle-sdk to 3.3.51 --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index ff0ea431..477fda73 100644 --- a/composer.lock +++ b/composer.lock @@ -8,21 +8,21 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.48", + "version": "3.3.51", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e" + "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e", - "reference": "0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/bb2a8414b0418b18c68c9ff1df3d7fb10467928d", + "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d", "shasum": "" }, "require-dev": { "codeinwp/phpcs-ruleset": "dev-main", - "yoast/phpunit-polyfills": "^2.0" + "yoast/phpunit-polyfills": "^4.0" }, "type": "library", "notification-url": "https://packagist.org/downloads/", @@ -36,16 +36,16 @@ "homepage": "https://themeisle.com" } ], - "description": "ThemeIsle SDK", + "description": "Themeisle SDK.", "homepage": "https://github.com/Codeinwp/themeisle-sdk", "keywords": [ "wordpress" ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.48" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.51" }, - "time": "2025-08-11T16:47:24+00:00" + "time": "2026-03-30T07:58:49+00:00" }, { "name": "codeinwp/twitteroauth",