balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

Insights.php (26194B)


      1 <?php
      2 namespace ReduxAppsero;
      3 
      4 /**
      5  * Appsero Insights
      6  *
      7  * This is a tracker class to track plugin usage based on if the customer has opted in.
      8  * No personal information is being tracked by this class, only general settings, active plugins, environment details
      9  * and admin email.
     10  */
     11 class Insights {
     12 
     13 	/**
     14 	 * The notice text
     15 	 *
     16 	 * @var string
     17 	 */
     18 	public $notice;
     19 
     20 	/**
     21 	 * Wheather to the notice or not
     22 	 *
     23 	 * @var boolean
     24 	 */
     25 	protected $show_notice = true;
     26 
     27 	/**
     28 	 * If extra data needs to be sent
     29 	 *
     30 	 * @var array
     31 	 */
     32 	protected $extra_data = array();
     33 
     34 	/**
     35 	 * AppSero\Client
     36 	 *
     37 	 * @var object
     38 	 */
     39 	protected $client;
     40 
     41 	/**
     42 	 * Initialize the class
     43 	 *
     44 	 * @param AppSero\Client
     45 	 */
     46 	public function __construct( $client, $name = null, $file = null ) {
     47 
     48 		if ( is_string( $client ) && ! empty( $name ) && ! empty( $file ) ) {
     49 			$client = new Client( $client, $name, $file );
     50 		}
     51 
     52 		if ( is_object( $client ) && is_a( $client, 'ReduxAppsero\Client' ) ) {
     53 			$this->client = $client;
     54 		}
     55 	}
     56 
     57 	/**
     58 	 * Don't show the notice
     59 	 *
     60 	 * @return \self
     61 	 */
     62 	public function hide_notice() {
     63 		$this->show_notice = false;
     64 
     65 		return $this;
     66 	}
     67 
     68 	/**
     69 	 * Add extra data if needed
     70 	 *
     71 	 * @param array $data
     72 	 *
     73 	 * @return \self
     74 	 */
     75 	public function add_extra( $data = array() ) {
     76 		$this->extra_data = $data;
     77 
     78 		return $this;
     79 	}
     80 
     81 	/**
     82 	 * Set custom notice text
     83 	 *
     84 	 * @param  string $text
     85 	 *
     86 	 * @return \self
     87 	 */
     88 	public function notice( $text ) {
     89 		$this->notice = $text;
     90 
     91 		return $this;
     92 	}
     93 
     94 	/**
     95 	 * Initialize insights
     96 	 *
     97 	 * @return void
     98 	 */
     99 	public function init() {
    100 		if ( $this->client->type == 'plugin' ) {
    101 			$this->init_plugin();
    102 		} elseif ( $this->client->type == 'theme' ) {
    103 			$this->init_theme();
    104 		}
    105 	}
    106 
    107 	/**
    108 	 * Initialize theme hooks
    109 	 *
    110 	 * @return void
    111 	 */
    112 	public function init_theme() {
    113 		$this->init_common();
    114 
    115 		add_action( 'switch_theme', array( $this, 'deactivation_cleanup' ) );
    116 		add_action( 'switch_theme', array( $this, 'theme_deactivated' ), 12, 3 );
    117 	}
    118 
    119 	/**
    120 	 * Initialize plugin hooks
    121 	 *
    122 	 * @return void
    123 	 */
    124 	public function init_plugin() {
    125 		// plugin deactivate popup
    126 		if ( ! $this->is_local_server() ) {
    127 			add_filter( 'plugin_action_links_' . $this->client->basename, array( $this, 'plugin_action_links' ) );
    128 			add_action( 'admin_footer', array( $this, 'deactivate_scripts' ) );
    129 		}
    130 
    131 		$this->init_common();
    132 
    133 		register_activation_hook( $this->client->file, array( $this, 'activate_plugin' ) );
    134 		register_deactivation_hook( $this->client->file, array( $this, 'deactivation_cleanup' ) );
    135 	}
    136 
    137 	/**
    138 	 * Initialize common hooks
    139 	 *
    140 	 * @return void
    141 	 */
    142 	protected function init_common() {
    143 
    144 		if ( $this->show_notice ) {
    145 			// tracking notice
    146 			add_action( 'admin_notices', array( $this, 'admin_notice' ) );
    147 		}
    148 
    149 		add_action( 'admin_init', array( $this, 'handle_optin_optout' ) );
    150 
    151 		// uninstall reason
    152 		add_action( 'wp_ajax_' . $this->client->slug . '_submit-uninstall-reason', array( $this, 'uninstall_reason_submission' ) );
    153 
    154 		// cron events
    155 		add_filter( 'cron_schedules', array( $this, 'add_weekly_schedule' ) );
    156 		add_action( $this->client->slug . '_tracker_send_event', array( $this, 'send_tracking_data' ) );
    157 		// add_action( 'admin_init', array( $this, 'send_tracking_data' ) ); // test
    158 	}
    159 
    160 	/**
    161 	 * Send tracking data to AppSero server
    162 	 *
    163 	 * @param  boolean $override
    164 	 *
    165 	 * @return void
    166 	 */
    167 	public function send_tracking_data( $override = false ) {
    168 		// skip on AJAX Requests
    169 		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
    170 			return;
    171 		}
    172 
    173 		if ( ! $this->tracking_allowed() && ! $override ) {
    174 			return;
    175 		}
    176 
    177 		// Send a maximum of once per week
    178 		$last_send = $this->get_last_send();
    179 
    180 		if ( $last_send && $last_send > strtotime( '-1 week' ) ) {
    181 			return;
    182 		}
    183 
    184 		$tracking_data = $this->get_tracking_data();
    185 
    186 		$response = $this->client->send_request( $tracking_data, 'track' );
    187 
    188 		update_option( $this->client->slug . '_tracking_last_send', time() );
    189 	}
    190 
    191 	/**
    192 	 * Get the tracking data points
    193 	 *
    194 	 * @return array
    195 	 */
    196 	protected function get_tracking_data() {
    197 		$all_plugins = $this->get_all_plugins();
    198 
    199 		$users = get_users(
    200 			array(
    201 				'role'    => 'administrator',
    202 				'orderby' => 'ID',
    203 				'order'   => 'ASC',
    204 				'number'  => 1,
    205 				'paged'   => 1,
    206 			)
    207 		);
    208 
    209 		$admin_user = ( is_array( $users ) && ! empty( $users ) ) ? $users[0] : false;
    210 		$first_name = $last_name = '';
    211 
    212 		if ( $admin_user ) {
    213 			$first_name = $admin_user->first_name ? $admin_user->first_name : $admin_user->display_name;
    214 			$last_name  = $admin_user->last_name;
    215 		}
    216 
    217 		$data = array(
    218 			'version'          => $this->client->project_version,
    219 			'url'              => esc_url( home_url() ),
    220 			'site'             => $this->get_site_name(),
    221 			'admin_email'      => get_option( 'admin_email' ),
    222 			'first_name'       => $first_name,
    223 			'last_name'        => $last_name,
    224 			'hash'             => $this->client->hash,
    225 			'server'           => $this->get_server_info(),
    226 			'wp'               => $this->get_wp_info(),
    227 			'users'            => $this->get_user_counts(),
    228 			'active_plugins'   => count( $all_plugins['active_plugins'] ),
    229 			'inactive_plugins' => count( $all_plugins['inactive_plugins'] ),
    230 			'ip_address'       => $this->get_user_ip_address(),
    231 			'theme'            => get_stylesheet(),
    232 			'project_version'  => $this->client->project_version,
    233 			'tracking_skipped' => false,
    234 		);
    235 
    236 		// Add metadata
    237 		if ( $extra = $this->get_extra_data() ) {
    238 			$data['extra'] = $extra;
    239 		}
    240 
    241 		// Check this has previously skipped tracking
    242 		$skipped = get_option( $this->client->slug . '_tracking_skipped' );
    243 
    244 		if ( $skipped === 'yes' ) {
    245 			delete_option( $this->client->slug . '_tracking_skipped' );
    246 
    247 			$data['tracking_skipped'] = true;
    248 		}
    249 
    250 		return apply_filters( $this->client->slug . '_tracker_data', $data );
    251 	}
    252 
    253 	/**
    254 	 * If a child class wants to send extra data
    255 	 *
    256 	 * @return mixed
    257 	 */
    258 	protected function get_extra_data() {
    259 		if ( is_callable( $this->extra_data ) ) {
    260 			return call_user_func( $this->extra_data );
    261 		}
    262 
    263 		if ( is_array( $this->extra_data ) ) {
    264 			return $this->extra_data;
    265 		}
    266 
    267 		return array();
    268 	}
    269 
    270 	/**
    271 	 * Explain the user which data we collect
    272 	 *
    273 	 * @return string
    274 	 */
    275 	protected function data_we_collect() {
    276 		$data = array(
    277 			'Server environment details (php, mysql, server, WordPress versions)',
    278 			'Number of users in your site',
    279 			'Site language',
    280 			'Number of active and inactive plugins',
    281 			'Site name and url',
    282 			'Your name and email address',
    283 		);
    284 
    285 		return $data;
    286 	}
    287 
    288 	/**
    289 	 * Check if the user has opted into tracking
    290 	 *
    291 	 * @return bool
    292 	 */
    293 	public function tracking_allowed() {
    294 		$allow_tracking = get_option( $this->client->slug . '_allow_tracking', 'no' );
    295 
    296 		return $allow_tracking == 'yes';
    297 	}
    298 
    299 	/**
    300 	 * Get the last time a tracking was sent
    301 	 *
    302 	 * @return false|string
    303 	 */
    304 	private function get_last_send() {
    305 		return get_option( $this->client->slug . '_tracking_last_send', false );
    306 	}
    307 
    308 	/**
    309 	 * Check if the notice has been dismissed or enabled
    310 	 *
    311 	 * @return boolean
    312 	 */
    313 	public function notice_dismissed() {
    314 		$hide_notice = get_option( $this->client->slug . '_tracking_notice', null );
    315 
    316 		if ( 'hide' == $hide_notice ) {
    317 			return true;
    318 		}
    319 
    320 		return false;
    321 	}
    322 
    323 	/**
    324 	 * Check if the current server is localhost
    325 	 *
    326 	 * @return boolean
    327 	 */
    328 	private function is_local_server() {
    329 
    330 		$is_local = false;
    331 
    332 		$domains_to_check = array_unique(
    333 			array(
    334 				'siteurl' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
    335 				'homeurl' => wp_parse_url( get_home_url(), PHP_URL_HOST ),
    336 			)
    337 		);
    338 
    339 		$forbidden_domains = array(
    340 			'wordpress.com',
    341 			'localhost',
    342 			'localhost.localdomain',
    343 			'127.0.0.1',
    344 			'::1',
    345 			'local.wordpress.test',         // VVV pattern.
    346 			'local.wordpress-trunk.test',   // VVV pattern.
    347 			'src.wordpress-develop.test',   // VVV pattern.
    348 			'build.wordpress-develop.test', // VVV pattern.
    349 		);
    350 
    351 		foreach ( $domains_to_check as $domain ) {
    352 			// If it's empty, just fail out.
    353 			if ( ! $domain ) {
    354 				$is_local = true;
    355 				break;
    356 			}
    357 
    358 			// None of the explicit localhosts.
    359 			if ( in_array( $domain, $forbidden_domains, true ) ) {
    360 				$is_local = true;
    361 				break;
    362 			}
    363 
    364 			// No .test or .local domains.
    365 			if ( preg_match( '#\.(test|local)$#i', $domain ) ) {
    366 				$is_local = true;
    367 				break;
    368 			}
    369 		}
    370 
    371 		return apply_filters( 'appsero_is_local', $is_local );
    372 	}
    373 
    374 	/**
    375 	 * Schedule the event weekly
    376 	 *
    377 	 * @return void
    378 	 */
    379 	private function schedule_event() {
    380 		$hook_name = $this->client->slug . '_tracker_send_event';
    381 
    382 		if ( ! wp_next_scheduled( $hook_name ) ) {
    383 			wp_schedule_event( time(), 'weekly', $hook_name );
    384 		}
    385 	}
    386 
    387 	/**
    388 	 * Clear any scheduled hook
    389 	 *
    390 	 * @return void
    391 	 */
    392 	private function clear_schedule_event() {
    393 		wp_clear_scheduled_hook( $this->client->slug . '_tracker_send_event' );
    394 	}
    395 
    396 	/**
    397 	 * Display the admin notice to users that have not opted-in or out
    398 	 *
    399 	 * @return void
    400 	 */
    401 	public function admin_notice() {
    402 
    403 		if ( $this->notice_dismissed() ) {
    404 			return;
    405 		}
    406 
    407 		if ( $this->tracking_allowed() ) {
    408 			return;
    409 		}
    410 
    411 		if ( ! current_user_can( 'manage_options' ) ) {
    412 			return;
    413 		}
    414 
    415 		// don't show tracking if a local server
    416 		if ( $this->is_local_server() ) {
    417 			return;
    418 		}
    419 
    420 		$optin_url  = add_query_arg( $this->client->slug . '_tracker_optin', 'true' );
    421 		$optout_url = add_query_arg( $this->client->slug . '_tracker_optout', 'true' );
    422 
    423 		if ( empty( $this->notice ) ) {
    424 			$notice = sprintf( $this->client->__trans( 'Want to help make <strong>%1$s</strong> even more awesome? Allow %1$s to collect non-sensitive diagnostic data and usage information.' ), $this->client->name );
    425 		} else {
    426 			$notice = $this->notice;
    427 		}
    428 
    429 		$policy_url = 'https://' . 'appsero.com/privacy-policy/';
    430 
    431 		$notice .= ' (<a class="' . $this->client->slug . '-insights-data-we-collect" href="#">' . $this->client->__trans( 'what we collect' ) . '</a>)';
    432 		$notice .= '<p class="description" style="display:none;">' . implode( ', ', $this->data_we_collect() ) . '. No sensitive data is tracked. ';
    433 		$notice .= 'We are using Appsero to collect your data. <a href="' . $policy_url . '">Learn more</a> about how Appsero collects and handle your data.</p>';
    434 
    435 		echo '<div class="updated"><p>';
    436 			echo $notice;
    437 			echo '</p><p class="submit">';
    438 			echo '&nbsp;<a href="' . esc_url( $optin_url ) . '" class="button-primary button-large">' . $this->client->__trans( 'Allow' ) . '</a>';
    439 			echo '&nbsp;<a href="' . esc_url( $optout_url ) . '" class="button-secondary button-large">' . $this->client->__trans( 'No thanks' ) . '</a>';
    440 		echo '</p></div>';
    441 
    442 		echo "<script type='text/javascript'>jQuery('." . $this->client->slug . "-insights-data-we-collect').on('click', function(e) {
    443                 e.preventDefault();
    444                 jQuery(this).parents('.updated').find('p.description').slideToggle('fast');
    445             });
    446             </script>
    447         ";
    448 
    449 	}
    450 
    451 	/**
    452 	 * handle the optin/optout
    453 	 *
    454 	 * @return void
    455 	 */
    456 	public function handle_optin_optout() {
    457 
    458 		if ( isset( $_GET[ $this->client->slug . '_tracker_optin' ] ) && $_GET[ $this->client->slug . '_tracker_optin' ] == 'true' ) {
    459 			$this->optin();
    460 
    461 			wp_redirect( remove_query_arg( $this->client->slug . '_tracker_optin' ) );
    462 			exit;
    463 		}
    464 
    465 		if ( isset( $_GET[ $this->client->slug . '_tracker_optout' ] ) && $_GET[ $this->client->slug . '_tracker_optout' ] == 'true' ) {
    466 			$this->optout();
    467 
    468 			wp_redirect( remove_query_arg( $this->client->slug . '_tracker_optout' ) );
    469 			exit;
    470 		}
    471 	}
    472 
    473 	/**
    474 	 * Tracking optin
    475 	 *
    476 	 * @return void
    477 	 */
    478 	public function optin() {
    479 		update_option( $this->client->slug . '_allow_tracking', 'yes' );
    480 		update_option( $this->client->slug . '_tracking_notice', 'hide' );
    481 
    482 		$this->clear_schedule_event();
    483 		$this->schedule_event();
    484 		$this->send_tracking_data();
    485 	}
    486 
    487 	/**
    488 	 * Optout from tracking
    489 	 *
    490 	 * @return void
    491 	 */
    492 	public function optout() {
    493 		update_option( $this->client->slug . '_allow_tracking', 'no' );
    494 		update_option( $this->client->slug . '_tracking_notice', 'hide' );
    495 
    496 		$this->send_tracking_skipped_request();
    497 
    498 		$this->clear_schedule_event();
    499 	}
    500 
    501 	/**
    502 	 * Get the number of post counts
    503 	 *
    504 	 * @param  string $post_type
    505 	 *
    506 	 * @return integer
    507 	 */
    508 	public function get_post_count( $post_type ) {
    509 		global $wpdb;
    510 
    511 		return (int) $wpdb->get_var( "SELECT count(ID) FROM $wpdb->posts WHERE post_type = '$post_type' and post_status = 'publish'" );
    512 	}
    513 
    514 	/**
    515 	 * Get server related info.
    516 	 *
    517 	 * @return array
    518 	 */
    519 	private static function get_server_info() {
    520 		global $wpdb;
    521 
    522 		$server_data = array();
    523 
    524 		if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
    525 			$server_data['software'] = $_SERVER['SERVER_SOFTWARE'];
    526 		}
    527 
    528 		if ( function_exists( 'phpversion' ) ) {
    529 			$server_data['php_version'] = phpversion();
    530 		}
    531 
    532 		$server_data['mysql_version'] = $wpdb->db_version();
    533 
    534 		$server_data['php_max_upload_size']  = size_format( wp_max_upload_size() );
    535 		$server_data['php_default_timezone'] = date_default_timezone_get();
    536 		$server_data['php_soap']             = class_exists( 'SoapClient' ) ? 'Yes' : 'No';
    537 		$server_data['php_fsockopen']        = function_exists( 'fsockopen' ) ? 'Yes' : 'No';
    538 		$server_data['php_curl']             = function_exists( 'curl_init' ) ? 'Yes' : 'No';
    539 
    540 		return $server_data;
    541 	}
    542 
    543 	/**
    544 	 * Get WordPress related data.
    545 	 *
    546 	 * @return array
    547 	 */
    548 	private function get_wp_info() {
    549 		$wp_data = array();
    550 
    551 		$wp_data['memory_limit'] = WP_MEMORY_LIMIT;
    552 		$wp_data['debug_mode']   = ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ? 'Yes' : 'No';
    553 		$wp_data['locale']       = get_locale();
    554 		$wp_data['version']      = get_bloginfo( 'version' );
    555 		$wp_data['multisite']    = is_multisite() ? 'Yes' : 'No';
    556 		$wp_data['theme_slug']   = get_stylesheet();
    557 
    558 		$theme = wp_get_theme( $wp_data['theme_slug'] );
    559 
    560 		$wp_data['theme_name']    = $theme->get( 'Name' );
    561 		$wp_data['theme_version'] = $theme->get( 'Version' );
    562 		$wp_data['theme_uri']     = $theme->get( 'ThemeURI' );
    563 		$wp_data['theme_author']  = $theme->get( 'Author' );
    564 
    565 		return $wp_data;
    566 	}
    567 
    568 	/**
    569 	 * Get the list of active and inactive plugins
    570 	 *
    571 	 * @return array
    572 	 */
    573 	private function get_all_plugins() {
    574 		// Ensure get_plugins function is loaded
    575 		if ( ! function_exists( 'get_plugins' ) ) {
    576 			require_once ABSPATH . '/wp-admin/includes/plugin.php';
    577 		}
    578 
    579 		$plugins             = get_plugins();
    580 		$active_plugins_keys = get_option( 'active_plugins', array() );
    581 		$active_plugins      = array();
    582 
    583 		foreach ( $plugins as $k => $v ) {
    584 			// Take care of formatting the data how we want it.
    585 			$formatted         = array();
    586 			$formatted['name'] = strip_tags( $v['Name'] );
    587 
    588 			if ( isset( $v['Version'] ) ) {
    589 				$formatted['version'] = strip_tags( $v['Version'] );
    590 			}
    591 
    592 			if ( isset( $v['Author'] ) ) {
    593 				$formatted['author'] = strip_tags( $v['Author'] );
    594 			}
    595 
    596 			if ( isset( $v['Network'] ) ) {
    597 				$formatted['network'] = strip_tags( $v['Network'] );
    598 			}
    599 
    600 			if ( isset( $v['PluginURI'] ) ) {
    601 				$formatted['plugin_uri'] = strip_tags( $v['PluginURI'] );
    602 			}
    603 
    604 			if ( in_array( $k, $active_plugins_keys ) ) {
    605 				// Remove active plugins from list so we can show active and inactive separately
    606 				unset( $plugins[ $k ] );
    607 				$active_plugins[ $k ] = $formatted;
    608 			} else {
    609 				$plugins[ $k ] = $formatted;
    610 			}
    611 		}
    612 
    613 		return array(
    614 			'active_plugins'   => $active_plugins,
    615 			'inactive_plugins' => $plugins,
    616 		);
    617 	}
    618 
    619 	/**
    620 	 * Get user totals based on user role.
    621 	 *
    622 	 * @return array
    623 	 */
    624 	public function get_user_counts() {
    625 		$user_count          = array();
    626 		$user_count_data     = count_users();
    627 		$user_count['total'] = $user_count_data['total_users'];
    628 
    629 		// Get user count based on user role
    630 		foreach ( $user_count_data['avail_roles'] as $role => $count ) {
    631 			if ( ! $count ) {
    632 				continue;
    633 			}
    634 			$user_count[ $role ] = $count;
    635 		}
    636 
    637 		return $user_count;
    638 	}
    639 
    640 	/**
    641 	 * Add weekly cron schedule
    642 	 *
    643 	 * @param array $schedules
    644 	 *
    645 	 * @return array
    646 	 */
    647 	public function add_weekly_schedule( $schedules ) {
    648 
    649 		$schedules['weekly'] = array(
    650 			'interval' => DAY_IN_SECONDS * 7,
    651 			'display'  => 'Once Weekly',
    652 		);
    653 
    654 		return $schedules;
    655 	}
    656 
    657 	/**
    658 	 * Plugin activation hook
    659 	 *
    660 	 * @return void
    661 	 */
    662 	public function activate_plugin() {
    663 		$allowed = get_option( $this->client->slug . '_allow_tracking', 'no' );
    664 
    665 		// if it wasn't allowed before, do nothing
    666 		if ( 'yes' !== $allowed ) {
    667 			return;
    668 		}
    669 
    670 		// re-schedule and delete the last sent time so we could force send again
    671 		$hook_name = $this->client->slug . '_tracker_send_event';
    672 		if ( ! wp_next_scheduled( $hook_name ) ) {
    673 			wp_schedule_event( time(), 'weekly', $hook_name );
    674 		}
    675 
    676 		delete_option( $this->client->slug . '_tracking_last_send' );
    677 
    678 		$this->send_tracking_data( true );
    679 	}
    680 
    681 	/**
    682 	 * Clear our options upon deactivation
    683 	 *
    684 	 * @return void
    685 	 */
    686 	public function deactivation_cleanup() {
    687 		$this->clear_schedule_event();
    688 
    689 		if ( 'theme' == $this->client->type ) {
    690 			delete_option( $this->client->slug . '_tracking_last_send' );
    691 			delete_option( $this->client->slug . '_allow_tracking' );
    692 		}
    693 
    694 		delete_option( $this->client->slug . '_tracking_notice' );
    695 	}
    696 
    697 	/**
    698 	 * Hook into action links and modify the deactivate link
    699 	 *
    700 	 * @param  array $links
    701 	 *
    702 	 * @return array
    703 	 */
    704 	public function plugin_action_links( $links ) {
    705 
    706 		if ( array_key_exists( 'deactivate', $links ) ) {
    707 			$links['deactivate'] = str_replace( '<a', '<a class="' . $this->client->slug . '-deactivate-link"', $links['deactivate'] );
    708 		}
    709 
    710 		return $links;
    711 	}
    712 
    713 	/**
    714 	 * Plugin uninstall reasons
    715 	 *
    716 	 * @return array
    717 	 */
    718 	private function get_uninstall_reasons() {
    719 		$reasons = array(
    720 			array(
    721 				'id'          => 'could-not-understand',
    722 				'text'        => $this->client->__trans( 'I couldn\'t understand how to make it work' ),
    723 				'type'        => 'textarea',
    724 				'placeholder' => $this->client->__trans( 'Would you like us to assist you?' )
    725 			),
    726 			array(
    727 				'id'          => 'found-better-plugin',
    728 				'text'        => $this->client->__trans( 'I found a better plugin' ),
    729 				'type'        => 'text',
    730 				'placeholder' => $this->client->__trans( 'Which plugin?' )
    731 			),
    732 			array(
    733 				'id'          => 'not-have-that-feature',
    734 				'text'        => $this->client->__trans( 'The plugin is great, but I need specific feature that you don\'t support' ),
    735 				'type'        => 'textarea',
    736 				'placeholder' => $this->client->__trans( 'Could you tell us more about that feature?' )
    737 			),
    738 			array(
    739 				'id'          => 'is-not-working',
    740 				'text'        => $this->client->__trans( 'The plugin is not working' ),
    741 				'type'        => 'textarea',
    742 				'placeholder' => $this->client->__trans( 'Could you tell us a bit more whats not working?' )
    743 			),
    744 			array(
    745 				'id'          => 'looking-for-other',
    746 				'text'        => $this->client->__trans( 'It\'s not what I was looking for' ),
    747 				'type'        => '',
    748 				'placeholder' => ''
    749 			),
    750 			array(
    751 				'id'          => 'did-not-work-as-expected',
    752 				'text'        => $this->client->__trans( 'The plugin didn\'t work as expected' ),
    753 				'type'        => 'textarea',
    754 				'placeholder' => $this->client->__trans( 'What did you expect?' )
    755 			),
    756 			array(
    757 				'id'          => 'other',
    758 				'text'        => $this->client->__trans( 'Other' ),
    759 				'type'        => 'textarea',
    760 				'placeholder' => $this->client->__trans( 'Could you tell us a bit more?' )
    761 			),
    762 		);
    763 
    764 		return $reasons;
    765 	}
    766 
    767 	/**
    768 	 * Plugin deactivation uninstall reason submission
    769 	 *
    770 	 * @return void
    771 	 */
    772 	public function uninstall_reason_submission() {
    773 
    774 		if ( ! isset( $_POST['reason_id'] ) ) {
    775 			wp_send_json_error();
    776 		}
    777 
    778 		$data                = $this->get_tracking_data();
    779 		$data['reason_id']   = sanitize_text_field( $_POST['reason_id'] );
    780 		$data['reason_info'] = isset( $_REQUEST['reason_info'] ) ? trim( stripslashes( $_REQUEST['reason_info'] ) ) : '';
    781 
    782 		$this->client->send_request( $data, 'deactivate' );
    783 
    784 		wp_send_json_success();
    785 	}
    786 
    787 	/**
    788 	 * Handle the plugin deactivation feedback
    789 	 *
    790 	 * @return void
    791 	 */
    792 	public function deactivate_scripts() {
    793 		global $pagenow;
    794 
    795 		if ( 'plugins.php' != $pagenow ) {
    796 			return;
    797 		}
    798 
    799 		$reasons = $this->get_uninstall_reasons();
    800 		?>
    801 
    802 		<div class="wd-dr-modal" id="<?php echo $this->client->slug; ?>-wd-dr-modal">
    803 			<div class="wd-dr-modal-wrap">
    804 				<div class="wd-dr-modal-header">
    805 					<h3><?php $this->client->_etrans( 'If you have a moment, please let us know why you are deactivating:' ); ?></h3>
    806 				</div>
    807 
    808 				<div class="wd-dr-modal-body">
    809 					<ul class="reasons">
    810 						<?php foreach ($reasons as $reason) { ?>
    811 							<li data-type="<?php echo esc_attr( $reason['type'] ); ?>" data-placeholder="<?php echo esc_attr( $reason['placeholder'] ); ?>">
    812 								<label><input type="radio" name="selected-reason" value="<?php echo $reason['id']; ?>"> <?php echo $reason['text']; ?></label>
    813 							</li>
    814 						<?php } ?>
    815 					</ul>
    816 					<p class="wd-dr-modal-reasons-bottom">
    817 						<?php
    818 						echo sprintf(
    819 							$this->client->__trans( 'We use this information to troubleshoot problems and make product improvements. <a href="%1$s" target="_blank">Learn more</a> about how we handle you data.' ),
    820 							esc_url( 'https://redux.io/privacy?utm_source=plugin&utm_medium=appsero&utm_campaign=deactivate' )
    821 						);
    822 						?>
    823 					</p>
    824 				</div>
    825 
    826 				<div class="wd-dr-modal-footer">
    827 					<a href="#" class="dont-bother-me"><?php $this->client->_etrans( "I rather wouldn't say" ); ?></a>
    828 					<button class="button-secondary"><?php $this->client->_etrans( 'Submit & Deactivate' ); ?></button>
    829 					<button class="button-primary"><?php $this->client->_etrans( 'Cancel' ); ?></button>
    830 				</div>
    831 			</div>
    832 		</div>
    833 
    834 		<style type="text/css">
    835 			.wd-dr-modal {
    836 				position: fixed;
    837 				z-index: 99999;
    838 				top: 0;
    839 				right: 0;
    840 				bottom: 0;
    841 				left: 0;
    842 				background: rgba(0,0,0,0.5);
    843 				display: none;
    844 			}
    845 
    846 			.wd-dr-modal.modal-active {
    847 				display: block;
    848 			}
    849 
    850 			.wd-dr-modal-wrap {
    851 				width: 475px;
    852 				position: relative;
    853 				margin: 10% auto;
    854 				background: #fff;
    855 			}
    856 
    857 			.wd-dr-modal-header {
    858 				border-bottom: 1px solid #eee;
    859 				padding: 8px 20px;
    860 			}
    861 
    862 			.wd-dr-modal-header h3 {
    863 				line-height: 150%;
    864 				margin: 0;
    865 			}
    866 
    867 			.wd-dr-modal-body {
    868 				padding: 5px 20px 20px 20px;
    869 			}
    870 
    871 			.wd-dr-modal-body .reason-input {
    872 				margin-top: 5px;
    873 				margin-left: 20px;
    874 			}
    875 			.wd-dr-modal-footer {
    876 				border-top: 1px solid #eee;
    877 				padding: 12px 20px;
    878 				text-align: right;
    879 			}
    880 			.wd-dr-modal-reasons-bottom {
    881 				margin: 15px 0 0 0;
    882 			}
    883 		</style>
    884 
    885 		<script type="text/javascript">
    886 			(function($) {
    887 				$(function() {
    888 					var modal = $( '#<?php echo $this->client->slug; ?>-wd-dr-modal' );
    889 					var deactivateLink = '';
    890 
    891 					$( '#the-list' ).on('click', 'a.<?php echo $this->client->slug; ?>-deactivate-link', function(e) {
    892 						e.preventDefault();
    893 
    894 						modal.addClass('modal-active');
    895 						deactivateLink = $(this).attr('href');
    896 						modal.find('a.dont-bother-me').attr('href', deactivateLink).css('float', 'left');
    897 					});
    898 
    899 					modal.on('click', 'button.button-primary', function(e) {
    900 						e.preventDefault();
    901 
    902 						modal.removeClass('modal-active');
    903 					});
    904 
    905 					modal.on('click', 'input[type="radio"]', function () {
    906 						var parent = $(this).parents('li:first');
    907 
    908 						modal.find('.reason-input').remove();
    909 
    910 						var inputType = parent.data('type'),
    911 							inputPlaceholder = parent.data('placeholder'),
    912 							reasonInputHtml = '<div class="reason-input">' + ( ( 'text' === inputType ) ? '<input type="text" size="40" />' : '<textarea rows="5" cols="45"></textarea>' ) + '</div>';
    913 
    914 						if ( inputType !== '' ) {
    915 							parent.append( $(reasonInputHtml) );
    916 							parent.find('input, textarea').attr('placeholder', inputPlaceholder).focus();
    917 						}
    918 					});
    919 
    920 					modal.on('click', 'button.button-secondary', function(e) {
    921 						e.preventDefault();
    922 
    923 						var button = $(this);
    924 
    925 						if ( button.hasClass('disabled') ) {
    926 							return;
    927 						}
    928 
    929 						var $radio = $( 'input[type="radio"]:checked', modal );
    930 
    931 						var $selected_reason = $radio.parents('li:first'),
    932 							$input = $selected_reason.find('textarea, input[type="text"]');
    933 
    934 						$.ajax({
    935 							url: ajaxurl,
    936 							type: 'POST',
    937 							data: {
    938 								action: '<?php echo $this->client->slug; ?>_submit-uninstall-reason',
    939 								reason_id: ( 0 === $radio.length ) ? 'none' : $radio.val(),
    940 								reason_info: ( 0 !== $input.length ) ? $input.val().trim() : ''
    941 							},
    942 							beforeSend: function() {
    943 								button.addClass('disabled');
    944 								button.text('Processing...');
    945 							},
    946 							complete: function() {
    947 								window.location.href = deactivateLink;
    948 							}
    949 						});
    950 					});
    951 				});
    952 			}(jQuery));
    953 		</script>
    954 
    955 		<?php
    956 	}
    957 
    958 	/**
    959 	 * Run after theme deactivated
    960 	 *
    961 	 * @param  string $new_name
    962 	 * @param  object $new_theme
    963 	 * @param  object $old_theme
    964 	 * @return void
    965 	 */
    966 	public function theme_deactivated( $new_name, $new_theme, $old_theme ) {
    967 		// Make sure this is appsero theme
    968 		if ( $old_theme->get_template() == $this->client->slug ) {
    969 			$this->client->send_request( $this->get_tracking_data(), 'deactivate' );
    970 		}
    971 	}
    972 
    973 	/**
    974 	 * Get user IP Address
    975 	 */
    976 	private function get_user_ip_address() {
    977 		$response = wp_remote_get( 'https://icanhazip.com/' );
    978 
    979 		if ( is_wp_error( $response ) ) {
    980 			return '';
    981 		}
    982 
    983 		$ip = trim( wp_remote_retrieve_body( $response ) );
    984 
    985 		if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
    986 			return '';
    987 		}
    988 
    989 		return $ip;
    990 	}
    991 
    992 	/**
    993 	 * Get site name
    994 	 */
    995 	private function get_site_name() {
    996 		$site_name = get_bloginfo( 'name' );
    997 
    998 		if ( empty( $site_name ) ) {
    999 			$site_name = get_bloginfo( 'description' );
   1000 			$site_name = wp_trim_words( $site_name, 3, '' );
   1001 		}
   1002 
   1003 		if ( empty( $site_name ) ) {
   1004 			$site_name = esc_url( home_url() );
   1005 		}
   1006 
   1007 		return $site_name;
   1008 	}
   1009 
   1010 	/**
   1011 	 * Send request to appsero if user skip to send tracking data
   1012 	 */
   1013 	private function send_tracking_skipped_request() {
   1014 		$skipped = get_option( $this->client->slug . '_tracking_skipped' );
   1015 
   1016 		$data = [
   1017 			'hash'               => $this->client->hash,
   1018 			'previously_skipped' => false,
   1019 		];
   1020 
   1021 		if ( $skipped === 'yes' ) {
   1022 			$data['previously_skipped'] = true;
   1023 		} else {
   1024 			update_option( $this->client->slug . '_tracking_skipped', 'yes' );
   1025 		}
   1026 
   1027 		$this->client->send_request( $data, 'tracking-skipped' );
   1028 	}
   1029 }