__( 'Account access', 'logvia' ), 'post_name' => 'auth', 'post_status' => 'publish', 'post_type' => 'page', 'post_content' => "\n[logvia]\n", 'comment_status' => 'closed', 'ping_status' => 'closed', ) ); if ( $id && ! is_wp_error( $id ) ) { Logvia_Settings::update( array( 'page_id' => (int) $id ) ); return (int) $id; } return 0; } /** * Copy Google and purchase settings from the original theme implementation, * so moving from the theme to the plugin keeps working credentials. */ private static function import_legacy_theme_settings() { if ( false !== get_option( Logvia_Settings::OPTION, false ) ) { return; } $values = array(); $google = get_option( 'mytheme_google_oauth' ); if ( is_array( $google ) && ! empty( $google['client_id'] ) ) { $values['google_enabled'] = empty( $google['enabled'] ) ? 0 : 1; $values['google_client_id'] = sanitize_text_field( $google['client_id'] ); $values['google_client_secret'] = sanitize_text_field( isset( $google['client_secret'] ) ? $google['client_secret'] : '' ); } $gate = get_option( 'mytheme_purchase_auth' ); if ( is_array( $gate ) ) { $values['gate_enabled'] = empty( $gate['enabled'] ) ? 0 : 1; $values['gate_notice'] = sanitize_text_field( isset( $gate['notice'] ) ? $gate['notice'] : '' ); } if ( $values ) { Logvia_Settings::update( $values ); } } /* ------------------------------------------------------------------ * Routing * ------------------------------------------------------------------ */ /** * Published auth page ID, or 0. * * @return int */ public static function page_id() { $id = (int) Logvia_Settings::get( 'page_id' ); return ( $id && 'publish' === get_post_status( $id ) ) ? $id : 0; } /** * Signature of everything the rewrite rules depend on. * * @return string */ private static function rewrite_signature() { $id = self::page_id(); return LOGVIA_VERSION . '|' . $id . '|' . ( $id ? get_page_uri( $id ) : '' ); } /** * Register /// rules. */ public static function register_rewrites() { $id = self::page_id(); if ( ! $id ) { return; } $uri = get_page_uri( $id ); if ( ! $uri ) { return; } $base = preg_quote( $uri, '#' ); add_rewrite_rule( '^' . $base . '/(login|register|verify|forgot|reset)/?$', 'index.php?page_id=' . $id . '&' . self::QUERY_VAR . '=$matches[1]', 'top' ); add_rewrite_rule( '^' . $base . '/google/(start|callback)/?$', 'index.php?page_id=' . $id . '&' . self::QUERY_VAR . '=google-$matches[1]', 'top' ); } /** * Flush rules once whenever the page, its slug or the plugin version changes. */ public static function maybe_flush_rewrites() { $sig = self::rewrite_signature(); if ( get_option( 'logvia_rewrite_signature' ) !== $sig ) { flush_rewrite_rules( false ); update_option( 'logvia_rewrite_signature', $sig ); } } /** * Register our query var. * * @param array $vars Query vars. * @return array */ public static function query_vars( $vars ) { $vars[] = self::QUERY_VAR; return $vars; } /** * Current screen/route ('' when not on the auth page). * * @return string */ public static function current_route() { $route = sanitize_key( (string) get_query_var( self::QUERY_VAR ) ); $allowed = array_merge( self::$screens, array( 'google-start', 'google-callback' ) ); return in_array( $route, $allowed, true ) ? $route : ''; } /** * True on the Logvia page. * * @return bool */ public static function is_auth_page() { $id = self::page_id(); return $id && is_page( $id ); } /** * URL of a screen or route. * * @param string $route login|register|verify|forgot|reset|google-start|google-callback. * @param array $args Query args (values are URL-encoded here). * @return string */ public static function url( $route = 'login', $args = array() ) { $id = self::page_id(); if ( ! $id ) { // No page: fall back to core screens (unfiltered to avoid recursion). $core = site_url( 'wp-login.php', 'login' ); if ( 'register' === $route ) { $core = add_query_arg( 'action', 'register', $core ); } elseif ( 'forgot' === $route ) { $core = add_query_arg( 'action', 'lostpassword', $core ); } $url = $core; } elseif ( get_option( 'permalink_structure' ) ) { $url = trailingslashit( get_permalink( $id ) ) . str_replace( 'google-', 'google/', $route ) . '/'; } else { $url = add_query_arg( self::QUERY_VAR, $route, get_permalink( $id ) ); } $args = array_filter( (array) $args, function ( $v ) { return '' !== $v && null !== $v; } ); if ( $args ) { $url = add_query_arg( array_map( 'rawurlencode', array_map( 'strval', $args ) ), $url ); } return $url; } /** * Redirect to a screen and stop. * * @param string $route Screen. * @param array $args Query args. */ public static function redirect( $route, $args = array() ) { wp_safe_redirect( self::url( $route, $args ) ); exit; } /** * Page-level behaviour: no caching, and logged-in visitors skip the forms. */ public static function page_guard() { if ( ! self::is_auth_page() ) { return; } nocache_headers(); if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Standard constant read by page-cache plugins. } $route = self::current_route(); $route = $route ? $route : 'login'; // Core screens that are reached through wp_login_url() with an action // (for example the "Is your admin email still correct?" check) belong to wp-login.php. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only, compared against a fixed list. $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; if ( in_array( $action, array( 'confirm_admin_email', 'postpass', 'logout', 'rp', 'resetpass', 'checkemail', 'confirmaction' ), true ) ) { $query = array(); foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Passed through unchanged to wp-login.php, which checks its own nonces. if ( is_string( $value ) ) { $query[ sanitize_key( $key ) ] = rawurlencode( sanitize_text_field( wp_unslash( $value ) ) ); } } wp_safe_redirect( add_query_arg( $query, site_url( 'wp-login.php', 'login' ) ) ); exit; } // Editors can still open the page to see it; everyone else goes on to their account. if ( is_user_logged_in() && in_array( $route, array( 'login', 'register', 'verify', 'forgot' ), true ) && ! current_user_can( 'edit_pages' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only redirect target, validated below. $requested = isset( $_GET['redirect_to'] ) ? sanitize_url( wp_unslash( $_GET['redirect_to'] ) ) : ''; wp_safe_redirect( self::login_redirect( wp_get_current_user(), $requested ) ); exit; } } /** * Keep auth screens out of search results. * * @param array $robots Robots directives. * @return array */ public static function robots( $robots ) { if ( self::is_auth_page() ) { $robots['noindex'] = true; $robots['nofollow'] = true; } return $robots; } /** * Keep the auth page out of automatic page menus. * * @param array $exclude Page IDs. * @return array */ public static function exclude_from_page_lists( $exclude ) { $id = self::page_id(); if ( $id ) { $exclude[] = $id; } return $exclude; } /* ------------------------------------------------------------------ * Redirect targets * ------------------------------------------------------------------ */ /** * Resolve a redirect mode to a URL. * * @param string $mode account | home | same | custom. * @param string $custom Custom URL. * @param string $from Page the form was on. * @param WP_User|null $user User. * @return string */ private static function resolve_mode( $mode, $custom, $from, $user ) { $url = ''; if ( 'custom' === $mode ) { $url = wp_validate_redirect( (string) $custom, '' ); } elseif ( 'same' === $mode && $from ) { $url = wp_validate_redirect( $from, '' ); } elseif ( 'home' === $mode ) { $url = home_url( '/' ); } if ( ! $url ) { if ( function_exists( 'wc_get_page_permalink' ) ) { $url = wc_get_page_permalink( 'myaccount' ); } elseif ( $user && user_can( $user, 'edit_posts' ) ) { $url = admin_url(); } } return $url ? $url : home_url( '/' ); } /** * Where a user goes after logging in when nothing else was requested. * * @param WP_User|null $user User. * @param string $from Page the form was on. * @return string */ public static function default_redirect( $user = null, $from = '' ) { $url = self::resolve_mode( (string) Logvia_Settings::get( 'redirect_after_login' ), Logvia_Settings::get( 'redirect_custom' ), $from, $user ); /** * Filter the default post-login destination. * * @param string $url URL. * @param WP_User|null $user User. */ return apply_filters( 'logvia_default_redirect', $url, $user ); } /** * Final post-login destination: explicit redirect_to, then the remembered * return URL (purchase flow), then the configured default. * * @param WP_User|null $user User. * @param string $requested Requested redirect_to. * @param string $from Page the form was on (for "same page"). * @return string */ public static function login_redirect( $user, $requested = '', $from = '' ) { $fallback = self::consume_return_url( self::default_redirect( $user, $from ) ); $to = $requested ? wp_validate_redirect( $requested, $fallback ) : $fallback; $to = self::not_auth_page( $to, $user ); /** * Filter the post-login destination. * * @param string $to URL. * @param WP_User|null $user User. * @param string $requested Requested redirect_to. */ return apply_filters( 'logvia_login_redirect', $to, $user, $requested ); } /** * Destination after registering. * * @param WP_User $user User. * @param string $requested Requested redirect_to. * @param string $from Page the form was on. * @return string */ public static function register_redirect( $user, $requested = '', $from = '' ) { $mode = (string) Logvia_Settings::get( 'redirect_after_register' ); if ( 'login' === $mode || '' === $mode ) { return self::login_redirect( $user, $requested, $from ); } $fallback = self::consume_return_url( self::resolve_mode( $mode, Logvia_Settings::get( 'redirect_register_custom' ), $from, $user ) ); $to = $requested ? wp_validate_redirect( $requested, $fallback ) : $fallback; $to = self::not_auth_page( $to, $user ); /** * Filter the post-registration destination. * * @param string $to URL. * @param WP_User $user User. */ return apply_filters( 'logvia_register_redirect', $to, $user ); } /** * Never send someone back to an auth screen after logging in. * * @param string $to URL. * @param WP_User|null $user User. * @return string */ private static function not_auth_page( $to, $user ) { $page = self::page_id(); if ( $page && 0 === strpos( $to, get_permalink( $page ) ) ) { return self::resolve_mode( 'account', '', '', $user ); } return $to; } /** * Destination after logging out. * * @param string $redirect Default. * @param string $requested Requested redirect_to. * @param WP_User|WP_Error $user User. * @return string */ public static function logout_redirect( $redirect, $requested, $user ) { $mode = (string) Logvia_Settings::get( 'redirect_after_logout' ); if ( 'same' === $mode ) { $to = $requested ? $requested : wp_get_referer(); } elseif ( 'home' === $mode ) { $to = home_url( '/' ); } elseif ( 'custom' === $mode ) { $to = (string) Logvia_Settings::get( 'redirect_logout_custom' ); } else { $to = self::page_id() ? self::url( 'login', array( 'logvia_msg' => 'logged_out' ) ) : $redirect; } $to = wp_validate_redirect( (string) $to, home_url( '/' ) ); return apply_filters( 'logvia_logout_redirect', $to, $user ); } /* ------------------------------------------------------------------ * Remembered return URL (cookie) * ------------------------------------------------------------------ */ /** * Cookie options. * * @param int $expires Expiry timestamp. * @return array */ private static function cookie_options( $expires ) { return array( 'expires' => $expires, 'path' => COOKIEPATH ? COOKIEPATH : '/', 'domain' => COOKIE_DOMAIN ? COOKIE_DOMAIN : '', 'secure' => is_ssl(), 'httponly' => true, 'samesite' => 'Lax', ); } /** * Remember where to send the visitor after login (survives switching * between the login and register screens). Same-site URLs only. * * @param string $url URL. */ public static function set_return_url( $url ) { $url = wp_validate_redirect( $url, '' ); if ( ! $url || headers_sent() ) { return; } setcookie( self::RETURN_COOKIE, $url, self::cookie_options( time() + HOUR_IN_SECONDS ) ); $_COOKIE[ self::RETURN_COOKIE ] = $url; } /** * Read and clear the remembered return URL. * * @param string $fallback Returned when nothing is stored. * @return string */ public static function consume_return_url( $fallback ) { if ( ! isset( $_COOKIE[ self::RETURN_COOKIE ] ) ) { return $fallback; } $url = wp_validate_redirect( sanitize_url( wp_unslash( $_COOKIE[ self::RETURN_COOKIE ] ) ), '' ); if ( ! headers_sent() ) { setcookie( self::RETURN_COOKIE, '', self::cookie_options( time() - HOUR_IN_SECONDS ) ); } unset( $_COOKIE[ self::RETURN_COOKIE ] ); return $url ? $url : $fallback; } /* ------------------------------------------------------------------ * Core link replacement * ------------------------------------------------------------------ */ /** * Filter login_url. * * @param string $login_url Original URL. * @param string $redirect Redirect target. * @param bool $force_reauth Force re-auth. * @return string */ public static function filter_login_url( $login_url, $redirect, $force_reauth ) { if ( $force_reauth || ! self::page_id() ) { return $login_url; } return self::url( 'login', array( 'redirect_to' => $redirect ) ); } /** * Filter register_url. * * @param string $url Original URL. * @return string */ public static function filter_register_url( $url ) { return self::page_id() ? self::url( 'register' ) : $url; } /** * Filter lostpassword_url. * * @param string $url Original URL. * @param string $redirect Redirect target. * @return string */ public static function filter_lostpassword_url( $url, $redirect ) { return self::page_id() ? self::url( 'forgot', array( 'redirect_to' => $redirect ) ) : $url; } /** * Send visitors of wp-login.php (login, register, lost password) to Logvia. * Password-reset links, logout and the admin session-expired popup keep * using wp-login.php. Define LOGVIA_DISABLE_WP_LOGIN_REDIRECT to turn this * off from wp-config.php if you ever get locked out. */ public static function redirect_wp_login() { if ( defined( 'LOGVIA_DISABLE_WP_LOGIN_REDIRECT' ) && LOGVIA_DISABLE_WP_LOGIN_REDIRECT ) { return; } if ( ! self::page_id() || 'GET' !== ( isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET' ) ) { return; } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only routing. if ( isset( $_GET['interim-login'] ) ) { return; } $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : 'login'; $map = array( 'login' => 'login', 'register' => 'register', 'lostpassword' => 'forgot', 'retrievepassword' => 'forgot', ); if ( ! isset( $map[ $action ] ) ) { return; } $args = array(); if ( ! empty( $_GET['redirect_to'] ) ) { $args['redirect_to'] = sanitize_url( wp_unslash( $_GET['redirect_to'] ) ); } if ( isset( $_GET['loggedout'] ) ) { $args['logvia_msg'] = 'logged_out'; } // phpcs:enable self::redirect( $map[ $action ], $args ); } }