plugin_name = $plugin_name; $this->version = $version; } /** * Register hooks -- but only when Pro is NOT present. * * @since 0.7.0 * @return void */ public function init() { // If Pro is present in any form, register absolutely nothing. if ( self::is_pro_present() ) { return; } // One-time admin notice (upload.php / plugins.php / plugin settings screen). add_action( 'admin_notices', array( $this, 'render_one_time_notice' ) ); // AJAX dismiss handlers. add_action( 'wp_ajax_protect_uploads_dismiss_banner', array( $this, 'ajax_dismiss_banner' ) ); add_action( 'wp_ajax_protect_uploads_dismiss_notice', array( $this, 'ajax_dismiss_notice' ) ); // Inline JS for AJAX dismissals (only on relevant screens). add_action( 'admin_footer', array( $this, 'print_dismiss_script' ) ); } /** * Determine whether Protect Uploads Pro is present. * * Pro is considered present if its license class is loaded OR the Pro * plugin is active. When present, the free plugin shows no upsell at all. * * @since 0.7.0 * @return bool True when Pro is present, false otherwise. */ public static function is_pro_present() { if ( class_exists( 'Protect_Uploads_Pro_License' ) ) { return true; } if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return is_plugin_active( 'protect-uploads-pro/protect-uploads-pro.php' ); } /** * Build a UTM-tagged upgrade URL for a given placement. * * @since 0.7.0 * @param string $placement The placement identifier (banner, inline-*, plugins-row, notice). * @return string The full URL with UTM query args. */ private function upgrade_url( $placement ) { return add_query_arg( array( 'utm_source' => 'plugin', 'utm_medium' => $placement, 'utm_campaign' => 'free-to-pro', ), 'https://protectuploads.com/' ); } /** * Whether the current user has dismissed the settings-page banner. * * @since 0.7.0 * @return bool */ private function banner_is_dismissed() { return (bool) get_user_meta( get_current_user_id(), self::BANNER_DISMISSED_META, true ); } /** * Whether the one-time notice has been dismissed for this site. * * @since 0.7.0 * @return bool */ private function notice_is_dismissed() { return (bool) get_option( self::NOTICE_DISMISSED_OPTION, false ); } /** * Render the settings-page banner (or, when dismissed, the quiet recovery link). * * Called directly from the admin settings page renderer. Does nothing when * Pro is present. * * @since 0.7.0 * @return void */ public function render_settings_banner() { if ( self::is_pro_present() ) { return; } if ( $this->banner_is_dismissed() ) { // Collapsed, quiet, recoverable link rendered at the bottom of the page. return; } $bullets = array( __( 'Image watermarks with 9 positions', 'protect-uploads' ), __( 'Expiring and single-use passwords', 'protect-uploads' ), __( 'Role-based access with server enforcement', 'protect-uploads' ), __( 'Download analytics and hotlink blocking', 'protect-uploads' ), ); ?>

print_banner_styles(); } /** * Render the quiet recovery link shown at the bottom of the settings page * once the banner has been dismissed. * * @since 0.7.0 * @return void */ public function render_settings_footer_link() { if ( self::is_pro_present() ) { return; } if ( ! $this->banner_is_dismissed() ) { return; } ?> array( 'placement' => 'inline-watermark', 'text' => __( 'Pro adds image watermarks with 9 placement positions, custom opacity, and one-click bulk re-watermarking across your library.', 'protect-uploads' ), ), 'passwords' => array( 'placement' => 'inline-passwords', 'text' => __( 'Pro unlocks expiring, single-use, and limited-use passwords plus brute-force rate limiting for stronger file security.', 'protect-uploads' ), ), 'protection' => array( 'placement' => 'inline-protection', 'text' => __( 'Pro enforces access at the server level with role-based rules and rotating-token hotlink protection.', 'protect-uploads' ), ), ); if ( ! isset( $map[ $section ] ) ) { return; } $hint = $map[ $section ]; ?>

upgrade_url( 'plugins-row' ) ) . '" target="_blank" rel="noopener noreferrer" style="color:#f7a933;font-weight:bold;">' . esc_html__( 'Upgrade to Pro', 'protect-uploads' ) . ''; $links[] = $upgrade_link; return $links; } /** * Render the one-time dismissible notice on relevant screens. * * Shown only on upload.php, plugins.php, and the plugin's settings page, and * only once per site (controlled by an option flag set on dismiss). * * @since 0.7.0 * @return void */ public function render_one_time_notice() { if ( self::is_pro_present() ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } if ( $this->notice_is_dismissed() ) { return; } $screen = get_current_screen(); if ( ! $screen ) { return; } $allowed_screens = array( 'upload', // upload.php 'plugins', // plugins.php 'media_page_' . $this->plugin_name . '-settings-page', // plugin settings page ); if ( ! in_array( $screen->id, $allowed_screens, true ) ) { return; } ?>

__( 'Insufficient permissions.', 'protect-uploads' ) ), 403 ); } update_user_meta( get_current_user_id(), self::BANNER_DISMISSED_META, 1 ); wp_send_json_success(); } /** * AJAX handler: dismiss the one-time notice (per site). * * @since 0.7.0 * @return void */ public function ajax_dismiss_notice() { check_ajax_referer( 'protect_uploads_dismiss_notice', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'protect-uploads' ) ), 403 ); } update_option( self::NOTICE_DISMISSED_OPTION, 1 ); wp_send_json_success(); } /** * Print the inline styles for the settings-page banner. * * Kept small and scoped; uses native-WP styling with a brand accent. * * @since 0.7.0 * @return void */ private function print_banner_styles() { ?> plugin_name . '-settings-page', ); if ( ! in_array( $screen->id, $relevant, true ) ) { return; } $banner_nonce = wp_create_nonce( 'protect_uploads_dismiss_banner' ); ?>