'United States', 'GB' => 'United Kingdom', 'CA' => 'Canada', 'AU' => 'Australia', 'AT' => 'Austria', 'BE' => 'Belgium', 'BR' => 'Brazil', 'BG' => 'Bulgaria', 'HR' => 'Croatia', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DK' => 'Denmark', 'EE' => 'Estonia', 'FI' => 'Finland', 'FR' => 'France', 'DE' => 'Germany', 'GR' => 'Greece', 'HK' => 'Hong Kong', 'HU' => 'Hungary', 'IE' => 'Ireland', 'IT' => 'Italy', 'JP' => 'Japan', 'LV' => 'Latvia', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MY' => 'Malaysia', 'MT' => 'Malta', 'MX' => 'Mexico', 'NL' => 'Netherlands', 'NZ' => 'New Zealand', 'NO' => 'Norway', 'PL' => 'Poland', 'PT' => 'Portugal', 'RO' => 'Romania', 'SG' => 'Singapore', 'SK' => 'Slovakia', 'SI' => 'Slovenia', 'ES' => 'Spain', 'SE' => 'Sweden', 'CH' => 'Switzerland', 'TH' => 'Thailand', 'AE' => 'United Arab Emirates', ); } function acta_get_supported_currencies() { return array( 'usd' => 'USD - US Dollar', 'eur' => 'EUR - Euro', 'gbp' => 'GBP - British Pound', 'cad' => 'CAD - Canadian Dollar', 'aud' => 'AUD - Australian Dollar', 'jpy' => 'JPY - Japanese Yen', 'chf' => 'CHF - Swiss Franc', 'nzd' => 'NZD - New Zealand Dollar', 'sek' => 'SEK - Swedish Krona', 'nok' => 'NOK - Norwegian Krone', 'dkk' => 'DKK - Danish Krone', 'sgd' => 'SGD - Singapore Dollar', 'hkd' => 'HKD - Hong Kong Dollar', 'mxn' => 'MXN - Mexican Peso', 'brl' => 'BRL - Brazilian Real', 'pln' => 'PLN - Polish Zloty', 'czk' => 'CZK - Czech Koruna', 'huf' => 'HUF - Hungarian Forint', 'ron' => 'RON - Romanian Leu', 'bgn' => 'BGN - Bulgarian Lev', 'thb' => 'THB - Thai Baht', 'myr' => 'MYR - Malaysian Ringgit', 'aed' => 'AED - UAE Dirham', ); } // ─── Activation: generate a secret key ─────────────────────────────────────── register_activation_hook( __FILE__, 'acta_activate' ); function acta_activate() { if ( ! get_option( ACTA_OPTION_KEY ) ) { update_option( ACTA_OPTION_KEY, bin2hex( random_bytes( 32 ) ) ); } // Initialize connection status if not set if ( ! get_option( ACTA_CONNECTION_STATUS ) ) { update_option( ACTA_CONNECTION_STATUS, 'not_registered' ); } // Redirect to the Acta settings page after activation. update_option( ACTA_ACTIVATION_REDIRECT_OPTION, 1 ); } // Also redirect after plugin update (upload zip) — WordPress shows "Go to Plugin Installer" link otherwise. add_action( 'upgrader_process_complete', 'acta_maybe_set_redirect_after_update', 10, 2 ); function acta_maybe_set_redirect_after_update( $upgrader_object, $options ) { if ( empty( $options['action'] ) || $options['action'] !== 'update' ) { return; } if ( empty( $options['type'] ) || $options['type'] !== 'plugin' ) { return; } $our_plugin = plugin_basename( __FILE__ ); $updated = array(); if ( ! empty( $options['plugins'] ) && is_array( $options['plugins'] ) ) { $updated = $options['plugins']; } elseif ( ! empty( $options['plugin'] ) ) { $updated = array( $options['plugin'] ); } if ( in_array( $our_plugin, $updated, true ) ) { update_option( ACTA_ACTIVATION_REDIRECT_OPTION, 1 ); } } add_action( 'admin_init', 'acta_maybe_redirect_after_activation' ); function acta_maybe_redirect_after_activation() { if ( ! get_option( ACTA_ACTIVATION_REDIRECT_OPTION ) ) { return; } delete_option( ACTA_ACTIVATION_REDIRECT_OPTION ); if ( ! is_admin() || ! current_user_can( 'manage_options' ) || wp_doing_ajax() ) { return; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return; } if ( isset( $_GET['activate-multi'] ) ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=' . ACTA_ADMIN_PAGE_SLUG ) ); exit; } // ─── Admin settings page ───────────────────────────────────────────────────── add_action( 'admin_menu', 'acta_add_admin_menu' ); add_action( 'admin_enqueue_scripts', 'acta_enqueue_admin_scripts' ); function acta_enqueue_admin_scripts( $hook ) { if ( 'toplevel_page_' . ACTA_ADMIN_PAGE_SLUG !== $hook ) { return; } $asset_url = plugin_dir_url( __FILE__ ) . 'assets/js/admin-copy-price.js'; wp_enqueue_script( 'acta-admin-copy-price', $asset_url, array(), ACTA_PLUGIN_VERSION, true ); } function acta_add_admin_menu() { add_menu_page( 'Acta Settings', 'Acta', 'manage_options', ACTA_ADMIN_PAGE_SLUG, 'acta_settings_page', ACTA_MENU_ICON, 30 ); } add_action( 'admin_init', 'acta_settings_init' ); function acta_settings_init() { register_setting( 'acta_settings', ACTA_OPTION_KEY, array( 'sanitize_callback' => 'sanitize_text_field', ) ); register_setting( 'acta_settings', ACTA_PUBLISHER_ID_KEY, array( 'sanitize_callback' => 'sanitize_text_field', ) ); } // ─── Backend API functions ─────────────────────────────────────────────────── /** * Self-service onboarding: create a new publisher on the Acta backend. * Called when publisher clicks "Get Started with Acta". * * @return array { success: bool, message: string, publisherId?: string, stripeOnboardingUrl?: string } */ function acta_onboard_to_backend( $email, $first_name, $last_name, $country, $currency, $article_price ) { $backend_url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/onboard-wordpress'; $secret = get_option( ACTA_OPTION_KEY, '' ); $endpoint = rest_url( 'acta/v1/content' ); $response = wp_remote_post( $backend_url, array( 'timeout' => 30, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'email' => $email, 'firstName' => $first_name, 'lastName' => $last_name, 'website' => home_url(), 'name' => get_bloginfo( 'name' ), 'country' => $country, 'currency' => $currency, 'articlePrice' => (float) $article_price, 'pluginEndpoint' => $endpoint, 'pluginSecretKey'=> $secret, ) ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => 'Connection failed: ' . $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 201 && ! empty( $body['success'] ) ) { return array( 'success' => true, 'message' => 'Publisher created successfully.', 'publisherId' => $body['publisherId'] ?? '', 'stripeOnboardingUrl'=> $body['stripeOnboardingUrl'] ?? '', ); } $error_msg = $body['error'] ?? ( 'HTTP ' . $code ); return array( 'success' => false, 'message' => $error_msg, ); } /** * Reconnect the plugin to an existing publisher on the Acta backend. * Used for plugin reinstall or v1 → v2 migration. */ function acta_connect_to_backend( $publisher_id, $plugin_endpoint, $plugin_secret_key ) { $backend_url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/connect-wordpress'; $response = wp_remote_post( $backend_url, array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'publisherId' => $publisher_id, 'siteUrl' => home_url(), 'pluginEndpoint' => $plugin_endpoint, 'pluginSecretKey' => $plugin_secret_key, ) ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => 'Connection failed: ' . $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 200 && ! empty( $body['success'] ) ) { return array( 'success' => true, 'message' => $body['message'] ?? 'Connected successfully.', ); } $error_msg = $body['error'] ?? ( 'HTTP ' . $code ); return array( 'success' => false, 'message' => $error_msg, ); } /** * Tell Acta this site is disconnecting — the plugin is being removed, or the * publisher pressed Disconnect. * * Acta marks the publisher inactive, which stops the paywall being served and * stops new purchases. Nothing is deleted: the account, Stripe details and any * balance owed are kept, so reinstalling picks up where this left off. * * @return array{ success: bool, message: string } */ function acta_disconnect_from_backend( $publisher_id, $secret, $site_url ) { if ( empty( $publisher_id ) || empty( $secret ) ) { return array( 'success' => false, 'message' => 'Not connected to Acta.', ); } $response = wp_remote_post( rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/disconnect-wordpress', array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'publisherId' => $publisher_id, 'siteUrl' => $site_url, 'pluginSecretKey' => $secret, ) ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => 'Connection failed: ' . $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( 200 === $code && ! empty( $body['success'] ) ) { return array( 'success' => true, 'message' => 'Disconnected from Acta.', ); } return array( 'success' => false, 'message' => $body['error'] ?? ( 'HTTP ' . $code ), ); } /** * Fetch publisher info (article price, rate limit) from Acta backend. * * @return array{ articlePrice: float, changesRemaining: int, revShare?: int }|null */ function acta_fetch_publisher_info( $publisher_id, $secret, $site_url ) { if ( empty( $publisher_id ) || empty( $secret ) ) { return null; } $url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/wordpress-publisher-info'; $response = wp_remote_post( $url, array( 'timeout' => 10, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'publisherId' => $publisher_id, 'key' => $secret, 'siteUrl' => $site_url, ) ), ) ); if ( is_wp_error( $response ) ) { return null; } $code = wp_remote_retrieve_response_code( $response ); if ( $code !== 200 ) { return null; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $body ) || ! isset( $body['articlePrice'] ) ) { return null; } return array( 'articlePrice' => (float) $body['articlePrice'], 'changesRemaining' => isset( $body['changesRemaining'] ) ? (int) $body['changesRemaining'] : 3, 'revShare' => isset( $body['revShare'] ) ? (int) $body['revShare'] : 15, ); } /** * Update default article price via Acta backend. * * @return array{ success: bool, message: string, articlePrice?: float, changesRemaining?: int } */ function acta_update_default_price( $publisher_id, $secret, $site_url, $article_price ) { $url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/wordpress-update-article-price'; $body = array( 'publisherId' => $publisher_id, 'key' => $secret, 'siteUrl' => $site_url, 'articlePrice' => (float) $article_price, ); $response = wp_remote_post( $url, array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 200 && ! empty( $body['success'] ) ) { return array( 'success' => true, 'message' => 'Default price updated.', 'articlePrice' => $body['articlePrice'] ?? $article_price, 'changesRemaining' => $body['changesRemaining'] ?? 0, ); } return array( 'success' => false, 'message' => $body['error'] ?? ( 'HTTP ' . $code ), ); } /** * Push the paywall mode (pay-per-article only vs. alongside subscriptions) to * the Acta backend. Stored on the publisher record and delivered to the embed * script at runtime — so the flag never lives in (cacheable) page HTML and a * toggle takes effect on the next page load without a cache purge. */ function acta_update_paywall_mode( $publisher_id, $secret, $site_url, $subscribe_override ) { $url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/wordpress-update-paywall-mode'; $body = array( 'publisherId' => $publisher_id, 'key' => $secret, 'siteUrl' => $site_url, 'subscribeOverride' => (bool) $subscribe_override, ); $response = wp_remote_post( $url, array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 200 && ! empty( $body['success'] ) ) { return array( 'success' => true ); } return array( 'success' => false, 'message' => $body['error'] ?? ( 'HTTP ' . $code ), ); } // ─── Settings page renderer ───────────────────────────────────────────────── function acta_settings_page() { $secret = get_option( ACTA_OPTION_KEY, '' ); $publisher_id = get_option( ACTA_PUBLISHER_ID_KEY, '' ); $stripe_url = get_option( ACTA_STRIPE_URL_KEY, '' ); $conn_status = get_option( ACTA_CONNECTION_STATUS, 'not_registered' ); $endpoint = rest_url( 'acta/v1/content' ); // ── Handle form actions ────────────────────────────────────────────────── // Handle "Get Started with Acta" (self-service onboarding) if ( isset( $_POST['acta_action'] ) && $_POST['acta_action'] === 'onboard' && check_admin_referer( 'acta_onboard' ) ) { $email = sanitize_email( wp_unslash( $_POST['acta_email'] ?? '' ) ); $first_name = sanitize_text_field( wp_unslash( $_POST['acta_first_name'] ?? '' ) ); $last_name = sanitize_text_field( wp_unslash( $_POST['acta_last_name'] ?? '' ) ); $country = sanitize_text_field( wp_unslash( $_POST['acta_country'] ?? '' ) ); $currency = sanitize_text_field( wp_unslash( $_POST['acta_currency'] ?? '' ) ); $article_price = floatval( wp_unslash( $_POST['acta_article_price'] ?? 0 ) ); if ( empty( $email ) || empty( $first_name ) || empty( $last_name ) || empty( $country ) || empty( $currency ) || $article_price <= 0 ) { echo '

' . esc_html__( 'All fields are required. Please fill in your email, name, country, currency, and a price greater than 0.', 'acta-pay-per-article' ) . '

'; } else { $result = acta_onboard_to_backend( $email, $first_name, $last_name, $country, $currency, $article_price ); if ( $result['success'] ) { update_option( ACTA_PUBLISHER_ID_KEY, $result['publisherId'] ); update_option( ACTA_STRIPE_URL_KEY, $result['stripeOnboardingUrl'] ); update_option( ACTA_CONNECTION_STATUS, 'registered' ); $publisher_id = $result['publisherId']; $stripe_url = $result['stripeOnboardingUrl']; $conn_status = 'registered'; echo '

' . esc_html__( 'Successfully registered! Complete your Stripe setup below to start accepting payments.', 'acta-pay-per-article' ) . '

'; } else { echo '

' . esc_html( $result['message'] ) . '

'; } } } // Handle "I've completed Stripe setup" — check if Stripe is done if ( isset( $_POST['acta_action'] ) && $_POST['acta_action'] === 'check_stripe' && check_admin_referer( 'acta_check_stripe' ) ) { // The Stripe onboarding URL becomes invalid once completed. // We mark as live — the backend's /connect/success endpoint already set account_url to null. update_option( ACTA_CONNECTION_STATUS, 'live' ); update_option( ACTA_STRIPE_URL_KEY, '' ); $conn_status = 'live'; $stripe_url = ''; echo '

' . esc_html__( 'Status updated. If your Stripe setup is complete, you\'re live!', 'acta-pay-per-article' ) . '

'; } // Handle "Update default price" if ( $conn_status === 'live' && isset( $_POST['acta_action'] ) && $_POST['acta_action'] === 'update_default_price' && check_admin_referer( 'acta_update_default_price' ) ) { $new_price = floatval( wp_unslash( $_POST['acta_default_price'] ?? 0 ) ); if ( $new_price > 0 ) { $result = acta_update_default_price( $publisher_id, $secret, home_url(), $new_price ); if ( $result['success'] ) { echo '

' . esc_html__( 'Default price updated to ', 'acta-pay-per-article' ) . esc_html( number_format( $result['articlePrice'], 2 ) ) . '.

'; } else { echo '

' . esc_html( $result['message'] ) . '

'; } } } // Handle "Save paywall mode" (subscription + pay-per-article vs pay-per-article only) if ( $conn_status === 'live' && isset( $_POST['acta_action'] ) && $_POST['acta_action'] === 'save_paywall_mode' && check_admin_referer( 'acta_save_paywall_mode' ) ) { $override = ( '1' === sanitize_text_field( wp_unslash( $_POST['acta_subscribe_override'] ?? '0' ) ) ) ? '1' : '0'; $result = acta_update_paywall_mode( $publisher_id, $secret, home_url(), '1' === $override ); if ( $result['success'] ) { update_option( ACTA_SUBSCRIBE_OVERRIDE_KEY, $override ); echo '

' . ( '1' === $override ? esc_html__( 'Saved. Acta will now replace your subscribe paywall with a pay-per-article button.', 'acta-pay-per-article' ) : esc_html__( 'Saved. Acta will mount alongside your existing subscription paywall.', 'acta-pay-per-article' ) ) . '

'; } else { echo '

' . esc_html( $result['message'] ) . '

'; } } // Handle "Disconnect Acta" — pause without deleting the plugin. if ( isset( $_POST['acta_action'] ) && $_POST['acta_action'] === 'disconnect' && check_admin_referer( 'acta_disconnect' ) ) { $result = acta_disconnect_from_backend( $publisher_id, $secret, home_url() ); if ( $result['success'] ) { // Keep the publisher id and secret. Acta kept the account, so // reconnecting later is a single click rather than a fresh signup. update_option( ACTA_CONNECTION_STATUS, 'disconnected' ); $conn_status = 'disconnected'; echo '

' . esc_html__( 'Acta is disconnected. Your account and any balance owed are safe — reconnect any time.', 'acta-pay-per-article' ) . '

'; } else { echo '

' . esc_html( $result['message'] ) . '

'; } } // Handle "Reconnect Acta" — resume after a disconnect. if ( isset( $_POST['acta_action'] ) && $_POST['acta_action'] === 'reconnect' && check_admin_referer( 'acta_reconnect' ) ) { $result = acta_connect_to_backend( $publisher_id, rest_url( 'acta/v1/content' ), $secret ); if ( $result['success'] ) { update_option( ACTA_CONNECTION_STATUS, 'live' ); $conn_status = 'live'; echo '

' . esc_html__( 'Reconnected. Acta is live on your site again.', 'acta-pay-per-article' ) . '

'; } else { echo '

' . esc_html( $result['message'] ) . '

'; } } // ── Render the settings page ───────────────────────────────────────────── $countries = acta_get_supported_countries(); $currencies = acta_get_supported_currencies(); $subscribe_override = '1' === get_option( ACTA_SUBSCRIBE_OVERRIDE_KEY, '' ); ?>

Acta Settings

Acta is a pay-per-content solution for publishers and creators.

Version

Get started accepting payments on your content

How much readers pay to unlock a single article.

✓ Connected to Acta

Publisher ID:

Complete your Stripe setup

You need to set up your Stripe account to receive payments from readers. This takes about 5 minutes.

Complete Stripe Setup →

✓ You're live with Acta!

✓ Stripe setup complete — readers can now purchase your articles.

Publisher ID:

How Acta works

Acta adds a “Read instantly” button to the paywall you already use, giving readers the option to buy a single article. Your subscription offer stays exactly as it is — Acta finds your paywall and adds the button automatically.

Works with: Jetpack, MemberPress, Paid Memberships Pro, Restrict Content Pro, and more. If something looks off, just contact us and we’ll sort it out.

No paywall yet? Install the free Jetpack plugin and add a Paywall block where the article should lock — Acta integrates right within it.

Your price: Set any price per article (see currency minimums).

Our fee: No setup or monthly fee; % rev share including processor fees.

Questions or need setup help? Reach us at contact@readwithacta.com.

Paywall mode

Change default price

Current default price:

Set a custom price on an article

By default, all articles use your default price. To set a different price on a specific article, add a Custom HTML block in the post editor (before the paywall break) with this snippet:

<div id="acta-price" data-price="ENTER_PRICE_HERE"></div>

Replace ENTER_PRICE_HERE with the price (e.g. 2.99). This overrides the default price for that article only.

Disconnect Acta

Turns off the Acta button on your site. Your account, your Stripe details and any money owed to you are kept — you can reconnect any time without signing up again.

Prefer this to deleting the plugin: deleting removes the settings stored on your site, and readers who already bought an article lose access to it.

'color: #b32d2e; border-color: #b32d2e; background: #fff;' ) ); ?>

Acta is disconnected

The Acta button is not being shown on your site and no new purchases are being taken.

Your account is intact — Publisher ID: . Any balance owed to you is unaffected.

Contact us at contact@readwithacta.com or visit readwithacta.com

'GET', 'callback' => 'acta_get_content', 'permission_callback' => 'acta_verify_secret', 'args' => array( 'slug' => array( 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), 'key' => array( 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), ), ) ); } /** * Verify the shared secret key sent in the request. * This is our auth layer — only Acta backend (which knows the key) can call this. */ function acta_verify_secret( WP_REST_Request $request ) { $secret = get_option( ACTA_OPTION_KEY, '' ); if ( empty( $secret ) ) { return new WP_Error( 'acta_not_configured', 'Acta plugin not configured.', array( 'status' => 503 ) ); } $provided = $request->get_param( 'key' ); // Constant-time comparison to prevent timing attacks if ( ! hash_equals( $secret, (string) $provided ) ) { return new WP_Error( 'acta_unauthorized', 'Invalid secret key.', array( 'status' => 401 ) ); } return true; } /** * Main content delivery callback. * Gets raw post content from DB, strips all paywall markers, then renders via the_content filter. */ function acta_get_content( WP_REST_Request $request ) { $slug = $request->get_param( 'slug' ); // Try post types in order: post, page, then any custom post type $post = acta_get_post_by_slug( $slug, array( 'post', 'page' ) ); if ( ! $post ) { return new WP_Error( 'acta_not_found', 'Post not found for slug: ' . $slug, array( 'status' => 404 ) ); } // Get the raw content from the database (no filters applied yet) $raw_content = $post->post_content; // Strip all known paywall markers from raw content $clean_content = acta_strip_paywall_markers( $raw_content ); // Apply the_content filters (image sizing, shortcodes, embeds, etc.) // but NOT the paywall filters (they've been stripped above) $clean_content = acta_apply_content_filters( $clean_content, $post->ID ); return new WP_REST_Response( array( 'slug' => $slug, 'id' => $post->ID, 'status' => $post->post_status, 'content' => $clean_content, 'version' => ACTA_PLUGIN_VERSION, ), 200 ); } /** * Find a post by slug across multiple post types. */ function acta_get_post_by_slug( $slug, $post_types = array( 'post', 'page' ) ) { $args = array( 'name' => $slug, 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => 1, ); $posts = get_posts( $args ); return ! empty( $posts ) ? $posts[0] : null; } /** * Strip all known paywall markers from raw Gutenberg/classic post content. */ function acta_strip_paywall_markers( $content ) { // ── Jetpack / WordPress.com $content = preg_replace( '//', '', $content ); $content = preg_replace( '/[\s\S]*?/', '', $content ); // ── MemberPress $content = preg_replace_callback( '/\[mepr-active[^\]]*\]([\s\S]*?)\[\/mepr-active\]/i', function( $m ) { return $m[1]; }, $content ); $content = preg_replace( '/\[mepr-unauthorized[^\]]*\][\s\S]*?\[\/mepr-unauthorized\]/i', '', $content ); // ── Restrict Content Pro $content = preg_replace_callback( '/\[restrict[^\]]*\]([\s\S]*?)\[\/restrict\]/i', function( $m ) { return $m[1]; }, $content ); // ── Paid Memberships Pro $content = preg_replace_callback( '/\[membership[^\]]*\]([\s\S]*?)\[\/membership\]/i', function( $m ) { return $m[1]; }, $content ); // ── WooCommerce Memberships $content = preg_replace_callback( '/\[wcm_restrict[^\]]*\]([\s\S]*?)\[\/wcm_restrict\]/i', function( $m ) { return $m[1]; }, $content ); // ── Paid Member Subscriptions $content = preg_replace_callback( '/\[pms-content[^\]]*\]([\s\S]*?)\[\/pms-content\]/i', function( $m ) { return $m[1]; }, $content ); // ── Simple Membership $content = preg_replace_callback( '/\[swpm_protected[^\]]*\]([\s\S]*?)\[\/swpm_protected\]/i', function( $m ) { return $m[1]; }, $content ); // ── s2Member $content = preg_replace_callback( '/\[s2If[^\]]*\]([\s\S]*?)\[\/s2If\]/i', function( $m ) { return $m[1]; }, $content ); // ── LeakyPaywall $content = preg_replace( '/\[leaky_paywall_content\][\s\S]*?\[\/leaky_paywall_content\]/i', '', $content ); // ── based paywalls if ( acta_uses_more_tag_as_gate() ) { $content = str_replace( '', '', $content ); $content = str_replace( '', '', $content ); } return $content; } /** * Detect whether any active plugin uses as its paywall gate. */ function acta_uses_more_tag_as_gate() { $more_gate_plugins = array( 'paid-memberships-pro/paid-memberships-pro.php', 'woocommerce-memberships/woocommerce-memberships.php', 'subscriptions-for-woocommerce/subscriptions-for-woocommerce.php', ); foreach ( $more_gate_plugins as $plugin ) { if ( is_plugin_active( $plugin ) ) { return true; } } return false; } /** * Apply WordPress content filters while temporarily suppressing paywall re-gating. */ function acta_apply_content_filters( $content, $post_id ) { $removed_hooks = acta_remove_paywall_hooks(); global $post; $original_post = $post; $post = get_post( $post_id ); setup_postdata( $post ); $rendered = apply_filters( 'the_content', $content ); $post = $original_post; if ( $original_post ) { setup_postdata( $original_post ); } else { wp_reset_postdata(); } acta_restore_paywall_hooks( $removed_hooks ); return $rendered; } /** * Remove known paywall plugin filter hooks from the_content. * Returns the removed hooks so they can be restored. */ function acta_remove_paywall_hooks() { global $wp_filter; $removed = array(); if ( ! isset( $wp_filter['the_content'] ) ) { return $removed; } $method_patterns = array( 'add_paywall', 'jetpack_memberships_protect_content', 'jetpack_subscriptions_protect_content', 'mepr_content_protection', 'meprContentProtection', 'rcp_restrict_content', 'rcp_post_content_filter', 'pmpro_the_content', 'pmpro_protect_content', 'wc_memberships_restrict_content', 'pms_restrict_content', 'swpm_the_content_filter', 's2member_the_content_filter', 'leaky_paywall_content_filter', 'um_restrict_content', 'sumo_restrict_content', ); $class_patterns = array( 'Jetpack\\Extensions\\Subscriptions', 'Jetpack\\Memberships', 'MeprContent', 'RCP_Content', 'PMPro', 'WC_Memberships', ); foreach ( $wp_filter['the_content']->callbacks as $priority => $callbacks ) { foreach ( $callbacks as $key => $callback ) { $func = $callback['function']; $func_name = ''; $class_name = ''; $should_remove = false; if ( is_string( $func ) ) { $func_name = $func; } elseif ( is_array( $func ) && isset( $func[1] ) ) { $func_name = is_string( $func[1] ) ? $func[1] : ''; $class_name = is_object( $func[0] ) ? get_class( $func[0] ) : (string) $func[0]; } foreach ( $method_patterns as $pattern ) { if ( stripos( $func_name, $pattern ) !== false ) { $should_remove = true; break; } } if ( ! $should_remove && $class_name ) { foreach ( $class_patterns as $pattern ) { if ( stripos( $class_name, $pattern ) !== false ) { $should_remove = true; break; } } } if ( $should_remove ) { $removed[] = array( 'priority' => $priority, 'callback' => $func, 'accepted_args' => $callback['accepted_args'], ); remove_filter( 'the_content', $func, $priority ); } } } return $removed; } /** * Restore previously removed paywall filter hooks. */ function acta_restore_paywall_hooks( $removed_hooks ) { foreach ( $removed_hooks as $hook ) { add_filter( 'the_content', $hook['callback'], $hook['priority'], $hook['accepted_args'] ); } }