diff --git a/assets/css/notice.css b/assets/css/notice.css new file mode 100644 index 00000000..2166c661 --- /dev/null +++ b/assets/css/notice.css @@ -0,0 +1,171 @@ +:root { + --ea11y-notice-dismiss-icon: #1e1e1e; + --ea11y-notice-error: #dc2626; + --ea11y-notice-error-bg: #fde8ec; + --ea11y-notice-error-custom-border: #93003f; + --ea11y-notice-white: #fff; + --ea11y-notice-warning-bg: #fef8ee; + --ea11y-notice-warning-border: #f0b849; + --ea11y-notice-warning-btn: #bb5b1d; + --ea11y-notice-warning-btn-hover: rgb(187 91 29 / 0.08); + --ea11y-notice-success-bg: #eff9f1; + --ea11y-notice-success-border: #4ab866; + --ea11y-notice-info-bg: #f1f6fb; + --ea11y-notice-info-border: #0288d1; + --ea11y-notice-pink-border: #d8d8da; + --ea11y-notice-pink-accent: #ff7be5; + --ea11y-notice-pink-text: #444950; + --ea11y-notice-pink-link: #d004d4; + --ea11y-notice-pink-icon-bg: #fae8ff; + --ea11y-notice-heading: #0c0d0e; + --ea11y-notice-subheading: #6d7882; + --ea11y-notice-error-btn-hover: rgb(220 38 38 / 0.08); +} + +.ea11y__notice { + padding: 12px; + border: none; +} + +.ea11y__notice p { + margin: 0; +} + +.ea11y__notice .notice-dismiss::before { + content: "\f335"; + font-family: dashicons, sans-serif; + font-weight: 400; + font-size: 22px; + line-height: 24px; + color: var(--ea11y-notice-dismiss-icon); + width: 24px; +} + +.ea11y__notice button:not(.notice-dismiss) { + display: block; + padding: 0; + background: none; + color: var(--ea11y-notice-error); + border: none; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 1.5; + letter-spacing: -0.247px; + text-decoration-line: underline; + cursor: pointer; +} + +.ea11y__notice--error { + background-color: var(--ea11y-notice-error-bg); + border-inline-start: 4px solid var(--ea11y-notice-error); +} + +.ea11y__notice--error-custom { + background-color: var(--ea11y-notice-white); + border-inline-start: 4px solid var(--ea11y-notice-error-custom-border); +} + +.ea11y__notice--warning { + background-color: var(--ea11y-notice-warning-bg); + border-inline-start: 4px solid var(--ea11y-notice-warning-border); +} + +.ea11y__notice--success { + background-color: var(--ea11y-notice-success-bg); + border-inline-start: 4px solid var(--ea11y-notice-success-border); +} + +.ea11y__notice--info { + background-color: var(--ea11y-notice-info-bg); + border-inline-start: 4px solid var(--ea11y-notice-info-border); +} + +.ea11y__renewal-notice { + border-inline-start: none; +} + +.ea11y__notice--pink { + padding-left: 54px; + position: relative; + background-color: var(--ea11y-notice-white); + border: 1px solid var(--ea11y-notice-pink-border); + border-inline-start: 4px solid var(--ea11y-notice-pink-accent); +} + +.ea11y__notice--pink b { + font-weight: 500; + color: var(--ea11y-notice-pink-text); +} + +.ea11y__notice--pink a { + font-weight: 500; + color: var(--ea11y-notice-pink-link); + margin-inline-start: 10px; + text-decoration: none; +} + +.ea11y__notice--pink .ea11y__icon-block { + display: flex; + justify-content: center; + align-items: center; + width: 48px; + position: absolute; + left: 0; + top: 0; + bottom: 0; + background-color: var(--ea11y-notice-pink-icon-bg); +} + +.ea11y__notice .primary-heading { + font-size: 17px; + color: var(--ea11y-notice-heading); + font-weight: 700; + margin-bottom: 8px; +} + +.ea11y__notice .sub-heading { + font-size: 13px; + color: var(--ea11y-notice-subheading); + margin-top: 0; + margin-bottom: 12px; +} + +/** Renewal notice styles **/ + +.ea11y__content-block { + display: flex; + gap: 15px; +} + +.ea11y__content-block .ea11y__notice-content { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; +} + +.ea11y__renewal-notice-btn { + text-decoration: none; + padding: 4px 10px; + border-radius: 4px; + width: auto; +} + +.ea11y__renewal-notice.ea11y__notice--warning .ea11y__renewal-notice-btn { + color: var(--ea11y-notice-warning-btn); + border: 1px solid var(--ea11y-notice-warning-btn); +} + +.ea11y__renewal-notice.ea11y__notice--error .ea11y__renewal-notice-btn { + color: var(--ea11y-notice-error); + border: 1px solid var(--ea11y-notice-error); +} + +.ea11y__renewal-notice.ea11y__notice--warning .ea11y__renewal-notice-btn:hover { + background: var(--ea11y-notice-warning-btn-hover); +} + +.ea11y__renewal-notice.ea11y__notice--error .ea11y__renewal-notice-btn:hover { + background-color: var(--ea11y-notice-error-btn-hover); +} diff --git a/classes/utils.php b/classes/utils.php index 38df00c0..ec86f3f0 100644 --- a/classes/utils.php +++ b/classes/utils.php @@ -15,7 +15,21 @@ public static function get_api_client(): ?Client { public static function is_plugin_settings_page(): bool { $current_screen = get_current_screen(); - return str_contains( $current_screen->id, '_page_accessibility-settings' ); + return $current_screen && str_contains( $current_screen->id, '_page_accessibility-settings' ); + } + + public static function is_wp_dashboard_page(): bool { + $current_screen = get_current_screen(); + return $current_screen && 'dashboard' === $current_screen->id; + } + + public static function is_wp_settings_page(): bool { + $current_screen = get_current_screen(); + return $current_screen && 'options-general' === $current_screen->id; + } + + public static function is_plugin_page(): bool { + return self::is_plugin_settings_page(); } public static function is_elementor_installed() :bool { diff --git a/modules/settings/module.php b/modules/settings/module.php index 02ea3526..106c7867 100644 --- a/modules/settings/module.php +++ b/modules/settings/module.php @@ -632,6 +632,7 @@ public function register_notices( Notices $notice_manager ) { $notices = [ 'Quota_80', 'Quota_100', + 'Renewal_Notice', ]; foreach ( $notices as $notice ) { diff --git a/modules/settings/notices/renewal-notice.php b/modules/settings/notices/renewal-notice.php new file mode 100644 index 00000000..492a7237 --- /dev/null +++ b/modules/settings/notices/renewal-notice.php @@ -0,0 +1,226 @@ +diff( $given_date ); + $this->days_diff = $interval->invert ? -$interval->days : $interval->days; + return $this->days_diff; + } + + private function get_notice_icon( $type ) { + if ( 'warning' === $type ) { + return ' + + '; + } + if ( 'error' === $type ) { + return ' + + '; + } + return ''; + } + + public function get_renewal_text(): array { + if ( $this->days_diff <= 30 && $this->days_diff > 0 ) { + return [ + 'title' => esc_html__( 'Ally Subscription Ending Soon!', 'pojo-accessibility' ), + 'description' => esc_html__( 'Renew now to keep access to Ally Assistant and continue improving your website\'s accessibility with guided fixes and scans.', 'pojo-accessibility' ), + 'btn' => esc_html__( 'Enable Auto-Renew', 'pojo-accessibility' ), + 'link' => esc_url( SettingsModule::get_upgrade_link( 'acc-renew-30' ) ), + 'type' => 'warning', + ]; + } + if ( $this->days_diff <= 0 && $this->days_diff > -7 ) { + return [ + 'title' => esc_html__( 'Your Ally subscription has expired', 'pojo-accessibility' ), + 'description' => esc_html__( 'Ally Assistant is no longer active. Renew now to resume accessibility scans and step by step fixes for your site.', 'pojo-accessibility' ), + 'btn' => esc_html__( 'Renew Now', 'pojo-accessibility' ), + 'link' => esc_url( SettingsModule::get_upgrade_link( 'acc-renew-expire' ) ), + 'type' => 'error', + ]; + } + return [ + 'title' => esc_html__( "It's not too late - renew Ally", 'pojo-accessibility' ), + 'description' => esc_html__( "Reactivate your subscription to restore Ally Assistant and continue improving your website's accessibility.", 'pojo-accessibility' ), + 'btn' => esc_html__( 'Reactivate Now', 'pojo-accessibility' ), + 'link' => esc_url( SettingsModule::get_upgrade_link( 'acc-renew-post-expire' ) ), + 'type' => 'error', + ]; + } + + /** + * Inner HTML for the renewal notice (icon + title + description + CTA). + */ + public function content(): string { + if ( null === $this->days_diff ) { + return ' '; + } + $text = $this->get_renewal_text(); + $this->type = $text['type']; + $icon = $this->get_notice_icon( $text['type'] ); + return sprintf( + '
%s
%s%s%s
', + $icon, + $text['title'], + $text['description'], + esc_url( $text['link'] ), + $text['btn'] + ); + } + + /** + * Custom wrapper for renewal notice with dismiss attributes. + */ + public function render() { + $text = ( null !== $this->days_diff ) ? $this->get_renewal_text() : [ 'type' => $this->type ]; + $type_attr = isset( $text['type'] ) ? $text['type'] : $this->type; + printf( + '
%5$s
', + esc_attr( $type_attr ), + esc_attr( $this->get_id() ), + esc_attr( $this->get_action_name() ), + esc_attr( wp_create_nonce( $this->get_action_name() ) ), + $this->content() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + public function is_dismissed(): bool { + $user_id = get_current_user_id(); + if ( ! $user_id ) { + return false; + } + $dismissed_at = get_user_meta( $user_id, self::DISMISSED_AT_USER_META, true ); + if ( ! $dismissed_at || ! is_numeric( $dismissed_at ) ) { + return false; + } + return ( time() * 1000 - (int) $dismissed_at ) < self::MS_IN_ONE_DAY; + } + + public function dismiss_per_user(): void { + $user_id = get_current_user_id(); + if ( ! $user_id ) { + wp_send_json_error( [ 'message' => 'Invalid user' ] ); + } + update_user_meta( $user_id, self::DISMISSED_AT_USER_META, (string) ( time() * 1000 ) ); + } + + public function maybe_set_conditions() { + if ( ! Connect::is_connected() || ! Utils::user_is_admin() ) { + return; + } + + $info = Settings::get( Settings::PLAN_DATA ); + + if ( empty( $info ) || is_wp_error( $info ) ) { + SettingsModule::refresh_plan_data(); + $info = Settings::get( Settings::PLAN_DATA ); + } + + if ( empty( $info ) || ! isset( $info->plan->next_cycle_date ) ) { + return; + } + + if ( isset( $info->plan->features->retention ) && 'None' !== $info->plan->features->retention ) { + return; + } + + if ( $this->date_diff_from_current( $info->plan->next_cycle_date ) > 30 ) { + return; + } + + if ( ! Utils::is_wp_dashboard_page() && ! Utils::is_wp_settings_page() && ! Utils::is_plugin_page() ) { + return; + } + + $this->conditions = true; + $this->days_diff = $this->date_diff_from_current( $info->plan->next_cycle_date ); + } + + /** + * Enqueue notice styles on screens where the renewal notice may appear. + * Must run on admin_enqueue_scripts so the stylesheet is output in the page head. + */ + public function enqueue_styles() { + if ( ! Utils::is_wp_dashboard_page() && ! Utils::is_wp_settings_page() && ! Utils::is_plugin_page() ) { + return; + } + wp_enqueue_style( + 'ea11y-renewal-notice', + EA11Y_ASSETS_URL . 'css/notice.css', + [], + EA11Y_VERSION + ); + } + + /** + * @throws \Exception + */ + public function __construct() { + add_action( 'current_screen', [ $this, 'maybe_set_conditions' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] ); + parent::__construct(); + } +}