helpscout-beacon.php
12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
<?php
namespace Yoast\WP\SEO\Integrations\Admin;
use WPSEO_Addon_Manager;
use WPSEO_Admin_Asset_Manager;
use WPSEO_Tracking_Server_Data;
use WPSEO_Utils;
use Yoast\WP\SEO\Conditionals\Admin_Conditional;
use Yoast\WP\SEO\Config\Migration_Status;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Integrations\Academy_Integration;
use Yoast\WP\SEO\Integrations\Integration_Interface;
use Yoast\WP\SEO\Integrations\Settings_Integration;
use Yoast\WP\SEO\Integrations\Support_Integration;
/**
* Class WPSEO_HelpScout
*/
class HelpScout_Beacon implements Integration_Interface {
/**
* The id for the beacon.
*
* @var string
*/
protected $beacon_id = '2496aba6-0292-489c-8f5d-1c0fba417c2f';
/**
* The id for the beacon for users that have tracking on.
*
* @var string
*/
protected $beacon_id_tracking_users = '6b8e74c5-aa81-4295-b97b-c2a62a13ea7f';
/**
* The products the beacon is loaded for.
*
* @var array
*/
protected $products = [];
/**
* Whether to ask the user's consent before loading in HelpScout.
*
* @var bool
*/
protected $ask_consent = true;
/**
* The options helper.
*
* @var Options_Helper
*/
protected $options;
/**
* The array of pages we need to show the beacon on with their respective beacon IDs.
*
* @var array
*/
protected $pages_ids;
/**
* The array of pages we need to show the beacon on.
*
* @var array
*/
protected $base_pages = [
'wpseo_dashboard',
Settings_Integration::PAGE,
Academy_Integration::PAGE,
Support_Integration::PAGE,
'wpseo_search_console',
'wpseo_tools',
'wpseo_licenses',
'wpseo_workouts',
'wpseo_integrations',
];
/**
* The current admin page
*
* @var string|null
*/
protected $page;
/**
* The asset manager.
*
* @var WPSEO_Admin_Asset_Manager
*/
protected $asset_manager;
/**
* The migration status object.
*
* @var Migration_Status
*/
protected $migration_status;
/**
* Headless_Rest_Endpoints_Enabled_Conditional constructor.
*
* @param Options_Helper $options The options helper.
* @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager.
* @param Migration_Status $migration_status The migrations status.
*/
public function __construct( Options_Helper $options, WPSEO_Admin_Asset_Manager $asset_manager, Migration_Status $migration_status ) {
$this->options = $options;
$this->asset_manager = $asset_manager;
$this->ask_consent = ! $this->options->get( 'tracking' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$this->page = \sanitize_text_field( \wp_unslash( $_GET['page'] ) );
}
else {
$this->page = null;
}
$this->migration_status = $migration_status;
foreach ( $this->base_pages as $page ) {
if ( $this->ask_consent ) {
// We want to be able to show surveys to people who have tracking on, so we give them a different beacon.
$this->pages_ids[ $page ] = $this->beacon_id_tracking_users;
}
else {
$this->pages_ids[ $page ] = $this->beacon_id;
}
}
}
/**
* {@inheritDoc}
*/
public function register_hooks() {
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_help_scout_script' ] );
\add_action( 'admin_footer', [ $this, 'output_beacon_js' ] );
}
/**
* Enqueues the HelpScout script.
*/
public function enqueue_help_scout_script() {
// Make sure plugins can filter in their "stuff", before we check whether we're outputting a beacon.
$this->filter_settings();
if ( ! $this->is_beacon_page() ) {
return;
}
$this->asset_manager->enqueue_script( 'help-scout-beacon' );
}
/**
* Outputs a small piece of javascript for the beacon.
*/
public function output_beacon_js() {
if ( ! $this->is_beacon_page() ) {
return;
}
\printf(
'<script type="text/javascript">window.%1$s(\'%2$s\', %3$s)</script>',
( $this->ask_consent ) ? 'wpseoHelpScoutBeaconConsent' : 'wpseoHelpScoutBeacon',
\esc_html( $this->pages_ids[ $this->page ] ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaping done in format_json_encode.
WPSEO_Utils::format_json_encode( (array) $this->get_session_data() )
);
}
/**
* Checks if the current page is a page containing the beacon.
*
* @return bool
*/
private function is_beacon_page() {
$return = false;
if ( ! empty( $this->page ) && $GLOBALS['pagenow'] === 'admin.php' && isset( $this->pages_ids[ $this->page ] ) ) {
$return = true;
}
/**
* Filter: 'wpseo_helpscout_show_beacon' - Allows overriding whether we show the HelpScout beacon.
*
* @api bool - Whether we show the beacon or not.
*/
return \apply_filters( 'wpseo_helpscout_show_beacon', $return );
}
/**
* Retrieves the identifying data.
*
* @return string The data to pass as identifying data.
*/
protected function get_session_data() {
// Short-circuit if we can get the needed data from a transient.
$transient_data = \get_transient( 'yoast_beacon_session_data' );
if ( \is_array( $transient_data ) ) {
return WPSEO_Utils::format_json_encode( $transient_data );
}
$current_user = \wp_get_current_user();
// Do not make these strings translatable! They are for our support agents, the user won't see them!
$data = \array_merge(
[
'name' => \trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ),
'email' => $current_user->user_email,
'Languages' => $this->get_language_settings(),
],
$this->get_server_info(),
[
'WordPress Version' => $this->get_wordpress_version(),
'Active theme' => $this->get_theme_info(),
'Active plugins' => $this->get_active_plugins(),
'Must-use and dropins' => $this->get_mustuse_and_dropins(),
'Indexables status' => $this->get_indexables_status(),
]
);
if ( ! empty( $this->products ) ) {
$addon_manager = new WPSEO_Addon_Manager();
foreach ( $this->products as $product ) {
$subscription = $addon_manager->get_subscription( $product );
if ( ! $subscription ) {
continue;
}
$data[ $subscription->product->name ] = $this->get_product_info( $subscription );
}
}
// Store the data in a transient for 5 minutes to prevent overhead on every backend pageload.
\set_transient( 'yoast_beacon_session_data', $data, ( 5 * \MINUTE_IN_SECONDS ) );
return WPSEO_Utils::format_json_encode( $data );
}
/**
* Returns basic info about the server software.
*
* @return array
*/
private function get_server_info() {
$server_tracking_data = new WPSEO_Tracking_Server_Data();
$server_data = $server_tracking_data->get();
$server_data = $server_data['server'];
$fields_to_use = [
'Server IP' => 'ip',
'PHP Version' => 'PhpVersion',
'cURL Version' => 'CurlVersion',
];
$server_data['CurlVersion'] = $server_data['CurlVersion']['version'] . ' (SSL Support ' . $server_data['CurlVersion']['sslSupport'] . ')';
$server_info = [];
foreach ( $fields_to_use as $label => $field_to_use ) {
if ( isset( $server_data[ $field_to_use ] ) ) {
$server_info[ $label ] = \esc_html( $server_data[ $field_to_use ] );
}
}
// Get the memory limits for the server and, if different, from WordPress as well.
$memory_limit = \ini_get( 'memory_limit' );
$server_info['Memory limits'] = 'Server memory limit: ' . $memory_limit;
if ( $memory_limit !== \WP_MEMORY_LIMIT ) {
$server_info['Memory limits'] .= ', WP_MEMORY_LIMIT: ' . \WP_MEMORY_LIMIT;
}
if ( $memory_limit !== \WP_MAX_MEMORY_LIMIT ) {
$server_info['Memory limits'] .= ', WP_MAX_MEMORY_LIMIT: ' . \WP_MAX_MEMORY_LIMIT;
}
return $server_info;
}
/**
* Returns info about the Yoast SEO plugin version and license.
*
* @param object $plugin The plugin.
*
* @return string The product info.
*/
private function get_product_info( $plugin ) {
if ( empty( $plugin ) ) {
return '';
}
$product_info = \sprintf(
'Expiration date %1$s',
$plugin->expiry_date
);
return $product_info;
}
/**
* Returns the WordPress version + a suffix about the multisite status.
*
* @return string The WordPress version string.
*/
private function get_wordpress_version() {
global $wp_version;
$wordpress_version = $wp_version;
if ( \is_multisite() ) {
$wordpress_version .= ' (multisite: yes)';
}
else {
$wordpress_version .= ' (multisite: no)';
}
return $wordpress_version;
}
/**
* Returns information about the current theme.
*
* @return string The theme info as string.
*/
private function get_theme_info() {
$theme = \wp_get_theme();
$theme_info = \sprintf(
'%1$s (Version %2$s, %3$s)',
\esc_html( $theme->display( 'Name' ) ),
\esc_html( $theme->display( 'Version' ) ),
\esc_attr( $theme->display( 'ThemeURI' ) )
);
if ( \is_child_theme() ) {
$theme_info .= \sprintf( ', this is a child theme of: %1$s', \esc_html( $theme->display( 'Template' ) ) );
}
return $theme_info;
}
/**
* Returns a stringified list of all active plugins, separated by a pipe.
*
* @return string The active plugins.
*/
private function get_active_plugins() {
$updates_available = \get_site_transient( 'update_plugins' );
$active_plugins = '';
foreach ( \wp_get_active_and_valid_plugins() as $plugin ) {
$plugin_data = \get_plugin_data( $plugin );
$plugin_file = \str_replace( \trailingslashit( \WP_PLUGIN_DIR ), '', $plugin );
$plugin_update_available = '';
if ( isset( $updates_available->response[ $plugin_file ] ) ) {
$plugin_update_available = ' [update available]';
}
$active_plugins .= \sprintf(
'%1$s (Version %2$s%3$s, %4$s) | ',
\esc_html( $plugin_data['Name'] ),
\esc_html( $plugin_data['Version'] ),
$plugin_update_available,
\esc_attr( $plugin_data['PluginURI'] )
);
}
return $active_plugins;
}
/**
* Returns a CSV list of all must-use and drop-in plugins.
*
* @return string The active plugins.
*/
private function get_mustuse_and_dropins() {
$dropins = \get_dropins();
$mustuse_plugins = \get_mu_plugins();
if ( ! \is_array( $dropins ) ) {
$dropins = [];
}
if ( ! \is_array( $mustuse_plugins ) ) {
$mustuse_plugins = [];
}
return \sprintf( 'Must-Use plugins: %1$d, Drop-ins: %2$d', \count( $mustuse_plugins ), \count( $dropins ) );
}
/**
* Return the indexables status details.
*
* @return string The indexables status in a string.
*/
private function get_indexables_status() {
$indexables_status = 'Indexing completed: ';
$indexing_completed = $this->options->get( 'indexables_indexing_completed' );
$indexing_reason = $this->options->get( 'indexing_reason' );
$indexables_status .= ( $indexing_completed ) ? 'yes' : 'no';
$indexables_status .= ( $indexing_reason ) ? ', latest indexing reason: ' . \esc_html( $indexing_reason ) : '';
foreach ( [ 'free', 'premium' ] as $migration_name ) {
$current_status = $this->migration_status->get_error( $migration_name );
if ( \is_array( $current_status ) && isset( $current_status['message'] ) ) {
$indexables_status .= ', migration error: ' . \esc_html( $current_status['message'] );
}
}
return $indexables_status;
}
/**
* Returns language settings for the website and the current user.
*
* @return string The locale settings of the site and user.
*/
private function get_language_settings() {
$site_locale = \get_locale();
$user_locale = \get_user_locale();
$language_settings = \sprintf(
'Site locale: %1$s, user locale: %2$s',
( \is_string( $site_locale ) ) ? \esc_html( $site_locale ) : 'unknown',
( \is_string( $user_locale ) ) ? \esc_html( $user_locale ) : 'unknown'
);
return $language_settings;
}
/**
* Returns the conditionals based on which this integration should be active.
*
* @return array The array of conditionals.
*/
public static function get_conditionals() {
return [ Admin_Conditional::class ];
}
/**
* Allows filtering of the HelpScout settings. Hooked to admin_head to prevent timing issues, not too early, not too late.
*/
protected function filter_settings() {
/**
* Filter: 'wpseo_helpscout_beacon_settings' - Allows overriding the HelpScout beacon settings.
*
* @api string - The HelpScout beacon settings.
*/
$filterable_helpscout_setting = [
'products' => $this->products,
'pages_ids' => $this->pages_ids,
];
$helpscout_settings = \apply_filters( 'wpseo_helpscout_beacon_settings', $filterable_helpscout_setting );
$this->products = $helpscout_settings['products'];
$this->pages_ids = $helpscout_settings['pages_ids'];
}
}