PluginProbe ʕ •ᴥ•ʔ
Atarim – Visual Feedback, Review & AI Collaboration / 5.0
Atarim – Visual Feedback, Review & AI Collaboration v5.0
5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 3.3.0 3.3.1 3.3.2 3.3.2.1 3.3.2.2 3.3.3 3.30 3.31 3.32 3.4 3.4.1 3.4.3 3.4.4 3.5 3.5.1 3.6 3.6.1 3.7 3.8 3.9 3.9.1 3.9.2 3.9.3 3.9.4 3.9.6 3.9.6.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4
atarim-visual-collaboration / admin / class-avcf-offboarding.php
atarim-visual-collaboration / admin Last commit date
atarim-connect-screen.php 2 weeks ago class-avcf-offboarding.php 2 weeks ago class-avcf-settings.php 2 weeks ago class-avcf-upgrade-notice.php 2 weeks ago class-user-meta.php 2 weeks ago
class-avcf-offboarding.php
169 lines
1 <?php
2 /**
3 * Deactivation feedback for the plugins.php screen.
4 *
5 * Loads a small React bundle on the Plugins list table that intercepts the
6 * plugin's own "Deactivate" link and shows an Elementor-style feedback modal.
7 * On submit we fire the feedback at the Atarim API (fire-and-forget) and
8 * immediately let WordPress deactivate the plugin. "Skip" deactivates without
9 * sending anything.
10 *
11 * Note: WordPress only exposes the "Delete" link for inactive plugins, whose
12 * code does not run — so the delete-time data choice cannot be driven from
13 * here. The destructive cleanup still lives in uninstall.php and stays dormant
14 * (defaults to keeping data) until a preference is wired up in the future.
15 */
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 class AVCF_Offboarding {
22
23 const NONCE_ACTION = 'avc_offboarding';
24
25 public function __construct() {
26 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
27 add_action( 'wp_ajax_avcf_deactivation_feedback', array( $this, 'handle_deactivation_feedback' ) );
28 }
29
30 /**
31 * Only load on the Plugins list table (plugins.php), never the network one.
32 *
33 * @param string $hook Current admin page hook.
34 */
35 public function enqueue_assets( $hook ) {
36 if ( 'plugins.php' !== $hook ) {
37 return;
38 }
39
40 if ( ! current_user_can( 'manage_options' ) ) {
41 return;
42 }
43
44 // Reuse the bundled wp-components styles for parity with the settings UI.
45 wp_enqueue_style( 'wp-components', includes_url( 'css/dist/components/style.min.css' ), array(), AVCF_VERSION );
46 wp_enqueue_style(
47 'avc-offboarding-style',
48 AVCF_PLUGIN_URL . 'assets/css/offboarding.css',
49 array(),
50 AVCF_VERSION
51 );
52
53 wp_enqueue_script(
54 'avc-offboarding',
55 AVCF_PLUGIN_URL . 'assets/build/offboarding.js',
56 array(),
57 AVCF_VERSION,
58 true
59 );
60
61 $reasons = array(
62 array(
63 'value' => 'no_longer_need',
64 'label' => __( 'I no longer need the plugin', 'atarim-visual-collaboration' ),
65 ),
66 array(
67 'value' => 'found_better',
68 'label' => __( 'I found a better plugin', 'atarim-visual-collaboration' ),
69 ),
70 array(
71 'value' => 'couldnt_work',
72 'label' => __( "I couldn't get the plugin to work", 'atarim-visual-collaboration' ),
73 ),
74 array(
75 'value' => 'temporary',
76 'label' => __( "It's a temporary deactivation", 'atarim-visual-collaboration' ),
77 ),
78 array(
79 'value' => 'other',
80 'label' => __( 'Other', 'atarim-visual-collaboration' ),
81 ),
82 );
83
84 wp_localize_script(
85 'avc-offboarding',
86 'avcOffboarding',
87 array(
88 'ajaxurl' => admin_url( 'admin-ajax.php' ),
89 'nonce' => wp_create_nonce( self::NONCE_ACTION ),
90 'pluginBasename' => AVCF_PLUGIN_BASE,
91 'logoUrl' => AVCF_PLUGIN_URL . 'images/logo.svg',
92 'reasons' => $reasons,
93 'otherValue' => 'other',
94 'i18n' => array(
95 'deactivateTitle' => __( 'Quick Feedback', 'atarim-visual-collaboration' ),
96 'deactivatePrompt' => __( 'If you have a moment, please share why you are deactivating Atarim:', 'atarim-visual-collaboration' ),
97 'otherPlaceholder' => __( 'Please tell us more...', 'atarim-visual-collaboration' ),
98 'submitDeactivate' => __( 'Submit & Deactivate', 'atarim-visual-collaboration' ),
99 'skipDeactivate' => __( 'Skip & Deactivate', 'atarim-visual-collaboration' ),
100 'otherRequired' => __( 'Please add a few words so we can improve.', 'atarim-visual-collaboration' ),
101 'cancel' => __( 'Cancel', 'atarim-visual-collaboration' ),
102 ),
103 )
104 );
105 }
106
107 /**
108 * Verify the modal nonce sent via header or POST body.
109 *
110 * @return bool
111 */
112 private function verify_request() {
113 if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
114 return false;
115 }
116
117 $nonce = '';
118 if ( isset( $_SERVER['HTTP_X_AVC_NONCE'] ) ) {
119 $nonce = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_AVC_NONCE'] ) );
120 } elseif ( isset( $_POST['nonce'] ) ) {
121 $nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) );
122 }
123
124 return (bool) wp_verify_nonce( $nonce, self::NONCE_ACTION );
125 }
126
127 /**
128 * Proxy the deactivation feedback to the Atarim API.
129 *
130 * The client does not wait for this; it fires the request (keepalive) and
131 * then lets WordPress deactivate. We still respond fast so keepalive can
132 * settle. The outbound call is made server-side so no token/CORS leaks to
133 * the browser.
134 */
135 public function handle_deactivation_feedback() {
136 if ( ! $this->verify_request() ) {
137 wp_send_json_error( array( 'message' => 'Unauthorized.' ), 403 );
138 }
139
140 $function = new AVCF_Functions();
141
142 $reason = isset( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : '';
143 $comment = isset( $_POST['comment'] ) ? sanitize_textarea_field( wp_unslash( $_POST['comment'] ) ) : '';
144
145 $payload = array(
146 'reason' => $reason,
147 'comment' => $comment,
148 'site_url' => AVCF_HOME_URL,
149 'site_id' => $function->avcf_get_setting_data( 'avc_site_id' ),
150 'plugin_version' => AVCF_VERSION,
151 'source' => 'wordpress',
152 );
153
154 // Endpoint does not exist yet; that is expected. We do not block on it.
155 $function->avcf_make_api_call(
156 AVCF_CRM_API . 'plugin-deactivation-reason',
157 $payload,
158 '',
159 $function->avcf_get_setting_data( 'avc_atarim_secret_token' ),
160 'POST',
161 true
162 );
163
164 wp_send_json_success( array( 'message' => 'Feedback received.' ) );
165 }
166 }
167
168 new AVCF_Offboarding();
169