diff --git a/inc/css/parrot.css b/inc/css/parrot.css index 44bdce3..6ff1fbb 100644 --- a/inc/css/parrot.css +++ b/inc/css/parrot.css @@ -1,3 +1,198 @@ +/* Support Parrot admin screen */ +.ti-parrot-wrap { + max-width: 900px; +} + +.ti-parrot-header { + display: flex; + align-items: center; + gap: 12px; + margin: 10px 0 20px; +} + +.ti-parrot-header h1 { + margin: 0; + padding: 0; + font-size: 23px; + font-weight: 600; + line-height: 1.3; +} + +.ti-parrot-logo { + width: auto; + height: 32px; + display: block; +} + +.ti-parrot-header-sep { + width: 1px; + height: 26px; + background: #c3c4c7; +} + +.ti-parrot-notice { + max-width: 720px; + margin: 0 0 16px; +} + +.ti-parrot-status { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + margin-bottom: 16px; + border: 1px solid; + border-radius: 6px; + font-size: 14px; +} + +.ti-parrot-status .dashicons { + width: 20px; + height: 20px; + font-size: 20px; + flex: 0 0 auto; +} + +.ti-parrot-status--active { + background: #edfaef; + border-color: #b8e6c1; + color: #135e96; +} + +.ti-parrot-status--active .dashicons { + color: #00a32a; +} + +.ti-parrot-status--inactive { + background: #f6f7f7; + border-color: #dcdcde; + color: #50575e; +} + +.ti-parrot-status--inactive .dashicons { + color: #8c8f94; +} + +.ti-parrot-card { + background: #fff; + border: 1px solid #c3c4c7; + border-radius: 6px; + padding: 16px 20px; + margin-bottom: 16px; + box-shadow: 0 1px 1px rgba( 0, 0, 0, 0.04 ); +} + +.ti-parrot-intro p { + margin: 0 0 6px; + font-size: 14px; + line-height: 1.6; + color: #3c434a; +} + +.ti-parrot-intro p:last-child { + margin-bottom: 0; +} + +.ti-parrot-intro-hint { + color: #646970; + font-size: 13px; + font-weight: 600; +} + +.ti-parrot-details-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 4px; +} + +.ti-parrot-details-title { + font-size: 14px; + font-weight: 600; + color: #1d2327; +} + +.ti-parrot-row { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 0; + border-top: 1px solid #f0f0f1; +} + +.ti-parrot-row-label { + flex: 0 0 150px; + font-size: 13px; + color: #646970; +} + +.ti-parrot-row-value { + flex: 1; + text-align: right; + font-size: 13px; + color: #1d2327; + word-break: break-all; +} + +.ti-parrot-mono { + font-family: Consolas, Monaco, monospace; +} + +.ti-parrot-copy .dashicons { + width: 18px; + height: 18px; + font-size: 18px; + vertical-align: text-bottom; +} + +.ti-parrot-copy-label { + margin-left: 4px; +} + +.ti-parrot-copy.ti-parrot-copied { + border-color: #00a32a; + color: #00a32a; +} + +.ti-parrot-copy.button-primary.ti-parrot-copied { + background: #00a32a; + border-color: #00a32a; + color: #fff; +} + +.ti-parrot-copy.button-primary.ti-parrot-copied:hover, +.ti-parrot-copy.button-primary.ti-parrot-copied:focus { + background: #00a32a; + border-color: #00a32a; + color: #fff; +} + +.ti-parrot-expiry { + margin: 12px 0 0; + font-size: 12px; + color: #646970; +} + +.ti-parrot-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.ti-parrot-actions .ti-parrot-release { + color: #b32d2e; + border-color: #b32d2e; + background: #fff; +} + +.ti-parrot-actions .ti-parrot-release:hover, +.ti-parrot-actions .ti-parrot-release:focus { + color: #fff; + background: #b32d2e; + border-color: #b32d2e; +} + #pp-log-console .pp-log { overflow: hidden; } diff --git a/inc/js/parrot.js b/inc/js/parrot.js index 61c9636..e464aa9 100644 --- a/inc/js/parrot.js +++ b/inc/js/parrot.js @@ -7,6 +7,19 @@ ); function init() { + $( document ).on( + "click", ".ti-parrot-copy", function( e ){ + e.preventDefault(); + var button = this; + var text = button.getAttribute( "data-clipboard-text" ) || ""; + copyText( text ).then( + function(){ + showCopied( button ); + } + ).catch( function(){} ); + } + ); + $( '#pp-flush' ).on( "click", function(e){ e.preventDefault(); @@ -83,6 +96,62 @@ ); } + function copyText( text ) { + if ( navigator.clipboard && navigator.clipboard.writeText ) { + return navigator.clipboard.writeText( text ).catch( function(){ + return legacyCopy( text ); + } ); + } + return legacyCopy( text ); + } + + function legacyCopy( text ) { + return new Promise( + function( resolve, reject ){ + var area = document.createElement( "textarea" ); + var ok = false; + area.value = text; + area.style.position = "fixed"; + area.style.opacity = "0"; + document.body.appendChild( area ); + area.focus(); + area.select(); + try { + ok = document.execCommand( "copy" ); + } catch ( err ) {} + document.body.removeChild( area ); + if ( ok ) { + resolve(); + } else { + reject( new Error( "copy_failed" ) ); + } + } + ); + } + + function showCopied( button ) { + var $button = $( button ); + var $icon = $button.find( ".dashicons" ); + var $label = $button.find( ".ti-parrot-copy-label" ); + var prev = $label.length ? $label.text() : ""; + + $button.addClass( "ti-parrot-copied" ); + $icon.removeClass( "dashicons-clipboard" ).addClass( "dashicons-yes" ); + if ( $label.length ) { + $label.text( ( typeof pp !== "undefined" && pp.copied ) ? pp.copied : "Copied!" ); + } + + setTimeout( + function(){ + $button.removeClass( "ti-parrot-copied" ); + $icon.removeClass( "dashicons-yes" ).addClass( "dashicons-clipboard" ); + if ( $label.length ) { + $label.text( prev ); + } + }, 1600 + ); + } + function showSpinner() { $( '#pp-spinner' ).css( 'visibility', 'visible' ).attr( 'aria-hidden', 'false' ).show(); } diff --git a/pirate-parrot.php b/pirate-parrot.php index eb5f725..9c67537 100644 --- a/pirate-parrot.php +++ b/pirate-parrot.php @@ -1,9 +1,9 @@ get_options(); add_action( 'admin_menu', array( $this, 'register_settings_page' ) ); + register_activation_hook( __FILE__, array( $this, 'wake_bird' ) ); register_deactivation_hook( __FILE__, array( $this, 'sleep_bird' ) ); add_action( 'ti_kill_parrot', array( $this, 'sleep_bird' ) ); add_action( 'init', array( $this, 'init' ) ); + add_action( 'admin_init', array( $this, 'maybe_activation_redirect' ) ); + } + + function wake_bird() { + set_transient( 'ti_parrot_activation_redirect', true, 5 * MINUTE_IN_SECONDS ); + } + + function maybe_activation_redirect() { + if ( ! get_transient( 'ti_parrot_activation_redirect' ) ) { + return; + } + delete_transient( 'ti_parrot_activation_redirect' ); + + // Avoid breaking AJAX/REST/cron requests. + if ( wp_doing_ajax() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) ) { + return; + } + + // Only redirect users who can actually access the screen. + if ( is_network_admin() || ! current_user_can( 'manage_options' ) ) { + return; + } + + // Don't redirect when activating multiple plugins at once. + if ( isset( $_GET['activate-multi'] ) ) { + return; + } + + wp_safe_redirect( admin_url( 'tools.php?page=ti_pirate_parrot' ) ); + exit; } function init() { @@ -94,7 +125,8 @@ function admin_enqueue_scripts() { 'pirate-parrot', 'pp', array( - 'nonce' => wp_create_nonce( 'parrot' ), + 'nonce' => wp_create_nonce( 'parrot' ), + 'copied' => __( 'Copied!', 'pirate-parrot' ), ) ); @@ -235,9 +267,7 @@ function register_settings_page() { ) ); - if ( $this->is_user_parrot() ) { - add_action( 'load-' . $submenu, array( $this, 'load_js_and_css' ) ); - } + add_action( 'load-' . $submenu, array( $this, 'load_js_and_css' ) ); } function is_user_parrot() { @@ -283,53 +313,54 @@ function ti_parrot_cage() { // delete the account if it's expired $this->kill_sleep_bird(); } - printf( - ' - - - -
- -

%1$s

- - %7$s - -

%3$s

- - %6$s - -
- %2$s - %5$s - -
-
', - 'Themeisle Parrot', - get_submit_button( ( 'regenerate' === $token_action ? 'Recall Parrot' : 'Call Parrot' ), 'primary', 'submit', false ), - 'This plugin was made to allow a secured assistance from our support team, with no need to use your admin password. It will create a temporary admin account for our team, so they can have access to your WordPress Dashboard. This thing will be possible through a secret token, which will be generated by the plugin and which will be available for only 5 days. If our work is not finished yet, a new token will be generated for another 5 days to let us log in to your admin area again. Once our job is done, you can remove the new account or you can disable the plugin which will automatically delete the account. -All you have to do is to click on the button below for a new token. Then, give it to the moderator who has requested access, using our Private Messaging.', - esc_attr( $token_action ), - ( $account_exists ? get_submit_button( 'Release Parrot', 'delete', 'token_delete', false ) : '' ), - $this->get_parrot_info(), - $this->get_status_message( $message ) - ); + $is_active = $account_exists && isset( $this->_options['token'] ); + $primary_label = $account_exists ? __( 'Regenerate token', 'pirate-parrot' ) : __( 'Call the parrot', 'pirate-parrot' ); + ?> +
+
+ + +

+
+ +
+ + get_status_message( $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped within the method. ?> + + + get_expiration_date(); ?> +
+ + + + + — + + +
+ +
+ + +
+ + +
+

+

+
+ + + render_parrot_details(); ?> + + +
+ + 'ti-parrot-release' ) ); ?> + +
+
+ handle_logging(); } @@ -374,31 +405,74 @@ function init_parrot_kill() { wp_schedule_event( time(), 'twicedaily', 'ti_kill_parrot' ); } - function get_parrot_info() { - $output = ''; - if ( isset( $this->_options['token'] ) ) { - $output = sprintf( - '

%1$s

', - sprintf( - 'Parrot info:

Copy info ', - esc_html( $this->_options['token'] ), - wp_login_url(), - get_bloginfo( 'version' ), - phpversion(), - get_locale(), - wp_get_theme()->get( 'Name' ) .' '. wp_get_theme()->get( 'Version' ) - ) - ); - $output .= sprintf( - '

%1$s

', - ( ! is_wp_error( $expiration_date = $this->get_expiration_date() ) - ? 'This parrot will leave on ' . esc_html( $expiration_date ) - : $expiration_date->get_error_message() - ) - ); - } + function get_parrot_info_rows() { + $theme = wp_get_theme(); - return $output; + return array( + array( + 'label' => __( 'Access token', 'pirate-parrot' ), + 'value' => isset( $this->_options['token'] ) ? $this->_options['token'] : '', + 'mono' => true, + ), + array( + 'label' => __( 'Login URL', 'pirate-parrot' ), + 'value' => wp_login_url(), + ), + array( + 'label' => __( 'WordPress version', 'pirate-parrot' ), + 'value' => get_bloginfo( 'version' ), + ), + array( + 'label' => __( 'PHP version', 'pirate-parrot' ), + 'value' => phpversion(), + ), + array( + 'label' => __( 'Site locale', 'pirate-parrot' ), + 'value' => get_locale(), + ), + array( + 'label' => __( 'Theme', 'pirate-parrot' ), + 'value' => trim( $theme->get( 'Name' ) . ' ' . $theme->get( 'Version' ) ), + ), + ); + } + + function render_parrot_details() { + $rows = $this->get_parrot_info_rows(); + + // Build the "copy all" payload, one "Label: value" per line. + $lines = array(); + foreach ( $rows as $row ) { + $lines[] = $row['label'] . ': ' . $row['value']; + } + $copy_all = implode( "\n", $lines ); + $expiration = $this->get_expiration_date(); + ?> +
+
+ + +
+ +
+ + +
+ +

+ get_error_message() ); + } + ?> +

+
+ %1$s

', $message ); + $output = sprintf( '

%1$s

', esc_html( $message ) ); } } else { - $output = sprintf( '

%1$s

', $message->get_error_message() ); + $output = sprintf( '

%1$s

', esc_html( $message->get_error_message() ) ); } if ( '' !== $output ) { $output = sprintf( - '
- %2$s -
', - ( $is_error_message ? 'error' : 'updated' ), + '
%2$s
', + ( $is_error_message ? 'notice-error' : 'notice-success' ), $output ); } diff --git a/themeisle-logo.png b/themeisle-logo.png new file mode 100644 index 0000000..6671f5f Binary files /dev/null and b/themeisle-logo.png differ