'; echo esc_html( $message ); echo '
'; } ); } endif; // Another PressNative plugin variant (e.g. legacy pressnative-engine) may already be active. // Bail early with an admin notice instead of triggering a class redeclaration fatal error. if ( class_exists( 'PressNative_Options', false ) ) { pressnative_fail_safe( __( 'PressNative Apps could not load because another PressNative plugin is already active. Deactivate and remove the other plugin folder (for example pressnative-engine), then activate PressNative Apps again.', 'pressnative-apps' ) ); return; } if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { pressnative_fail_safe( sprintf( /* translators: %s: current PHP version */ __( 'PressNative Apps requires PHP 7.4 or higher. This site is running PHP %s. The rest of the site is still running.', 'pressnative-apps' ), PHP_VERSION ) ); return; } define( 'PRESSNATIVE_VERSION', '1.3.2' ); define( 'PRESSNATIVE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'PRESSNATIVE_PLUGIN_FILE', __FILE__ ); $pressnative_missing = array(); foreach ( pressnative_required_files() as $pressnative_rel ) { if ( ! is_readable( PRESSNATIVE_PLUGIN_DIR . $pressnative_rel ) ) { $pressnative_missing[] = $pressnative_rel; } } if ( ! empty( $pressnative_missing ) ) { pressnative_fail_safe( sprintf( /* translators: %s: comma-separated file list */ __( 'PressNative Apps is missing required files (%s), usually after a failed plugin update. The rest of the site is still running. Reinstall PressNative Apps from a zip, or restore the previous version from your host backup.', 'pressnative-apps' ), implode( ', ', $pressnative_missing ) ) ); unset( $pressnative_missing, $pressnative_rel ); return; } unset( $pressnative_missing, $pressnative_rel ); foreach ( pressnative_required_files() as $pressnative_rel ) { require_once PRESSNATIVE_PLUGIN_DIR . $pressnative_rel; } unset( $pressnative_rel ); /** * Activation: create devices table. Registry contact happens only after the site owner connects (API key). */ register_activation_hook( __FILE__, function () { PressNative_Devices::create_table(); // Schedule async AOT warm-up so existing content is pre-compiled. PressNative_AOT_Compiler::schedule_warmup(); // Register .well-known rewrite rules. PressNative_Well_Known::flush_rewrite_rules(); } ); /** * Deactivation: currently a no-op. Data is preserved so reactivation is seamless. * Full cleanup (options + database table) happens in uninstall.php. */ register_deactivation_hook( __FILE__, function () { // Stop any in-progress AOT batch compilation. wp_clear_scheduled_hook( PressNative_AOT_Compiler::CRON_HOOK ); // Data is preserved for reactivation. See uninstall.php for full cleanup. } ); /** * REST API: layout/home and register-device. */ add_action( 'rest_api_init', function () { try { $layout = new PressNative_Layout(); register_rest_route( 'pressnative/v1', '/layout/home', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) use ( $layout ) { $data = $layout->get_home_layout(); $response = rest_ensure_response( $data ); $response->header( 'X-PressNative-Version', PRESSNATIVE_VERSION ); $response = PressNative_Http_Cache::apply( $request, $response, $data ); if ( ! is_wp_error( $response ) && $response->get_status() === 200 ) { $device_id = $request->get_param( 'device_id' ); PressNative_Analytics::forward_event_to_registry( 'home', 'home', get_bloginfo( 'name' ), null, $device_id ); } return $response; }, // Public: read-only published content for the mobile apps. 'permission_callback' => '__return_true', ) ); register_rest_route( 'pressnative/v1', '/layout/posts', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) use ( $layout ) { $page = (int) $request->get_param( 'page' ); $per_page = (int) $request->get_param( 'per_page' ); if ( $page < 1 ) { $page = 1; } if ( $per_page < 1 || $per_page > 50 ) { $per_page = 20; } $data = $layout->get_posts_list_layout( $page, $per_page ); $response = rest_ensure_response( $data ); $response->header( 'X-PressNative-Version', PRESSNATIVE_VERSION ); $response = PressNative_Http_Cache::apply( $request, $response, $data ); return $response; }, // Public: read-only published content for the mobile apps. 'permission_callback' => '__return_true', 'args' => array( 'page' => array( 'type' => 'integer', 'default' => 1, 'minimum' => 1, 'sanitize_callback' => 'absint', ), 'per_page' => array( 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 50, 'sanitize_callback' => 'absint', ), ), ) ); register_rest_route( 'pressnative/v1', '/layout/post/(?PNot found
'; exit; } // Set up post data so shortcodes and the_content work. setup_postdata( $post ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core WordPress filter $content = apply_filters( 'the_content', $post->post_content ); // Enqueue content styles for WebView (output in wp_head). wp_register_style( 'pressnative-webview-content', false, array(), PRESSNATIVE_VERSION ); wp_add_inline_style( 'pressnative-webview-content', 'body{font-family:sans-serif;font-size:16px;line-height:1.5;margin:0;padding:16px;}img{max-width:100%;height:auto;}table{width:100%;border-collapse:collapse;}' ); wp_enqueue_style( 'pressnative-webview-content' ); // Capture the full output so we can relativize absolute site URLs. // This ensures script/style URLs work in the mobile WebView which may // access the site via a different host (e.g. 10.0.2.2 on Android emulator). ob_start(); ?> > getMessage() ) ); } } ); try { /** * Admin: PressNative menu and Registry URL setting. */ PressNative_Admin::init(); /** * QR code shortcode for app deep links. */ PressNative_QR::init(); /** * AOT compiler: pre-build SDUI JSON on every post/page/product save. */ PressNative_AOT_Compiler::init(); /** * Notify Registry when branding/layout options are saved (invalidates site branding cache). */ PressNative_Registry_Notify::init(); /** * Serve .well-known app association files for Universal / App Links. */ PressNative_Well_Known::init(); /** * Editorial push metabox (notify on publish). */ PressNative_Editorial_Push::init(); /** * WooCommerce abandoned-cart / back-in-stock push. */ PressNative_Cart_Recovery::init(); /** * One-time option migrations after an upgrade. */ add_action( 'admin_init', array( 'PressNative_Options', 'maybe_migrate' ) ); } catch ( Throwable $e ) { pressnative_fail_safe( sprintf( /* translators: %s: exception message */ __( 'PressNative Apps failed to start (%s). The rest of the site is still running.', 'pressnative-apps' ), $e->getMessage() ) ); return; } /** * Whether the current WooCommerce page was opened from the app's checkout handoff. * Display-only: decides whether to show a "Return to app" link; no state changes. * * @return bool */ function pressnative_is_app_checkout_request() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only flag, no state change if ( isset( $_GET['pressnative_app_checkout'] ) || isset( $_GET['pressnative_checkout_token'] ) ) { return true; } $referer = wp_get_referer(); return $referer && false !== strpos( $referer, 'pressnative_checkout_token=' ); } /** * Handle checkout completion and return to app. */ add_action( 'woocommerce_thankyou', function ( $order_id ) { if ( ! $order_id ) { return; } if ( ! pressnative_is_app_checkout_request() ) { return; } // Enqueue script to redirect to app after a short delay. wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'pressnative-return-to-app', false, array( 'jquery' ), PRESSNATIVE_VERSION, true ); wp_add_inline_script( 'pressnative-return-to-app', '(function(){var o=' . (int) $order_id . ';var u="app.pressnative.hub://checkout-complete?order_id="+o;setTimeout(function(){var b=document.createElement("button");b.innerHTML="Return to App";b.style.cssText="background:#0073aa;color:white;padding:12px 24px;border:none;border-radius:4px;font-size:16px;margin:20px 0;cursor:pointer";b.onclick=function(){window.location.href=u};var d=document.querySelector(".woocommerce-order");(d||document.body).appendChild(b)},1000);setTimeout(function(){window.location.href=u},10000)})();', 'after' ); }, 10, 1 ); /** * Add return to app link on cart page when accessed from app. */ add_action( 'woocommerce_after_cart', function () { if ( ! pressnative_is_app_checkout_request() ) { return; } ?> getMessage() ) ); } /** * Register app download shortcodes. */ add_action( 'init', function () { add_shortcode( 'pressnative_download', function ( $atts ) { $atts = shortcode_atts( array( 'style' => 'both', // 'ios', 'android', 'both' 'size' => 'medium', // 'small', 'medium', 'large' 'text' => '', // Custom text override 'ios_url' => '', // Custom iOS URL 'android_url' => '', // Custom Android URL ), $atts, 'pressnative_download' ); $site_url = untrailingslashit( home_url() ); // Generate fallback HTML for web browsers with deferred deep linking $html = ''; return wp_kses_post( $html ); } ); // Debug shortcode for testing deep links add_shortcode( 'pressnative_debug_links', function () { if ( ! current_user_can( 'manage_options' ) ) { return ''; // Only show to admins } $site_url = untrailingslashit( home_url() ); $deep_link_url = 'https://pressnative.app/open?site=' . rawurlencode( $site_url ); $custom_scheme_url = 'app.pressnative.hub://site?url=' . rawurlencode( $site_url ); $allowed_protocols = array_merge( wp_allowed_protocols(), array( 'app.pressnative.hub' ) ); $html = 'Deep Link (HTTPS):
' . esc_html( $deep_link_url ) . '
Custom Scheme:
' . esc_html( $custom_scheme_url ) . '
Click these links on your mobile device to test deep linking.
'; $html .= '