PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.10.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.10.0
4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Analytics / AnalyticsEventDto.php
wp-staging / Framework / Analytics Last commit date
Actions 4 days ago AnalyticsCleanup.php 4 years ago AnalyticsConsent.php 1 month ago AnalyticsEventDto.php 4 days ago AnalyticsEventWithTimeDto.php 10 months ago AnalyticsGenericEventHandler.php 4 days ago AnalyticsSender.php 10 months ago WithAnalyticsAPI.php 10 months ago WithAnalyticsSiteInfo.php 6 months ago
AnalyticsEventDto.php
266 lines
1 <?php
2
3 namespace WPStaging\Framework\Analytics;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Framework\Experiments\ExperimentManager;
7 use WPStaging\Framework\Filesystem\DebugLogReader;
8
9 abstract class AnalyticsEventDto implements \JsonSerializable
10 {
11 use WithAnalyticsSiteInfo;
12
13 /** @var string Which action is triggering this Analytics, eg: backup creation, staging push, etc */
14 protected $event;
15
16 /** @var string The Job ID or similar. */
17 protected $job_identifier;
18
19 /** @var string A unique hash that prevents duplicated events in a scenario where this database is restored in another site and the events are sent again. */
20 protected $event_hash;
21
22 /** @var bool Whether this job has naturally finished. Eg: Came to the expected ending. */
23 protected $is_finished = false;
24
25 /** @var bool Whether this job has started but did not finish during an expected time-frame. */
26 protected $is_stale = false;
27
28 /** @var bool Whether this job terminated in error. */
29 protected $is_error = false;
30
31 /** @var bool Whether this job has been cancelled by the user. */
32 protected $is_cancelled = false;
33
34 /** @var bool Whether the requirement check has failed for this job. */
35 protected $is_requirement_check_fail = false;
36
37 /** @var string The reason for the requirement check fail, if so. */
38 protected $requirement_fail_reason = '';
39
40 /** @var string The error message as shown in the front-end, if event terminated in error. */
41 protected $error_message;
42
43 /** @var string The last lines from the debug log file. */
44 protected $last_debug_logs;
45
46 /** @var bool An internal flag to check if this event is ready to be sent. */
47 protected $ready_to_send = false;
48
49 /** @var int A UNIX timestamp for when this event started. */
50 protected $start_time;
51
52 /** @var int A UNIX timestamp for when this event ended. */
53 protected $end_time;
54
55 /** @var int The duration in seconds between the start and end of the event. */
56 protected $duration;
57
58 /** @var array A collection of generic site information. */
59 protected $site_info;
60
61 /** @var string|null */
62 protected $experiment;
63
64 /** @var string|null */
65 protected $variant;
66
67 public function __construct()
68 {
69 $this->event = $this->getEventAction();
70 $this->site_info = $this->getAnalyticsSiteInfo();
71
72 // Stamped here so a backup or staging site created days after the
73 // onboarding is still attributable to the variant that led the user there.
74 $attribution = WPStaging::make(ExperimentManager::class)->getAttribution();
75
76 $this->experiment = isset($attribution['experiment']) ? $attribution['experiment'] : null;
77 $this->variant = isset($attribution['variant']) ? $attribution['variant'] : null;
78 }
79
80 #[\ReturnTypeWillChange]
81 public function jsonSerialize()
82 {
83 return get_object_vars($this);
84 }
85
86 /**
87 * @return string The name of this analytics event.
88 */
89 abstract public function getEventAction();
90
91 public function enqueueStartEvent($jobId, $eventData)
92 {
93 $this->job_identifier = $jobId;
94 $this->start_time = time();
95 $this->event_hash = microtime(true) . rand();
96
97 try {
98 $this->saveEvent($jobId, $this);
99 } catch (\Exception $e) {
100 \WPStaging\functions\debug_log("WP STAGING: Could not register start event analytics data for job ID $jobId.", 'debug', false);
101 }
102 }
103
104 public function enqueueFinishEvent($jobId, $eventData, $eventOverrides = [])
105 {
106 try {
107 $event = $this->getEventByJobId($jobId);
108 } catch (\Exception $e) {
109 \WPStaging\functions\debug_log("WP STAGING: Could not register finish event analytics data for job ID $jobId", 'debug', false);
110
111 return;
112 }
113
114 $event->is_finished = true;
115 $event->end_time = time();
116 $event->duration = time() - $event->start_time;
117 $event->ready_to_send = true;
118
119 // Allow concrete instances of this abstract class to modify this event.
120 foreach ($eventOverrides as $key => $value) {
121 $event->$key = $value;
122 }
123
124 try {
125 $this->saveEvent($jobId, $event);
126 } catch (\Exception $e) {
127 \WPStaging\functions\debug_log("WP STAGING: Could not save finish event analytics data for job ID $jobId.", 'debug', false);
128 }
129 }
130
131 /**
132 * Cancel event is static as it's a generic event not related to any specific type of event.
133 */
134 public static function enqueueCancelEvent($jobId)
135 {
136 try {
137 $event = static::getEventByJobId($jobId);
138 } catch (\Exception $e) {
139 \WPStaging\functions\debug_log("WP STAGING: Could not register cancel event analytics data for job ID $jobId", 'debug', false);
140
141 return;
142 }
143
144 // Early bail: Already cancelled
145 if ($event->is_cancelled) {
146 return;
147 }
148
149 /*
150 * The Cancel routine may be called automatically when an error occurs
151 * to perform cleanup tasks, so let's not register the cancel event
152 * if this event is being triggered by a job that already has an error.
153 */
154 if ($event->is_error) {
155 return;
156 }
157
158 $event->is_finished = false;
159 $event->is_cancelled = true;
160 $event->end_time = time();
161 $event->duration = time() - $event->start_time;
162 $event->ready_to_send = true;
163
164 try {
165 static::saveEvent($jobId, $event);
166 } catch (\Exception $e) {
167 \WPStaging\functions\debug_log("WP STAGING: Could not save cancel event analytics data for job ID $jobId.", 'debug', false);
168 }
169 }
170
171 /**
172 * Error event is static as it's a generic event not related to any specific type of event.
173 */
174 public static function enqueueErrorEvent($jobId, $errorMessage)
175 {
176 try {
177 $event = static::getEventByJobId($jobId);
178 } catch (\Exception $e) {
179 \WPStaging\functions\debug_log("WP STAGING: Could not register cancel event analytics data for job ID $jobId", 'debug', false);
180
181 return;
182 }
183
184 $lastDebugLogErrors = WPStaging::make(DebugLogReader::class)->getLastLogEntries(8 * KB_IN_BYTES);
185
186 $event->is_finished = false;
187 $event->is_error = true;
188 $event->error_message = $errorMessage;
189 $event->last_debug_logs = $lastDebugLogErrors;
190 $event->end_time = time();
191 $event->duration = time() - $event->start_time;
192 $event->ready_to_send = true;
193
194 try {
195 static::saveEvent($jobId, $event);
196 } catch (\Exception $e) {
197 \WPStaging\functions\debug_log("WP STAGING: Could not save cancel event analytics data for job ID $jobId.", 'debug', false);
198 }
199 }
200
201 protected static function getEventByJobId($jobId)
202 {
203 if (!$event = get_option("wpstg_analytics_event_$jobId")) {
204 throw new \UnexpectedValueException();
205 }
206
207 $event = json_decode($event);
208
209 if (empty($event) || !is_object($event)) {
210 throw new \UnexpectedValueException();
211 }
212
213 return $event;
214 }
215
216 protected static function saveEvent($jobId, $event)
217 {
218 $event = wp_json_encode($event);
219
220 if (!update_option("wpstg_analytics_event_$jobId", $event, false)) {
221 throw new \UnexpectedValueException();
222 }
223 }
224
225 /**
226 * @param mixed $eventData
227 * @param string $key
228 * @param mixed $default
229 * @return mixed
230 */
231 protected function getEventDataValue($eventData, string $key, $default = null)
232 {
233 if (is_array($eventData) && array_key_exists($key, $eventData)) {
234 return $eventData[$key];
235 }
236
237 if (!is_object($eventData)) {
238 return $default;
239 }
240
241 $publicProperties = get_object_vars($eventData);
242 if (array_key_exists($key, $publicProperties)) {
243 return $publicProperties[$key];
244 }
245
246 $getter = 'get' . ucfirst($key);
247 if (method_exists($eventData, $getter)) {
248 return $eventData->{$getter}();
249 }
250
251 $isser = 'is' . ucfirst($key);
252 if (method_exists($eventData, $isser)) {
253 return $eventData->{$isser}();
254 }
255
256 return $default;
257 }
258
259 protected function getStagingEngine($eventData): string
260 {
261 $engine = $this->getEventDataValue($eventData, 'stagingEngine', 'legacy');
262
263 return in_array($engine, ['legacy', 'next_gen'], true) ? $engine : 'legacy';
264 }
265 }
266