.php` * inside the theme (or child theme). * * @param string $name Template name without `.php` (e.g. `buy-box`). * @param array $args Variables made available to the template. * @return string */ function paygrab_get_template( $name, $args = array() ) { $name = sanitize_file_name( $name ); $file = locate_template( 'paygrab/' . $name . '.php' ); if ( ! $file ) { $file = PAYGRAB_PATH . 'templates/' . $name . '.php'; } /** * Filters the template file used for a PAYGRAB screen. * * @param string $file Absolute path. * @param string $name Template name. * @param array $args Template variables. */ $file = apply_filters( 'paygrab_template_file', $file, $name, $args ); if ( ! file_exists( $file ) ) { return ''; } ob_start(); ( static function ( $paygrab_file, $args ) { extract( $args, EXTR_SKIP ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract include $paygrab_file; } )( $file, $args ); return (string) ob_get_clean(); } /** * Punctuation that differs by language (full-width in Japanese, ASCII in English): translated, not written into the code. * * @return string */ function paygrab_colon() { return _x( ': ', 'colon after a label', 'paygrab' ); } /** * Opening bracket of a note after a text. * * @return string */ function paygrab_open() { return _x( ' (', 'opening bracket of a note after a text', 'paygrab' ); } /** * Closing bracket of a note after a text. * * @return string */ function paygrab_close() { return _x( ')', 'closing bracket of a note after a text', 'paygrab' ); } /** * Separator between short items on one line. * * @return string */ function paygrab_sep() { return _x( ' · ', 'separator between short items', 'paygrab' ); } /** * Separator in a list. * * @return string */ function paygrab_comma() { return _x( ', ', 'separator in a list', 'paygrab' ); } /** * Format a yen amount for display. * * @param int $amount Amount in yen. * @return string */ function paygrab_price( $amount ) { return '¥' . number_format_i18n( (int) $amount ); } /** * Consumption tax contained in a tax-inclusive amount (rounded down once). * * @param int $total Tax-inclusive amount. * @param int $rate Tax rate in percent (10 or 8). * @return int */ function paygrab_tax_included( $total, $rate ) { $rate = (int) $rate; if ( $rate <= 0 ) { return 0; } return (int) floor( (int) $total * $rate / ( 100 + $rate ) ); } /** * Current time as a MySQL datetime in UTC (how PAYGRAB stores every date). * * @param int $offset Seconds to add. * @return string */ function paygrab_now( $offset = 0 ) { return gmdate( 'Y-m-d H:i:s', time() + (int) $offset ); } /** * Format a stored UTC datetime in the site's timezone. * * @param string|null $gmt MySQL datetime in UTC. * @param string $format Date format. Empty = the site's date format. * @return string */ function paygrab_date( $gmt, $format = '' ) { if ( empty( $gmt ) || '0000-00-00 00:00:00' === $gmt ) { return ''; } $ts = strtotime( $gmt . ' UTC' ); return wp_date( $format ? $format : get_option( 'date_format' ), $ts ); } /** * Format a stored UTC datetime as a short month and day (e.g. 11月17日), for "download until". * A date in another year carries the year, so "until October 30" is not read as today. * * @param string|null $gmt MySQL datetime in UTC. * @return string */ function paygrab_short_date( $gmt ) { if ( empty( $gmt ) || '0000-00-00 00:00:00' === $gmt ) { return ''; } if ( wp_date( 'Y', strtotime( $gmt . ' UTC' ) ) !== wp_date( 'Y' ) ) { /* translators: date format for short dates in another year, see https://www.php.net/manual/datetime.format.php */ return paygrab_date( $gmt, __( 'F j, Y', 'paygrab' ) ); } /* translators: date format for short dates such as the last download day, see https://www.php.net/manual/datetime.format.php */ return paygrab_date( $gmt, __( 'F j', 'paygrab' ) ); } /** * Format a stored UTC datetime as a short date with weekday (e.g. 11月3日(月)). * * @param string|null $gmt MySQL datetime in UTC. * @return string */ function paygrab_deadline( $gmt ) { /* translators: date format for payment deadlines, see https://www.php.net/manual/datetime.format.php */ return paygrab_date( $gmt, __( 'F j (D)', 'paygrab' ) ); } /** * URL of a page PAYGRAB uses (checkout, mypage, register, tokushoho, terms). * * @param string $key Page key. * @return string Empty when the page is not set. */ function paygrab_page_url( $key ) { $id = (int) Paygrab_Settings::get( 'page_' . $key ); if ( ! $id || 'publish' !== get_post_status( $id ) ) { return ''; } return (string) get_permalink( $id ); } /** * URL of the buyer's page, optionally pointing at one order or at the downloads. * * When YOMERU is active the buyer's page is a tab inside YOMERU's My page * (see Paygrab_Yomeru), and inside the My page of a TCD theme with paid articles (PANDORA, VOYAGE …) that has the add-on tab hooks (see Paygrab_Tcd_Theme); * otherwise it is PAYGRAB's own page. * * @param string $order_number Order number. * @param string $view 'orders' or 'downloads'. * @return string */ function paygrab_mypage_url( $order_number = '', $view = 'orders' ) { $host = paygrab_mypage_host(); if ( 'yomeru' === $host ) { $url = add_query_arg( 'yomeru_paywall_membership_mypage_tab', 'downloads' === $view ? Paygrab_Yomeru::TAB_DOWNLOADS : Paygrab_Yomeru::TAB_FILES, paygrab_yomeru_mypage_url() ); } elseif ( 'tcd_theme' === $host ) { $url = add_query_arg( 'my_paid_mypage_tab', 'downloads' === $view ? Paygrab_Tcd_Theme::TAB_DOWNLOADS : Paygrab_Tcd_Theme::TAB_FILES, paygrab_tcd_theme_mypage_url() ); } else { $url = paygrab_page_url( 'mypage' ); if ( ! $url ) { $url = home_url( '/' ); } if ( in_array( $view, array( 'downloads', 'account' ), true ) ) { $url = add_query_arg( 'tab', $view, $url ); } } return $order_number ? add_query_arg( 'paygrab_order', rawurlencode( $order_number ), $url ) : $url; } /** * Admin URL of the order list, or of one order. * * Use this instead of building admin.php?page=… by hand (the screen moved once already). * * @param int $order_id Order ID (0 = the list). * @return string */ function paygrab_admin_order_url( $order_id = 0 ) { $url = admin_url( 'admin.php?page=paygrab-orders' ); return $order_id ? add_query_arg( 'order', (int) $order_id, $url ) : $url; } /** * Whether new members may get this role: it must exist and carry nothing beyond reading (no editing, no admin rights). * * @param string $role Role key. * @return bool */ function paygrab_role_is_safe( $role ) { $object = get_role( (string) $role ); if ( ! $object ) { return false; } $caps = array_keys( array_filter( (array) $object->capabilities ) ); return ! array_diff( $caps, array( 'read', 'level_0' ) ); } /** * The HTTP method of this request ('GET' when unknown), upper case. * * @return string */ function paygrab_request_method() { return isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET'; } /** * Order number asked for in the URL (paygrab_order, or order from links sent before 0.1.1). * * @return string */ function paygrab_requested_order() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only screen selection. foreach ( array( 'paygrab_order', 'order' ) as $key ) { $value = isset( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : ''; if ( preg_match( '/^PG-\d{8}-\d+$/', $value ) ) { return $value; } } // phpcs:enable return ''; } /** * Which plugin or theme shows PAYGRAB's buyer page as tabs inside its own My page. * * - "yomeru": YOMERU 1.0.1 and later (the add-on tab hooks). * - "tcd_theme": a TCD theme with paid articles (PANDORA, VOYAGE …) that has the add-on tab hooks * (MY_PAID_ARTICLE_MYPAGE_ADDON_API) and whose paid-article My page is on (its rewrite rule exists). * - "": none (or switched off with the filter); PAYGRAB shows its own page. * * When both serve a My page at the same address, the one whose rule WordPress meets first is the one that * really answers there, so it is the host. At different addresses, YOMERU is the host. * * @return string */ function paygrab_mypage_host() { $hosts = array(); if ( paygrab_yomeru_active() && version_compare( (string) YOMERU_PAYWALL_MEMBERSHIP_VERSION, '1.0.1', '>=' ) ) { $hosts['yomeru'] = paygrab_yomeru_mypage_slug(); } if ( paygrab_tcd_theme_mypage_enabled() ) { $hosts['tcd_theme'] = paygrab_tcd_theme_mypage_slug(); } $host = ''; if ( 1 === count( $hosts ) ) { $host = (string) array_key_first( $hosts ); } elseif ( 2 === count( $hosts ) ) { global $wp_rewrite; $host = 'yomeru'; if ( $hosts['yomeru'] === $hosts['tcd_theme'] ) { foreach ( (array) $wp_rewrite->extra_rules_top as $regex => $query ) { if ( ! is_string( $query ) || ! paygrab_rewrite_rule_matches( $regex, $hosts['yomeru'] ) ) { continue; } if ( str_contains( $query, 'my_paid_mypage=1' ) ) { $host = 'tcd_theme'; break; } if ( str_contains( $query, 'yomeru_paywall_membership_mypage=1' ) ) { break; } } } } /** * Filters whether PAYGRAB's buyer page is merged into YOMERU's or a TCD theme's My page. * * @param bool $ok Merge. * @param string $host "yomeru" or "tcd_theme" ("" when there is none). */ if ( '' === $host || ! apply_filters( 'paygrab_merge_yomeru_mypage', true, $host ) ) { return ''; } /** * Filters which plugin or theme shows PAYGRAB's buyer page as tabs ("yomeru", "tcd_theme" or "" for PAYGRAB's own page). * * @param string $host Host. */ return (string) apply_filters( 'paygrab_mypage_host', $host ); } /** * Whether PAYGRAB's buyer page is shown inside another My page (YOMERU's or a TCD theme's). * * @return bool */ function paygrab_mypage_merged() { return '' !== paygrab_mypage_host(); } /** * Whether a TCD theme with the add-on tab hooks serves its paid-article My page. * * These themes serve that page with a rewrite rule (only when its paid-article feature is on), so the rule is the proof. * * @return bool */ function paygrab_tcd_theme_mypage_enabled() { global $wp_rewrite; if ( ! defined( 'MY_PAID_ARTICLE_MYPAGE_ADDON_API' ) || ! function_exists( 'my_paid_article_get_default_options' ) || ! $wp_rewrite instanceof WP_Rewrite ) { return false; } foreach ( (array) $wp_rewrite->extra_rules_top as $query ) { if ( is_string( $query ) && str_contains( $query, 'my_paid_mypage=1' ) ) { return true; } } return false; } /** * URL slug the TCD theme uses for its My page ('mypage' unless the seller changed it). * * @return string */ function paygrab_tcd_theme_mypage_slug() { $opts = (array) get_option( 'my_paid_article_options', array() ); $slug = ! empty( $opts['mypage_slug'] ) ? sanitize_title( $opts['mypage_slug'] ) : 'mypage'; return '' !== $slug ? $slug : 'mypage'; } /** * URL of the TCD theme's My page. * * @return string */ function paygrab_tcd_theme_mypage_url() { return home_url( '/' . paygrab_tcd_theme_mypage_slug() . '/' ); } /** * URL of YOMERU's My page. * * @return string */ function paygrab_yomeru_mypage_url() { return home_url( '/' . paygrab_yomeru_mypage_slug() . '/' ); } /** * Whether members sign up and log in through YOMERU's form. * * Only when YOMERU Pro's email verification and spam protection are on; otherwise * PAYGRAB's own sign-up page (which always has them) is used. * * @return bool */ function paygrab_yomeru_signup() { $ok = 'yomeru' === paygrab_mypage_host() && function_exists( 'yomeru_paywall_membership_pro_registration_security_available' ) && function_exists( 'yomeru_paywall_membership_pro_email_verification_enabled' ) && yomeru_paywall_membership_pro_registration_security_available() && yomeru_paywall_membership_pro_email_verification_enabled(); return (bool) apply_filters( 'paygrab_use_yomeru_signup', $ok ); } /** * Whether a TCD theme with paid articles uses its email verification for sign-ups (its own setting is on). * * Only while the theme's My page is where PAYGRAB's pages live (the theme's paid articles are in use). The * theme's choice wins over PAYGRAB's: members are shared, so a looser sign-up in PAYGRAB would let people around * the verification the seller asked for in the theme. * * @return bool */ function paygrab_tcd_theme_verification_on() { return paygrab_tcd_theme_mypage_enabled() && function_exists( 'my_paid_article_email_verification_enabled' ) && (bool) my_paid_article_email_verification_enabled(); } /** * Whether the YOMERU plugin (paywall & membership) is active. * * @return bool */ function paygrab_yomeru_active() { return defined( 'YOMERU_PAYWALL_MEMBERSHIP_VERSION' ) && function_exists( 'yomeru_paywall_membership_get_default_options' ); } /** * URL slug YOMERU uses for its own My page ('' when YOMERU is not active). * * YOMERU serves that URL with a rewrite rule, so a page with the same slug is never shown. * * @return string */ function paygrab_yomeru_mypage_slug() { if ( ! paygrab_yomeru_active() ) { return ''; } $opts = (array) get_option( 'yomeru_paywall_membership_options', array() ); $slug = ! empty( $opts['mypage_slug'] ) ? sanitize_title( $opts['mypage_slug'] ) : 'mypage'; return '' !== $slug ? $slug : 'mypage'; } /** * Whether a rewrite rule of WordPress's pattern matches a path (the same match WordPress makes). * * @param string $regex Rule pattern as registered. * @param string $path Path without the leading slash, e.g. "mypage". * @return bool */ function paygrab_rewrite_rule_matches( $regex, $path ) { $path = trim( (string) $path, '/' ); foreach ( array( $path, $path . '/' ) as $try ) { if ( 1 === @preg_match( '#^' . $regex . '#', $try ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- a rule from another plugin may be an invalid pattern. return true; } } return false; } /** * Whether a rewrite rule of another plugin or theme already serves this address. * * Plugins that draw their own My page with a rewrite rule (YOMERU, the paid articles of TCD themes) take the * address before WordPress looks for a page, so a page with the same slug can never be opened. * Only the rules added with "top" count: WordPress puts those before the pages, but rules added * at the bottom after them, so an existing page still opens there. * * @param string $slug Page slug, e.g. "mypage". * @return bool */ function paygrab_slug_claimed_by_rewrite( $slug ) { global $wp_rewrite; $slug = trim( (string) $slug, '/' ); if ( '' === $slug || ! $wp_rewrite instanceof WP_Rewrite ) { return false; } foreach ( array_keys( (array) $wp_rewrite->extra_rules_top ) as $regex ) { if ( paygrab_rewrite_rule_matches( $regex, $slug ) ) { return true; } } return false; } /** * Where members change their account details. * * With YOMERU or a TCD theme, their My page has an account tab (they keep members out of wp-admin). * * @return string */ function paygrab_account_url() { if ( 'tcd_theme' === paygrab_mypage_host() ) { return add_query_arg( 'my_paid_mypage_tab', 'account', paygrab_tcd_theme_mypage_url() ); } $slug = paygrab_yomeru_mypage_slug(); if ( '' !== $slug ) { return add_query_arg( 'yomeru_paywall_membership_mypage_tab', 'account', home_url( '/' . $slug . '/' ) ); } return (string) apply_filters( 'paygrab_account_url', paygrab_mypage_url( '', 'account' ) ); } /** * Arguments that carry where a member came from: the article (source) and the product. * * @param string $redirect URL to return to (used when there is no source article). * @param array $args source (post ID), product (product ID). * @return array Query arguments. */ function paygrab_return_args( $redirect = '', $args = array() ) { $query = array(); $source = (int) ( $args['source'] ?? 0 ); if ( $source && paygrab_is_source( $source ) ) { $query[ PAYGRAB_SOURCE_ARG ] = $source; } elseif ( $redirect ) { $query['redirect_to'] = rawurlencode( wp_validate_redirect( $redirect, '' ) ); } $product = (int) ( $args['product'] ?? 0 ); if ( $product && Paygrab_Product::TYPE === get_post_type( $product ) ) { $query['paygrab_product'] = $product; } return array_filter( $query ); } /** * Login URL that comes back to the given page (or to the article the member came from). * * @param string $redirect URL to return to. * @param array $args source (post ID), product (product ID). * @return string */ function paygrab_login_url( $redirect = '', $args = array() ) { if ( paygrab_yomeru_signup() ) { $back = ! empty( $args['source'] ) ? paygrab_source_url( (int) $args['source'] ) : $redirect; return add_query_arg( 'paygrab_redirect', rawurlencode( $back ? $back : $redirect ), paygrab_yomeru_mypage_url() ); } $url = paygrab_page_url( 'login' ); if ( ! $url ) { return wp_login_url( $redirect ); } return add_query_arg( paygrab_return_args( $redirect, $args ), $url ); } /** * Register URL that comes back to the given page (or to the article) after verification. * * @param string $redirect URL to return to. * @param array $args source (post ID), product (product ID). * @return string */ function paygrab_register_url( $redirect = '', $args = array() ) { if ( paygrab_yomeru_signup() ) { $back = ! empty( $args['source'] ) ? paygrab_source_url( (int) $args['source'] ) : $redirect; $url = paygrab_yomeru_mypage_url(); return $back ? add_query_arg( 'paygrab_redirect', rawurlencode( $back ), $url ) : $url; } $url = paygrab_page_url( 'register' ); if ( ! $url ) { return wp_registration_url(); } return add_query_arg( paygrab_return_args( $redirect, $args ), $url ); } /** * Where a member goes back to, read from the request: the article, a page on this site, or My page. * * @return string */ function paygrab_request_return() { $source = paygrab_request_source(); if ( $source ) { return paygrab_source_url( $source ); } // phpcs:ignore WordPress.Security.NonceVerification -- only a URL on this site is accepted. $to = isset( $_REQUEST['redirect_to'] ) ? wp_validate_redirect( esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ), '' ) : ''; return $to ? $to : paygrab_mypage_url(); } /** * The product a member was about to buy, read from the request. * * @return WP_Post|null */ function paygrab_request_product() { // phpcs:ignore WordPress.Security.NonceVerification -- only a product ID that is checked below. $id = isset( $_REQUEST['paygrab_product'] ) ? absint( $_REQUEST['paygrab_product'] ) : 0; $post = $id ? get_post( $id ) : null; return $post && Paygrab_Product::TYPE === $post->post_type && 'publish' === $post->post_status ? $post : null; } /** * Order statuses and their labels. * * @return array */ function paygrab_statuses() { return array( 'pending' => __( 'Awaiting payment', 'paygrab' ), 'paid' => __( 'Paid', 'paygrab' ), 'cancelled' => __( 'Cancelled', 'paygrab' ), 'refunded' => __( 'Refunded', 'paygrab' ), 'failed' => __( 'Failed', 'paygrab' ), ); } /** * Label of one order status. * * @param string $status Status key. * @return string */ function paygrab_status_label( $status ) { $all = paygrab_statuses(); return $all[ $status ] ?? $status; } /** * Status badge for the front end. * * @param string $status Status key. * @return string HTML. */ function paygrab_status_badge( $status ) { return sprintf( '%2$s', esc_attr( $status ), esc_html( paygrab_status_label( $status ) ) ); } /** * The visitor's IP address (used only for rate limits, never stored with orders). * * @return string */ function paygrab_client_ip() { $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; /** * Filters the visitor's IP address used for rate limits and the login lock. * * Behind a CDN or a reverse proxy every visitor has the proxy's REMOTE_ADDR; return the * address from the header your proxy sets (only a header it overwrites, so it cannot be faked). * * @param string $ip REMOTE_ADDR. */ $ip = (string) apply_filters( 'paygrab_client_ip', $ip ); return filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : '0.0.0.0'; } /** * Take a short lock, so that one thing is done by one request at a time (e.g. placing the same order twice at once). * * The lock is a row in the options table made with INSERT IGNORE (the way WordPress locks its own updates): only one * request can make it. A lock older than $ttl seconds belonged to a request that ended abruptly, and is taken over. * Release it with paygrab_unlock() as soon as the work is done. * * @param string $key Name of the thing locked. * @param int $ttl Seconds after which a lock counts as left behind. * @return bool Whether the lock was taken. */ function paygrab_lock( $key, $ttl = 30 ) { global $wpdb; $name = 'paygrab_lock_' . substr( md5( (string) $key ), 0, 24 ); $now = (string) time(); // phpcs:disable WordPress.DB.DirectDatabaseQuery -- a lock has to be a single atomic write that no cache can stand in for. if ( 1 === (int) $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->options} ( option_name, option_value, autoload ) VALUES ( %s, %s, 'no' )", $name, $now ) ) ) { return true; } $held = (string) $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", $name ) ); if ( '' !== $held && (int) $held < time() - (int) $ttl ) { // Left behind: take it over (only one of several requests can change the old value). return 1 === (int) $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value = %s", $now, $name, $held ) ); } // phpcs:enable WordPress.DB.DirectDatabaseQuery return false; } /** * Release a lock taken with paygrab_lock(). * * @param string $key Name of the thing locked. * @return void */ function paygrab_unlock( $key ) { global $wpdb; $wpdb->delete( $wpdb->options, array( 'option_name' => 'paygrab_lock_' . substr( md5( (string) $key ), 0, 24 ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- see paygrab_lock(). } /** * Count an action per IP and tell whether it went over the limit. * * The window starts with the first attempt and does not move when more come in. * * @param string $action Action name. * @param int $limit Allowed count within the window. * @param int $window Window in seconds. * @param bool $count Count this attempt (false: only look). * @return bool True when over the limit. */ function paygrab_rate_limited( $action, $limit, $window, $count = true ) { $key = 'paygrab_rl_' . md5( $action . '|' . paygrab_client_ip() ); $data = get_transient( $key ); $data = is_array( $data ) && isset( $data['n'], $data['start'] ) && time() - (int) $data['start'] < $window ? $data : array( 'n' => 0, 'start' => time() ); if ( (int) $data['n'] >= $limit ) { return true; } if ( $count ) { ++$data['n']; set_transient( $key, $data, max( 1, $window - ( time() - (int) $data['start'] ) ) ); } return false; } /** * Human-readable file size. * * @param int $bytes Size in bytes. * @return string */ function paygrab_size( $bytes ) { return size_format( (int) $bytes, $bytes >= MB_IN_BYTES ? 1 : 0 ); } /** * Query argument that carries the post a buyer started from. */ const PAYGRAB_SOURCE_ARG = 'paygrab_from'; /** * Whether a post can be the place a buyer goes back to. * * @param int $post_id Post ID. * @return bool */ function paygrab_is_source( $post_id ) { $post = $post_id ? get_post( $post_id ) : null; return $post && 'publish' === $post->post_status && is_post_publicly_viewable( $post ) && Paygrab_Product::TYPE !== $post->post_type; } /** * The post being shown, when the buy box sits in one. * * @return int Post ID or 0. */ function paygrab_current_source() { $id = (int) get_the_ID(); if ( ! $id && is_singular() ) { $id = (int) get_queried_object_id(); } return paygrab_is_source( $id ) ? $id : 0; } /** * The post a buyer started from, read from the request (?paygrab_from=ID). * * @return int Post ID or 0. */ function paygrab_request_source() { // phpcs:ignore WordPress.Security.NonceVerification -- only a post ID that is checked below. $id = isset( $_REQUEST[ PAYGRAB_SOURCE_ARG ] ) ? absint( $_REQUEST[ PAYGRAB_SOURCE_ARG ] ) : 0; return paygrab_is_source( $id ) ? $id : 0; } /** * URL of the post a buyer started from. * * Only post IDs travel in URLs; the address is built here, so a buyer can never be sent off the site. * * @param int $post_id Post ID. * @return string Empty when the post cannot be shown. */ function paygrab_source_url( $post_id ) { return paygrab_is_source( (int) $post_id ) ? (string) get_permalink( (int) $post_id ) : ''; } /** * Whether the page being shown is one of PAYGRAB's pages (checkout, mypage, register, login, …). * * Compares the post ID only. is_page() also matches titles and slugs, and virtual pages * of other plugins (such as YOMERU's My page) can look like a page to it. * * @param string $key Page key. * @return bool */ function paygrab_is_page( $key ) { $id = (int) Paygrab_Settings::get( 'page_' . $key ); if ( ! $id || get_query_var( 'yomeru_paywall_membership_mypage' ) || get_query_var( 'my_paid_mypage' ) ) { return false; } $object = get_queried_object(); return $object instanceof WP_Post && $id === (int) $object->ID; } /** * HTML allowed in the terms of use (bold, headings, links, lists, paragraphs). * * @return array */ function paygrab_terms_allowed_html() { return array( 'p' => array(), 'br' => array(), 'strong' => array(), 'b' => array(), 'em' => array(), 'h3' => array(), 'h4' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), 'a' => array( 'href' => true, 'target' => true, 'rel' => true, ), ); } /** * The terms of use as safe HTML (plain text gets paragraphs). * * @param string $text Saved text. * @return string */ function paygrab_terms_html( $text ) { $text = trim( (string) $text ); if ( '' === $text ) { return ''; } // Plain text from older versions may contain a lone "<" (e.g. "5 < 10"); only real tags mean HTML. return preg_match( '# for each new line (and pasted text often has them); keep them as paragraphs. $html = (string) preg_replace( array( '#]*>#i', '##i' ), array( '

', '

' ), (string) $html ); // Nested blocks (pasted text) would become

…

. $html = (string) preg_replace( array( '#(

\s*)+

#i', '#

(\s*

)+#i' ), array( '

', '

' ), $html ); // Headings and lists are blocks of their own; a paragraph around them would leave an empty one. $html = (string) preg_replace( array( '#

\s*(<(h3|h4|ul|ol)\b)#i', '#()\s*

#i' ), array( '$1', '$1' ), $html ); $html = trim( wp_kses( $html, paygrab_terms_allowed_html() ) ); // Empty paragraphs, also those left after kses removed what was inside. $html = trim( (string) preg_replace( '#

(\s| |)*

#i', '', $html ) ); $tags = new WP_HTML_Tag_Processor( $html ); while ( $tags->next_tag( 'a' ) ) { if ( '_blank' === $tags->get_attribute( 'target' ) ) { $tags->set_attribute( 'rel', 'noopener noreferrer' ); } } return $tags->get_updated_html(); }