wp-2fa
Last commit date
css
10 hours ago
dist
10 hours ago
includes
10 hours ago
js
10 hours ago
languages
10 hours ago
templates
10 hours ago
vendor
10 hours ago
class-plugin-deactivation.php
10 hours ago
index.php
10 hours ago
license.txt
10 hours ago
readme.txt
10 hours ago
wp-2fa.php
10 hours 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 |