wp-2fa
Last commit date
css
2 days ago
dist
2 days ago
includes
2 days ago
js
2 days ago
languages
2 days ago
templates
2 weeks ago
vendor
2 days ago
class-plugin-deactivation.php
2 weeks ago
index.php
5 years ago
license.txt
2 years ago
readme.txt
2 days ago
wp-2fa.php
2 days ago
class-plugin-deactivation.php
357 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Plugin deactivation class. |
| 4 | * |
| 5 | * Adds a form to request the reason for deactivation. |
| 6 | * |
| 7 | * @package Progress_Planner |
| 8 | */ |
| 9 | |
| 10 | namespace WP2FA_Deactivation_Feedback_Server; |
| 11 | |
| 12 | if ( ! class_exists( '\WP2FA_Deactivation_Feedback_Server\Plugin_Deactivation' ) ) { |
| 13 | |
| 14 | /** |
| 15 | * Plugin deactivation class. |
| 16 | */ |
| 17 | class Plugin_Deactivation { |
| 18 | |
| 19 | /** |
| 20 | * The plugin slug. |
| 21 | * |
| 22 | * This is used to identify and catch the deactivation trigger. |
| 23 | * |
| 24 | * @var string |
| 25 | */ |
| 26 | const PLUGIN_SLUG = 'wp-2fa'; |
| 27 | |
| 28 | /** |
| 29 | * The remote API URL to send the deactivation reason to. |
| 30 | * |
| 31 | * @var string |
| 32 | */ |
| 33 | const REMOTE_URL = 'https://proxytron.melapress.com'; |
| 34 | |
| 35 | /** |
| 36 | * Get the plugin slug suffix for premium version. |
| 37 | * |
| 38 | * @return string |
| 39 | */ |
| 40 | public static function plugin_slug() { |
| 41 | $premium_version_slug = 'wp-2fa-premium/wp-2fa.php'; |
| 42 | if ( \is_plugin_active( $premium_version_slug ) ) { |
| 43 | if ( ! function_exists( 'get_plugin_data' ) ) { |
| 44 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 45 | } |
| 46 | $plugin_file = WP_PLUGIN_DIR . '/' . $premium_version_slug; |
| 47 | $plugin_data = array(); |
| 48 | if ( file_exists( $plugin_file ) ) { |
| 49 | $plugin_data = \get_plugin_data( $plugin_file, false, false ); |
| 50 | } |
| 51 | $plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_data['Name'] ); |
| 52 | |
| 53 | return $plugin_slug; |
| 54 | } |
| 55 | |
| 56 | return self::PLUGIN_SLUG; |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * Constructor. |
| 61 | */ |
| 62 | public function __construct() { |
| 63 | \add_action( 'admin_footer', array( $this, 'maybe_add_script' ) ); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Maybe add the script to the admin footer. |
| 68 | * |
| 69 | * @return void |
| 70 | */ |
| 71 | public function maybe_add_script() { |
| 72 | // Check if we're in the plugins page. |
| 73 | if ( ! \function_exists( 'get_current_screen' ) || ! \get_current_screen() || ( 'plugins' !== \get_current_screen()->id && 'plugins-network' !== \get_current_screen()->id ) ) { |
| 74 | return; |
| 75 | } |
| 76 | $this->the_popover(); |
| 77 | $this->the_inline_script(); |
| 78 | $this->the_inline_style(); |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * The popover. |
| 83 | * |
| 84 | * @return void |
| 85 | */ |
| 86 | protected function the_popover() { |
| 87 | $reasons = array( |
| 88 | array( |
| 89 | 'id' => 'unexpected-behavior', |
| 90 | 'label' => \__( 'The plugin isn\'t working, caused issues, or has a bug', 'wp-2fa' ), |
| 91 | 'feedback_placeholder' => \__( 'Can you briefly describe the issue?', 'wp-2fa' ), |
| 92 | 'feedback_type' => 'textarea', |
| 93 | ), |
| 94 | // [ |
| 95 | // 'id' => 'wrong-feature', |
| 96 | // 'label' => \__( "It's not what I was looking for", 'wp-2fa' ), |
| 97 | // 'feedback_placeholder' => \__( 'What were you looking for?', 'wp-2fa' ), |
| 98 | // 'feedback_type' => 'textarea', |
| 99 | // ], |
| 100 | // [ |
| 101 | // 'id' => 'not-working', |
| 102 | // 'label' => \__( 'The plugin is not working', 'wp-2fa' ), |
| 103 | // 'feedback_placeholder' => \__( 'Kindly share what didn\'t work so we can fix it for future users...', 'wp-2fa' ), |
| 104 | // 'feedback_type' => 'textarea', |
| 105 | // ], |
| 106 | array( |
| 107 | 'id' => 'found-better-plugin', |
| 108 | 'label' => \__( 'I found a better alternative', 'wp-2fa' ), |
| 109 | 'feedback_placeholder' => \__( 'Which plugin did you switch to?', 'wp-2fa' ), |
| 110 | 'feedback_type' => 'text', |
| 111 | ), |
| 112 | array( |
| 113 | 'id' => 'missing-feature', |
| 114 | 'label' => \__( 'The plugin is missing a specific feature', 'wp-2fa' ), |
| 115 | 'feedback_placeholder' => \__( 'What feature were you looking for?', 'wp-2fa' ), |
| 116 | 'feedback_type' => 'textarea', |
| 117 | ), |
| 118 | array( |
| 119 | 'id' => 'hard-to-understand', |
| 120 | 'label' => \__( 'The plugin is too hard to set up or understand', 'wp-2fa' ), |
| 121 | 'feedback_placeholder' => \__( 'Can you tell us a bit more about this?', 'wp-2fa' ), |
| 122 | 'feedback_type' => 'text', |
| 123 | ), |
| 124 | array( |
| 125 | 'id' => 'temporary-deactivation', |
| 126 | 'label' => \__( 'This is a temporary deactivation', 'wp-2fa' ), |
| 127 | 'feedback_type' => false, |
| 128 | 'feedback_placeholder' => false, |
| 129 | ), |
| 130 | ); |
| 131 | |
| 132 | // Randomize the order of the reasons. |
| 133 | \shuffle( $reasons ); |
| 134 | |
| 135 | // Add the "other" reason at the end. |
| 136 | $reasons[] = array( |
| 137 | 'id' => 'other', |
| 138 | 'label' => \__( 'Other', 'wp-2fa' ), |
| 139 | 'feedback_placeholder' => false, |
| 140 | 'feedback_type' => false, |
| 141 | ); |
| 142 | |
| 143 | ?> |
| 144 | <div id="<?php echo \esc_attr( self::plugin_slug() ); ?>-popover" popover> |
| 145 | <div style="text-align: center; margin-bottom: 1.5rem;"> |
| 146 | <img src="<?php echo \esc_url( WP_2FA_URL . 'dist/images/wizard-logo.png' ); ?>" alt="<?php echo \esc_attr( 'Plugin Logo', 'wp-2fa' ); ?>" style="width: 50px; margin-bottom: 1rem;"> </div> |
| 147 | <h1><?php \esc_html_e( "We're sorry to see you go", 'wp-2fa' ); ?></h1> |
| 148 | <p><?php \esc_html_e( 'If you have a moment, please let us know why you are deactivating this plugin:', 'wp-2fa' ); ?></p> |
| 149 | <form> |
| 150 | <?php foreach ( $reasons as $reason ) : ?> |
| 151 | <div class="reason-wrapper" data-reason="<?php echo \esc_attr( $reason['id'] ); ?>"> |
| 152 | <span class="radio-wrapper"> |
| 153 | <input |
| 154 | id="deactivate-plugin-reason-<?php echo \esc_attr( $reason['id'] ); ?>" |
| 155 | type="radio" |
| 156 | name="reason" |
| 157 | value="<?php echo \esc_attr( $reason['id'] ); ?>" |
| 158 | > |
| 159 | <label for="deactivate-plugin-reason-<?php echo \esc_attr( $reason['id'] ); ?>"> |
| 160 | <?php echo \esc_html( $reason['label'] ); ?> |
| 161 | </label> |
| 162 | </span> |
| 163 | <?php if ( $reason['feedback_type'] ) : ?> |
| 164 | <div class="feedback-wrapper"> |
| 165 | <?php if ( 'textarea' === $reason['feedback_type'] ) : ?> |
| 166 | <textarea |
| 167 | id="deactivate-plugin-reason-<?php echo \esc_attr( $reason['id'] ); ?>-feedback" |
| 168 | name="feedback" |
| 169 | placeholder="<?php echo \esc_attr( $reason['feedback_placeholder'] ); ?>" |
| 170 | ></textarea> |
| 171 | <?php else : ?> |
| 172 | <input |
| 173 | id="deactivate-plugin-reason-<?php echo \esc_attr( $reason['id'] ); ?>-feedback" |
| 174 | type="text" |
| 175 | name="feedback" |
| 176 | placeholder="<?php echo \esc_attr( $reason['feedback_placeholder'] ); ?>" |
| 177 | > |
| 178 | <?php endif; ?> |
| 179 | </div> |
| 180 | <?php endif; ?> |
| 181 | </div> |
| 182 | <?php endforeach; ?> |
| 183 | |
| 184 | <div class="actions"> |
| 185 | <button type="button" class="submit"><?php \esc_html_e( 'Submit & Deactivate', 'wp-2fa' ); ?></button> |
| 186 | <button type="button" class="dismiss"><?php \esc_html_e( 'Skip & Deactivate', 'wp-2fa' ); ?></button> |
| 187 | </div> |
| 188 | </form> |
| 189 | </div> |
| 190 | <?php |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * The inline script. |
| 195 | * |
| 196 | * @return void |
| 197 | */ |
| 198 | protected function the_inline_script() { |
| 199 | ?> |
| 200 | <script> |
| 201 | (function() { |
| 202 | // A helper function to make AJAX requests. |
| 203 | const deactivatePluginFeedbackAjaxRequest = ( { url, data, action } ) => { |
| 204 | const http = new XMLHttpRequest(); |
| 205 | http.open( 'POST', url, true ); |
| 206 | http.onreadystatechange = () => { |
| 207 | let response; |
| 208 | try { |
| 209 | response = JSON.parse( http.response ); |
| 210 | } catch ( e ) {} |
| 211 | return action( response ); |
| 212 | }; |
| 213 | const dataForm = new FormData(); |
| 214 | for ( let [ key, value ] of Object.entries( data ) ) { |
| 215 | dataForm.append( key, value ); |
| 216 | } |
| 217 | http.send( dataForm ); |
| 218 | } |
| 219 | |
| 220 | // Add an event listener to the deactivate button. |
| 221 | const deactivateButton = document.getElementById( 'deactivate-<?php echo \esc_attr( self::plugin_slug() ); ?>' ); |
| 222 | |
| 223 | const deactivationPopover = document.getElementById( '<?php echo \esc_attr( self::plugin_slug() ); ?>-popover' ); |
| 224 | if ( deactivateButton && deactivationPopover ) { |
| 225 | deactivateButton.addEventListener( 'click', function( e ) { |
| 226 | e.preventDefault(); |
| 227 | deactivationPopover.showPopover(); |
| 228 | } ); |
| 229 | |
| 230 | // Show/hide the feedback fields based on the selected reason. |
| 231 | deactivationPopover.querySelectorAll( '.reason-wrapper' ).forEach( function( reasonWrapper ) { |
| 232 | reasonWrapper.addEventListener( 'click', function( changeEvent ) { |
| 233 | const radio = reasonWrapper.querySelector( 'input[type="radio"]' ); |
| 234 | if ( radio ) { |
| 235 | radio.checked = true; |
| 236 | } |
| 237 | const feedbackWrapper = reasonWrapper.querySelector( '.feedback-wrapper' ); |
| 238 | // Hide any existing feedback fields. |
| 239 | deactivationPopover.querySelectorAll( '.feedback-wrapper' ).forEach( function( feedbackWrapper ) { |
| 240 | feedbackWrapper.style.display = 'none'; |
| 241 | } ); |
| 242 | if ( feedbackWrapper ) { |
| 243 | reasonWrapper.querySelector( '.feedback-wrapper' ).style.display = 'block'; |
| 244 | } |
| 245 | } ); |
| 246 | } ); |
| 247 | |
| 248 | // Handle clicking on the dismiss button. |
| 249 | deactivationPopover.querySelector( 'button.dismiss' ).addEventListener( 'click', function( dismissEvent ) { |
| 250 | dismissEvent.preventDefault(); |
| 251 | window.location.href = deactivateButton.href; |
| 252 | } ); |
| 253 | |
| 254 | // Handle clicking on the submit button. |
| 255 | deactivationPopover.querySelector( 'button.submit' ).addEventListener( 'click', function( submitEvent ) { |
| 256 | submitEvent.preventDefault(); |
| 257 | const requestData = { |
| 258 | action: 'plugin_deactivation', |
| 259 | plugin: '<?php echo \esc_attr( self::plugin_slug() ); ?>', |
| 260 | site: '<?php echo \esc_attr( get_site_url() ); ?>', |
| 261 | }; |
| 262 | deactivatePluginFeedbackAjaxRequest( { |
| 263 | // Get a nonce from the remote server. |
| 264 | url: '<?php echo \esc_url( self::REMOTE_URL ); ?>/?rest_route=/deactivation-feedback-server/v1/get-nonce', |
| 265 | data: requestData, |
| 266 | action: ( response ) => { |
| 267 | response = response || {}; |
| 268 | // Add the nonce to the request data, and build the data object for the feedback. |
| 269 | requestData.nonce = response.nonce; |
| 270 | const formData = new FormData( deactivationPopover.querySelector( 'form' ) ); |
| 271 | requestData.reason = formData.get( 'reason' ); |
| 272 | const feedbackEl = document.getElementById( `deactivate-plugin-reason-${requestData.reason}-feedback` ); |
| 273 | requestData.feedback = feedbackEl ? feedbackEl.value : ''; |
| 274 | |
| 275 | // Make the request to the remote server to submit the feedback. |
| 276 | deactivatePluginFeedbackAjaxRequest( { |
| 277 | url: '<?php echo \esc_url( self::REMOTE_URL ); ?>/?rest_route=/deactivation-feedback-server/v1/submit-feedback', |
| 278 | data: requestData, |
| 279 | action: ( response ) => { |
| 280 | window.location.href = deactivateButton.href; |
| 281 | }, |
| 282 | } ); |
| 283 | }, |
| 284 | } ); |
| 285 | |
| 286 | // Submit the form. |
| 287 | deactivationPopover.hidePopover(); |
| 288 | } ); |
| 289 | } |
| 290 | })(); |
| 291 | </script> |
| 292 | <?php |
| 293 | } |
| 294 | |
| 295 | /** |
| 296 | * The inline style. |
| 297 | * |
| 298 | * @return void |
| 299 | */ |
| 300 | protected function the_inline_style() { |
| 301 | ?> |
| 302 | <style> |
| 303 | #<?php echo \esc_attr( self::plugin_slug() ); ?>-popover { |
| 304 | border: 1px solid #ccc; |
| 305 | padding: 2rem; |
| 306 | border-radius: 8px; |
| 307 | |
| 308 | form { |
| 309 | display: flex; |
| 310 | flex-direction: column; |
| 311 | gap: 1rem; |
| 312 | |
| 313 | .reason-wrapper { |
| 314 | display: flex; |
| 315 | gap: 1rem; |
| 316 | flex-direction: column; |
| 317 | |
| 318 | .feedback-wrapper { |
| 319 | display: none; |
| 320 | |
| 321 | textarea, input { |
| 322 | width: 100%; |
| 323 | border: 1px solid #ccc; |
| 324 | border-radius: 4px; |
| 325 | padding: 0.5rem; |
| 326 | } |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | .actions { |
| 331 | display: flex; |
| 332 | gap: 1rem; |
| 333 | } |
| 334 | |
| 335 | button { |
| 336 | padding: 0.5rem 1rem; |
| 337 | border-radius: 4px; |
| 338 | border: 1px solid #ccc; |
| 339 | background-color: #40D3F0; |
| 340 | cursor: pointer; |
| 341 | color: #fff; |
| 342 | margin: 0; |
| 343 | |
| 344 | &.dismiss { |
| 345 | background-color: #fff; |
| 346 | cursor: pointer; |
| 347 | color: #000; |
| 348 | } |
| 349 | } |
| 350 | } |
| 351 | } |
| 352 | </style> |
| 353 | <?php |
| 354 | } |
| 355 | } |
| 356 | } |
| 357 |