PluginProbe
Social Media Auto Poster – Schedule & Publish to Buffer / 6.2.0
Social Media Auto Poster – Schedule & Publish to Buffer v6.2.0
6.2.4 6.2.3 6.2.2 6.2.1 6.2.0 6.1.2 6.1.1 6.1.0 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 6.0.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 All 125 releases
wp-to-buffer / lib / social / includes / class-admin.php

class-admin.php in Social Media Auto Poster – Schedule & Publish to Buffer 6.2.0, at lib/social/includes/class-admin.php

1,180 lines 37.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Administration class.
4 *
5 * @package WPZinc\Social
6 * @author WP Zinc
7 */
8
9 namespace WPZinc\Social;
10
11 /**
12 * Plugin settings screen and JS/CSS.
13 *
14 * @package WPZinc\Social
15 * @author WP Zinc
16 * @version 3.0.0
17 */
18 class Admin {
19
20 /**
21 * Holds the base class object.
22 *
23 * @since 3.2.0
24 *
25 * @var object
26 */
27 public $base;
28
29 /**
30 * Holds the success and error messages
31 *
32 * @since 3.2.6
33 *
34 * @var array
35 */
36 public $notices = array(
37 'success' => array(),
38 'error' => array(),
39 );
40
41 /**
42 * Constructor
43 *
44 * @since 3.0.0
45 *
46 * @param object $base Base Plugin Class.
47 */
48 public function __construct( $base ) {
49
50 // Store base class.
51 $this->base = $base;
52
53 // Actions.
54 add_action( 'init', array( $this, 'maybe_get_access_token' ) );
55 add_action( 'init', array( $this, 'oauth' ) );
56 add_action( 'init', array( $this, 'check_plugin_setup' ) );
57 add_action( 'admin_notices', array( $this, 'admin_notices' ) );
58 add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts_css' ) );
59 add_action( 'admin_menu', array( $this, 'admin_menu' ) );
60 add_filter( 'plugin_action_links_' . $this->base->plugin->name . '/' . $this->base->plugin->name . '.php', array( $this, 'plugin_action_links_settings_page' ) );
61
62 }
63
64 /**
65 * Exchanges the authorization code for an access token, if included in the request.
66 *
67 * @since 6.0.0
68 */
69 public function maybe_get_access_token() {
70
71 // If a code is included in the request, exchange it for an access token.
72 if ( ! filter_has_var( INPUT_GET, $this->base->plugin->settingsName . '-code' ) ) {
73 return;
74 }
75
76 // Bail if nonce is not valid.
77 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['_wpnonce'] ) ), $this->base->plugin->filter_name . '_oauth' ) ) {
78 return;
79 }
80
81 // Bail if the current user cannot manage the plugin's settings.
82 if ( ! current_user_can( 'manage_options' ) ) {
83 return;
84 }
85
86 // Setup notices class.
87 $this->base->get_class( 'notices' )->set_key_prefix( $this->base->plugin->filter_name . '_' . wp_get_current_user()->ID );
88
89 // Sanitize token.
90 $authorization_code = filter_input( INPUT_GET, $this->base->plugin->settingsName . '-code', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
91
92 // Exchange the authorization code and verifier for an access token.
93 $tokens = $this->base->get_class( 'api' )->get_access_token( $authorization_code );
94
95 // If an error occured, add it to the notices.
96 if ( is_wp_error( $tokens ) ) {
97 $this->base->get_class( 'notices' )->add_error_notice( $tokens->get_error_message() );
98 return;
99 }
100
101 // Store messages.
102 $this->base->get_class( 'notices' )->enable_store();
103
104 // Fetch Organizations.
105 $organizations = $this->base->get_class( 'api' )->organizations( true );
106
107 // If an error occured, add it to the notices.
108 if ( is_wp_error( $organizations ) ) {
109 $this->base->get_class( 'notices' )->add_error_notice( $organizations->get_error_message() );
110 return;
111 }
112
113 // If an account ID is included in the request, delete that account before adding the account.
114 // This handles account re-connection where we're coming from the old API.
115 $existing_account_id = false;
116 if ( filter_has_var( INPUT_GET, 'account_id' ) ) {
117 $existing_account_id = filter_input( INPUT_GET, 'account_id', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
118 $this->base->get_class( 'settings' )->delete_account( $existing_account_id );
119 }
120
121 // For each organization, fetch the profiles and store the organization as an account in the Plugin.
122 foreach ( $organizations as $account ) {
123 // If the existing account ID is set, and it matches the current account ID, skip.
124 if ( $existing_account_id && $existing_account_id !== 'default' && $existing_account_id !== $account['id'] ) {
125 continue;
126 }
127
128 // Fetch Profiles.
129 $profiles = $this->base->get_class( 'api' )->profiles( true, $account['id'] );
130
131 // If something went wrong, show an error.
132 if ( is_wp_error( $profiles ) ) {
133 $this->base->get_class( 'notices' )->add_error_notice( $profiles->get_error_message() );
134 continue;
135 }
136
137 // Update account.
138 $this->base->get_class( 'settings' )->update_account(
139 $tokens['access_token'],
140 $tokens['refresh_token'],
141 $tokens['token_expires'],
142 $account['id'],
143 $account['name'],
144 $account['email'],
145 $account['channel_limit'],
146 $account['plan'],
147 array_keys( $profiles )
148 );
149 }
150
151 // Store success message.
152 $this->base->get_class( 'notices' )->add_success_notice(
153 sprintf(
154 /* translators: %1$s: Social Media Service Name (Buffer, Hootsuite), %2$s: Social Media Service Name (Buffer, Hootsuite) */
155 __( 'Thanks! You\'ve connected our Plugin to %1$s. Now select profiles below to enable, and define your statuses to start sending Posts to %2$s', 'wp-to-buffer' ),
156 $this->base->plugin->account,
157 $this->base->plugin->account
158 )
159 );
160
161 // Redirect to Post tab.
162 wp_safe_redirect( 'admin.php?page=' . $this->base->plugin->name . '-settings&tab=post&type=post' );
163 die();
164
165 }
166
167 /**
168 * Handles displaying any errors from the OAuth process, and storing the access token if supplied,
169 * when the OAuth gateway exchanges the authorization code for an access token.
170 *
171 * Used by:
172 * - WP to Hootsuite
173 * - WP to Hootsuite Pro
174 *
175 * @since 3.3.3
176 */
177 public function oauth() {
178
179 // Setup notices class.
180 $this->base->get_class( 'notices' )->set_key_prefix( $this->base->plugin->filter_name . '_' . wp_get_current_user()->ID );
181
182 // Bail if nonce is not valid, to prevent OAuth callback CSRF.
183 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['_wpnonce'] ) ), $this->base->plugin->filter_name . '_oauth' ) ) {
184 return;
185 }
186
187 // Bail if the current user cannot manage the plugin's settings.
188 if ( ! current_user_can( 'manage_options' ) ) {
189 return;
190 }
191
192 /**
193 * Perform any pre-oAuth actions now, such as starting the oAuth process
194 *
195 * @since 4.2.0
196 */
197 do_action( $this->base->plugin->filter_name . '_save_settings_auth' );
198
199 // If we've returned from the oAuth process and an error occured, add it to the notices.
200 if ( filter_has_var( INPUT_GET, $this->base->plugin->settingsName . '-oauth-error' ) ) {
201 $oauth_error = filter_input( INPUT_GET, $this->base->plugin->settingsName . '-oauth-error', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
202 switch ( $oauth_error ) {
203 /**
204 * Access Denied
205 * - User denied our app access
206 */
207 case 'access_denied':
208 $this->base->get_class( 'notices' )->add_error_notice(
209 sprintf(
210 /* translators: %1$s: Social Media Service Name (Buffer, Hootsuite), %2$s: Social Media Service Name (Buffer, Hootsuite) */
211 __( 'You did not grant our Plugin access to your %1$s account. We are unable to post to %2$s until you do this. Please click on the Authorize Plugin button.', 'wp-to-buffer' ),
212 $this->base->plugin->account,
213 $this->base->plugin->account
214 )
215 );
216 break;
217
218 /**
219 * Invalid Grant
220 * - A parameter sent by the oAuth gateway is wrong
221 */
222 case 'invalid_grant':
223 $this->base->get_class( 'notices' )->add_error_notice(
224 sprintf(
225 '%1$s <a href="%2$s" target="_blank">%3$s</a>',
226 sprintf(
227 /* translators: Social Media Service Name (Buffer, Hootsuite) */
228 __( 'We were unable to complete authentication with %s. Please try again, or', 'wp-to-buffer' ),
229 $this->base->plugin->account
230 ),
231 esc_html( $this->base->plugin->support_url ),
232 __( 'contact us for support', 'wp-to-buffer' )
233 )
234 );
235 break;
236
237 /**
238 * Expired Token
239 * - The oAuth gateway did not exchange the code for an access token within 30 seconds
240 */
241 case 'expired_token':
242 $this->base->get_class( 'notices' )->add_error_notice(
243 sprintf(
244 '%1$s <a href="%2$s" target="_blank">%3$s</a> %4$s',
245 __( 'The oAuth process has expired. Please try again, or', 'wp-to-buffer' ),
246 esc_html( $this->base->plugin->support_url ),
247 __( 'contact us for support', 'wp-to-buffer' ),
248 __( 'if this issue persists.', 'wp-to-buffer' )
249 )
250 );
251 break;
252
253 /**
254 * Other Error
255 */
256 default:
257 $this->base->get_class( 'notices' )->add_error_notice(
258 filter_input( INPUT_GET, $this->base->plugin->settingsName . '-oauth-error', FILTER_SANITIZE_FULL_SPECIAL_CHARS )
259 );
260 break;
261 }
262 }
263
264 // If an Access Token is included in the request, store it and show a success message.
265 if ( filter_has_var( INPUT_GET, $this->base->plugin->settingsName . '-oauth-access-token' ) ) {
266 // Define tokens and expiry.
267 $access_token = filter_input( INPUT_GET, $this->base->plugin->settingsName . '-oauth-access-token', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
268 $refresh_token = filter_input( INPUT_GET, $this->base->plugin->settingsName . '-oauth-refresh-token', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
269 $expiry = filter_input( INPUT_GET, $this->base->plugin->settingsName . '-oauth-expires', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
270 if ( $expiry > 0 ) {
271 $expiry = strtotime( '+' . $expiry . ' seconds' );
272 }
273
274 // Setup API.
275 $this->base->get_class( 'api' )->set_tokens( $access_token, $refresh_token, $expiry );
276
277 // Fetch Account.
278 $account = $this->base->get_class( 'api' )->account();
279
280 // If something went wrong, show an error.
281 if ( is_wp_error( $account ) ) {
282 $this->base->get_class( 'notices' )->add_error_notice( $account->get_error_message() );
283 return;
284 }
285
286 // Fetch Profiles.
287 $profiles = $this->base->get_class( 'api' )->profiles( true, $account['id'] );
288
289 // If something went wrong, show an error.
290 if ( is_wp_error( $profiles ) ) {
291 $this->base->get_class( 'notices' )->add_error_notice( $profiles->get_error_message() );
292 return;
293 }
294
295 // Test worked! Save Tokens and Expiry.
296 $this->base->get_class( 'settings' )->update_account(
297 $access_token,
298 $refresh_token,
299 $expiry,
300 $account['id'],
301 $account['name'],
302 $account['email'],
303 $account['channel_limit'],
304 $account['plan'],
305 array_keys( $profiles )
306 );
307
308 // Store success message.
309 $this->base->get_class( 'notices' )->enable_store();
310 $this->base->get_class( 'notices' )->add_success_notice(
311 sprintf(
312 /* translators: %1$s: Social Media Service Name (Buffer, Hootsuite), %2$s: Social Media Service Name (Buffer, Hootsuite) */
313 __( 'Thanks! You\'ve connected our Plugin to %1$s. Now select profiles below to enable, and define your statuses to start sending Posts to %2$s', 'wp-to-buffer' ),
314 $this->base->plugin->account,
315 $this->base->plugin->account
316 )
317 );
318
319 // Redirect to Post tab.
320 wp_safe_redirect( 'admin.php?page=' . $this->base->plugin->name . '-settings&tab=post&type=post' );
321 die();
322 }
323
324 }
325
326 /**
327 * Checks that the oAuth authorization flow has been completed, and that
328 * at least one Post Type with one Social Media account has been enabled.
329 *
330 * Displays a dismissible WordPress notification if this has not been done.
331 *
332 * @since 1.0.0
333 */
334 public function check_plugin_setup() {
335
336 // Show an error if cURL hasn't been installed.
337 if ( ! function_exists( 'curl_init' ) ) {
338 $this->base->get_class( 'notices' )->add_error_notice(
339 sprintf(
340 /* translators: Plugin Name */
341 __( '%s requires the PHP cURL extension to be installed and enabled by your web host.', 'wp-to-buffer' ),
342 $this->base->plugin->displayName
343 )
344 );
345 }
346
347 // Don't display the notice if this request is for the settings auth screen.
348 $screen = $this->base->get_class( 'screen' )->get_current_screen();
349 if ( $screen['screen'] === 'settings' && $screen['section'] === 'auth' ) {
350 return;
351 }
352
353 // Check the API is connected.
354 if ( ! $this->base->get_class( 'settings' )->account_connected() ) {
355 // Display the notice.
356 $this->base->get_class( 'notices' )->add_error_notice(
357 sprintf(
358 '%1$s <a href="%2$s">%3$s</a>',
359 sprintf(
360 /* translators: %1$s: Plugin Name, %2$s, %3$s: Social Media Service Name (Buffer, Hootsuite), %4$s: URL to Authorize Plugin Screen, %5$s: URL to Register Account with Service */
361 esc_html__( '%1$s needs to be authorized with %2$s before you can start sending Posts to %3$s.', 'wp-to-buffer' ),
362 $this->base->plugin->displayName,
363 $this->base->plugin->account,
364 $this->base->plugin->account
365 ),
366 admin_url( 'admin.php?page=' . $this->base->plugin->name . '-settings' ),
367 esc_html__( 'Click here to Authorize.', 'wp-to-buffer' )
368 )
369 );
370 }
371
372 // Buffer: If an access token begins with '2/', it's from the old API.
373 $accounts = $this->base->get_class( 'settings' )->get_accounts();
374 foreach ( $accounts as $account ) {
375 if ( strpos( $account['access_token'], '2/' ) === 0 ) {
376 $this->base->get_class( 'notices' )->add_error_notice(
377 sprintf(
378 /* translators: %1$s: Plugin Name, %2$s: Social Media Service Name (Buffer, Hootsuite) */
379 __( '%1$s uses a new API. Please click the `Reconnect` button at %2$s Settings > Authentication to reconnect your account. You won\'t need to do this again.', 'wp-to-buffer' ),
380 $this->base->plugin->displayName,
381 $this->base->plugin->account
382 )
383 );
384 }
385 }
386 }
387
388 /**
389 * Checks the transient to see if any admin notices need to be output now.
390 *
391 * @since 3.9.6
392 */
393 public function admin_notices() {
394
395 // Output notices.
396 $this->base->get_class( 'notices' )->set_key_prefix( $this->base->plugin->filter_name . '_' . wp_get_current_user()->ID );
397 $this->base->get_class( 'notices' )->output_notices();
398
399 }
400
401 /**
402 * Register and enqueue any JS and CSS for the WordPress Administration
403 *
404 * @since 1.0.0
405 */
406 public function admin_scripts_css() {
407
408 global $id, $post;
409
410 // Get current screen.
411 $screen = $this->base->get_class( 'screen' )->get_current_screen();
412
413 // CSS - always load.
414 wp_enqueue_style( $this->base->plugin->name, $this->base->plugin->url . 'lib/social/assets/css/admin.css', array(), $this->base->plugin->version );
415
416 // Define CSS variables for design.
417 wp_register_style( $this->base->plugin->name . '-vars', false, array(), $this->base->plugin->version );
418 wp_enqueue_style( $this->base->plugin->name . '-vars' );
419 wp_add_inline_style(
420 $this->base->plugin->name . '-vars',
421 trim(
422 ':root {
423 --wpzinc-logo: url(\'' . esc_attr( $this->base->plugin->logo ) . '\');
424 --wpzinc-header-background-color: ' . esc_attr( $this->base->plugin->header_background_color ) . ';
425 --wpzinc-header-primary-text-color: ' . esc_attr( $this->base->plugin->header_primary_text_color ) . ';
426 --wpzinc-header-secondary-text-color: ' . esc_attr( $this->base->plugin->header_secondary_text_color ) . ';
427 --wpzinc-plugin-display-name: "' . esc_attr( $this->base->plugin->displayName ) . ' ";
428 }'
429 )
430 );
431
432 // Don't load anything else if we're not on a Plugin or Post screen.
433 if ( ! $screen['screen'] ) {
434 return;
435 }
436
437 // Determine whether to load minified versions of JS.
438 $minified = $this->base->dashboard->should_load_minified_js();
439
440 // Define JS and localization.
441 wp_register_script( $this->base->plugin->name . '-log', $this->base->plugin->url . 'lib/social/assets/js/' . ( $minified ? 'min/' : '' ) . 'log' . ( $minified ? '-min' : '' ) . '.js', array( 'jquery' ), $this->base->plugin->version, true );
442 wp_register_script( $this->base->plugin->name . '-statuses', $this->base->plugin->url . 'lib/social/assets/js/' . ( $minified ? 'min/' : '' ) . 'statuses' . ( $minified ? '-min' : '' ) . '.js', array( 'jquery' ), $this->base->plugin->version, true );
443
444 // Define localization for statuses.
445 $localization = array(
446 'ajax' => admin_url( 'admin-ajax.php' ),
447
448 'clear_log_nonce' => wp_create_nonce( $this->base->plugin->name . '-clear-log' ),
449 'clear_log_completed' => sprintf(
450 /* translators: Social Media Service Name (Buffer, Hootsuite) */
451 __( 'No log entries exist, or no status updates have been sent to %s.', 'wp-to-buffer' ),
452 $this->base->plugin->account
453 ),
454
455 'get_log_nonce' => wp_create_nonce( $this->base->plugin->name . '-get-log' ),
456
457 'delete_condition_message' => __( 'Are you sure you want to delete this condition?', 'wp-to-buffer' ),
458 'delete_status_message' => __( 'Are you sure you want to delete this status?', 'wp-to-buffer' ),
459
460 'get_status_row_action' => $this->base->plugin->filter_name . '_get_status_row',
461 'get_status_row_nonce' => wp_create_nonce( $this->base->plugin->name . '-get-status-row' ),
462
463 'post_id' => ( isset( $post->ID ) ? $post->ID : (int) $id ),
464
465 // Plugin specific Status Form Container and Status Form, so statuses.js knows where to look for the form
466 // relative to this Plugin.
467 'plugin_name' => $this->base->plugin->name,
468 'status_form_container' => '#' . $this->base->plugin->name . '-status-form-container',
469 'status_form' => '#' . $this->base->plugin->name . '-status-form',
470
471 // status.js appends profile service to this e.g. twitter,facebook.
472 'usernames_search_action' => $this->base->plugin->filter_name . '_usernames_search_',
473 );
474
475 // If here, we're on a Plugin or Post screen.
476 // Conditionally load scripts and styles depending on which section of the Plugin we're loading.
477 switch ( $screen['screen'] ) {
478 /**
479 * Post
480 */
481 case 'post':
482 switch ( $screen['section'] ) {
483 /**
484 * WP_List_Table
485 */
486 case 'wp_list_table':
487 break;
488
489 /**
490 * Add/Edit
491 */
492 case 'edit':
493 // Plugin JS.
494 wp_enqueue_script( $this->base->plugin->name . '-log' );
495
496 // Localize.
497 wp_localize_script( $this->base->plugin->name . '-log', 'wpzinc_social', $localization );
498 break;
499 }
500 break;
501
502 /**
503 * Settings
504 */
505 case 'settings':
506 // JS.
507 wp_enqueue_script( 'wpzinc-admin-conditional' );
508 wp_enqueue_media();
509 wp_enqueue_script( 'wpzinc-admin-tabs' );
510 wp_enqueue_script( 'wpzinc-admin' );
511
512 switch ( $screen['section'] ) {
513 /**
514 * General
515 */
516 case 'auth':
517 break;
518
519 /**
520 * Post Type
521 */
522 default:
523 // JS.
524 wp_enqueue_script( 'wpzinc-admin-autocomplete' );
525 wp_enqueue_script( 'wpzinc-admin-autosize' );
526 wp_enqueue_script( 'wpzinc-admin-modal' );
527 wp_enqueue_script( 'jquery-ui-sortable' );
528
529 // Plugin JS.
530 wp_enqueue_script( $this->base->plugin->name . '-statuses' );
531
532 // Add Twitter Username Save Action and Nonce.
533 $localization['username_save_twitter_action'] = $this->base->plugin->filter_name . '_username_save_twitter';
534 $localization['username_save_twitter_nonce'] = wp_create_nonce( $this->base->plugin->name . '-username-save-twitter' );
535
536 // Localize.
537 wp_localize_script( $this->base->plugin->name . '-settings', 'wpzinc_social', $localization );
538
539 // Add Post Type, Action and Nonce to allow AJAX saving.
540 $localization['post_type'] = $this->get_post_type_tab();
541 $localization['prompt_unsaved_changes'] = true;
542 $localization['save_statuses_action'] = $this->base->plugin->filter_name . '_save_statuses';
543 $localization['save_statuses_modal'] = array(
544 'title' => __( 'Saving', 'wp-to-buffer' ),
545 'title_success' => __( 'Saved!', 'wp-to-buffer' ),
546 );
547 $localization['save_statuses_nonce'] = wp_create_nonce( $this->base->plugin->name . '-save-statuses' );
548
549 // Localize Statuses.
550 wp_localize_script( $this->base->plugin->name . '-statuses', 'wpzinc_social', $localization );
551
552 // Localize Autocomplete.
553 wp_localize_script( 'wpzinc-admin-autocomplete', 'wpzinc_autocomplete', $this->get_autocomplete_configuration( $localization['post_type'] ) );
554 break;
555 }
556 break;
557
558 /**
559 * Log
560 */
561 case 'log':
562 // Plugin JS.
563 wp_enqueue_script( $this->base->plugin->name . '-log' );
564
565 // Localize.
566 wp_localize_script( $this->base->plugin->name . '-log', 'wpzinc_social', $localization );
567 break;
568 }
569
570 }
571
572 /**
573 * Returns configuration for tribute.js autocomplete instances for Tags, Facebook Pages and Twitter Username mentions.
574 *
575 * @since 4.5.7
576 *
577 * @param string $post_type Post Type.
578 * @return array Javascript Autocomplete Configuration
579 */
580 private function get_autocomplete_configuration( $post_type ) {
581
582 $autocomplete_configuration = array(
583 // Tags.
584 array(
585 'fields' => array(
586 'textarea.message',
587 'input.url',
588 ),
589 'triggers' => array(
590 // Tags.
591 array(
592 'trigger' => '{',
593 'values' => $this->base->get_class( 'common' )->get_tags_flat( $post_type ),
594 ),
595 ),
596 ),
597 );
598
599 /**
600 * Defines configuration for tribute.js autocomplete instances for Tags, Facebook Pages and Twitter Username mentions.
601 *
602 * @since 4.5.7
603 *
604 * @param array $autocomplete_configuration Javascript Autocomplete Configuration.
605 * @param string $post_type Post Type.
606 */
607 $autocomplete_configuration = apply_filters( $this->base->plugin->filter_name . '_admin_get_autocomplete_configuration', $autocomplete_configuration );
608
609 // Return.
610 return $autocomplete_configuration;
611
612 }
613
614 /**
615 * Add the Plugin to the WordPress Administration Menu
616 *
617 * @since 1.0.0
618 */
619 public function admin_menu() {
620
621 // Define the minimum capability required to access settings.
622 $minimum_capability = 'manage_options';
623
624 /**
625 * Defines the minimum capability required to access the Plugin's
626 * Menu and Sub Menus
627 *
628 * @since 4.3.6
629 *
630 * @param string $capability Minimum Required Capability.
631 * @return string Minimum Required Capability
632 */
633 $minimum_capability = apply_filters( $this->base->plugin->filter_name . '_admin_admin_menu_minimum_capability', $minimum_capability );
634
635 /**
636 * Add settings menus and sub menus for the Plugin's settings.
637 *
638 * @since 5.2.4
639 *
640 * @param string $minimum_capability Minimum capability required.
641 */
642 do_action( $this->base->plugin->filter_name . '_admin_admin_menu', $minimum_capability );
643
644 }
645
646 /**
647 * Define links to display below the Plugin Name on the WP_List_Table at in the Plugins screen.
648 *
649 * @since 5.0.2
650 *
651 * @param array $links Links.
652 * @return array Links
653 */
654 public function plugin_action_links_settings_page( $links ) {
655
656 // Add link to Plugin settings screen.
657 $links['settings'] = sprintf(
658 '<a href="%s">%s</a>',
659 add_query_arg(
660 array(
661 'page' => $this->base->plugin->name . '-settings',
662 ),
663 admin_url( 'admin.php' )
664 ),
665 __( 'Settings', 'wp-to-buffer' )
666 );
667
668 // Return.
669 return $links;
670
671 }
672
673 /**
674 * Upgrade Screen
675 *
676 * @since 3.2.5
677 */
678 public function upgrade_screen() {
679 // We never reach here, as we redirect earlier in the process.
680 }
681
682 /**
683 * Outputs the Settings Screen
684 *
685 * @since 3.0.0
686 */
687 public function settings_screen() {
688
689 // Setup notices class.
690 $this->base->get_class( 'notices' )->set_key_prefix( $this->base->plugin->filter_name . '_' . wp_get_current_user()->ID );
691
692 // Maybe disconnect an account.
693 $this->maybe_disconnect_account();
694
695 // Maybe refresh profiles.
696 $this->maybe_refresh_profiles();
697
698 // Maybe save settings.
699 $result = $this->save_settings();
700 if ( is_wp_error( $result ) ) {
701 // Error notice.
702 $this->base->get_class( 'notices' )->add_error_notice( $result->get_error_message() );
703 } elseif ( $result === true ) {
704 // Success notice.
705 $this->base->get_class( 'notices' )->add_success_notice( __( 'Settings saved successfully.', 'wp-to-buffer' ) );
706 }
707
708 // If the Plugin isn't connected an account, show the screen to do this now.
709 if ( ! $this->base->get_class( 'settings' )->account_connected() ) {
710 $this->auth_screen();
711 return;
712 }
713
714 // Get Profiles for accounts.
715 $profiles = $this->get_cached_profiles();
716
717 // Get Settings Tab and Post Type we're managing settings for.
718 $tab = $this->get_tab( $profiles );
719 $post_type = $this->get_post_type_tab();
720 $disable_save_button = false;
721
722 // Post Types.
723 $post_types = $this->base->get_class( 'common' )->get_post_types();
724
725 // Accounts.
726 $accounts = $this->base->get_class( 'settings' )->get_accounts();
727
728 // Depending on the screen we're on, load specific options.
729 switch ( $tab ) {
730 /**
731 * Settings
732 */
733 case 'auth':
734 // Log Settings.
735 $log_levels = $this->base->get_class( 'log' )->get_level_options();
736
737 // Documentation URL.
738 $documentation_url = $this->base->plugin->documentation_url . '/authentication-settings';
739 break;
740
741 /**
742 * No Profiles
743 */
744 case 'profiles-missing':
745 // Disable Save button, as there are no settings displayed to save.
746 $disable_save_button = true;
747
748 // Documentation URL.
749 $documentation_url = $this->base->plugin->documentation_url . '/status-settings';
750 break;
751
752 /**
753 * Profiles Error
754 */
755 case 'profiles-error':
756 // Disable Save button, as there are no settings displayed to save.
757 $disable_save_button = true;
758
759 // Documentation URL.
760 $documentation_url = $this->base->plugin->documentation_url . '/status-settings';
761 break;
762
763 /**
764 * Post Type
765 */
766 default:
767 // Get original statuses that will be stored in a hidden field so they are preserved if the screen is saved
768 // with no changes that trigger an update to the hidden field.
769 $original_statuses = $this->base->get_class( 'settings' )->get_settings( $post_type );
770
771 // Get some other information.
772 $post_type_object = get_post_type_object( $post_type );
773 $actions_plural = $this->base->get_class( 'common' )->get_post_actions_past_tense();
774 $post_actions = $this->base->get_class( 'common' )->get_post_actions();
775 $documentation_url = $this->base->plugin->documentation_url . '/status-settings';
776 $is_post_screen = false; // Disables the 'specific' schedule option, which can only be used on individual Per-Post Settings.
777
778 // Check if this Post Type is enabled.
779 if ( ! $this->base->get_class( 'settings' )->is_post_type_enabled( $post_type ) ) {
780 $this->base->get_class( 'notices' )->add_warning_notice(
781 sprintf(
782 '%1$s <a href="%2$s" target="_blank">%3$s</a>',
783 sprintf(
784 /* translators: %1$s: Post Type, %2$s: Social Media Service Name (Buffer, Hootsuite), %3$s: Documentation URL */
785 __( 'To send %1$s to %2$s, at least one action on the Defaults tab must be enabled with a status defined, and at least one social media profile must be enabled below by clicking the applicable profile name and ticking the "Account Enabled" box.', 'wp-to-buffer' ),
786 $post_type_object->label,
787 $this->base->plugin->account
788 ),
789 $documentation_url,
790 __( 'See Documentation', 'wp-to-buffer' )
791 )
792 );
793 }
794 break;
795 }
796
797 // Load View.
798 include_once $this->base->plugin->folder . 'lib/social/views/settings.php';
799
800 // Add footer action to output overlay modal markup.
801 add_action( 'admin_footer', array( $this, 'output_modal' ) );
802
803 }
804
805 /**
806 * Outputs the auth screen, allowing the user to begin the process of connecting the Plugin
807 * to the API, without showing other settings.
808 *
809 * @since 4.6.4
810 */
811 public function auth_screen() {
812
813 // Load View.
814 include_once $this->base->plugin->folder . 'lib/social/views/settings-auth-required.php';
815
816 }
817
818 /**
819 * Outputs the hidden Javascript Modal and Overlay in the Footer
820 *
821 * @since 1.0.0
822 */
823 public function output_modal() {
824
825 // Load view.
826 require_once $this->base->plugin->folder . 'lib/shared/views/modal.php';
827
828 }
829
830 /**
831 * Outputs the Log Screen
832 *
833 * @since 3.9.6
834 */
835 public function log_screen() {
836
837 // Init table.
838 $table = new \WPZinc\Social\Log_Table( $this->base );
839 $table->prepare_items();
840
841 // Load View.
842 include_once $this->base->plugin->folder . 'lib/social/views/log.php';
843
844 }
845
846 /**
847 * Helper method to get the setting value from the plugin settings
848 *
849 * @since 3.0.0
850 *
851 * @param string $type Setting Type.
852 * @param string $key Setting Key.
853 * @param mixed $default_value Default Value if Setting does not exist.
854 * @return mixed Value
855 */
856 public function get_setting( $type = '', $key = '', $default_value = '' ) {
857
858 // Post Type Setting or Bulk Setting.
859 if ( post_type_exists( $type ) ) {
860 return $this->base->get_class( 'settings' )->get_setting( $type, $key, $default_value );
861 }
862
863 // Depending on the type, return settings / options.
864 switch ( $type ) {
865 case 'text_to_image':
866 case 'log':
867 case 'hide_meta_box_by_roles':
868 case 'roles':
869 case 'custom_tags':
870 case 'repost':
871 return $this->base->get_class( 'settings' )->get_setting( $type, $key, $default_value );
872
873 default:
874 return $this->base->get_class( 'settings' )->get_option( $key, $default_value );
875 }
876
877 }
878
879 /**
880 * Fetches fresh profiles from the API for the given account, if the
881 * user clicks the refresh profiles link. Bypasses the transient cache
882 * and updates the stored profile IDs on the account.
883 *
884 * @since 6.1.2
885 */
886 private function maybe_refresh_profiles() {
887
888 // Bail if no nonce.
889 if ( ! isset( $_GET['nonce'] ) ) {
890 return;
891 }
892
893 // Bail if nonce is invalid.
894 if ( ! wp_verify_nonce( sanitize_key( $_GET['nonce'] ), $this->base->plugin->name . '-refresh-profiles' ) ) {
895 return;
896 }
897
898 // Bail if account ID is not set.
899 if ( ! isset( $_GET[ $this->base->plugin->name . '-refresh-profiles' ] ) ) {
900 return;
901 }
902
903 // Get account.
904 $account_id = sanitize_text_field( wp_unslash( $_GET[ $this->base->plugin->name . '-refresh-profiles' ] ) );
905 $accounts = $this->base->get_class( 'settings' )->get_accounts();
906 if ( ! isset( $accounts[ $account_id ] ) ) {
907 return;
908 }
909 $account = $accounts[ $account_id ];
910
911 // Configure API for this account.
912 $this->base->get_class( 'api' )->set_tokens( $account['access_token'], $account['refresh_token'], $account['token_expires'] );
913
914 // Fetch fresh profiles from the API.
915 $profiles = $this->base->get_class( 'api' )->profiles( true, $account_id );
916
917 // Display error and bail.
918 if ( is_wp_error( $profiles ) ) {
919 $this->base->get_class( 'notices' )->add_error_notice( $profiles->get_error_message() );
920 return;
921 }
922
923 // If the service supports organizations, refresh the stored account
924 // information (name, email, channel limit, plan) alongside the profiles.
925 $organizations = array();
926 $api = $this->base->get_class( 'api' );
927 if ( method_exists( $api, 'organizations' ) ) {
928 $organizations = $api->organizations( true );
929
930 // Display error and bail.
931 if ( is_wp_error( $organizations ) ) {
932 $this->base->get_class( 'notices' )->add_error_notice( $organizations->get_error_message() );
933 return;
934 }
935 }
936
937 // Update the stored account information (where available) and profile IDs.
938 if ( isset( $organizations[ $account_id ] ) ) {
939 $this->base->get_class( 'settings' )->update_account_information(
940 $account_id,
941 $organizations[ $account_id ]['name'],
942 $organizations[ $account_id ]['email'],
943 $organizations[ $account_id ]['channel_limit'],
944 $organizations[ $account_id ]['plan'],
945 array_keys( $profiles )
946 );
947
948 // Schedule the event to refresh this account's access token before it expires.
949 $this->base->get_class( 'cron' )->reschedule_refresh_token_event();
950 } else {
951 $this->base->get_class( 'settings' )->update_account_profile_ids( $account_id, array_keys( $profiles ) );
952 }
953
954 $this->base->get_class( 'notices' )->add_success_notice(
955 __( 'Profiles refreshed successfully.', 'wp-to-buffer' )
956 );
957
958 }
959
960 /**
961 * Disconnects an account if the user clicks the disconnect link.
962 *
963 * @since 5.4.0
964 */
965 private function maybe_disconnect_account() {
966
967 // Bail if no nonce.
968 if ( ! isset( $_GET['nonce'] ) ) {
969 return;
970 }
971
972 // Bail if nonce is invalid.
973 if ( ! wp_verify_nonce( sanitize_key( $_GET['nonce'] ), $this->base->plugin->name . '-disconnect' ) ) {
974 return;
975 }
976
977 // Bail if account ID is not set.
978 if ( ! isset( $_GET[ $this->base->plugin->name . '-disconnect' ] ) ) {
979 return;
980 }
981
982 // Disconnect account.
983 $this->base->get_class( 'settings' )->delete_account( sanitize_text_field( wp_unslash( $_GET[ $this->base->plugin->name . '-disconnect' ] ) ) );
984 $this->base->get_class( 'notices' )->add_success_notice(
985 sprintf(
986 /* translators: Social Media Service Name (Buffer, Hootsuite) */
987 __( '%s account disconnected successfully.', 'wp-to-buffer' ),
988 $this->base->plugin->account
989 )
990 );
991
992 }
993
994 /**
995 * Helper method to save settings
996 *
997 * @since 3.0.0
998 *
999 * @return mixed WP_Error | bool
1000 */
1001 public function save_settings() {
1002
1003 // Check if a POST request was made.
1004 if ( ! isset( $_POST['submit'] ) ) {
1005 return false;
1006 }
1007
1008 // Missing nonce.
1009 if ( ! isset( $_POST[ $this->base->plugin->name . '_nonce' ] ) ) {
1010 return new \WP_Error(
1011 $this->base->plugin->filter_name . '_admin_save_settings_error',
1012 __( 'Nonce field is missing. Settings NOT saved.', 'wp-to-buffer' )
1013 );
1014 }
1015
1016 // Invalid nonce.
1017 if ( ! wp_verify_nonce( sanitize_key( $_POST[ $this->base->plugin->name . '_nonce' ] ), $this->base->plugin->name ) ) {
1018 return new \WP_Error(
1019 $this->base->plugin->filter_name . '_admin_save_settings_error',
1020 __( 'Invalid nonce specified. Settings NOT saved.', 'wp-to-buffer' )
1021 );
1022 }
1023
1024 // Get URL parameters.
1025 $tab = $this->get_tab();
1026 $post_type = $this->get_post_type_tab();
1027
1028 switch ( $tab ) {
1029 /**
1030 * Authentication
1031 */
1032 case 'auth':
1033 // oAuth settings are now handled by this class' oauth() function.
1034 // Save other Settings.
1035 $settings = map_deep( $_POST, 'sanitize_text_field' );
1036
1037 // General Settings.
1038 $this->base->get_class( 'settings' )->update_option( 'test_mode', ( isset( $settings['test_mode'] ) ? 1 : 0 ) );
1039 $this->base->get_class( 'settings' )->update_option( 'force_trailing_forwardslash', ( isset( $settings['force_trailing_forwardslash'] ) ? 1 : 0 ) );
1040 $this->base->get_class( 'settings' )->update_option( 'proxy', ( isset( $settings['proxy'] ) ? 1 : 0 ) );
1041
1042 // Log Settings.
1043 // Always force errors.
1044 $log = isset( $settings['log'] ) ? $settings['log'] : array();
1045 if ( ! isset( $log['log_level'] ) ) {
1046 $log['log_level'] = array(
1047 'error',
1048 );
1049 } else {
1050 // 'Error' is disabled on the form and not sent if another option is chosen.
1051 // We always want errors to be logged so add it to the log levels now.
1052 $log['log_level'][] = 'error';
1053 }
1054 $this->base->get_class( 'settings' )->update_option( 'log', $log );
1055
1056 // Reschedule CRON events.
1057 $this->base->get_class( 'cron' )->reschedule_log_cleanup_event();
1058 $this->base->get_class( 'cron' )->reschedule_media_cleanup_event();
1059
1060 // Done.
1061 return true;
1062
1063 /**
1064 * Post Type
1065 */
1066 default:
1067 if ( ! isset( $_POST[ $this->base->plugin->name ]['statuses'] ) ) {
1068 return new \WP_Error(
1069 $this->base->plugin->filter_name . '_admin_save_settings_error',
1070 __( 'Statuses field is missing. Settings NOT saved.', 'wp-to-buffer' )
1071 );
1072 }
1073
1074 // Unslash and decode JSON field.
1075 $settings = json_decode( wp_unslash( $_POST[ $this->base->plugin->name ]['statuses'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1076
1077 // Save Settings for this Post Type.
1078 return $this->base->get_class( 'settings' )->update_settings( $post_type, $settings );
1079 }
1080
1081 }
1082
1083 /**
1084 * Returns the profiles for all accounts from the cache.
1085 * Queries the API if the cache is empty.
1086 *
1087 * @since 6.0.5
1088 *
1089 * @return array
1090 */
1091 private function get_cached_profiles() {
1092
1093 $profiles = array();
1094
1095 foreach ( $this->base->get_class( 'settings' )->get_accounts() as $account_id => $account ) {
1096 // Configure API for this account.
1097 $this->base->get_class( 'api' )->set_tokens( $account['access_token'], $account['refresh_token'], $account['token_expires'] );
1098
1099 // Get account profiles.
1100 $account_profiles = $this->base->get_class( 'api' )->profiles( false, $account_id );
1101
1102 // Display an error.
1103 if ( is_wp_error( $account_profiles ) ) {
1104 $this->base->get_class( 'notices' )->add_error_notice( $account_profiles->get_error_message() );
1105 continue;
1106 }
1107
1108 // Merge profiles with existing profiles from other accounts.
1109 // array_merge() is not used here as it will re-index numeric keys.
1110 foreach ( $account_profiles as $profile ) {
1111 $profiles[ $profile['id'] ] = $profile;
1112 }
1113 }
1114
1115 return $profiles;
1116
1117 }
1118
1119 /**
1120 * Returns the settings tab that the user has selected.
1121 *
1122 * @since 3.7.2
1123 *
1124 * @param mixed $profiles API Profiles (false|WP_Error|array).
1125 * @return string Tab
1126 */
1127 private function get_tab( $profiles = false ) {
1128
1129 // If no tab, default to auth.
1130 if ( ! filter_has_var( INPUT_GET, 'tab' ) ) {
1131 return 'auth';
1132 }
1133
1134 // Get current tab.
1135 $tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
1136
1137 // If Profiles are an error, show error.
1138 if ( is_wp_error( $profiles ) ) {
1139 return 'profiles-error';
1140 }
1141
1142 // If no Profiles exist, show error.
1143 if ( is_array( $profiles ) && ! count( $profiles ) ) {
1144 return 'profiles-missing';
1145 }
1146
1147 // Return tab.
1148 return $tab;
1149
1150 }
1151
1152 /**
1153 * Returns the Post Type tab that the user has selected.
1154 *
1155 * @since 3.7.2
1156 *
1157 * @return string Tab
1158 */
1159 private function get_post_type_tab() {
1160
1161 // If no type, default to empty string.
1162 if ( ! filter_has_var( INPUT_GET, 'type' ) ) {
1163 return '';
1164 }
1165
1166 // Get supported post types.
1167 $post_types = array_keys( $this->base->get_class( 'common' )->get_post_types() );
1168 $post_type = filter_input( INPUT_GET, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
1169
1170 // If the post type is not supported, return empty string.
1171 if ( ! in_array( $post_type, $post_types, true ) ) {
1172 return '';
1173 }
1174
1175 return $post_type;
1176
1177 }
1178
1179 }
1180