| Current Path : /home/ereika83/www/wp-content/plugins/sfwd-lms/vendor-prefixed/trustedlogin/client/src/ |
| Current File : /home/ereika83/www/wp-content/plugins/sfwd-lms/vendor-prefixed/trustedlogin/client/src/Form.php |
<?php
/**
* @license GPL-2.0-or-later
*
* Modified by learndash on 18-December-2024 using Strauss.
* @see https://github.com/BrianHenryIE/strauss
*/
/**
* Class Form
*
* @package StellarWP\Learndash\TrustedLogin\Client
*
* @copyright 2021 Katz Web Services, Inc.
*/
namespace StellarWP\Learndash\TrustedLogin;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Creates the TrustedLogin support user form.
* - Makes the HTML
* - Manages assets
* Does not
* - Handle the form submission. {@see ./Ajax.php }
* - Setup the menu {@see ./Admin.php}
*
* @since 1.5.0
*/
final class Form {
/**
* URL pointing to the "About TrustedLogin" page, shown below the Grant Access dialog
*/
const ABOUT_TL_URL = 'https://www.trustedlogin.com/about/easy-and-safe/';
const ABOUT_LIVE_ACCESS_URL = 'https://www.trustedlogin.com/about/live-access/';
/**
* Config object.
*
* @var Config $config
*/
private $config;
/**
* SiteAccess object.
*
* @var SiteAccess $site_access
*/
private $site_access;
/**
* SupportUser object.
*
* @var SupportUser $support_user
*/
private $support_user;
/**
* Logging object.
*
* @var null|Logging $logging
*/
private $logging;
/**
* Admin constructor.
*
* @param Config $config Config object.
* @param Logging $logging Logging object.
* @param SupportUser $support_user SupportUser object.
* @param SiteAccess $site_access SiteAccess object.
*/
public function __construct( Config $config, Logging $logging, SupportUser $support_user, SiteAccess $site_access ) {
$this->config = $config;
$this->logging = $logging;
$this->support_user = $support_user;
$this->site_access = $site_access;
}
/**
* Register the required scripts and styles
*
* @since 1.0.0
*/
public function register_assets() {
$registered = array();
// Already registered by the integrating code.
if ( wp_script_is( 'trustedlogin-' . $this->config->ns() ) ) {
$registered['trustedlogin-js'] = true;
} else {
$registered['trustedlogin-js'] = wp_register_script(
'trustedlogin-' . $this->config->ns(),
$this->config->get_setting( 'paths/js' ),
array( 'jquery', 'wp-a11y' ),
Client::VERSION,
true
);
}
if ( wp_style_is( 'trustedlogin-' . $this->config->ns() ) ) {
$registered['trustedlogin-css'] = true;
} else {
$registered['trustedlogin-css'] = wp_register_style(
'trustedlogin-' . $this->config->ns(),
$this->config->get_setting( 'paths/css' ),
array(),
Client::VERSION,
'all'
);
}
$registered_filtered = array_filter( $registered );
if ( count( $registered ) !== count( $registered_filtered ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
$this->logging->log( 'Not all scripts and styles were registered: ' . print_r( $registered_filtered, true ), __METHOD__, 'error' );
}
}
/**
* If the current request is a valid login screen override, print the TrustedLogin request screen.
*
* @return void
*/
public function maybe_print_request_screen() {
if ( ! $this->is_login_screen() ) {
return;
}
// Once logged-in, take user back to auth request screen.
if ( ! is_user_logged_in() ) {
$_REQUEST['redirect_to'] = site_url( add_query_arg( array() ) );
return;
}
if ( ! current_user_can( 'create_users' ) ) {
return;
}
$this->print_request_screen();
}
/**
* Print the TrustedLogin override of the WP Login screen.
*
* @return void
*/
public function print_request_screen() {
global $interim_login, $wp_version;
/**
* This prevents the `#backtoblog` "← Go to {Site Name}" link from showing up on the login page.
* Since we're overriding the login screen, setting this global using $_REQUEST['interim-login'] isn't possible.
*
* @see login_footer()
*/
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$interim_login = true;
// The login_headertitle filter was deprecated in WP 5.2.0 for login_headertext.
if ( version_compare( $wp_version, '5.2.0', '<' ) ) {
add_filter( 'login_headertitle', '__return_empty_string' );
} else {
add_filter( 'login_headertext', '__return_empty_string' );
}
add_filter( 'login_headerurl', array( $this, 'callback_return_vendor_website' ) );
login_header();
wp_enqueue_style( 'common' );
$inline_css = $this->get_login_inline_css();
wp_add_inline_style( 'common', $inline_css );
// Print the styles before the HTML to prevent FOUC.
wp_print_styles( 'trustedlogin-' . $this->config->ns() );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->get_auth_screen();
login_footer();
die();
}
/**
* Returns the vendor website setting.
* This needs to be a separate method because it's used as a callback, and PHP 5.3 doesn't support $this in
* anonymous functions.
*
* @since 1.8.0
* @internal
* @return array|false|int|string|null
*/
public function callback_return_vendor_website() {
return (string) $this->config->get_setting( 'vendor/website' );
}
/**
* Returns inline CSS overrides for the `common` CSS dependency
*
* @since 1.0.0
*
* @return string
*/
private function get_login_inline_css() {
return '
#login {
width: auto;
}
.login .button-primary {
float: none;
}
.login h1 {
margin-top: 36px;
}
.login h1 a {
background-image: url("' . $this->config->get_setting( 'vendor/logo_url' ) . '")!important;
background-size: contain!important;
}
';
}
/**
* Outputs the TrustedLogin authorization screen
*
* @since 1.0.0
*
* @return void
*/
public function print_auth_screen() {
// Print the styles before the HTML to prevent FOUC.
wp_print_styles( 'trustedlogin-' . $this->config->ns() );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->get_auth_screen();
}
/**
* Returns the HTML for the auth header, which shows whether support user has access, and the Revoke Access button.
*
* @return string
*/
public function get_auth_header_html() {
$support_users = $this->support_user->get_all();
if ( empty( $support_users ) ) {
return '';
}
$support_user = $support_users[0];
$_user_creator_id = get_user_option( $this->support_user->created_by_meta_key, $support_user->ID ); // @phpstan-ignore-line
$_user_creator = $_user_creator_id ? get_user_by( 'id', $_user_creator_id ) : false;
// translators: %s is the ID of the user who created the support session. The user can't be found; only the User ID is known.
$unknown_user_text = sprintf( esc_html__( 'Unknown (User #%d)', 'trustedlogin' ), $_user_creator_id );
$auth_meta = ( $_user_creator && $_user_creator->exists() ) ? esc_html( $_user_creator->display_name ) : $unknown_user_text;
$revoke_url = $this->support_user->get_revoke_url( $support_user );
$template = '
{{revoke_access_button}}
<h3>{{display_name}}</h3>
<span class="tl-{{ns}}-auth__meta">{{auth_meta}}</span>';
$content = array(
'display_name' => $support_user->display_name,
'revoke_access_button' => sprintf( '<a href="%1$s" class="button button-danger alignright tl-client-revoke-button">%2$s</a>', $revoke_url, esc_html__( 'Revoke Access', 'trustedlogin' ) ),
// translators: %s is the display name of the user who granted access.
'auth_meta' => sprintf( esc_html__( 'Created %1$s ago by %2$s', 'trustedlogin' ), human_time_diff( strtotime( $support_user->user_registered ) ), $auth_meta ),
);
return $this->prepare_output( $template, $content );
}
/**
* Output the contents of the Auth Link Page in wp-admin
*
* @since 1.0.0
*
* @return string HTML of the Auth screen
*/
public function get_auth_screen() {
// If the CSS has not already been printed, make sure it's enqueued.
wp_enqueue_style( 'trustedlogin-' . $this->config->ns() );
$content = array(
'ns' => $this->config->ns(),
'has_access_class' => $this->support_user->get_all() ? 'has-access' : 'grant-access',
'notices' => $this->get_notices_html(),
'header' => $this->get_header_html(),
'intro' => $this->get_intro(),
'auth_header' => $this->get_auth_header_html(),
'details' => $this->get_details_html(),
'button' => $this->generate_button( 'size=hero&class=authlink button-primary tl-client-grant-button', false ),
'secured_by_trustedlogin' => '<span class="trustedlogin-logo-medium"></span>' . esc_html__( 'Secured by TrustedLogin', 'trustedlogin' ),
'footer' => $this->get_footer_html(),
'reference' => $this->get_reference_html(),
'admin_debug' => $this->get_admin_debug_html(),
'terms_of_service' => $this->get_terms_of_service_html(),
);
$auth_screen_template = '
<div class="tl-{{ns}}-auth tl-{{ns}}-{{has_access_class}}">
{{header}}
<section class="tl-{{ns}}-auth__body">
<h2 class="tl-{{ns}}-auth__intro">{{intro}}</h2>
<div class="tl-{{ns}}-auth__content">
<header class="tl-{{ns}}-auth__header">
{{auth_header}}
</header>
<div class="tl-{{ns}}-auth__details">
{{details}}
</div>
<div class="tl-{{ns}}-auth__response" aria-live="assertive"></div>
{{notices}}
<div class="tl-{{ns}}-auth__actions">
{{button}}
</div>
{{terms_of_service}}
</div>
<div class="tl-{{ns}}-auth__secured_by">{{secured_by_trustedlogin}}</div>
</section>
<footer class="tl-{{ns}}-auth__footer">
{{footer}}
{{reference}}
</footer>
{{admin_debug}}
</div>';
/**
* Filter trustedlogin/{ns}/template/auth
*
* @param string $output_template The Auth form HTML
*/
$auth_screen_template = apply_filters( 'trustedlogin/' . $this->config->ns() . '/template/auth', $auth_screen_template );
$output = $this->prepare_output( $auth_screen_template, $content );
return $output;
}
/**
* Returns the HTML for the header of the Auth screen, which includes the vendor logo.
*
* @since 1.6.0
*
* @return string
*/
private function get_header_html() {
if ( $this->is_login_screen() ) {
return '';
}
$header_template = '
<header class="tl-{{ns}}-auth__header__top">
<div class="tl-{{ns}}-auth__logo">{{logo}}</div>
</header>';
$variables = array(
'ns' => $this->config->ns(),
'logo' => $this->get_logo_html(),
);
return $this->prepare_output( $header_template, $variables );
}
/**
* Returns the HTML for the optional Terms of Service agreement text & link.
*
* @since 1.6.0
*
* @return string Empty if `terms_of_service/url` setting is not set.
*/
private function get_terms_of_service_html() {
$terms_of_service_url = $this->config->get_setting( 'terms_of_service/url' );
if ( ! $terms_of_service_url ) {
return '';
}
/**
* Filter trustedlogin/{ns}/template/auth/terms_of_service/anchor.
*
* @since 1.6.0
* @param string $tos_anchor The text of the link to the Terms of Service.
*/
$tos_anchor = apply_filters( 'trustedlogin/' . $this->config->ns() . '/template/auth/terms_of_service/anchor', esc_html__( 'Terms of Service', 'trustedlogin' ) );
$tos_link_template = '<a href="{{url}}" target="_blank" rel="noopener noreferrer">{{anchor}}</a>';
$tos_link_variables = array(
'url' => esc_url( $terms_of_service_url ),
'anchor' => esc_html( $tos_anchor ),
);
$terms_of_service_template = '
<div class="tl-{{ns}}-auth__tos">
<p>{{tos_text}}</p>
</div>';
$variables = array(
'ns' => $this->config->ns(),
'tos_text' => esc_html__( 'By granting access, you agree to the {{tos_link}}.', 'trustedlogin' ),
'tos_link' => $this->prepare_output( $tos_link_template, $tos_link_variables ),
);
return $this->prepare_output( $terms_of_service_template, $variables );
}
/**
* Shows the current site URL and, if passed as $_GET['ref'], a support reference ID
*
* @return string Empty string if there is no reference or if the `trustedlogin/{ns}/template/auth/display_reference` filter returns false.
*/
private function get_reference_html() {
$reference_id = Client::get_reference_id();
if ( null === $reference_id ) {
return '';
}
/**
* Filter trustedlogin/{ns}/template/auth/display_reference
*
* Used to hide or show the reference ID in the auth screen template.
*
* @since 1.3
*
* @param bool $display_reference Whether to display the reference ID on the auth screen. Default: true.
* @param bool $is_login_screen Whether the auth form is being displayed on the login screen.
* @param string $ref The reference ID.
*/
$display_reference = apply_filters( 'trustedlogin/' . $this->config->ns() . '/template/auth/display_reference', true, $this->is_login_screen(), $reference_id );
if ( ! $display_reference ) {
return '';
}
$template = '<div class="tl-{{ns}}-auth__ref"><p><span class="tl-{{ns}}-auth_ref__id">{{reference_text}}</span></p></div>';
$content = array(
// translators: %s is the reference ID.
'reference_text' => sprintf( esc_html__( 'Reference #%s', 'trustedlogin' ), $reference_id ),
'ns' => $this->config->ns(),
'site_url' => esc_html( str_replace( array( 'https://', 'http://' ), '', get_site_url() ) ),
);
return $this->prepare_output( $template, $content );
}
/**
* Returns the HTML for the intro section of the Auth screen (e.g. "Grant Acme Widgets access to this site.")
*
* @since 1.5.0
*
* @return string
*/
private function get_intro() {
$has_access = $this->support_user->get_all();
if ( $has_access ) {
foreach ( $has_access as $access ) {
// translators: %1$s is replaced with the name of the software developer (e.g. "Acme Widgets"). %2$s is the amount of time remaining for access ("1 week").
$intro = sprintf( esc_html__( '%1$s has site access that expires in %2$s.', 'trustedlogin' ), '<a href="' . esc_url( $this->config->get_setting( 'vendor/website' ) ) . '" target="_blank" rel="noopener noreferrer">' . $this->config->get_setting( 'vendor/title' ) . '</a>', str_replace( ' ', ' ', $this->support_user->get_expiration( $access, true, false ) ) );
}
return $intro;
}
if ( $this->is_login_screen() ) {
// translators: %1$s is replaced with the name of the software developer (e.g. "Acme Widgets").
$intro = sprintf( esc_html__( '%1$s would like support access to this site.', 'trustedlogin' ), '<a href="' . esc_url( $this->config->get_setting( 'vendor/website' ) ) . '">' . $this->config->get_display_name() . '</a>' );
} else {
// translators: %1$s is replaced with the name of the software developer (e.g. "Acme Widgets").
$intro = sprintf( esc_html__( 'Grant %1$s access to this site.', 'trustedlogin' ), '<a href="' . esc_url( $this->config->get_setting( 'vendor/website' ) ) . '">' . $this->config->get_display_name() . '</a>' );
}
return $intro;
}
/**
* Returns whether sending support ticket to the vendor is enabled.
*
* @since 1.5.0
* @return bool
*/
private function is_create_ticket_enabled() {
// There's already an existing ticket; no need to create another one.
if ( Client::get_reference_id() ) {
return false;
}
return $this->config->get_setting( 'webhook/url' ) && $this->config->get_setting( 'webhook/create_ticket', false );
}
/**
* Returns whether sending debug data to the support vendor is enabled.
*
* @since 1.5.0
* @return bool
*/
private function is_debug_data_enabled() {
return $this->config->get_setting( 'webhook/url' ) && $this->config->get_setting( 'webhook/debug_data', false );
}
/**
* Returns the HTML for the details section, which includes the roles, capabilities, and expiration details.
*
* @since 1.0.0
*
* @return string
*/
private function get_details_html() {
$has_access = $this->support_user->get_all();
// Has access.
if ( $has_access ) {
$output_template = '';
$output_template .= '{{users_table}}';
$content = array(
'users_table' => $this->output_support_users( false ),
);
return $this->prepare_output( $output_template, $content, false );
}
$ns = $this->config->ns();
$output_template = '
<p><span class="dashicons dashicons-info-outline dashicons--small"></span> This will allow <strong>{{name}}</strong> to:</p>
<div class="tl-{{ns}}-auth__roles">
<h2>
<span class="dashicons dashicons-admin-users dashicons--large"></span>{{roles_summary}}
</h2>
{{caps}}
</div>
<div class="tl-{{ns}}-auth__expire">
<h2>
<span class="dashicons dashicons-clock dashicons--large"></span>{{expire_summary}}{{expire_desc}}
</h2>
</div>
';
if ( $this->is_create_ticket_enabled() ) {
$message_summary = sprintf(
'<span
class="tl-{{ns}}-toggle"
data-toggle=".tl-{{ns}}-ticket__fields">
%s <span class="dashicons dashicons--small dashicons-arrow-down-alt2"></span>
</span>',
esc_html__( 'Include a message for support?', 'trustedlogin' )
);
$message_fields = sprintf(
'
<fieldset class="tl-{{ns}}-ticket__fields hidden">
<textarea
class="tl-{{ns}}-ticket-field__message large-text"
id="tl-{{ns}}-ticket-message"
placeholder="%s"
cols="50"
rows="8"
></textarea>
</fieldset>
',
esc_html__( 'Please describe the issue you are having.', 'trustedlogin' )
);
$output_template .= $this->prepare_output(
'<div class="tl-{{ns}}-ticket">
<h2>
<span class="dashicons dashicons-format-chat dashicons--large"></span>
{{message_summary}}
</h2>
{{message_fields}}
</div>',
array(
'ns' => $ns,
'message_summary' => $this->prepare_output( $message_summary, array( 'ns' => $ns ) ),
'message_fields' => $this->prepare_output( $message_fields, array( 'ns' => $ns ) ),
)
);
}
if ( $this->is_debug_data_enabled() ) {
$output_template .= '
<div class="tl-{{ns}}-auth__debug">
{{debug_data_consent}}
</div>';
}
// translators: %s is replaced with the of time that the login will be active for (e.g. "1 week").
$expire_summary = sprintf( esc_html__( 'Access this site for %s.', 'trustedlogin' ), '<strong>' . human_time_diff( 0, $this->config->get_setting( 'decay' ) ) . '</strong>' );
// translators: %s is replaced by the amount of time that the login will be active for (e.g. "1 week").
$expire_desc = '<small>' . sprintf( esc_html__( 'Access auto-expires in %s. You may revoke access at any time.', 'trustedlogin' ), human_time_diff( 0, $this->config->get_setting( 'decay' ) ) ) . '</small>';
$cloned_role = translate_user_role( ucfirst( $this->config->get_setting( 'role' ) ) );
if ( $this->config->get_setting( 'clone_role' ) ) {
// translators: %s is replaced with the name of the role (e.g. "Administrator").
$roles_summary = sprintf( esc_html__( 'Create a user with a role based on %s.', 'trustedlogin' ), '<strong>' . $cloned_role . '</strong>' );
if ( $this->config->get_setting( 'caps/add' ) || $this->config->get_setting( 'caps/remove' ) ) {
$roles_summary .= sprintf( '<small class="tl-' . $ns . '-toggle" data-toggle=".tl-' . $ns . '-auth__role-container">%s <span class="dashicons dashicons--small dashicons-arrow-down-alt2"></span></small>', esc_html__( 'View modified role capabilities', 'trustedlogin' ) );
}
} else {
// translators: %s is replaced with the name of the role (e.g. "Administrator").
$roles_summary = sprintf( esc_html__( 'Create a user with a role of %s.', 'trustedlogin' ), '<strong>' . $cloned_role . '</strong>' );
}
$content = array(
'ns' => $ns,
'name' => $this->config->get_display_name(),
'expire_summary' => $expire_summary,
'expire_desc' => $expire_desc,
'debug_data_consent' => $this->get_debug_data_consent_html(),
'roles_summary' => $roles_summary,
'caps' => $this->get_caps_html(),
);
return $this->prepare_output( $output_template, $content );
}
/**
* Get the HTML for the debug data consent checkbox.
*
* This is only shown if the webhook/url is defined and webhook/debug_data setting is true.
*
* @since 1.4.0
*
* @return string
*/
private function get_debug_data_consent_html() {
// translators: [link] and [/link] are replaced with a link to the Site Health page. Do not translate.
$output = sprintf(
'<h2><label><input type="checkbox" id="tl-{{ns}}-debug-data-consent" class="tl-{{ns}}-auth__checkbox--large" /> %s</label></h2>',
strtr(
esc_html__( 'Include the [link]Site Health[/link] troubleshooting report', 'trustedlogin' ),
array(
'[link]' => '<a href="' . esc_url( admin_url( 'site-health.php?tab=debug' ) ) . '">',
'[/link]' => '</a>',
)
)
);
$content = array(
'ns' => $this->config->ns(),
);
return $this->prepare_output( $output, $content );
}
/**
* Get role capabilities HTML the Auth form
*
* @return string Empty string if there are no caps defined. Otherwise, HTML of caps in lists.
*/
private function get_caps_html() {
$added = $this->config->get_setting( 'caps/add' );
$removed = $this->config->get_setting( 'caps/remove' );
$caps = '';
$caps .= $this->get_caps_section( $added, __( 'Additional capabilities:', 'trustedlogin' ), 'dashicons-yes-alt' );
$caps .= $this->get_caps_section( $removed, __( 'Removed capabilities:', 'trustedlogin' ), 'dashicons-dismiss' );
if ( empty( $caps ) ) {
return $caps;
}
return '<div class="tl-' . $this->config->ns() . '-auth__role-container hidden">' . $caps . '</div>';
}
/**
* Generate additional/removed capabilities sections.
*
* @param array $caps_array Associative array of cap => reason why cap is set.
* @param string $heading Text to show for the heading of the caps section.
* @param string $dashicon CSS class for the specific dashicon.
*
* @return string
*/
private function get_caps_section( $caps_array, $heading = '', $dashicon = '' ) {
$caps_array = array_filter( (array) $caps_array, array( $this->config, 'is_not_null' ) );
if ( empty( $caps_array ) ) {
return '';
}
$output = '';
$output .= '<div>';
$output .= '<h3>' . esc_html( $heading ) . '</h3>';
$output .= '<ul>';
foreach ( (array) $caps_array as $cap => $reason ) {
$dashicon = '<span class="dashicons ' . esc_attr( $dashicon ) . ' dashicons--small"></span>';
$reason = empty( $reason ) ? '' : '<small>' . esc_html( $reason ) . '</small>';
$output .= sprintf( '<li>%s<span class="code">%s</span>%s</li>', $dashicon, esc_html( $cap ), $reason );
}
$output .= '</ul>';
$output .= '</div>';
return $output;
}
/**
* Generates HTML for notices about current server environment perhaps not being accessible.
*
* @return string
*/
private function get_notices_html() {
if ( ! function_exists( 'wp_get_environment_type' ) ) {
return '';
}
if ( in_array( wp_get_environment_type(), array( 'staging', 'production' ), true ) ) {
return '';
}
if ( defined( 'TRUSTEDLOGIN_DISABLE_LOCAL_NOTICE' ) && TRUSTEDLOGIN_DISABLE_LOCAL_NOTICE ) {
return '';
}
$notice_template = '
<div class="inline notice notice-alt notice-warning">
<h3>{{local_site}}</h3>
<p>{{need_access}} <a href="{{about_live_access_url}}" target="_blank" rel="noopener noreferrer">{{learn_more}}</a></p>
</div>';
$content = array(
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
'local_site' => sprintf( esc_html__( '%s support may not be able to access this site.', 'trustedlogin' ), $this->config->get_setting( 'vendor/title' ) ),
'need_access' => esc_html__( 'This website is running in a local development environment. To provide support, we must be able to access your site using a publicly-accessible URL.', 'trustedlogin' ),
'about_live_access_url' => esc_url( $this->config->get_setting( 'vendor/about_live_access_url', self::ABOUT_LIVE_ACCESS_URL ) ),
'learn_more' => esc_html__( 'Learn more.', 'trustedlogin' ),
);
return $this->prepare_output( $notice_template, $content );
}
/**
* Returns the logo HTML shown at the top of the Auth form.
*
* @return string
*/
private function get_logo_html() {
$logo_url = $this->config->get_setting( 'vendor/logo_url' );
$logo_output = '';
if ( ! empty( $logo_url ) ) {
$logo_output = sprintf(
'<a href="%1$s" title="%2$s" target="_blank" rel="noreferrer noopener"><img src="%3$s" alt="%4$s" /></a>',
esc_url( $this->config->get_setting( 'vendor/website' ) ),
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
sprintf( 'Visit the %s website (opens in a new tab)', $this->config->get_setting( 'vendor/title' ) ),
esc_attr( $this->config->get_setting( 'vendor/logo_url' ) ),
esc_attr( $this->config->get_setting( 'vendor/title' ) )
);
}
return $logo_output;
}
/**
* Returns the HTML for the footer in the Auth form
*
* @return string
*/
private function get_footer_html() {
$support_url = $this->config->get_setting( 'vendor/support_url' );
$reference_id = Client::get_reference_id();
if ( $reference_id ) {
$support_args = array(
'tl' => Client::VERSION,
'ref' => $reference_id,
'ns' => $this->config->ns(),
);
$support_url = add_query_arg( $support_args, $support_url );
}
$footer_links = array(
esc_html__( 'Learn about TrustedLogin', 'trustedlogin' ) => self::ABOUT_TL_URL,
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
sprintf( 'Visit %s support', $this->config->get_setting( 'vendor/title' ) ) => $support_url,
);
/**
* Filter trustedlogin/{ns}/template/auth/footer_links
*
* Used to add/remove Footer Links on grantlink page
*
* @since 1.0.0
*
* @param array $footer_links Array of links to show in auth footer (Key is anchor text; Value is URL)
*/
$footer_links = apply_filters( 'trustedlogin/' . $this->config->ns() . '/template/auth/footer_links', $footer_links );
$footer_links_output = '';
foreach ( $footer_links as $text => $link ) {
$footer_links_output .= sprintf(
'<li><a href="%1$s" target="_blank">%2$s</a></li>',
esc_url( $link ),
esc_html( $text )
);
}
$footer_output = '';
if ( ! empty( $footer_links_output ) ) {
$footer_output = sprintf( '<ul>%1$s</ul>', $footer_links_output );
}
return $footer_output;
}
/**
* Returns the HTML for helpful debug info in the Auth form.
*
* Only shown if ?debug is present in the URL and the user has `manage_options` capability.
*
* @since 1.5.0
*
* @return string
*/
private function get_admin_debug_html() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['debug'] ) ) {
return '';
}
if ( ! current_user_can( 'manage_options' ) ) {
return '';
}
$remote = new Remote( $this->config, $this->logging );
$encryption = new Encryption( $this->config, $remote, $this->logging );
$items = array(
esc_html__( 'TrustedLogin Status', 'trustedlogin' ) => sprintf( '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', 'https://status.trustedlogin.com', is_wp_error( wp_remote_request( 'https://app.trustedlogin.com/api/status' ) ) ? esc_html__( 'Offline', 'trustedlogin' ) : esc_html__( 'Online', 'trustedlogin' ) ),
esc_html__( 'API Key', 'trustedlogin' ) => sprintf( '<code>%s</code>', $this->config->get_setting( 'auth/api_key' ) ),
esc_html__( 'License Key', 'trustedlogin' ) => sprintf( '<code>%s</code>', $this->config->get_setting( 'auth/license_key' ) ),
esc_html__( 'Log URL', 'trustedlogin' ) => sprintf( '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', str_replace( ABSPATH, get_site_url() . '/', $this->logging->get_log_file_path() ), esc_html__( 'Download the log', 'trustedlogin' ) ),
esc_html__( 'Log Level', 'trustedlogin' ) => $this->config->get_setting( 'logging/threshold', esc_html__( '(Default)', 'trustedlogin' ) ),
esc_html__( 'Webhook URL', 'trustedlogin' ) => sprintf( '<code>%s</code>', $this->config->get_setting( 'webhook/url', '(Empty)' ) ),
esc_html__( 'Vendor Public Key', 'trustedlogin' ) => sprintf( '<code>%s</code> (<a href="%s" target="_blank">%s</a>)', $encryption->get_vendor_public_key(), $encryption->get_remote_encryption_key_url(), esc_html__( 'Verify key', 'trustedlogin' ) ),
);
$debugging_info = '';
foreach ( $items as $label => $value ) {
$debugging_info .= sprintf( '<p><strong>%s</strong>: %s</p>', $label, $value );
}
$debugging_output = '<div class="tl-{{ns}}-auth__admin_debugging">
<h3>{{debugging_label}}</h3>
{{debugging_info}}
<h3>{{tl_config_label}}</h3>
{{tl_config}}
</div>';
return $this->prepare_output(
$debugging_output,
array(
'ns' => $this->config->ns(),
'debugging_label' => esc_html__( 'Debugging Info', 'trustedlogin' ),
'debugging_info' => $debugging_info,
'tl_config_label' => esc_html__( 'TrustedLogin Config', 'trustedlogin' ),
'tl_config' => '<pre>' . print_r( $this->config->get_settings(), true ) . '</pre>', // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
)
);
}
/**
* Replace placeholders in a template with content.
*
* @param string $template The template to use for the output.
* @param array $content The content to replace in the template.
* @param bool $wp_kses Whether to run the output through wp_kses.
*
* @return string
*/
private function prepare_output( $template, $content, $wp_kses = true ) {
$output_html = $template;
foreach ( $content as $key => $value ) {
$output_html = str_replace( '{{' . $key . '}}', $value, $output_html );
}
if ( $wp_kses ) {
// Allow SVGs for logos.
$allowed_protocols = wp_allowed_protocols();
$allowed_protocols[] = 'data';
$output_html = wp_kses(
$output_html,
array(
'a' => array(
'class' => array(),
'id' => array(),
'href' => array(),
'title' => array(),
'rel' => array(),
'target' => array(),
'data-toggle' => array(),
'data-access' => array(),
),
'img' => array(
'class' => array(),
'id' => array(),
'src' => array(),
'href' => array(),
'alt' => array(),
'title' => array(),
),
'span' => array(
'class' => array(),
'id' => array(),
'title' => array(),
'data-toggle' => array(),
'style' => array(),
),
'label' => array(
'class' => array(),
'id' => array(),
'for' => array(),
),
'code' => array(
'class' => array(),
'id' => array(),
),
'tt' => array(
'class' => array(),
'id' => array(),
),
'pre' => array(
'class' => array(),
'id' => array(),
),
'table' => array(
'class' => array(),
'id' => array(),
),
'thead' => array(),
'tfoot' => array(),
'td' => array(
'class' => array(),
'id' => array(),
'colspan' => array(),
),
'th' => array(
'class' => array(),
'id' => array(),
'colspan' => array(),
'scope' => array(),
),
'ul' => array(
'class' => array(),
'id' => array(),
),
'li' => array(
'class' => array(),
'id' => array(),
),
'p' => array(
'class' => array(),
'id' => array(),
),
'h1' => array(
'class' => array(),
'id' => array(),
),
'h2' => array(
'class' => array(),
'id' => array(),
),
'h3' => array(
'class' => array(),
'id' => array(),
'style' => array(),
),
'h4' => array(
'class' => array(),
'id' => array(),
),
'h5' => array(
'class' => array(),
'id' => array(),
),
'div' => array(
'class' => array(),
'id' => array(),
'aria-live' => array(),
'style' => array(),
),
'small' => array(
'class' => array(),
'id' => array(),
'data-toggle' => array(),
),
'header' => array(
'class' => array(),
'id' => array(),
),
'footer' => array(
'class' => array(),
'id' => array(),
),
'section' => array(
'class' => array(),
'id' => array(),
),
'br' => array(),
'strong' => array(),
'em' => array(),
'fieldset' => array(
'class' => array(),
'id' => array(),
),
'input' => array(
'class' => array(),
'id' => array(),
'type' => array( 'text' ),
'value' => array(),
'size' => array(),
'aria-live' => array(),
'aria-label' => array(),
'style' => array(),
),
'textarea' => array(
'class' => array(),
'id' => array(),
'rows' => array(),
'cols' => array(),
'placeholder' => array(),
),
'button' => array(
'class' => array(),
'id' => array(),
'aria-live' => array(),
'style' => array(),
'title' => array(),
),
),
$allowed_protocols
);
}
return $output_html;
}
/**
* Output the TrustedLogin Button and required scripts
*
* @since 1.0.0
*
* @param array|string $atts {@see get_button()} for configuration array.
* @param bool $print_and_return Should results be printed and returned (true) or only returned (false).
*
* @return string the HTML output
*/
public function generate_button( $atts = array(), $print_and_return = true ) {
if ( ! current_user_can( 'create_users' ) ) {
return '';
}
if ( ! wp_script_is( 'trustedlogin-' . $this->config->ns(), 'registered' ) ) {
$this->logging->log( 'JavaScript is not registered. Make sure `trustedlogin` handle is added to "no-conflict" plugin settings.', __METHOD__, 'error' );
}
if ( ! wp_style_is( 'trustedlogin-' . $this->config->ns(), 'registered' ) ) {
$this->logging->log( 'Style is not registered. Make sure `trustedlogin` handle is added to "no-conflict" plugin settings.', __METHOD__, 'error' );
}
// Still enqueue the style, since the button may be generated separately from the auth page.
wp_enqueue_style( 'trustedlogin-' . $this->config->ns() );
$button_settings = array(
'vendor' => $this->config->get_setting( 'vendor' ),
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'_nonce' => wp_create_nonce( 'tl_nonce-' . get_current_user_id() ),
'lang' => $this->translations(),
'debug' => $this->logging->is_enabled(),
'selector' => '.button-trustedlogin-' . $this->config->ns(),
'reference_id' => Client::get_reference_id(),
'query_string' => esc_url(
remove_query_arg(
array(
Endpoint::REVOKE_SUPPORT_QUERY_PARAM,
'_wpnonce',
)
)
),
'create_ticket' => $this->is_create_ticket_enabled(),
);
// TODO: Add data to tl_obj when detecting that it's already been localized by another vendor.
wp_localize_script( 'trustedlogin-' . $this->config->ns(), 'tl_obj', $button_settings );
wp_enqueue_script( 'trustedlogin-' . $this->config->ns() );
$return = $this->get_button( $atts );
if ( $print_and_return ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $return;
}
return $return;
}
/**
* Generates HTML for a TrustedLogin Grant Access button
*
* @param array $atts {
* Settings for the button.
* @type string $text Button text to grant access. Sanitized using esc_html(). Default: "Grant %s Access"
* (%s replaced with vendor/title setting)
* @type string $exists_text Button text when vendor already has a support account. Sanitized using esc_html().
* Default: "Extend %s Access" (%s replaced with vendor/title setting)
* @type string $size WordPress CSS button size. Options: 'small', 'normal', 'large', 'hero'. Default: "hero"
* @type string $class CSS class added to the button. Default: "button-primary"
* @type string $tag Tag used to display the button. Options: 'a', 'button', 'span'. Default: "a"
* @type bool $powered_by Whether to display the TrustedLogin badge on the button. Default: true
* @type string $support_url The URL to use as a backup if JavaScript fails or isn't available. Sanitized using
* esc_url(). Default: `vendor/support_url` configuration setting URL.
* }
*
* @return string
*/
public function get_button( $atts = array() ) {
$defaults = array(
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
'text' => sprintf( esc_html__( 'Grant %s Access', 'trustedlogin' ), $this->config->get_display_name() ),
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
'exists_text' => sprintf( esc_html__( 'Extend %s Access', 'trustedlogin' ), $this->config->get_display_name(), ucwords( human_time_diff( time(), time() + (int) $this->config->get_setting( 'decay' ) ) ) ),
'size' => 'hero',
'class' => 'button-primary',
'tag' => 'a', // Inline tags only.
'powered_by' => false,
'support_url' => $this->config->get_setting( 'vendor/support_url' ),
);
$sizes = array( 'small', 'normal', 'large', 'hero' );
$atts = wp_parse_args( $atts, $defaults );
switch ( $atts['size'] ) {
case '':
$css_class = '';
break;
case 'normal':
$css_class = 'button';
break;
default:
if ( ! in_array( $atts['size'], $sizes, true ) ) {
$atts['size'] = 'hero';
}
$css_class = 'button button-' . $atts['size'];
}
$_valid_tags = array( 'a', 'button', 'span' );
if ( ! empty( $atts['tag'] ) && in_array( strtolower( $atts['tag'] ), $_valid_tags, true ) ) {
$tag = $atts['tag'];
} else {
$tag = 'a';
}
$data_atts = array();
if ( $this->support_user->get_all() ) {
$text = '<span class="dashicons dashicons-update-alt dashicons--small"></span> ' . esc_html( $atts['exists_text'] );
$href = admin_url( 'users.php?role=' . $this->support_user->role->get_name() );
$data_atts['access'] = 'extend';
} else {
$text = esc_html( $atts['text'] );
$href = $atts['support_url'];
$data_atts['access'] = 'grant';
}
$css_class = implode( ' ', array( $css_class, $atts['class'] ) );
$css_class = trim( $css_class );
$data_string = '';
foreach ( $data_atts as $key => $value ) {
$data_string .= sprintf( ' data-%s="%s"', esc_attr( $key ), esc_attr( $value ) );
}
$powered_by = '';
if ( $atts['powered_by'] ) {
$powered_by = sprintf(
'<small><span class="trustedlogin-logo"></span>%s</small>',
esc_html__( 'Secured by TrustedLogin', 'trustedlogin' )
);
}
$anchor_html = $text . $powered_by;
return sprintf(
'<%1$s href="%2$s" class="%3$s button-trustedlogin-%4$s" aria-role="button" %5$s>%6$s</%1$s>',
/* %1$s */
$tag,
/* %2$s */
esc_url( $href ),
/* %3$s */
esc_attr( $css_class ),
/* %4$s */
$this->config->ns(),
/* %5$s */
$data_string,
/* %6$s */
$anchor_html
);
}
/**
* Helper function: Build translate-able strings for alert messages
*
* @since 1.0.0
*
* @return array of Translations and strings to be localized to JS variables
*/
public function translations() {
$vendor_title = $this->config->get_setting( 'vendor/title' );
/**
* Filter: Allow for adding into GET parameters on support_url
*
* @since 1.0.0
*
* ```
* $url_query_args = [
* 'message' => (string) What error should be sent to the support system.
* ];
* ```
*
* @param array $url_query_args {
*
* @type string $message What error should be sent to the support system.
* @type string|null $ref A sanitized reference ID, if passed. Otherwise, null.
* }
*/
$query_args = apply_filters(
'trustedlogin/' . $this->config->ns() . '/support_url/query_args',
array(
'message' => __( 'Could not create TrustedLogin access.', 'trustedlogin' ),
'ref' => Client::get_reference_id(),
)
);
$error_content = sprintf(
'<p>%s</p><p>%s</p>',
sprintf(
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
esc_html__( 'The user details could not be sent to %1$s automatically.', 'trustedlogin' ),
$vendor_title
),
sprintf(
// translators: %1$s is the vendor support url and %2$s is the vendor title.
__( 'Please <a href="%1$s" target="_blank">click here</a> to go to the %2$s support site', 'trustedlogin' ),
esc_url( add_query_arg( $query_args, $this->config->get_setting( 'vendor/support_url' ) ) ),
$vendor_title
)
);
$translations = array(
'buttons' => array(
'confirm' => esc_html__( 'Confirm', 'trustedlogin' ),
'ok' => esc_html__( 'Ok', 'trustedlogin' ),
// translators: %1$s is the vendor title.
'go_to_site' => sprintf( __( 'Go to %1$s support site', 'trustedlogin' ), $vendor_title ),
'close' => esc_html__( 'Close', 'trustedlogin' ),
'cancel' => esc_html__( 'Cancel', 'trustedlogin' ),
// translators: %1$s is the vendor title.
'revoke' => sprintf( esc_html__( 'Revoke %1$s support access', 'trustedlogin' ), $vendor_title ),
'copy' => esc_html__( 'Copy', 'trustedlogin' ),
'copied' => esc_html__( 'Copied!', 'trustedlogin' ),
),
'a11y' => array(
'opens_new_window' => esc_attr__( '(This link opens in a new window.)', 'trustedlogin' ),
'copied_text' => esc_html__( 'The access key has been copied to your clipboard.', 'trustedlogin' ),
),
'status' => array(
'synced' => array(
'title' => esc_html__( 'Support access granted', 'trustedlogin' ),
'content' => sprintf(
// translators: %1$s is the vendor title.
__( 'A temporary support user has been created, and sent to %1$s support.', 'trustedlogin' ),
$vendor_title
),
),
'pending' => array(
// translators: %1$s is the vendor title.
'content' => sprintf( __( 'Generating & encrypting secure support access for %1$s', 'trustedlogin' ), $vendor_title ),
),
'extending' => array(
// translators: %1$s is the vendor title and %2$s is the human-readable expiration time (for example, "1 week").
'content' => sprintf( __( 'Extending support access for %1$s by %2$s', 'trustedlogin' ), $vendor_title, human_time_diff( time(), time() + (int) $this->config->get_setting( 'decay' ) ) ),
),
'syncing' => array(
// translators: %1$s is the vendor title.
'content' => sprintf( __( 'Sending encrypted access to %1$s.', 'trustedlogin' ), $vendor_title ),
),
'error' => array(
// translators: %1$s is the vendor title.
'title' => sprintf( __( 'Error syncing support user to %1$s', 'trustedlogin' ), $vendor_title ),
'content' => wp_kses(
$error_content,
array(
'a' => array(
'href' => array(),
'rel' => array(),
'target' => array(),
),
'p' => array(),
)
),
),
'cancel' => array(
'title' => esc_html__( 'Action Cancelled', 'trustedlogin' ),
'content' => sprintf(
// translators: %1$s is the vendor title.
__( 'A support account for %1$s was not created.', 'trustedlogin' ),
$vendor_title
),
),
'failed' => array(
'title' => esc_html__( 'Support Access Was Not Granted', 'trustedlogin' ),
'content' => esc_html__( 'There was an error granting access: ', 'trustedlogin' ),
),
'failed_permissions' => array(
'content' => esc_html__( 'Your authorized session has expired. Please refresh the page.', 'trustedlogin' ),
),
'accesskey' => array(
'title' => esc_html__( 'TrustedLogin Key Created', 'trustedlogin' ),
'content' => sprintf(
// translators: %1$s is the vendor title.
__( 'Share this TrustedLogin Key with %1$s to give them secure access:', 'trustedlogin' ),
$vendor_title
),
'revoke_link' => esc_url( add_query_arg( array( Endpoint::REVOKE_SUPPORT_QUERY_PARAM => $this->config->ns() ), admin_url() ) ),
),
'error404' => array(
'title' => esc_html__( 'The TrustedLogin vendor could not be found.', 'trustedlogin' ),
'content' => '',
),
'error409' => array(
'title' => sprintf(
// translators: %1$s is the vendor title.
__( '%1$s Support user already exists', 'trustedlogin' ),
$vendor_title
),
'content' => sprintf(
wp_kses(
// translators: %1$s is the vendor title, %2$s is the URL to the users list page.
__( 'A support user for %1$s already exists. You may revoke this support access from your <a href="%2$s" target="_blank">Users list</a>.', 'trustedlogin' ),
array(
'a' => array(
'href' => array(),
'target' => array(),
),
)
),
$vendor_title,
esc_url( admin_url( 'users.php?role=' . $this->support_user->role->get_name() ) )
),
),
),
);
return $translations;
}
/**
* Outputs table of created support users
*
* @since 1.0.0
*
* @param bool|string $print_and_return Whether to print & return (true) or return (false) results. Default: true.
*
* @return string HTML table of active support users for vendor. Empty string if current user can't `create_users`
*/
public function output_support_users( $print_and_return = true ) {
if ( ( ! is_admin() && ! $this->is_login_screen() ) || ! current_user_can( 'create_users' ) ) {
return '';
}
// The `trustedlogin/{$ns}/button` action passes an empty string.
if ( '' === $print_and_return ) {
$print_and_return = true;
}
$support_users = $this->support_user->get_all();
if ( empty( $support_users ) ) {
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
$return = '<h3>' . sprintf( esc_html__( 'No %s users exist.', 'trustedlogin' ), esc_html( $this->config->get_setting( 'vendor/title' ) ) ) . '</h3>';
if ( $print_and_return ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $return;
}
return $return;
}
$return = '';
$access_key = $this->site_access->get_access_key();
if ( is_wp_error( $access_key ) ) {
$access_key_template = <<<EOD
<%3\$s class="tl-%1\$s-auth__accesskey">
<h3>%2\$s</h3>
<p>%4\$s <samp>%5\$s</samp></p>
</%3\$s>
EOD;
$access_key_output = sprintf(
$access_key_template,
/* %1$s */
sanitize_title( $this->config->ns() ),
/* %2$s */
esc_html__( 'Error', 'trustedlogin' ),
/* %3$s */
'div',
/* %4$s */
esc_html__( 'There was an error returning the access key.', 'trustedlogin' ),
/* %5$s */
esc_html( $access_key->get_error_message() )
);
} else {
$access_key_template = <<<EOD
<%6\$s class="tl-%1\$s-auth__accesskey">
<label for="tl-%1\$s-access-key"><h3>%2\$s</h3></label>
<p>%8\$s</p>
<div class="tl-%1\$s-auth__accesskey_wrapper">
<input id="tl-%1\$s-access-key" type="text" value="%4\$s" size="64" class="tl-%1\$s-auth__accesskey_field code" aria-label="%3\$s">
<button id="tl-%1\$s-copy" class="tl-%1\$s-auth__accesskey_copy button" aria-live="off" title="%7\$s"><span class="screen-reader-text">%5\$s</span></button>
</div>
</%6\$s>
EOD;
$access_key_output = sprintf(
$access_key_template,
/* %1$s */
sanitize_title( $this->config->ns() ),
/* %2$s */
esc_html__( 'Site access key:', 'trustedlogin' ),
/* %3$s */
esc_html__( 'Access Key', 'trustedlogin' ),
/* %4$s */
esc_attr( $access_key ),
/* %5$s */
esc_html__( 'Copy', 'trustedlogin' ),
/* %6$s */
'div',
/* %7$s */
esc_html__( 'Copy the access key to your clipboard', 'trustedlogin' ),
// %8$s
// translators: %s is the display name of the TrustedLogin support user.
sprintf( esc_html__( 'The access key is not a password; only %1$s will be able to access your site using this code. You may share this access key on support forums.', 'trustedlogin' ), esc_html( $this->support_user->get_first()->display_name ) )
);
}
$return .= $access_key_output;
if ( $print_and_return ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $return;
}
return $return;
}
/**
* Notice: Shown when a support user is manually revoked by admin;
*
* @return void
*/
public function admin_notice_revoked() {
static $displayed_notice;
// Only show notice once.
if ( $displayed_notice ) {
return;
}
?>
<div class="notice notice-success is-dismissible">
<h3>
<?php
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets").
echo esc_html( sprintf( __( '%s access revoked.', 'trustedlogin' ), $this->config->get_setting( 'vendor/title' ) ) );
?>
</h3>
<?php if ( ! current_user_can( 'delete_users' ) ) { ?>
<p><?php echo esc_html__( 'You may safely close this window.', 'trustedlogin' ); ?></p>
<?php } ?>
</div>
<?php
$displayed_notice = true;
}
/**
* Is this a login screen and should TrustedLogin override the login screen for the current namespace?
*
* @return bool
*/
private function is_login_screen() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return did_action( 'login_init' ) && isset( $_GET['ns'] ) && $_GET['ns'] === $this->config->ns();
}
}