Key value pair for setting options.
*/
public function get_fields(): array {
- $settings_start = [
- 'info-start' => [
- 'type' => 'html',
- 'html' => '',
- ]
- ];
-
- $settings_end = [
- 'info-end' => [
- 'type' => 'html',
- 'html' => '
',
- ]
- ];
-
- /**
- * Filters the fields in the Tickets > Settings > Integrations tab.
- * Utilizes the name from Event Tickets Plus as this is a replacement if that plugin is deactivated.
- *
- * @since 6.0.0 Migrated to Common from Event Automator
- *
- * @param array $fields The current fields.
- *
- * @return array The fields, as updated by the settings.
- */
- $fields = apply_filters( 'tec_tickets_plus_integrations_tab_fields', [] );
+ _deprecated_function( __METHOD__, '6.4.1' );
- return array_merge( $settings_start, $fields, $settings_end );
+ return [];
}
}
diff --git a/src/Common/Event_Automator/Admin/Tabs/Tabs_Provider.php b/src/Common/Event_Automator/Admin/Tabs/Tabs_Provider.php
index c878a1ed5b..5e3717f022 100644
--- a/src/Common/Event_Automator/Admin/Tabs/Tabs_Provider.php
+++ b/src/Common/Event_Automator/Admin/Tabs/Tabs_Provider.php
@@ -10,6 +10,8 @@
* @package TEC\Event_Automator\Admin\Tabs
*
* @since 6.0.0 Migrated to Common from Event Automator
+ *
+ * @deprecated 6.4.1
*/
class Tabs_Provider extends Service_Provider {
@@ -17,40 +19,33 @@ class Tabs_Provider extends Service_Provider {
* Register the provider.
*
* @since 6.0.0 Migrated to Common from Event Automator
+ *
+ * @deprecated 6.4.1
*/
public function register() {
-
- // If Plugin Settings does not exist, return as there is no settings tab to add.
- if ( ! class_exists('Tribe\Tickets\Admin\Settings', false ) ) {
- return;
- }
-
- // If Event Tickets Plus is Active, do not add the Integrations tab as it will do it.
- if ( class_exists('TEC\Tickets_Plus\Admin\Tabs\Provider', false ) ) {
- return;
- }
-
- // Hook actions and filters.
- $this->add_actions();
- $this->add_filters();
+ _deprecated_function( __METHOD__, '6.4.1' );
}
/**
* Add the action hooks.
*
* @since 6.0.0 Migrated to Common from Event Automator
+ *
+ * @deprecated 6.4.1
*/
public function add_actions() {
- add_action( 'tribe_settings_do_tabs', [ $this, 'add_tabs' ] );
+ _deprecated_function( __METHOD__, '6.4.1' );
}
/**
* Add fhe filter hooks.
*
* @since 6.0.0 Migrated to Common from Event Automator
+ *
+ * @deprecated 6.4.1
*/
public function add_filters() {
- add_filter( 'tec_tickets_settings_tabs_ids', [ $this, 'filter_include_integrations_tab_id' ] );
+ _deprecated_function( __METHOD__, '6.4.1' );
}
/**
@@ -58,12 +53,14 @@ public function add_filters() {
*
* @since 6.0.0 Migrated to Common from Event Automator
*
+ * @deprecated 6.4.1
+ *
* @param string Admin page id.
*
* @return void
*/
public function add_tabs( $admin_page ) {
- $this->container->make( Integrations::class )->register_tab( $admin_page );
+ _deprecated_function( __METHOD__, '6.4.1' );
}
/**
@@ -71,11 +68,14 @@ public function add_tabs( $admin_page ) {
*
* @since 6.0.0 Migrated to Common from Event Automator
*
+ * @deprecated 6.4.1
+ *
* @param array $tabs Array of tabs IDs for the Events settings page.
*
* @return array The filtered list of tab ids.
*/
public function filter_include_integrations_tab_id( array $tabs ): array {
- return $this->container->make( Integrations::class )->register_tab_id( $tabs );
+ _deprecated_function( __METHOD__, '6.4.1' );
+ return [];
}
}
diff --git a/src/Common/Event_Automator/Hooks.php b/src/Common/Event_Automator/Hooks.php
index 9e980bc2de..2f6055393e 100644
--- a/src/Common/Event_Automator/Hooks.php
+++ b/src/Common/Event_Automator/Hooks.php
@@ -41,7 +41,6 @@ public function register() {
$this->container->singleton( 'event-automator.hooks', $this );
add_action( 'admin_init', [ $this, 'run_updates' ], 10, 0 );
- add_action( 'admin_init', [ $this, 'admin_register' ], 0 );
}
/**
@@ -62,8 +61,9 @@ public function run_updates() {
* Register providers at admin_init, so dependencies are loaded.
*
* @since 6.0.0 Migrated to Common from Event Automator
+ * @deprecated 6.4.1
*/
public function admin_register() {
- $this->container->register( Tabs_Provider::class );
+ _deprecated_function( __METHOD__, '6.4.1' );
}
}
diff --git a/src/Common/Integrations/Plugin_Merge_Provider_Abstract.php b/src/Common/Integrations/Plugin_Merge_Provider_Abstract.php
index 904eaf22bb..384dcb3988 100644
--- a/src/Common/Integrations/Plugin_Merge_Provider_Abstract.php
+++ b/src/Common/Integrations/Plugin_Merge_Provider_Abstract.php
@@ -94,7 +94,7 @@ public function get_plugin_real_path(): string {
}
// Get plugin data.
- $plugin_data = get_plugin_data( $plugin_file_path );
+ $plugin_data = get_plugin_data( $plugin_file_path, false, false );
// Check for TextDomain and match.
if ( isset( $plugin_data['TextDomain'] ) && $plugin_data['TextDomain'] === $text_domain ) {
diff --git a/src/Common/Notifications/Conditionals.php b/src/Common/Notifications/Conditionals.php
new file mode 100644
index 0000000000..b2e457963a
--- /dev/null
+++ b/src/Common/Notifications/Conditionals.php
@@ -0,0 +1,186 @@
+normalize_optin_status();
+
+ // We don't care what the value stored in tribe_options is - give us Telemetry's Opt_In\Status value.
+ $status = Config::get_container()->get( Status::class );
+ $telemetry = $status->get() === $status::STATUS_ACTIVE;
+
+ // Check if the user has opted in to telemetry, then If Telemetry is off, return the IAN opt-in value.
+ return apply_filters( 'tec_common_ian_opt_in', tribe_is_truthy( $telemetry ) || tribe_is_truthy( tribe_get_option( 'ian-notifications-opt-in', false ) ) );
+ }
+
+ /**
+ * Check if the conditions are met for the notifications.
+ *
+ * @since 6.4.0
+ *
+ * @param array $feed The feed of notifications from the server.
+ *
+ * @return array The notifications that meet the conditions.
+ */
+ public static function filter_feed( $feed ): array {
+ $notifications = array_filter(
+ $feed,
+ function ( $item ) {
+ if ( empty( $item['conditions'] ) || ! is_array( $item['conditions'] ) ) {
+ return true;
+ }
+
+ $matches = [];
+ foreach ( $item['conditions'] as $condition ) {
+ if ( 0 === strpos( $condition, 'wp_version' ) ) {
+ $version = substr( $condition, strlen( 'wp_version' ) );
+
+ $matches['wp_version'] = self::check_wp_version( $version );
+ } elseif ( 0 === strpos( $condition, 'php_version' ) ) {
+ $version = substr( $condition, strlen( 'php_version' ) );
+
+ $matches['php_version'] = self::check_php_version( $version );
+ } elseif ( 0 === strpos( $condition, 'plugin_version' ) ) {
+ $split = explode( ':', $condition );
+ $plugins = explode( ',', $split[1] );
+
+ $matches['plugin_version'] = self::check_plugin_version( (array) $plugins );
+ }
+ }
+
+ return ! in_array( false, $matches, true );
+ }
+ );
+
+ // Ensure slugs are always unique.
+ $notifications = array_map(
+ function ( $item ) {
+ $item['slug'] = $item['id'] . '_' . $item['slug'];
+
+ return $item;
+ },
+ $notifications
+ );
+
+ return array_values( $notifications );
+ }
+
+ /**
+ * Check if the PHP version is correct.
+ *
+ * @since 6.4.0
+ *
+ * @param string $version The version to check against.
+ *
+ * @return bool
+ */
+ public static function check_php_version( $version ): bool {
+ if ( empty( $version ) ) {
+ return true;
+ }
+
+ $version = preg_split( '/(?=\d)/', $version, 2 );
+
+ return (bool) apply_filters( 'tec_common_ian_conditional_php', version_compare( PHP_VERSION, $version[1], $version[0] ?? '>=' ) );
+ }
+
+ /**
+ * Check if the WP version is correct.
+ *
+ * @since 6.4.0
+ *
+ * @param string $version The version to check against.
+ *
+ * @return bool
+ */
+ public static function check_wp_version( $version ): bool {
+ if ( empty( $version ) ) {
+ return true;
+ }
+
+ global $wp_version;
+ $version = preg_split( '/(?=\d)/', $version, 2 );
+
+ return (bool) apply_filters( 'tec_common_ian_conditional_wp', version_compare( $wp_version, $version[1], $version[0] ?? '>=' ) );
+ }
+
+ /**
+ * Check if the plugin version matches requirements.
+ *
+ * @since 6.4.0
+ *
+ * @param array $plugins The required plugins to check.
+ *
+ * @return bool
+ */
+ public static function check_plugin_version( array $plugins ): bool {
+ // If no plugins are specified as a condition, we can assume the condition is met.
+ if ( empty( $plugins ) ) {
+ return true;
+ }
+
+ // Get all installed plugins data, keyed by plugin file name.
+ $all_plugins = get_plugins();
+
+ foreach ( $plugins as $plugin ) {
+ $pieces = explode( '@', $plugin );
+
+ // Find the actual plugin directory/file from the list.
+ $plugin_file = '';
+ foreach ( $all_plugins as $k => $data ) {
+ // If the plugin directory/file_name contains the required slug.
+ if ( strpos( $k, $pieces[0] ) !== false ) {
+ $plugin_file = $k;
+ $installed = $data['Version'];
+ break;
+ }
+ }
+
+ // We didn't find the plugin in the list of installed plugins.
+ if ( empty( $plugin_file ) ) {
+ return false;
+ }
+
+ // If the plugin is not active, the condition is not met.
+ if ( ! is_plugin_active( $plugin_file ) ) {
+ return false;
+ }
+
+ // Plugin is installed and active so compare its version to the required.
+ $version = preg_split( '/(?=\d)/', $pieces[1], 2 );
+ if ( ! version_compare( $installed, $version[1], $version[0] ?: '>=' ) ) {
+ return false;
+ }
+ }
+
+ // All plugins met the conditions.
+ return true;
+ }
+}
diff --git a/src/Common/Notifications/Controller.php b/src/Common/Notifications/Controller.php
new file mode 100644
index 0000000000..97b0bf9148
--- /dev/null
+++ b/src/Common/Notifications/Controller.php
@@ -0,0 +1,233 @@
+ [ $this, 'is_ian_page' ],
+ 'in_footer' => false,
+ 'localize' => [
+ 'name' => 'commonIan',
+ 'data' => [
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'common_ian_nonce' ),
+ 'readTxt' => esc_html__( 'Read notifications', 'tribe-common' ),
+ 'feed' => (object) [
+ 'read' => [],
+ 'unread' => [],
+ ],
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Define which pages will show the notification icon.
+ *
+ * @since 6.4.0
+ *
+ * @return bool
+ */
+ public function is_ian_page() {
+ $screen = get_current_screen();
+ $allowed = [ 'tribe_events', 'edit-tribe_events', 'tribe_events_page_tec-events-settings' ];
+
+ /**
+ * Filter the allowed pages for the Notifications icon.
+ *
+ * @since 6.4.0
+ *
+ * @param array $allowed The allowed pages for the Notifications icon.
+ */
+ $allowed = apply_filters( 'tec_common_ian_allowed_pages', $allowed );
+
+ /**
+ * Filter the showing of the Notifications icon.
+ *
+ * @since 6.4.0
+ *
+ * @param bool Whether to show the icon or not.
+ */
+ return apply_filters( 'tec_common_ian_show_icon', in_array( $screen->id, $allowed, true ) );
+ }
+
+ /**
+ * Logic for if the Notifications icon should be shown.
+ *
+ * @since 6.4.0
+ *
+ * @param string $slug The slug of the plugin to show the notifications in.
+ *
+ * @return void
+ */
+ public function show_icon( $slug ) {
+ if ( self::is_ian_page() && current_user_can( 'manage_options' ) ) {
+ $this->container->make( Notifications::class )->show_icon( $slug );
+ }
+ }
+
+ /**
+ * AJAX handler for opting in to Notifications.
+ *
+ * @since 6.4.0
+ */
+ public function opt_in() {
+ $this->container->make( Notifications::class )->opt_in();
+ }
+
+ /**
+ * AJAX handler for getting notifications.
+ *
+ * @since 6.4.0
+ */
+ public function get_feed() {
+ $this->container->make( Notifications::class )->get_feed();
+ }
+
+ /**
+ * AJAX handler for dismissing notifications.
+ *
+ * @since 6.4.0
+ */
+ public function handle_dismiss() {
+ $this->container->make( Notifications::class )->handle_dismiss();
+ }
+
+ /**
+ * AJAX handler for marking notifications as read.
+ *
+ * @since 6.4.0
+ */
+ public function handle_read() {
+ $this->container->make( Notifications::class )->handle_read();
+ }
+
+ /**
+ * AJAX handler for marking all notifications as read.
+ *
+ * @since 6.4.0
+ */
+ public function handle_read_all() {
+ $this->container->make( Notifications::class )->handle_read_all();
+ }
+
+ /**
+ * Adds the opt in/out control to the general tab debug section.
+ *
+ * @since 6.1.1
+ *
+ * @param array $fields The fields for the general tab Debugging section.
+ *
+ * @return array The fields, with the optin control appended.
+ */
+ public function filter_tribe_general_settings_debugging_section( $fields ): array {
+ $telemetry = tribe( Telemetry::class );
+ $telemetry->init();
+ $status = $telemetry::get_status_object();
+ $opted = $status->get( Telemetry::get_plugin_slug() );
+
+ switch ( $opted ) {
+ case Status::STATUS_ACTIVE:
+ $attributes = [
+ 'disabled' => 'disabled',
+ 'checked' => 'checked',
+ ];
+ break;
+ default:
+ $attributes = [];
+ break;
+ }
+
+ $tooltip = esc_html__( 'Enable this option to receive notifications about The Events Calendar, including updates, fixes, and features. This is enabled if you have opted in to Telemetry.', 'tribe-common' );
+
+ /**
+ * Filter the tooltip text for the IAN opt-in setting.
+ *
+ * @since 6.4.0
+ */
+ $tooltip = apply_filters( 'tec_common_ian_setting_optin_tooltip', $tooltip );
+
+ $fields['ian-notifications-opt-in'] = [
+ 'type' => 'checkbox_bool',
+ 'label' => esc_html__( 'In-App Notifications', 'tribe-common' ),
+ 'tooltip' => $tooltip,
+ 'default' => false,
+ 'validation_type' => 'boolean',
+ 'attributes' => $attributes,
+ ];
+
+ return $fields;
+ }
+}
diff --git a/src/Common/Notifications/Notifications.php b/src/Common/Notifications/Notifications.php
new file mode 100644
index 0000000000..29314cb03f
--- /dev/null
+++ b/src/Common/Notifications/Notifications.php
@@ -0,0 +1,286 @@
+api_url = $this->get_api_url();
+ $this->slugs = $this->get_plugins();
+
+ /**
+ * Allow plugins to hook in and add themselves,
+ * running their own actions after IAN is initiated.
+ *
+ * @since 6.4.0
+ *
+ * @param self $ian The IAN instance.
+ */
+ do_action( 'tec_common_ian_loaded', $this );
+ }
+
+ /**
+ * Get the API URL for the In-App Notifications.
+ *
+ * @since 6.4.0
+ *
+ * @return string
+ */
+ public function get_api_url() {
+ $api = defined( 'TEC_COMMON_IAN_API_URL' ) ? TEC_COMMON_IAN_API_URL : 'https://ian.stellarwp.com/feed/stellar/tec/plugins.json';
+
+ /**
+ * Filter the API URL for the In-App Notifications.
+ *
+ * @since 6.4.0
+ *
+ * @param string $api The API URL for the In-App Notifications.
+ * @param object $this The current instance of the class.
+ */
+ $api = apply_filters( 'tec_common_ian_api_url', $api, $this );
+
+ return $api;
+ }
+
+ /**
+ * Register the plugins that support In-App Notifications.
+ *
+ * @since 6.4.0
+ *
+ * @return array The slugs for plugins that support IAN.
+ */
+ public function get_plugins() {
+ $plugins = [ 'the-events-calendar', 'event-tickets' ];
+
+ /**
+ * Filter the plugin slugs for the In-App Notifications.
+ *
+ * @since 6.4.0
+ *
+ * @param array $slugs The slugs for plugins that support IAN.
+ */
+ return apply_filters( 'tec_common_ian_slugs', $plugins );
+ }
+
+ /**
+ * Show our notification icon.
+ *
+ * @since 6.4.0
+ *
+ * @param string $slug The plugin slug for IAN.
+ *
+ * @return void
+ */
+ public function show_icon( $slug ): void {
+ if ( ! in_array( $slug, $this->get_plugins(), true ) ) {
+ return;
+ }
+
+ /**
+ * Filter allowing disabling of the Notifications by returning false.
+ *
+ * @since 6.4.0
+ *
+ * @param bool $show Whether to render the IAN sidebar or not.
+ */
+ $show = (bool) apply_filters( 'tec_common_ian_render', true, $slug );
+
+ if ( ! $show ) {
+ return;
+ }
+
+ $template = new Template();
+ $template->render_sidebar( [ 'slug' => $slug ], true );
+ }
+
+ /**
+ * Optin to IAN notifications.
+ *
+ * @since 6.4.0
+ *
+ * @return void
+ */
+ public function opt_in() {
+ if ( ! wp_verify_nonce( tec_get_request_var( 'nonce' ), 'common_ian_nonce' ) ) {
+ wp_send_json_error( esc_html__( 'Invalid nonce', 'tribe-common' ), 403 );
+ return;
+ }
+
+ tribe_update_option( 'ian-notifications-opt-in', 1 );
+
+ wp_send_json_success( esc_html__( 'Notifications opt-in successful', 'tribe-common' ), 200 );
+ }
+
+ /**
+ * Get the IAN notifications.
+ *
+ * @since 6.4.0
+ *
+ * @return void
+ */
+ public function get_feed() {
+ if ( ! wp_verify_nonce( tec_get_request_var( 'nonce' ), 'common_ian_nonce' ) ) {
+ wp_send_json_error( esc_html__( 'Invalid nonce', 'tribe-common' ), 403 );
+ return;
+ }
+
+ $cache = tribe_cache();
+ $feed = $cache->get_transient( 'tec_ian_api_feed' );
+ if ( false === $feed || ! is_array( $feed ) ) {
+ // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
+ $response = wp_remote_get( $this->api_url );
+ if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
+ $cache->set_transient( 'tec_ian_api_feed', [], 15 * MINUTE_IN_SECONDS );
+ wp_send_json_error( wp_remote_retrieve_response_message( $response ), wp_remote_retrieve_response_code( $response ) );
+ return;
+ }
+ $body = json_decode( wp_remote_retrieve_body( $response ), true );
+ $feed = Conditionals::filter_feed( $body['notifications_by_area']['general-tec'] ?? [] );
+ $cache->set_transient( 'tec_ian_api_feed', $feed, 15 * MINUTE_IN_SECONDS );
+ }
+
+ $template = new Template();
+ foreach ( $feed as $k => $notification ) {
+ $this->slug = $notification['slug'];
+ if ( $this->has_user_dismissed() ) {
+ unset( $feed[ $k ] );
+ continue;
+ }
+
+ $notification['read'] = $this->has_user_read();
+
+ $feed[ $k ]['html'] = $template->render_notification( $notification, false );
+ $feed[ $k ]['read'] = $notification['read'] ?? false;
+ }
+ array_values( $feed );
+
+ wp_send_json_success( $feed, 200 );
+ }
+
+ /**
+ * AJAX handler for dismissing IAN notifications.
+ *
+ * @since 6.4.0
+ *
+ * @return void
+ */
+ public function handle_dismiss(): void {
+ $id = tec_get_request_var( 'id' );
+
+ if ( ! wp_verify_nonce( tec_get_request_var( 'nonce' ), 'ian_nonce_' . $id ) ) {
+ wp_send_json_error( esc_html__( 'Invalid nonce', 'tribe-common' ), 403 );
+ return;
+ }
+
+ $slug = tec_get_request_var( 'slug' );
+
+ if ( empty( $slug ) ) {
+ wp_send_json_error( esc_html__( 'Invalid notification slug', 'tribe-common' ), 403 );
+ return;
+ }
+
+ $this->slug = $slug;
+ $this->dismiss();
+
+ wp_send_json_success( esc_html__( 'Notification dismissed', 'tribe-common' ), 200 );
+ }
+
+ /**
+ * AJAX handler for marking IAN notifications as read.
+ *
+ * @since 6.4.0
+ *
+ * @return void
+ */
+ public function handle_read(): void {
+ $id = tec_get_request_var( 'id' );
+
+ if ( ! wp_verify_nonce( tec_get_request_var( 'nonce' ), 'ian_nonce_' . $id ) ) {
+ wp_send_json_error( esc_html__( 'Invalid nonce', 'tribe-common' ), 403 );
+ return;
+ }
+
+ $slug = tec_get_request_var( 'slug' );
+
+ if ( empty( $slug ) ) {
+ wp_send_json_error( esc_html__( 'Invalid notification slug', 'tribe-common' ), 403 );
+ return;
+ }
+
+ $this->slug = $slug;
+ $this->read();
+
+ wp_send_json_success( esc_html__( 'Notification marked as read', 'tribe-common' ), 200 );
+ }
+
+ /**
+ * AJAX handler for marking all IAN notifications as read.
+ *
+ * @since 6.4.0
+ *
+ * @return void
+ */
+ public function handle_read_all(): void {
+ if ( ! wp_verify_nonce( tec_get_request_var( 'nonce' ), 'common_ian_nonce' ) ) {
+ wp_send_json_error( esc_html__( 'Invalid nonce', 'tribe-common' ), 403 );
+ return;
+ }
+
+ $unread = json_decode( stripslashes( tec_get_request_var( 'unread' ) ), true );
+
+ foreach ( $unread as $slug ) {
+ $this->slug = $slug;
+ $this->read();
+ }
+
+ wp_send_json_success( esc_html__( 'All notifications marked as read', 'tribe-common' ), 200 );
+ }
+}
diff --git a/src/Common/Notifications/Readable_Trait.php b/src/Common/Notifications/Readable_Trait.php
new file mode 100644
index 0000000000..3f166574bc
--- /dev/null
+++ b/src/Common/Notifications/Readable_Trait.php
@@ -0,0 +1,189 @@
+slug ) ) {
+ return '';
+ }
+
+ return $this->read_nonce_action_prefix . $this->slug;
+ }
+
+ /**
+ * Get the nonce for this readable content.
+ *
+ * @since 6.4.0
+ *
+ * @return string
+ */
+ public function get_read_nonce(): string {
+ return wp_create_nonce( $this->get_read_nonce_action() );
+ }
+
+ /**
+ * This will allow the user to read the notification using JS.
+ *
+ * @since 6.4.0
+ *
+ * @return void
+ */
+ public function handle_read(): void {
+ if ( empty( $this->slug ) ) {
+ wp_send_json( false );
+ }
+
+ $slug = tribe_get_request_var( 'slug', false );
+ if ( empty( $slug ) ) {
+ wp_send_json( false );
+ }
+
+ $slug = sanitize_key( $slug );
+
+ if ( $this->slug !== $slug ) {
+ wp_send_json( false );
+ }
+
+ $nonce = tribe_get_request_var( 'nonce', false );
+ $nonce_action = $this->get_read_nonce_action();
+
+ if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
+ wp_send_json( false );
+ }
+
+ // Send a JSON answer with the status of reading.
+ wp_send_json( $this->read() );
+ }
+
+ /**
+ * A Method to add the Meta value that this notification has been read.
+ *
+ * @since 6.4.0
+ *
+ * @param int|null|string $user_id The user ID.
+ *
+ * @return boolean
+ */
+ protected function read( $user_id = null ): bool {
+ if ( empty( $this->slug ) ) {
+ return false;
+ }
+
+ if ( is_null( $user_id ) ) {
+ $user_id = get_current_user_id();
+ }
+
+ // If this user has read we don't care either.
+ if ( $this->has_user_read( $user_id ) ) {
+ return true;
+ }
+
+ update_user_meta( $user_id, $this->read_meta_key_time_prefix . $this->slug, time() );
+
+ return (bool) add_user_meta( $user_id, $this->read_meta_key, $this->slug );
+ }
+
+ /**
+ * Removes the user meta that holds if this content has been read.
+ *
+ * @since 6.4.0
+ *
+ * @param int|null|string $user_id The user ID.
+ *
+ * @return boolean
+ */
+ public function unread( $user_id = null ): bool {
+ if ( empty( $this->slug ) ) {
+ return false;
+ }
+
+ if ( null === $user_id ) {
+ $user_id = get_current_user_id();
+ }
+
+ // If this user has read we don't care either.
+ if ( ! $this->has_user_read( $user_id ) ) {
+ return false;
+ }
+
+ return delete_user_meta( $user_id, $this->read_meta_key, $this->slug );
+ }
+
+ /**
+ * Checks if a given user has read a given notification.
+ *
+ * @since 6.4.0
+ *
+ * @param int|null|string $user_id The user ID.
+ *
+ * @return boolean
+ */
+ public function has_user_read( $user_id = null ): bool {
+ if ( empty( $this->slug ) ) {
+ return false;
+ }
+
+ if ( null === $user_id ) {
+ $user_id = get_current_user_id();
+ }
+
+ $read_notifications = get_user_meta( $user_id, $this->read_meta_key );
+
+ if ( ! is_array( $read_notifications ) ) {
+ return false;
+ }
+
+ if ( ! in_array( $this->slug, $read_notifications, true ) ) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/Common/Notifications/Template.php b/src/Common/Notifications/Template.php
new file mode 100644
index 0000000000..d9330184fb
--- /dev/null
+++ b/src/Common/Notifications/Template.php
@@ -0,0 +1,88 @@
+set_template_origin( Tribe__Main::instance() );
+ $this->set_template_folder( 'src/admin-views/notifications' );
+ $this->set_template_context_extract( true );
+ $this->set_template_folder_lookup( false );
+ }
+
+ /**
+ * Render the notification sidebar.
+ *
+ * @since 6.4.0
+ *
+ * @param array $args Array of arguments that will ultimately be sent to the template.
+ * @param bool $output Whether or not to echo the HTML. Defaults to true.
+ *
+ * @return string HTML of notification sidebar.
+ */
+ public function render_sidebar( $args, $output = true ) {
+ $args = wp_parse_args(
+ $args,
+ [
+ 'slug' => $args['slug'],
+ 'main' => Tribe__Main::instance(),
+ 'optin' => Conditionals::get_opt_in(),
+ 'url' => Telemetry::get_permissions_url(),
+ ]
+ );
+
+ return $this->template( 'sidebar', $args, $output );
+ }
+
+ /**
+ * Render the notification.
+ *
+ * @since 6.4.0
+ *
+ * @param array $args Array of arguments that will ultimately be sent to the template.
+ * @param bool $output Whether or not to echo the HTML. Defaults to true.
+ *
+ * @return string HTML of notification.
+ */
+ public function render_notification( $args, $output = true ) {
+ $args = wp_parse_args(
+ $args,
+ [
+ 'type' => $args['type'] ?? 'notice',
+ 'id' => $args['id'] ?? '',
+ 'dismissible' => $args['dismissible'] ?? true,
+ 'slug' => $args['slug'] ?? '',
+ 'title' => $args['title'] ?? '',
+ 'html' => $args['html'] ?? '',
+ 'actions' => $args['actions'] ?? [],
+ 'read' => $args['read'] ?? false,
+ ]
+ );
+
+ return $this->template( 'notification', $args, $output );
+ }
+}
diff --git a/src/Tribe/Admin/Help_Page.php b/src/Tribe/Admin/Help_Page.php
index d839013ca0..5640f2a79c 100644
--- a/src/Tribe/Admin/Help_Page.php
+++ b/src/Tribe/Admin/Help_Page.php
@@ -4,6 +4,7 @@
* Administration Help Page
*
* @since 4.0
+ * @deprecated 6.3.2 This class is deprecated and should no longer be used. Use \TEC\Common\Admin\Help_Hub\Hub instead.
*/
// Don't load directly.
@@ -15,6 +16,7 @@
* Class with a few helpers for the Administration Pages
*
* @since 4.0
+ * @deprecated 6.3.2 This class is deprecated. Use \TEC\Common\Admin\Help_Hub\Hub instead.
*/
class Tribe__Admin__Help_Page {
//phpcs:ignore - legacy class naming.
@@ -24,6 +26,7 @@ class Tribe__Admin__Help_Page {
* @return Tribe__Admin__Help_Page
*/
public static function instance() {
+ _deprecated_function( __METHOD__, '6.3.2', '\TEC\Common\Admin\Help_Hub\Hub' );
return tribe( static::class );
}
@@ -543,7 +546,7 @@ private function get_plugin_api_data( $plugin = null ) {
if ( ! is_wp_error( $data ) ) {
// Format Downloaded Infomation.
- $data->downloaded = $data->downloaded ? number_format( $data->downloaded ) : _x( 'n/a', 'not available', 'tribe-common' );
+ $data->downloaded = $data->downloaded ? number_format( (float) $data->downloaded ) : _x( 'n/a', 'not available', 'tribe-common' );
} else {
// If there was a bug on the Current Request just leave.
return false;
@@ -966,7 +969,7 @@ public function print_plugin_box( $plugin ) {
requires ); ?>+
- active_installs ) ); ?>+
+ active_installs ) ); ?>+
diff --git a/src/Tribe/Admin/Notice/Date_Based.php b/src/Tribe/Admin/Notice/Date_Based.php
index be2ed2e1ca..192f7bed26 100644
--- a/src/Tribe/Admin/Notice/Date_Based.php
+++ b/src/Tribe/Admin/Notice/Date_Based.php
@@ -112,7 +112,7 @@ abstract class Date_Based {
'events_page_tribe-app-shop', // App shop.
'toplevel_page_tec-events', // New Events Welcome.
'tribe_events_page_tec-events-settings', // New Events Settings.
- 'tribe_events_page_tec-events-help', // New Events Help.
+ 'tribe_events_page_tec-events-help-hub', // New Events Help.
'tribe_events_page_tec-troubleshooting', // New Events Troubleshooting.
'tickets_page_tec-tickets-settings', // New Tickets Settings.
'toplevel_page_tec-tickets', // New Tickets Welcome.
diff --git a/src/Tribe/Admin/Notice/Service_Provider.php b/src/Tribe/Admin/Notice/Service_Provider.php
index 67e744bf17..0085f297b8 100644
--- a/src/Tribe/Admin/Notice/Service_Provider.php
+++ b/src/Tribe/Admin/Notice/Service_Provider.php
@@ -10,6 +10,7 @@
namespace Tribe\Admin\Notice;
use TEC\Common\Contracts\Service_Provider as Provider_Contract;
+use TEC\Common\StellarWP\AdminNotices\AdminNotices;
/**
* Class Notice
@@ -30,6 +31,10 @@ public function register() {
tribe_singleton( 'pue.notices', 'Tribe__PUE__Notices' );
tribe_singleton( WP_Version::class, WP_Version::class, [ 'hook' ] );
tribe_singleton( 'admin.notice.php.version', \Tribe__Admin__Notice__Php_Version::class, [ 'hook' ] );
+ AdminNotices::initialize(
+ 'tec_common',
+ plugin_dir_url( \Tribe__Main::instance()->plugin_path ) . 'common/vendor/vendor-prefixed/stellarwp/admin-notices'
+ );
$this->hooks();
}
diff --git a/src/Tribe/Admin/Notices.php b/src/Tribe/Admin/Notices.php
index 86072e4bfc..e975abd632 100644
--- a/src/Tribe/Admin/Notices.php
+++ b/src/Tribe/Admin/Notices.php
@@ -292,6 +292,8 @@ public function render( $slug, $content = null, $return = true, $wrap = false )
$classes[] = 'inline';
}
+ $content ??= $notice->content;
+
// Prevents Empty Notices
if ( empty( $content ) ) {
return false;
diff --git a/src/Tribe/Cost_Utils.php b/src/Tribe/Cost_Utils.php
index 42a112a19f..8243a9297b 100644
--- a/src/Tribe/Cost_Utils.php
+++ b/src/Tribe/Cost_Utils.php
@@ -109,7 +109,7 @@ public function maybe_replace_cost_with_free( $cost ) {
if (
is_numeric( $cost_with_period )
- && '0.00' === number_format( $cost_with_period, 2, '.', ',' )
+ && '0.00' === number_format( (float) $cost_with_period, 2, '.', ',' )
) {
return esc_html__( 'Free', 'tribe-common' );
}
@@ -353,7 +353,7 @@ public function parse_cost_range( $costs, $max_decimals = null, $sort = true ) {
if ( is_numeric( $numeric_cost ) ) {
// Creates a Well Balanced Index that will perform good on a Key Sorting method
- $index = str_replace( [ '.', ',' ], '', number_format( $numeric_cost, $max ) );
+ $index = str_replace( [ '.', ',' ], '', number_format( (float) $numeric_cost, $max ) );
} else {
// Makes sure that we have "index-safe" string
$index = sanitize_title( $numeric_cost );
diff --git a/src/Tribe/Log/Admin.php b/src/Tribe/Log/Admin.php
index c5aa2472df..fc7ac420ca 100644
--- a/src/Tribe/Log/Admin.php
+++ b/src/Tribe/Log/Admin.php
@@ -127,7 +127,7 @@ public function register_script() {
* @return boolean True if the assets should be enqueued.
*/
public function should_enqueue_assets() {
- return Tribe__Admin__Help_Page::instance()->is_current_page() || tribe( Troubleshooting::class )->is_current_page();
+ return tribe( Troubleshooting::class )->is_current_page();
}
/**
diff --git a/src/Tribe/Main.php b/src/Tribe/Main.php
index a983307159..6c4e188c57 100644
--- a/src/Tribe/Main.php
+++ b/src/Tribe/Main.php
@@ -4,6 +4,7 @@
use TEC\Common\Translations_Loader;
use Tribe\Admin\Settings;
use Tribe\DB_Lock;
+use TEC\Common\Asset;
// Don't load directly.
if ( ! defined( 'ABSPATH' ) ) {
@@ -19,7 +20,7 @@ class Tribe__Main {
const OPTIONNAME = 'tribe_events_calendar_options';
const OPTIONNAMENETWORK = 'tribe_events_calendar_network_options';
const FEED_URL = 'https://theeventscalendar.com/feed/';
- const VERSION = '6.4.0';
+ const VERSION = '6.4.1';
protected $plugin_context;
protected $plugin_context_class;
@@ -221,13 +222,21 @@ public function init_libraries() {
* Registers resources that can/should be enqueued
*/
public function load_assets() {
+ Asset::add(
+ 'tribe-clipboard',
+ 'vendor/clipboard.min.js',
+ self::VERSION
+ )
+ ->prefix_asset_directory( false )
+ ->use_asset_file( false )
+ ->register();
+
// These ones are only registered
tribe_assets(
$this,
[
[ 'tribe-accessibility-css', 'accessibility.css' ],
[ 'tribe-query-string', 'utils/query-string.js' ],
- [ 'tribe-clipboard', 'node_modules/clipboard/dist/clipboard.min.js' ],
[ 'datatables', 'vendor/datatables/datatables.js', [ 'jquery' ] ],
[ 'tribe-select2', 'vendor/tribe-selectWoo/dist/js/selectWoo.full.js', [ 'jquery' ] ],
[ 'tribe-select2-css', 'vendor/tribe-selectWoo/dist/css/selectWoo.css' ],
@@ -759,6 +768,7 @@ public function bind_implementations() {
tribe_register_provider( Tribe\Service_Providers\Onboarding::class );
tribe_register_provider( Tribe\Admin\Notice\Service_Provider::class );
tribe_register_provider( \TEC\Common\Admin\Conditional_Content\Controller::class );
+ tribe_register_provider( \TEC\Common\Notifications\Controller::class );
tribe_register_provider( Libraries\Provider::class );
// Load the new third-party integration system.
@@ -766,6 +776,9 @@ public function bind_implementations() {
// Load Site Health and Telemetry.
tribe_register_provider( TEC\Common\Site_Health\Provider::class );
tribe_register_provider( TEC\Common\Telemetry\Provider::class );
+
+ // Load Help Hub.
+ tribe_register_provider( TEC\Common\Admin\Help_Hub\Provider::class );
}
/**
diff --git a/src/Tribe/PUE/Checker.php b/src/Tribe/PUE/Checker.php
index 4d4d6def15..486bd87c53 100755
--- a/src/Tribe/PUE/Checker.php
+++ b/src/Tribe/PUE/Checker.php
@@ -181,6 +181,20 @@ class Tribe__PUE__Checker {
*/
private $validate_query = [];
+ /**
+ * A unique list of instances of the PUE Checker that has been initialized.
+ *
+ * @since 6.3.2
+ *
+ * @var Tribe__PUE__Checker[] Instances of checkers that have been registered.
+ */
+ protected static $instances = [];
+
+ /**
+ * @var string The transient key.
+ */
+ public const IS_ANY_LICENSE_VALID_TRANSIENT_KEY = 'TEC_IS_ANY_LICENSE_VALID_TRANSIENT';
+
/**
* Class constructor.
*
@@ -206,14 +220,23 @@ public function __construct( $pue_update_url, $slug = '', $options = [], $plugin
$this->set_options( $options );
$this->hooks();
$this->set_key_status_name();
+ // So we can reference our "registered" instances later.
+ self::$instances[ $slug ] ??= $this;
}
/**
* Gets whether the license key is valid or not.
*
* @since 4.14.9
+ * @since 6.4.1 Added uplink resource check.
*/
public function is_key_valid() {
+ $uplink_resource = get_resource( $this->get_slug() );
+
+ if ( $uplink_resource ) {
+ return $uplink_resource->has_valid_license();
+ }
+
// @todo remove transient in a major feature release where we release all plugins.
$status = get_transient( $this->pue_key_status_transient_name );
@@ -224,6 +247,57 @@ public function is_key_valid() {
return 'valid' === $status;
}
+ /**
+ * Iterate on all the registered PUE Product Licenses we have and find if any are valid.
+ * Will revalidate the licenses if none are found to be valid.
+ *
+ * @todo In scenarios where a user goes from a Free license to an active license the transient may give a false positive.
+ *
+ * @since 6.3.2
+ *
+ * @return bool
+ */
+ public static function is_any_license_valid(): bool {
+ $valid_slug = 'valid';
+ $has_valid = false;
+
+ // Check our transient.
+ $transient_value = get_transient( self::IS_ANY_LICENSE_VALID_TRANSIENT_KEY );
+ if ( ! empty( $transient_value ) ) {
+ return $transient_value === $valid_slug;
+ }
+
+ // Check our local transient/cache first.
+ foreach ( self::$instances as $checker ) {
+ if ( $checker->is_key_valid() ) {
+ set_transient( self::IS_ANY_LICENSE_VALID_TRANSIENT_KEY, $valid_slug, HOUR_IN_SECONDS );
+ $has_valid = true;
+ break;
+ }
+ }
+
+ if ( ! $has_valid ) {
+ // Revalidate if we haven't found a valid license yet.
+ foreach ( self::$instances as $checker ) {
+ $license = get_option( $checker->get_license_option_key() );
+ $response = $checker->validate_key( $license );
+ // Is it valid?
+ if ( ! empty( $response['status'] ) ) {
+ set_transient( self::IS_ANY_LICENSE_VALID_TRANSIENT_KEY, $valid_slug, HOUR_IN_SECONDS );
+ $has_valid = true;
+ break;
+ }
+ }
+ }
+
+ // We found no valid licenses above.
+ if ( ! $has_valid ) {
+ set_transient( self::IS_ANY_LICENSE_VALID_TRANSIENT_KEY, 'invalid', HOUR_IN_SECONDS );
+ }
+
+ return get_transient( self::IS_ANY_LICENSE_VALID_TRANSIENT_KEY ) === $valid_slug;
+ }
+
/**
* Gets whether or not the PUE key validation check is expired.
*
@@ -325,6 +399,8 @@ public function hooks() {
add_filter( 'upgrader_pre_download', [ Tribe__PUE__Package_Handler::instance(), 'filter_upgrader_pre_download' ], 5, 3 );
}
+
+
/********************** Getter / Setter Functions **********************/
/**
@@ -1029,6 +1105,12 @@ public function validate_key( $key, $network = false ) {
$response = [];
$response['status'] = 0;
+ $uplink_resource = get_resource( $this->get_slug() );
+
+ if ( $uplink_resource ) {
+ $key = $uplink_resource->get_license_key();
+ }
+
if ( ! $key ) {
$response['message'] = sprintf(
/* Translators: %1$s and %2$s are opening and closing tags, respectively. */
diff --git a/src/Tribe/PUE/Notices.php b/src/Tribe/PUE/Notices.php
index 4f9c8702df..8d8eb00ca1 100644
--- a/src/Tribe/PUE/Notices.php
+++ b/src/Tribe/PUE/Notices.php
@@ -1,4 +1,5 @@
populate();
add_action( 'current_screen', [ $this, 'setup_notices' ] );
add_action( 'tribe_pue_notices_save_notices', [ $this, 'maybe_undismiss_notices' ] );
+ add_filter( 'stellarwp/uplink/tec/client_validate_license', [ $this, 'clear_notices_after_uplink_connect' ] );
+ }
+
+ /**
+ * Clears any license key notices for the specified plugin.
+ *
+ * @since 6.4.1
+ *
+ * @param Validation_Response $results The validation results from uplink.
+ *
+ * @return Validation_Response
+ */
+ public function clear_notices_after_uplink_connect( Validation_Response $results ): Validation_Response {
+ if ( ! $results->is_valid() ) {
+ return $results;
+ }
+
+ $this->populate();
+ if ( empty( $this->notices ) || ! is_array( $this->notices ) ) {
+ return $results;
+ }
+
+ foreach ( $this->notices as $key => $data ) {
+ if ( isset( $results->slug ) ) {
+ unset( $this->notices[ $key ][ $results->slug ] );
+ }
+
+ if ( isset( $results->name ) ) {
+ unset( $this->notices[ $key ][ $results->name ] );
+ }
+ }
+
+ $this->save_notices();
+
+ return $results;
}
/**
diff --git a/src/Tribe/Service_Providers/Onboarding.php b/src/Tribe/Service_Providers/Onboarding.php
index b26b29fa73..ec763f8540 100644
--- a/src/Tribe/Service_Providers/Onboarding.php
+++ b/src/Tribe/Service_Providers/Onboarding.php
@@ -3,6 +3,7 @@
use \Tribe\Onboarding\Main as Onboarding_Main;
use TEC\Common\Contracts\Service_Provider;
+use TEC\Common\Asset;
/**
* Class Onboarding
@@ -57,29 +58,30 @@ protected function hooks() {
public function register_assets() {
$main = \Tribe__Main::instance();
- tribe_asset(
- $main,
+ Asset::add(
'tec-intro-js',
- 'node_modules/intro.js/intro.js',
- [],
- [ 'admin_enqueue_scripts' ],
- [
- 'groups' => self::$group_key,
- 'conditionals' => [ $this, 'should_enqueue_assets' ],
- ]
- );
-
- tribe_asset(
- $main,
+ 'vendor/intro.min.js',
+ \Tribe__Main::VERSION
+ )
+ ->add_to_group( self::$group_key )
+ ->set_condition( [ $this, 'should_enqueue_assets' ] )
+ ->enqueue_on( 'admin_enqueue_scripts' )
+ ->prefix_asset_directory( false )
+ ->use_asset_file( false )
+ ->register();
+
+ Asset::add(
'tec-intro-styles',
- 'node_modules/intro.js/introjs.css',
- [],
- [ 'admin_enqueue_scripts' ],
- [
- 'groups' => self::$group_key,
- 'conditionals' => [ $this, 'should_enqueue_assets' ],
- ]
- );
+ 'vendor/introjs.min.css',
+ \Tribe__Main::VERSION
+ )
+ ->add_to_group( self::$group_key )
+ ->set_condition( [ $this, 'should_enqueue_assets' ] )
+ ->enqueue_on( 'admin_enqueue_scripts' )
+ ->prefix_asset_directory( false )
+ ->use_asset_file( false )
+ ->register();
+
tribe_asset(
$main,
diff --git a/src/Tribe/Settings.php b/src/Tribe/Settings.php
index 4a984298bd..e0cafbbe12 100755
--- a/src/Tribe/Settings.php
+++ b/src/Tribe/Settings.php
@@ -13,6 +13,7 @@
use TEC\Common\Admin\Entities\Element_With_Children;
use TEC\Common\Admin\Entities\Field_Wrapper;
use Tribe\Admin\Pages as Admin_Pages;
+use TEC\Common\Notifications\Controller;
if ( did_action( 'tec_settings_init' ) ) {
return;
@@ -304,7 +305,7 @@ public function __construct() {
/**
* Magic getter for deprecated properties.
*
- * @since TBD
+ * @since 6.3.1
*
* @param string $name The property name we are looking for.
*
@@ -589,12 +590,17 @@ public function get_settings_page_url( array $args = [] ) {
*/
public function do_page_header( $admin_page ): void {
?>
-
- is_event_settings() ) : ?>
- get_page_logo( $admin_page ) ); ?>
+
get_current_tab();
$wrap_classes = apply_filters( 'tribe_settings_wrap_classes', [ 'tribe_settings', 'wrap' ], $admin_page );
$is_event_settings = $this->is_event_settings( $admin_page );
- $form_classes = [ "tec-settings-form__{$current_tab}-tab--active" ];
-
- if ( $this->get_tab( $current_tab )->has_parent() ) {
- $form_classes[] = 'tec-settings-form__subnav-active';
- }
+ $tab_object = $this->get_tab( $current_tab );
+ $form_classes = [
+ "tec-settings-form__{$current_tab}-tab--active" => true,
+ 'tec-settings-form__subnav-active' => ( $tab_object && $tab_object->has_parent() ),
+ ];
/**
* Filter the classes for the settings form.
*
* @since 6.1.0
*
- * @param array $form_classes The classes for the settings form.
+ * @param array $form_classes The classes for the settings form.
+ * @param string $admin_page The admin page ID.
+ * @param Tribe__Settings_Tab|null $tab_object The current tab object.
*/
- $form_classes = apply_filters( 'tribe_settings_form_class', $form_classes, $admin_page );
+ $form_classes = apply_filters( 'tribe_settings_form_class', $form_classes, $admin_page, $tab_object );
ob_start();
do_action( 'tribe_settings_top', $admin_page );
@@ -822,16 +831,13 @@ public function generate_tabs( $modal = false ): void {
}
$nav_id = $modal ? 'tec-settings-modal-nav' : 'tribe-settings-tabs';
+ $tab_object = $this->get_tab( $this->get_current_tab() );
$wrapper_classes = [
'tec-nav__wrapper' => true,
- 'tec-settings__nav-wrapper' => $this->is_event_settings(),
- 'tec-nav__wrapper--subnav-active' => false,
+ 'tec-settings__nav-wrapper' => (bool) $this->is_event_settings(),
+ 'tec-nav__wrapper--subnav-active' => (bool) ( $tab_object && $tab_object->has_parent() ),
];
- if ( $this->get_tab( $this->get_current_tab() )->has_parent() ) {
- $wrapper_classes['tec-nav__wrapper--subnav-active'] = true;
- }
-
ob_start();
?>