File: /var/www/vhosts/amphoragames.com/httpdocs/wp-content/plugins/backwpup/src/Tracking/Tracking.php
<?php
declare(strict_types=1);
namespace WPMedia\BackWPup\Tracking;
use WPMedia\BackWPup\Adapters\OptionAdapter;
use WPMedia\Mixpanel\Optin;
use WPMedia\Mixpanel\TrackingPlugin as MixpanelTracking;
class Tracking {
/**
* Optin instance.
*
* @var Optin
*/
private $optin;
/**
* Mixpanel Tracking instance.
*
* @var MixpanelTracking
*/
private $mixpanel;
/**
* Option Adapter instance.
*
* @var OptionAdapter
*/
private $option_adapter;
/**
* Tracking constructor.
*
* @param Optin $optin Optin instance.
* @param MixpanelTracking $mixpanel Mixpanel Tracking instance.
* @param OptionAdapter $option_adapter Option Adapter instance.
*/
public function __construct( Optin $optin, MixpanelTracking $mixpanel, OptionAdapter $option_adapter ) {
$this->optin = $optin;
$this->mixpanel = $mixpanel;
$this->option_adapter = $option_adapter;
}
/**
* Update the Mixpanel opt-in setting.
*
* @return void
*/
public function update_setting(): void {
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'backwpup_page' ) ) {
return;
}
if ( get_site_option( 'backwpup_onboarding', false ) ) {
return;
}
$value = isset( $_POST['mixpanel'] ) ? 1 : 0;
if ( 0 === $value ) {
if ( $this->optin->is_enabled() ) {
$this->optin->disable();
}
return;
}
if ( 1 === $value ) {
$this->optin->enable();
}
}
/**
* Track an opt-in change event.
*
* @param bool $optin The new opt-in value.
*
* @return void
*/
public function track_optin_change( $optin ): void {
$user = wp_get_current_user();
$this->mixpanel->identify( $user->user_email );
$this->mixpanel->track_optin( $optin );
}
/**
* Track the beta opt-in change event.
*
* @param int $optin The new opt-in value.
*
* @return void
*/
public function track_beta_optin_change( $optin ): void {
if ( ! $this->optin->is_enabled() ) {
return;
}
$user = wp_get_current_user();
$this->mixpanel->identify( $user->user_email );
$this->mixpanel->track(
'WordPress Plugin Beta Opt-in Changed',
[
'context' => 'wp_plugin',
'opt_in_status' => (bool) $optin,
]
);
}
/**
* Track the addition of a new job.
*
* @param array $job The job data.
*
* @return void
*/
public function track_add_job( $job ) {
if ( ! $this->optin->is_enabled() ) {
return;
}
$user = wp_get_current_user();
$this->mixpanel->identify( $user->user_email );
$this->mixpanel->track(
'Scheduled Backup Job Created',
$this->get_event_properties( $job, true )
);
}
/**
* Track the deletion of a job.
*
* @param int $job_id The ID of the job to delete.
*
* @return void
*/
public function track_delete_job( $job_id ) {
if ( ! $this->optin->is_enabled() ) {
return;
}
$user = wp_get_current_user();
$this->mixpanel->identify( $user->user_email );
$job = $this->option_adapter->get_job( $job_id );
$this->mixpanel->track(
'Scheduled Backup Job Deleted',
$this->get_event_properties( $job )
);
}
/**
* Track the start of a job.
*
* @param array $job The Job data.
*
* @return void
*/
public function track_start_job( array $job ): void {
if ( ! $this->optin->can_track() ) {
return;
}
$user = wp_get_current_user();
$email = $user->user_email ?? $job['mailaddresslog'];
$this->mixpanel->identify( $email );
// @todo Refactor it when the Mixpanel library supports system capabilities.
$this->with_system_cap(
function () use ( $job ) {
$this->mixpanel->track(
'Scheduled Backup Job Started',
$this->get_backup_event_properties( $job ),
'bwu_mixpanel_send_event'
);
}
);
}
/**
* Track end of job.
*
* @param int $job_id The job id.
* @param array $job_details The status of the job.
*
* @return void
*/
public function track_end_job( $job_id, array $job_details ): void {
if ( ! $this->optin->can_track() ) {
return;
}
$job = $this->option_adapter->get_job( $job_id );
$user = wp_get_current_user();
$email = $user->user_email ?? $job['mailaddresslog'];
$this->mixpanel->identify( $email );
$status = true;
$properties = $this->get_backup_event_properties( $job, true );
foreach ( $job_details as $job ) {
$properties[ 'storage_' . $job['storage'] ] = [
'storage' => $job['storage'],
'status' => $job['status'],
'error_code' => $job['error_code'],
'error_message' => $job['error_message'],
];
if ( 'completed' !== $job['status'] ) {
$status = false;
}
}
$properties['job_completed'] = $status;
// @todo Refactor it when the Mixpanel library supports system capabilities.
$this->with_system_cap(
function () use ( $properties ) {
$this->mixpanel->track(
'Scheduled Backup Job Ended',
$properties,
'bwu_mixpanel_send_event'
);
}
);
}
/**
* Get backup event properties.
*
* @param array $job The job data.
* @param bool $included_job_completion Include job completion info.
*
* @return array
*/
private function get_backup_event_properties( array $job, bool $included_job_completion = false ): array {
$backup_trigger = $job['activetype'];
$legacy_job = ! empty( $job['legacy'] );
$properties = [
'backup_trigger' => $backup_trigger,
'legacy_job' => $legacy_job,
];
if ( $included_job_completion ) {
$properties['duration'] = $job['lastruntime'];
}
return array_merge( $this->get_event_properties( $job, false ), $properties );
}
/**
* Get the properties for the event.
*
* @param array $job The job data.
* @param bool $exclude_scheduled_info Exclude some scheduled info.
*
* @return array
*/
private function get_event_properties( array $job, bool $exclude_scheduled_info = false ): array {
if ( empty( $job['archiveformat'] ) ) {
$job['archiveformat'] = get_site_option( 'backwpup_archiveformat', '.tar' );
}
$properties = [
'context' => 'wp_plugin',
'job_id' => $job['jobid'],
'storage' => $job['destinations'],
'format' => $job['archiveformat'],
'manually_excluded_data' => $this->has_manual_exclude( $job ),
];
if ( ! $exclude_scheduled_info ) {
$properties['frequency'] = $job['frequency'];
$properties['type'] = $job['type'];
}
return $properties;
}
/**
* Check if the job has manual exclusions.
*
* @param array $job The job data.
*
* @return bool
*/
private function has_manual_exclude( array $job ): bool {
$defaults = $this->option_adapter->defaults_job();
$job_types = $job['type'] ?? [];
// Define exclusions by backup type.
$database_exclusions = [
'dbdumpexclude',
];
$file_exclusions = [
'backuprootexcludedirs',
'backupcontentexcludedirs',
'backuppluginsexcludedirs',
'backupthemesexcludedirs',
'backupuploadsexcludedirs',
'fileexclude',
'backuproot',
'backupuploads',
'backupthemes',
'backupplugins',
'backupcontent',
'backupspecialfiles',
];
$exclusions = [];
// Only check database exclusions if DBDUMP is in job types.
if ( in_array( 'DBDUMP', $job_types, true ) ) {
$exclusions = array_merge( $exclusions, $database_exclusions );
}
// Only check file exclusions if FILE is in job types.
if ( in_array( 'FILE', $job_types, true ) ) {
$exclusions = array_merge( $exclusions, $file_exclusions );
}
foreach ( $exclusions as $exclusion ) {
if ( is_array( $job[ $exclusion ] ) && is_array( $defaults[ $exclusion ] ) ) {
if ( ! empty( array_diff( $job[ $exclusion ], $defaults[ $exclusion ] ) ) ) {
return true;
}
} elseif ( $job[ $exclusion ] !== $defaults[ $exclusion ] ) {
return true;
}
}
return false;
}
/**
* Handle the AJAX request for the opt-in notice.
*
* @return void
*/
public function notice_optin_callback(): void {
check_ajax_referer( 'backwpup_analytics_optin' );
if ( ! isset( $_POST['value'] ) ) {
wp_send_json_error( __( 'Missing opt-in value', 'backwpup' ) );
}
$value = sanitize_text_field( wp_unslash( $_POST['value'] ) );
if ( 'no' === $value ) {
wp_send_json_success();
}
$this->optin->enable();
wp_send_json_success();
}
/**
* Run a callback with the system capability to send events.
* The system capability is only granted to the callback and removed afterwards.
* The capability is only granted if the current capability check is for sending events with no user (0) and to the capability bwu_mixpanel_send_event.
* The capability is granted via the 'user_has_cap' filter.
* This is to allow sending events from contexts where no user is logged in, such as cron jobs.
*
* @param callable $cb The callback to run.
*
* @return void
*/
private function with_system_cap( callable $cb ) {
$grant_cap = static function ( $allcaps, $caps, $args ) {
if ( isset( $args[0], $args[1] ) && 'bwu_mixpanel_send_event' === $args[0] && 0 === (int) $args[1] ) {
$allcaps['bwu_mixpanel_send_event'] = true;
}
return $allcaps;
};
add_filter( 'user_has_cap', $grant_cap, 10, 3 );
try {
$cb();
} finally {
remove_filter( 'user_has_cap', $grant_cap, 10 );
}
}
}