From 6829d98c7fd7b493a52f2d0ca17de720e91841c0 Mon Sep 17 00:00:00 2001 From: Peter Kiss Date: Wed, 27 Mar 2024 19:41:35 +0100 Subject: [PATCH 1/6] Add new notice about latest release --- assets/css/admin-notices.scss | 56 ++++++- includes/admin/class-release-notice.php | 92 ++++++++++ .../class-wp-job-manager-admin-notices.php | 157 ++++++++++++++---- 3 files changed, 272 insertions(+), 33 deletions(-) create mode 100644 includes/admin/class-release-notice.php diff --git a/assets/css/admin-notices.scss b/assets/css/admin-notices.scss index 4ccb73f9b..d3aacc5d6 100644 --- a/assets/css/admin-notices.scss +++ b/assets/css/admin-notices.scss @@ -51,8 +51,19 @@ $notice-success: #43af99; aspect-ratio: 1; } + &__label { + font-weight: 600; + background: var(--wpjm-brand-color-shade-4); + color: var(--wpjm-brand-color-primary); + border-radius: 40px; + padding: 6px 12px; + display: inline-block; + text-transform: uppercase; + align-self: flex-start; + font-size: 12px; + } + &__heading { - margin: 0 0 8px 0; font-weight: 700; font-size: 16px; letter-spacing: -0.36px; @@ -62,12 +73,26 @@ $notice-success: #43af99; &__message { flex: 1 1 min-content; align-self: center; + display: flex; + flex-direction: column; + gap: 10px; min-width: min(100%, 400px); + line-height: 1.5; &, p { font-size: 14px; } + ul { + margin: 0; + padding-left: 10px; + list-style-type: '•'; + } + li { + padding-left: 10px; + } + + } &__actions { @@ -108,6 +133,35 @@ $notice-success: #43af99; } } + &--landing { + padding: 40px 40px; + align-items: flex-start; + flex-direction: row; + justify-content: space-between; + } + &--landing &__top { + flex-direction: column; + gap: 20px; + align-items: flex-start; + } + + &--landing &__message { + gap: 20px; + } + + &--landing &__heading { + font-size: 40px; + letter-spacing: -0.8px; + } + + &--landing &__image { + max-width: 50%; + } + &--landing &__image img { + max-width: 100%; + max-height: 500px; + } + } .wpjm-addon-update-notice-info { diff --git a/includes/admin/class-release-notice.php b/includes/admin/class-release-notice.php new file mode 100644 index 000000000..6968f9f47 --- /dev/null +++ b/includes/admin/class-release-notice.php @@ -0,0 +1,92 @@ + \WP_Job_Manager_Settings::instance()->set_setting( \WP_Job_Manager\Stats::OPTION_ENABLE_STATS, '1' ) ); + } + + /** + * Add a release notice for the 2.3.0 release. + * + * @param array $notices + * + * @return array + */ + public static function add_release_notice( $notices ) { + + // Make sure to update the version number in the notice ID when changing this notice for a new release. + + $notice_id = 'release_notice_2_3_0'; + + $action_url = \WP_Job_Manager_Admin_Notices::get_action_url( 'enable_stats', $notice_id ); + $notices[ $notice_id ] = [ + 'type' => 'site-wide', + 'label' => 'New', + 'heading' => 'Job Statistics', + + 'message' => '
' . __( + ' +

Collect analytics data about site visitors for each job listing. Display the detailed statistics in the refreshed jobs dashboard.

+ +', + 'wp-job-manager' + ) . '
', + 'actions' => [ + [ + 'label' => __( 'Enable', 'wp-job-manager' ), + 'url' => $action_url, + 'primary' => true, + ], + [ + 'label' => __( 'Dismiss', 'wp-job-manager' ), + 'url' => \WP_Job_Manager_Admin_Notices::get_dismiss_url( $notice_id ), + 'primary' => false, + ], + [ + 'label' => __( 'See what\'s new in 2.3', 'wp-job-manager' ), + 'url' => 'https://wpjobmanager.com/2024/03/27/new-in-2-3-job-statistics/', + 'class' => 'is-link', + ], + ], + 'icon' => false, + 'level' => 'landing', + 'image' => 'https://wpjobmanager.com/wp-content/uploads/2024/03/jm-230-release.png', + 'dismissible' => false, + 'extra_details' => '', + 'conditions' => [ + [ + 'type' => 'screens', + 'screens' => [ 'edit-job_listing' ], + ], + ], + ]; + + return $notices; + } + +} diff --git a/includes/admin/class-wp-job-manager-admin-notices.php b/includes/admin/class-wp-job-manager-admin-notices.php index d00c23ab3..9daab44b1 100644 --- a/includes/admin/class-wp-job-manager-admin-notices.php +++ b/includes/admin/class-wp-job-manager-admin-notices.php @@ -10,6 +10,7 @@ } use WP_Job_Manager\Admin\Notices_Conditions_Checker; +use WP_Job_Manager\Admin\Release_Notice; use WP_Job_Manager\WP_Job_Manager_Com_API; /** @@ -46,6 +47,9 @@ class WP_Job_Manager_Admin_Notices { 'class' => [], ], 'em' => [], + 'ul' => [], + 'ol' => [], + 'li' => [], 'p' => [], 'strong' => [], ]; @@ -62,12 +66,15 @@ class WP_Job_Manager_Admin_Notices { */ public static function init() { add_action( 'admin_notices', [ __CLASS__, 'display_notices' ] ); - add_action( 'wp_loaded', [ __CLASS__, 'dismiss_notices' ] ); + add_action( 'wp_loaded', [ __CLASS__, 'handle_notice_action' ], 10 ); + add_action( 'wp_loaded', [ __CLASS__, 'dismiss_notices' ], 11 ); add_action( 'wp_ajax_wp_job_manager_dismiss_notice', [ __CLASS__, 'handle_notice_dismiss' ] ); - add_filter( 'wpjm_admin_notices', [ __CLASS__, 'maybe_add_addon_update_available_notice' ], 10, 1 ); - add_filter( 'wpjm_admin_notices', [ __CLASS__, 'paid_listings_renewal_notice' ], 10, 1 ); - add_filter( 'wpjm_admin_notices', [ __CLASS__, 'we_have_addons_notice' ], 10, 1 ); - add_filter( 'wpjm_admin_notices', [ __CLASS__, 'maybe_add_core_setup_notice' ], 10, 1 ); + add_filter( 'wpjm_admin_notices', [ __CLASS__, 'maybe_add_addon_update_available_notice' ] ); + add_filter( 'wpjm_admin_notices', [ __CLASS__, 'paid_listings_renewal_notice' ] ); + add_filter( 'wpjm_admin_notices', [ __CLASS__, 'we_have_addons_notice' ] ); + add_filter( 'wpjm_admin_notices', [ __CLASS__, 'maybe_add_core_setup_notice' ] ); + + Release_Notice::init(); } /** @@ -161,11 +168,11 @@ public static function init_core_notices() { } /** - * Dismiss notices as requested by user. Inspired by WooCommerce's approach. + * Dismiss a notice. */ public static function dismiss_notices() { if ( isset( $_GET['wpjm_hide_notice'] ) && isset( $_GET['_wpjm_notice_nonce'] ) ) { - if ( ! wp_verify_nonce( wp_unslash( $_GET['_wpjm_notice_nonce'] ), 'job_manager_hide_notices_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce should not be modified. + if ( ! wp_verify_nonce( wp_unslash( $_GET['_wpjm_notice_nonce'] ), 'job_manager_notices_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce should not be modified. wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'wp-job-manager' ) ); } @@ -173,15 +180,36 @@ public static function dismiss_notices() { wp_die( esc_html__( 'You don’t have permission to do this.', 'wp-job-manager' ) ); } - $hide_notice = sanitize_key( wp_unslash( $_GET['wpjm_hide_notice'] ) ); + $notice_id = sanitize_key( wp_unslash( $_GET['wpjm_hide_notice'] ) ); - self::remove_notice( $hide_notice ); + self::dismiss_notice( $notice_id ); - wp_safe_redirect( remove_query_arg( [ 'wpjm_hide_notice', '_wpjm_notice_nonce' ] ) ); + wp_safe_redirect( remove_query_arg( [ 'wpjm_hide_notice', '_wpjm_notice_nonce', 'wpjm_notice_action' ] ) ); exit; } } + /** + * Call an action when a notice button is clicked. + */ + public static function handle_notice_action() { + + if ( isset( $_GET['wpjm_notice_action'] ) && isset( $_GET['_wpjm_notice_nonce'] ) ) { + + $action = sanitize_key( wp_unslash( $_GET['wpjm_notice_action'] ) ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce should not be modified. + if ( ! wp_verify_nonce( wp_unslash( $_GET['_wpjm_notice_nonce'] ), 'job_manager_notices_nonce' ) ) { + wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'wp-job-manager' ) ); + } + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'You don’t have permission to do this.', 'wp-job-manager' ) ); + } + + do_action( 'job_manager_action_' . $action ); + } + } + /** * Returns all the registered notices. * This includes notices coming from wpjobmanager.com and notices added by other plugins using the `job_manager_admin_notices` filter. @@ -208,6 +236,25 @@ public static function get_notices() { return $all_notices; } + /** + * Get a notice by ID. + * + * @param string $notice_id Notice ID. + * + * @return array|false + */ + public static function get_notice( $notice_id ) { + $notices = self::get_notices(); + + if ( ! isset( $notices[ $notice_id ] ) ) { + return false; + } + + $notice = self::normalize_notice( $notices[ $notice_id ] ); + + return $notice; + } + /** * Check if a notice was dismissed. * @@ -226,7 +273,6 @@ public static function is_dismissed( $notice_id, $is_user_notification ) { * Note: For internal use only. Do not call manually. */ public static function display_notices() { - /** * Allows WPJM related plugins to set up their notice hooks. * @@ -332,6 +378,7 @@ private static function get_dismissed_notices( $is_user_notification ) { * @param bool $is_user_notification True if we are setting user notifications (vs site-wide notifications). */ private static function save_dismissed_notices( $dismissed_notices, $is_user_notification ) { + $dismissed_notices = array_unique( $dismissed_notices ); if ( $is_user_notification ) { update_user_meta( get_current_user_id(), self::DISMISSED_NOTICES_USER_META, $dismissed_notices ); @@ -348,31 +395,43 @@ private static function save_dismissed_notices( $dismissed_notices, $is_user_not public static function handle_notice_dismiss() { check_ajax_referer( self::DISMISS_NOTICE_ACTION, 'nonce' ); - $notices = self::get_notices(); $notice_id = isset( $_POST['notice'] ) ? sanitize_text_field( wp_unslash( $_POST['notice'] ) ) : false; - if ( ! $notice_id || ! isset( $notices[ $notice_id ] ) ) { + if ( ! $notice_id ) { return; } - $notice = self::normalize_notice( $notices[ $notice_id ] ); + $notice = self::get_notice( $notice_id ); - $is_dismissible = $notice['dismissible']; - $is_user_notification = 'user' === $notice['type']; - if ( - ! $is_dismissible - || ( ! $is_user_notification && ! current_user_can( 'manage_options' ) ) - ) { + if ( ! $notice || ! $notice['dismissible'] || ( 'user' !== $notice['type'] && ! current_user_can( 'manage_options' ) ) ) { wp_die( '', '', 403 ); } + self::dismiss_notice( $notice_id ); + exit; + } + + /** + * Save notice state as dismissed. + * + * @param int $notice_id Notice ID. + * + * @return void + */ + public static function dismiss_notice( $notice_id ) { + + $notice = self::get_notice( $notice_id ); + if ( ! $notice ) { + return; + } + + $is_user_notification = 'user' === $notice['type']; + $dismissed_notices = self::get_dismissed_notices( $is_user_notification ); $dismissed_notices[] = $notice_id; self::save_dismissed_notices( $dismissed_notices, $is_user_notification ); - do_action( 'wp_job_manager_notice_dismissed', $notices[ $notice_id ], $notice_id, $is_user_notification ); - - exit; + do_action( 'wp_job_manager_notice_dismissed', $notice, $notice_id, $is_user_notification ); } /** @@ -453,7 +512,7 @@ public static function maybe_add_core_setup_notice( $notices ) { ], [ 'primary' => false, - 'url' => esc_url( wp_nonce_url( add_query_arg( 'wpjm_hide_notice', self::NOTICE_CORE_SETUP ), 'job_manager_hide_notices_nonce', '_wpjm_notice_nonce' ) ), + 'url' => self::get_dismiss_url( self::NOTICE_CORE_SETUP ), 'label' => __( 'Skip Setup*', 'wp-job-manager' ), ], ], @@ -500,6 +559,34 @@ public static function maybe_add_addon_update_available_notice( $notices ) { return $notices; } + /** + * Get URL for dismiss action for a notice. + * + * @param string $notice_name + * @return string + */ + public static function get_dismiss_url( $notice_name ) { + return wp_nonce_url( add_query_arg( [ 'wpjm_hide_notice' => $notice_name ] ), 'job_manager_notices_nonce', '_wpjm_notice_nonce' ); + } + + /** + * Get URL for a custom action for a notice. + * + * @param string $action_name Name of action. + * @param string $dismiss_notice Optional notice ID, if this action should also dismiss the notice. + * @return string + */ + public static function get_action_url( $action_name, $dismiss_notice = null ) { + + $base_url = ''; + + if ( $dismiss_notice ) { + $base_url = self::get_dismiss_url( $dismiss_notice ); + } + + return wp_nonce_url( add_query_arg( [ 'wpjm_notice_action' => $action_name ], $base_url ), 'job_manager_notices_nonce', '_wpjm_notice_nonce' ); + } + /** * Gets the current admin notices to be displayed. * @@ -595,13 +682,8 @@ private static function render_notice( $notice_id, $notice ) { $notice['actions'] = []; } - $notice_class = []; - $notice_levels = [ 'error', 'warning', 'success', 'info', 'upsell' ]; - if ( isset( $notice['level'] ) && in_array( $notice['level'], $notice_levels, true ) ) { - $notice_class[] = 'wpjm-admin-notice--' . $notice['level']; - } else { - $notice_class[] = 'wpjm-admin-notice--info'; - } + $notice_class = []; + $notice_class[] = 'wpjm-admin-notice--' . ( $notice['level'] ?? 'info' ); $is_dismissible = $notice['dismissible'] ?? true; $notice_wrapper_extra = ''; @@ -625,6 +707,11 @@ private static function render_notice( $notice_id, $notice ) { echo 'WP Job Manager Icon'; } echo '
'; + if ( ! empty( $notice['label'] ) ) { + echo '
'; + echo wp_kses( $notice['label'], self::ALLOWED_HTML ); + echo '
'; + } if ( ! empty( $notice['heading'] ) ) { echo '
'; echo wp_kses( $notice['heading'], self::ALLOWED_HTML ); @@ -639,7 +726,7 @@ private static function render_notice( $notice_id, $notice ) { continue; } - $button_class = ! isset( $action['primary'] ) || $action['primary'] ? 'is-primary' : 'is-outline'; + $button_class = $action['class'] ?? ( ! isset( $action['primary'] ) || $action['primary'] ? 'is-primary' : 'is-outline' ); echo ''; echo esc_html( $action['label'] ); @@ -653,6 +740,12 @@ private static function render_notice( $notice_id, $notice ) { echo '
'; echo '
'; + if ( ! empty( $notice['image'] ) ) { + echo '
'; + echo ''; + echo '
'; + } + if ( ! empty( $notice['extra_details'] ) ) { echo '
'; echo wp_kses( $notice['extra_details'], self::ALLOWED_HTML ); From 13bdbe98bd45425dcc5956364cf29162f5aac124 Mon Sep 17 00:00:00 2001 From: Peter Kiss Date: Thu, 28 Mar 2024 17:43:02 +0100 Subject: [PATCH 2/6] Add missing method --- includes/admin/class-wp-job-manager-settings.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/includes/admin/class-wp-job-manager-settings.php b/includes/admin/class-wp-job-manager-settings.php index 0b5103740..ea48d69f3 100644 --- a/includes/admin/class-wp-job-manager-settings.php +++ b/includes/admin/class-wp-job-manager-settings.php @@ -82,6 +82,16 @@ public function get_settings() { return $this->settings; } + /** + * Set a single setting. + * + * @param string $option Option name. + * @param mixed $value Value. + */ + public function set_setting( $option, $value ) { + update_option( $option, $value ); + } + /** * Initializes the configuration for the plugin's setting fields. * From 17835c9f1766c3d2ec4b26eaf7788124ffe59783 Mon Sep 17 00:00:00 2001 From: Peter Kiss Date: Fri, 12 Apr 2024 16:01:16 +0200 Subject: [PATCH 3/6] Update notice, fix issues --- assets/css/admin-notices.scss | 2 +- includes/admin/class-release-notice.php | 31 ++++++++++++------- .../class-wp-job-manager-admin-notices.php | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/assets/css/admin-notices.scss b/assets/css/admin-notices.scss index d3aacc5d6..14a381452 100644 --- a/assets/css/admin-notices.scss +++ b/assets/css/admin-notices.scss @@ -159,7 +159,7 @@ $notice-success: #43af99; } &--landing &__image img { max-width: 100%; - max-height: 500px; + max-height: 400px; } } diff --git a/includes/admin/class-release-notice.php b/includes/admin/class-release-notice.php index 6968f9f47..1a25fae5e 100644 --- a/includes/admin/class-release-notice.php +++ b/includes/admin/class-release-notice.php @@ -13,16 +13,26 @@ /** * Admin notice about changes and new features introduced in the latest release. + * Update this class for each release to highlight new features or changes. */ class Release_Notice { + public const NOTICE_ID = 'release_notice_2_3_0'; /** * Set up notice. */ public static function init() { add_filter( 'wpjm_admin_notices', [ __CLASS__, 'add_release_notice' ] ); + add_action( 'job_manager_action_enable_stats', [ self::class, 'enable_feature' ] ); + } - add_action( 'job_manager_action_enable_stats', fn() => \WP_Job_Manager_Settings::instance()->set_setting( \WP_Job_Manager\Stats::OPTION_ENABLE_STATS, '1' ) ); + /** + * Enable the highlighted feature. + * + * @return void + */ + public static function enable_feature() { + \WP_Job_Manager_Settings::instance()->set_setting( \WP_Job_Manager\Stats::OPTION_ENABLE_STATS, '1' ); } /** @@ -35,23 +45,20 @@ public static function init() { public static function add_release_notice( $notices ) { // Make sure to update the version number in the notice ID when changing this notice for a new release. - - $notice_id = 'release_notice_2_3_0'; - - $action_url = \WP_Job_Manager_Admin_Notices::get_action_url( 'enable_stats', $notice_id ); - $notices[ $notice_id ] = [ + $action_url = \WP_Job_Manager_Admin_Notices::get_action_url( 'enable_stats', self::NOTICE_ID ); + $notices[ self::NOTICE_ID ] = [ 'type' => 'site-wide', 'label' => 'New', 'heading' => 'Job Statistics', 'message' => '
' . __( ' -

Collect analytics data about site visitors for each job listing. Display the detailed statistics in the refreshed jobs dashboard.

+

Collect analytics about site visitors for each job listing. Display the detailed statistics in the refreshed jobs dashboard.

    -
  • Page views and unique visitors with daily breakdown
  • -
  • Search impressions and apply button clicks
  • -
  • Add-on integration: Job alert impressions, bookmarks, application stats
  • -
  • GDPR-compliant, with no personal user information collected
  • +
  • Tracks page views and unique visitors, search impressions and apply button clicks.
  • +
  • Adds a new overlay to the employer dashboard with aggregated statistics and a daily breakdown chart.
  • +
  • Integrates with Job Alerts, Applications, and Bookmarks add-ons.
  • +
  • GDPR-compliant, with no personal user information collected.
', 'wp-job-manager' @@ -64,7 +71,7 @@ public static function add_release_notice( $notices ) { ], [ 'label' => __( 'Dismiss', 'wp-job-manager' ), - 'url' => \WP_Job_Manager_Admin_Notices::get_dismiss_url( $notice_id ), + 'url' => \WP_Job_Manager_Admin_Notices::get_dismiss_url( self::NOTICE_ID ), 'primary' => false, ], [ diff --git a/includes/admin/class-wp-job-manager-admin-notices.php b/includes/admin/class-wp-job-manager-admin-notices.php index 9daab44b1..bfcc63302 100644 --- a/includes/admin/class-wp-job-manager-admin-notices.php +++ b/includes/admin/class-wp-job-manager-admin-notices.php @@ -413,7 +413,7 @@ public static function handle_notice_dismiss() { /** * Save notice state as dismissed. * - * @param int $notice_id Notice ID. + * @param string $notice_id Notice ID. * * @return void */ From 4def35cf706de894ceb9f2161cc88fcd066ada44 Mon Sep 17 00:00:00 2001 From: Peter Kiss Date: Fri, 12 Apr 2024 16:01:42 +0200 Subject: [PATCH 4/6] Enable stats for new installs --- includes/class-wp-job-manager-install.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/includes/class-wp-job-manager-install.php b/includes/class-wp-job-manager-install.php index c289376ab..31630809b 100644 --- a/includes/class-wp-job-manager-install.php +++ b/includes/class-wp-job-manager-install.php @@ -5,6 +5,8 @@ * @package wp-job-manager */ +use WP_Job_Manager\Admin\Release_Notice; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -74,6 +76,9 @@ public static function install() { $permalink_options = (array) json_decode( get_option( 'job_manager_permalinks', '[]' ), true ); $permalink_options['jobs_archive'] = ''; update_option( 'job_manager_permalinks', wp_json_encode( $permalink_options ) ); + + update_option( \WP_Job_Manager\Stats::OPTION_ENABLE_STATS, true ); + \WP_Job_Manager_Admin_Notices::dismiss_notice( Release_Notice::NOTICE_ID ); } \WP_Job_Manager\Stats::instance()->migrate_db(); From 8d9906655e746a9c59fe1e0b4e83d6842dd8548e Mon Sep 17 00:00:00 2001 From: Peter Kiss Date: Fri, 12 Apr 2024 16:29:46 +0200 Subject: [PATCH 5/6] Psalm --- .psalm/psalm-baseline.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.psalm/psalm-baseline.xml b/.psalm/psalm-baseline.xml index dc350445a..712c4a51b 100644 --- a/.psalm/psalm-baseline.xml +++ b/.psalm/psalm-baseline.xml @@ -204,9 +204,6 @@ getMessage() )]]> - - - From 317970f2af056ecf60393476759d59cbabe6abfc Mon Sep 17 00:00:00 2001 From: Peter Kiss Date: Mon, 15 Apr 2024 21:24:14 +0200 Subject: [PATCH 6/6] Update includes/admin/class-release-notice.php Co-authored-by: gikaragia <53191348+gikaragia@users.noreply.github.com> --- includes/admin/class-release-notice.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/admin/class-release-notice.php b/includes/admin/class-release-notice.php index 1a25fae5e..8cdacc550 100644 --- a/includes/admin/class-release-notice.php +++ b/includes/admin/class-release-notice.php @@ -50,7 +50,6 @@ public static function add_release_notice( $notices ) { 'type' => 'site-wide', 'label' => 'New', 'heading' => 'Job Statistics', - 'message' => '
' . __( '

Collect analytics about site visitors for each job listing. Display the detailed statistics in the refreshed jobs dashboard.